ActiveRecord - 资料表关联

    在「ActiveRecord - 基本操作与关联设计」一章我们已经有了关联设计的基本概念,这一章我们将进一步深入了解细节的设定,以及多型关联设计。

    在关联的集合上,我们有以下方法可以使用:

    • «(*records) and create
    • any? and empty?
    • build and new
    • count
    • delete_all
    • destroy_all
    • find(id)
    • ids
    • include?(record)
    • first, last
    • reload

    例如:

    has_many 的设定

    可以变更关联的类别名称,例如以下新增了关联,和另一个has_many :attendees都关联到同一个_attendees table

    1. class Event < ApplicationRecord
    2. has_many :attendees
    3. has_many :paid_attendees, :class_name => "Attendee"
    4. #...
    5. end

    foreign_key

    可以变更Foreign Key的字段名称,例如改成paid_user_id

    1. class Event < ApplicationRecord
    2. belongs_to :paid_user, :class_name => "User", :foreign_key => "paid_user_id"
    3. #...
    4. end

    scope

    在第二个参数传入匿名函式,可以设定关联的范围条件,例如:

    1. class Event < ApplicationRecord
    2. has_many :attendees
    3. #...
    4. end

    这个语法跟我们之前学过的Arel串接写法是一样的,所以可以继续串接加上排序等其他条件:

    1. class Event < ApplicationRecord
    2. has_many :attendees
    3. has_many :paid_attendees, -> { where(:status => "paid").order("id DESC") }, :class_name => 'Attendee'
    4. #...
    5. end

    可以设定当物件删除时,怎么处理依赖它的资料,例如:

    1. class Event < ApplicationRecord
    2. has_many :attendees, :dependent => :destroy
    3. end

    其中:dependent可以设定有几种不同的处理方式,例如:

    • :destroy 把依赖的attendees也一并删除,并且执行Attendeedestroy回呼
    • :delete 把依赖的attendees也一并删除,但不执行Attendeedestroy回呼
    • :nullify 不会帮忙删除attendees,但会把attendees的外部键event_id都设成NULL
    • :restrictwith_exception 如果有任何依赖的_attendees资料,则连event都不允许删除。执行删除时会丢出错误例外ActiveRecord::DeleteRestrictionError
    • :restrict_with_error 不允许删除。执行删除时会回传false,在@event.errors中会留有错误讯息。

    如果没有设定:dependent的话,就不会特别去处理。

    through

    1. class Event < ApplicationRecord
    2. has_many :event_groupships
    3. has_many :groups, :through => :event_groupships
    4. end

    source

    搭配through设定使用,当关联的名称不一致的时候,需要加上指名是哪一种物件。

    1. class Event < ApplicationRecord
    2. has_many :event_groupships
    3. has_many :classifications, :through => :event_groupships, :source => :group
    4. end

    has_one 的集合物件

    多了两个方法可以新增关联物件:

    • build_{association_name}
    • create_{association_name}

    例如:

    classnamedependent、_scope条件等设定,都和has_many一样。

    belongs_to 的设定

    在 Rails 5.1 之后的版本,belongs_to 关联的 model 默认改成必填了,也就是一定要有。透过 optional => true 可以允许 event 没有 category 的情况。

    1. class Event < ApplicationRecord
    2. belongs_to :category, :optional => true
    3. end

    class_name

    可以变更关联的类别名称,例如:

    1. class Event < ApplicationRecord
    2. belongs_to :manager, :class_name => "User" # 默认的外部键叫做 manager_id
    3. end

    foreign_key

    可以变更Foreign Key的字段名称,例如改成user_id

    1. class Event < ApplicationRecord
    2. belongs_to :manager, :class_name => "User", :foreign_key => "user_id"
    3. end

    这会在修改时,也顺道修改关联资料的updated_at时间:

    1. class Attendee < ApplicationRecord
    2. belongs_to :event, :touch => true
    3. end

    counter_cache

    针对关联作计数的快取,假设Event身上有attendees_count这个字段,那么:

    1. class Attendee < ApplicationRecord
    2. belongs_to :event, :counter_cache => true
    3. end

    这样ActiveRecord就会自动更新attendees_count的数字。

    joins 和 includes 查询

    1. Event.joins(:category)
    2. # SELECT "events".* FROM "events" INNER JOIN "categories" ON "categories"."id" = "events"."category_id"

    可以一次关连多个:

      透过joins抓出来的event物件是没有包括其关连物件的,因为Rails默认只有select event.而没有select categories.,因此joins主要的用途是来搭配where的条件查询,帮忙过滤events资料:

      如果需要其关连物件的资料,例如上述的categories,我们会偏好使用includesincludes会将关连物件的资料也一并读取出来,避免N+1问题(见效能一章),例如:

      1. Event.includes(:category)
      2. # SELECT * FROM events
      3. # SELECT * FROM categories WHERE categories.id IN (1,2,3...)

      同理,也可以一次加载多个关连:

      1. Event.includes(:category, :attendees)
      2. # SELECT "events".* FROM "events"
      3. # SELECT "attendees".* FROM "attendees" WHERE "attendees"."event_id" IN (4, 5, 6, 7, 8...)

      includes方法也可以加上条件:

      1. Event.includes(:category).where( :category => { :position => 1 } )

      多型关联(Polymorphic Associations)

      多型关连(Polymorphic Associations)可以让一个 Model 不一定关连到某一个特定的 Model,秘诀在于除了整数的id外部键之外,再加一个字串的_type字段说明是哪一种_Model

      例如一个Comment model,我们可以透过多型关连让它belongsto到各种不同的 _Model上,假设我们已经有了ArticlePhoto这两个Model,然后我们希望这两个Model都可以被留言。不用多型关连的话,你得分别建立ArticleCommentPhotoCommentmodel。用多型关连的话,无论有多少种需要被留言的Model,只需要一个Comment model即可:

      1. rails g model comment content:text commentable_id:integer commentable_type

      这样会产生下面的 Migration 档案:

      1. class CreateComments < ActiveRecord::Migration[5.1]
      2. def change
      3. create_table :comments do |t|
      4. t.text :content
      5. t.integer :commentable_id
      6. t.string :commentable_type
      7. t.timestamps
      8. end
      9. end
      10. end

      这个Migration档案中,我们用content这个字段来储存留言的内容,commentable_id用来储存被留言的物件的idcommentable_type则用来储存被留言物件的种类,以这个例子来说被留言的对象就是ArticlePhoto这两种Model,这个Migration档案也可以改写成下面这样:

      1. class CreateComments < ActiveRecord::Migration[5.1]
      2. def change
      3. create_table :comments do |t|
      4. t.text :content
      5. t.belongs_to :commentable, :polymorphic => true
      6. t.timestamps
      7. end
      8. end
      9. end

      回到我们的Model,我们必须指定他们的关联关系:

      1. class Comment < ApplicationRecord
      2. belongs_to :commentable, :polymorphic => true
      3. end
      4. class Article < ApplicationRecord
      5. has_many :comments, :as => :commentable
      6. end
      7. class Photo < ApplicationRecord
      8. has_many :comments, :as => :commentable
      9. end

      这样会告诉Rails如何去设定你的多型关系,现在让我们进console实验看看:

      更多线上资源