iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0
Software Development

SQL rookie 之天天魯一下系列 第 12

Day 12 - 利用SampleDB seeding 資料(4)

  • 分享至 

  • xImage
  •  

啊呃,果然現實是殘酷的,原本今天想開始練習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. 用migration 修改column_name
  2. 利用foreign_key 來指定欄位名稱

但因為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 的關聯設定好再睡!今天就這麼廢下去好了,謝謝大家~~


上一篇
Day 11 - 插話:利用SampleDB seeding 資料(3)
下一篇
Day 13 - 魯魯亂入SQL 領域(1) - 誤闖ActiveRecord::Base 篇
系列文
SQL rookie 之天天魯一下30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言