iT邦幫忙

2022 iThome 鐵人賽

DAY 7
0
Software Development

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

Day 7 - 為Model、Database 加上關聯(2)

  • 分享至 

  • xImage
  •  

大家好!

昨天研究到在兩個Model 間加上has_many、belongs_to 等,即可利用Rails 黑魔法為彼此加上associations,亦即為彼此加上了幾個方法:

irb(main):022:0> methods_after.flatten - methods_before.flatten
[
    [0] "autosave_associated_records_for_podcast",
    [1] "build_podcast",
    [2] "create_podcast",
    [3] "create_podcast!",
    [4] "podcast",
    [5] "podcast=",
    [6] "reload_podcast"
]

被翻爛的書今天想研究的是為Model 加上對應的PK & FK 欄位,但沒加上Model associations 會怎麼樣,讓我們來實驗看看吧:

# 首先檢視目前的podcasts & episodes table 各有哪些欄位:
  create_table "podcasts", force: :cascade do |t|
    t.string "name"
    t.integer "genres"
    t.string "host"
    t.text "introduction"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
 
    create_table "episodes", force: :cascade do |t|
    t.string "name"
    t.integer "duration"
    t.datetime "record_on"
    t.string "host"
    t.string "guest"
    t.text "introduction"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

並根據Model 關聯圖增加migration 檔:
魯魯專案的Model 關聯圖_ver1

❯ rails g migration add_podcast_id_to_episodes
Running via Spring preloader in process 64826
      invoke  active_record
      create    db/migrate/20220921141701_add_podcast_id_to_episodes.rb
      
# 修改migration
class AddPodcastIdToEpisodes < ActiveRecord::Migration[6.0]
  def change
    add_reference :episodes, :podcast
  end
end

# 能看到多了一個podcast_id 的欄位在episodes 資料表上
create_table "episodes", force: :cascade do |t|
  t.bigint "podcast_id"
  t.index ["podcast_id"], name: "index_episodes_on_podcast_id"
end

接著我們來修改episode 的seeding tasks:

namespace :seeding do desc "Seeding episodes"
  task seed_episodes: :environment do
    Episode.destroy_all

    0.upto(19) do |no|
      Episode.create!([{
        name: "第#{no}話",
        duration: 60,
        record_on: DateTime.now + (no * 7).days,
        host: "COLATIME",
        introduction: "開播啦~~",
         podcast_id: Podcast.pluck(:id).sample #新增這行
      }])
      p "Created #{Episode.count} episodes"
    end
  end
end

❯ rails seeding:seed_episodes

seeding 了20 筆的episodes 後,我們進console 來看一下:

# 可看到是有資料的
irb(main):003:0> Episode.last
  Episode Load (0.6ms)  SELECT "episodes".* FROM "episodes" ORDER BY "episodes"."id" DESC LIMIT $1  [["LIMIT", 1]]
#<Episode:0x00007fe52618c078> {
              :id => 60,
            :name => "第19話",
        :duration => 60,
       :record_on => Wed, 01 Feb 2023 14:25:48 UTC +00:00,
            :host => "COLATIME",
           :guest => nil,
    :introduction => "開播啦~~",
      :created_at => Wed, 21 Sep 2022 14:25:48 UTC +00:00,
      :updated_at => Wed, 21 Sep 2022 14:25:48 UTC +00:00,
      :podcast_id => 282
}

# 但我們能隨意指派一個不存在的podcast_id 嗎?我們來試試看 XD

irb(main):004:0> Episode.last.update(podcast_id: 1)
  Episode Load (0.7ms)  SELECT "episodes".* FROM "episodes" ORDER BY "episodes"."id" DESC LIMIT $1  [["LIMIT", 1]]
   (0.2ms)  BEGIN
  Episode Update (0.8ms)  UPDATE "episodes" SET "podcast_id" = $1, "updated_at" = $2 WHERE "episodes"."id" = $3  [["podcast_id", 1], ["updated_at", "2022-09-21 14:32:51.324768"], ["id", 80]]
   (1.1ms)  COMMIT
true # => 居然可以!XD

