啊呃,果然現實是殘酷的,原本今天想開始練習Active Record 的query methods,但很不幸地是剛研究沒多久就碰壁啦!
猜猜為什麼?
irb(main):001:0> Post.first.user
  Post Load (0.3ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1  [["LIMIT", 1]]
Traceback (most recent call last):
        1: from (irb):001
NoMethodError (undefined method `user' for #<Post:0x00007f9cec456790>)
Did you mean?  userId
是的,我忘了加上關聯!看到這訊息馬上就知道是少了associations 的方法(可回顧Day 6-8 的文章,),那我們就加上關聯吧:
class Post < ApplicationRecord
  belongs_to :user
end
# 加了後:
irb(main):002:0> Post.first.user
  Post Load (0.2ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1  [["LIMIT", 1]]
nil # -> nilllllll!?
嗯?為什麼?XD
我們來確定一下Post 的欄位:
  create_table "posts", force: :cascade do |t|
    t.integer "userId"
    t.text "title"
    t.text "body"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end # -> 看起來也沒問題呀!?
  
# 看一下attributes 的資訊
irb(main):003:0> Post.first.userId
  Post Load (0.8ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1  [["LIMIT", 1]]
1
irb(main):004:0> Post.first.userId.class
  Post Load (0.4ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1  [["LIMIT", 1]]
Integer < Numeric # -> 嗯?形態、資料都對,可那為什麼咧?
讓我們來試試.joins(:user):
irb(main):008:0> Post.joins(:user)
  Post Load (3.1ms)  SELECT "posts".* FROM "posts" INNER JOIN "users" ON "users"."id" = "posts"."user_id"
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR:  column posts.user_id does not exist)
LINE 1: ...FROM "posts" INNER JOIN "users" ON "users"."id" = "posts"."u...
                                                             ^
HINT:  Perhaps you meant to reference the column "posts.userId". # !!!!!!
看到這可發現,Rails 已善意地提醒我們了:加上belongs_to 後,黑魔法預設幫我們設定foreign_key 為posts.user_id,可因為我們的欄位名稱是userId,那怎麼辦呢?
但因為1 可能會影響昨天的懶人seeding 法,所以我比較想嘗試2 的方法:
class Post < ApplicationRecord
  belongs_to :user, foreign_key: :userId
end
irb(main):010:0> Post.first.user
  Post Load (0.7ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1  [["LIMIT", 1]]
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
#<User:0x00007f9ceabc91a0> {
            :id => 1,
    :created_at => Mon, 26 Sep 2022 13:45:53 UTC +00:00,
    :updated_at => Mon, 26 Sep 2022 13:45:53 UTC +00:00,
          :name => "Leanne Graham",
      :username => "Bret",
         :email => "Sincere@april.biz",
         :phone => "1-770-736-8031 x56442",
       :website => "hildegard.org"
}
成功!
那我們就知道能用指定foreign_key 的方法處理belongs_to :user(posts, albums, todos) 的資料表
可如果反過來呢?我們是否能用User.first.posts來看has_many 的posts 呢:
# 同樣的,需要先在User 加上has_many :post,但事與願違:
irb(main):003:0> User.first.posts
  User Load (0.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR:  column posts.user_id does not exist)
LINE 1: SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = $1
                                            ^
HINT:  Perhaps you meant to reference the column "posts.userId". # !!!!?!?
可神奇的是:
class User < ApplicationRecord
  has_many :posts, foreign_key: :userId
end
# 就能找到資料啦!
irb(main):010:0> User.first.posts
  User Load (0.5ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Post Load (0.7ms)  SELECT "posts".* FROM "posts" WHERE "posts"."userId" = $1  [["userId", 1]]
[
    [0] #<Post:0x00007f9ceac1e920> {
                :id => 1,
            :userId => 1,
             :title => "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
              :body => "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto",
        :created_at => Mon, 26 Sep 2022 13:45:30 UTC +00:00,
        :updated_at => Mon, 26 Sep 2022 13:45:30 UTC +00:00
    },
不知道有沒有魯魯跟我一樣好奇,或以為在User Model 加上的foreign_key 應該是指自己的attributes/columns?
可其實不然的喲~~
我們來看看APIdock 怎麼寫:
foreign_key public
Creates a foreign key name from a class name. (就是這個from!) separate_class_name_and_id_with_underscore sets whether the method should put ‘_’ between the name and ‘id’.
'Message'.foreign_key        # => "message_id"
'Message'.foreign_key(false) # => "messageid"
'Admin::Post'.foreign_key    # => "post_id"
讓我們在console 試試:
irb(main):006:0> 'Post'.foreign_key
"post_id"
irb(main):007:0> 'User'.foreign_key
"user_id"
可以看到convention 上其實都將foreign_key 視為classname_id 的啦,難怪我們需要特別指定了~~
不過當然我們也能這麼做:
class User < ApplicationRecord
  has_many :posts, foreign_key: 'userId'
end
如此也是行得通的囉!
那今天的任務就是把所有Model 和tables 的關聯設定好再睡!今天就這麼廢下去好了,謝謝大家~~