# 那讓我們試著叫出Episode.last 所屬的Podcast
irb(main):005:0> Episode.last.podcast
  Episode Load (0.5ms)  SELECT "episodes".* FROM "episodes" ORDER BY "episodes"."id" DESC LIMIT $1  [["LIMIT", 1]]
Traceback (most recent call last):
        1: from (irb):33
NoMethodError (undefined method `podcast' for #<Episode:0x00007fe524d8a2a0>)
Did you mean?  podcast_id # => Oh, no!

顯然,雖然Episode 有寫入podcast_id,但因為沒在Model 加上belongs_to :podcast的關聯,因此無法藉由關聯方法叫出他所屬的Podcast。

但我還想這麼嘗試:

# 使用ActiveRecord 的joins:
irb(main):001:0> Episode.joins(:podcast)
Traceback (most recent call last):
ActiveRecord::ConfigurationError (Can't join 'Episode' to association named 'podcast'; perhaps you misspelled it?) # => 會被告知沒有關聯

# 但若是混用SQL 語法在內:
irb(main):002:0> Episode.joins("LEFT JOIN podcasts ON episodes.podcast_id = podcasts.id").first
  Episode Load (1.2ms)  SELECT "episodes".* FROM "episodes" LEFT JOIN podcasts ON episodes.podcast_id = podcasts.id ORDER BY "episodes"."id" ASC LIMIT $1  [["LIMIT", 1]]
#<Episode:0x00007fe51e09ea88> {
              :id => 81,
            :name => "第0話",
        :duration => 60,
       :record_on => Wed, 21 Sep 2022 14:46:26 UTC +00:00,
            :host => "COLATIME",
           :guest => nil,
    :introduction => "開播啦~~",
      :created_at => Wed, 21 Sep 2022 14:46:26 UTC +00:00,
      :updated_at => Wed, 21 Sep 2022 14:46:26 UTC +00:00,
      :podcast_id => 289
}

# 卻能看到兩個表被join 了起來,也能叫得出資料!

那我想再做個嘗試:
rollback migration,但這次不用add_reference 的方法,而是自己加上podcast_id 的欄位在Episode 上:

# 再次修改migration
class AddPodcastIdToEpisodes < ActiveRecord::Migration[6.0]
  def change
    # add_reference :episodes, :podcast
    add_column :episodes, :podcast_id, :integer
  end
end

# 可看到目前的schema 跟剛剛有些微不同:
create_table "episodes", force: :cascade do |t|
  t.integer "podcast_id"
end

# 那我們再跑一次seeding episodes 的tasks、進console 確認資料、再試試看上面的query:
irb(main):003:0> Episode.joins("LEFT JOIN podcasts ON episodes.podcast_id = podcasts.id").first
  Episode Load (2.1ms)  SELECT "episodes".* FROM "episodes" LEFT JOIN podcasts ON episodes.podcast_id = podcasts.id ORDER BY "episodes"."id" ASC LIMIT $1  [["LIMIT", 1]]
#<Episode:0x00007fe51fc62b20> {
              :id => 121,
                # 略
              :podcast_id => 281
}

有看出什麼端倪嗎?

回到昨天的第二、第三題:在資料表要加上PK、FK,那麼加了PK、FK 就算加上關聯了嗎?

...

補充:

若Episode 有加上belongs_to :podcast,此時還能隨意指派一個不存在的podcast_id 嗎?

irb(main):005:0> Episode.last.update(podcast_id: 1)
  Episode Load (0.6ms)  SELECT "episodes".* FROM "episodes" ORDER BY "episodes"."id" DESC LIMIT $1  [["LIMIT", 1]]
   (0.2ms)  BEGIN
  Podcast Load (0.4ms)  SELECT "podcasts".* FROM "podcasts" WHERE "podcasts"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
   (0.3ms)  ROLLBACK
false # => 但為什麼會false 呢?

# 大概是黑魔法幫我們加上了驗證吧:XD
ActiveRecord::RecordInvalid: Validation failed: Podcast must exist

...

今天的研究先到這,明天再繼續研究;謝謝大家~


上一篇
Day 6 - 為Model、Database 加上關聯(1)
下一篇
Day 8 - 為Model、Database 加上關聯(3)
系列文
SQL rookie 之天天魯一下30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言