Active Record 關聯
關聯是一種大型類別方法組合,透過外來金鑰連結物件在一起。它們表達類似「專案擁有一個專案管理員」或「專案屬於一個投資組合」的關聯性。每個大型函式會新增一些方法至類別,這些方法會針對集合或關聯符號以及選項雜湊進行專門化。它的運作方式與 Ruby 本身的 attr*
方法非常相似。
class Project < ActiveRecord::Base
belongs_to :portfolio
has_one :project_manager
has_many :milestones
has_and_belongs_to_many :categories
end
專案類別現在有下列方法(以及其他方法)來簡化其關係的遍歷與處理:
project = Project.first
project.portfolio
project.portfolio = Portfolio.first
project.reload_portfolio
project.project_manager
project.project_manager = ProjectManager.first
project.reload_project_manager
project.milestones.empty?
project.milestones.size
project.milestones
project.milestones << Milestone.first
project.milestones.delete(Milestone.first)
project.milestones.destroy(Milestone.first)
project.milestones.find(Milestone.first.id)
project.milestones.build
project.milestones.create
project.categories.empty?
project.categories.size
project.categories
project.categories << Category.first
project.categories.delete(category1)
project.categories.destroy(category1)
警告
請勿建立與 ActiveRecord::Base
執行個體方法 同名的關聯。由於關聯會針對模型新增含有此名稱的方法,因此使用與 ActiveRecord::Base
所提供的名稱同名的關聯會覆寫由 ActiveRecord::Base
繼承而來的該方法,並導致錯誤發生。例如,attributes
和 connection
會是作為關聯名稱的不當選擇,因為這些名稱已存在於 ActiveRecord::Base
執行個體方法清單中。
自動產生方法
另見下方「執行個體公開方法」(來自 belongs_to
)以了解詳細資料。
單數關聯(一對一)
| | belongs_to |
generated methods | belongs_to | :polymorphic | has_one
----------------------------------+------------+--------------+---------
other | X | X | X
other=(other) | X | X | X
build_other(attributes={}) | X | | X
create_other(attributes={}) | X | | X
create_other!(attributes={}) | X | | X
reload_other | X | X | X
other_changed? | X | X |
other_previously_changed? | X | X |
集合關聯(一對多/多對多)
| | | has_many
generated methods | habtm | has_many | :through
----------------------------------+-------+----------+----------
others | X | X | X
others=(other,other,...) | X | X | X
other_ids | X | X | X
other_ids=(id,id,...) | X | X | X
others<< | X | X | X
others.push | X | X | X
others.concat | X | X | X
others.build(attributes={}) | X | X | X
others.create(attributes={}) | X | X | X
others.create!(attributes={}) | X | X | X
others.size | X | X | X
others.length | X | X | X
others.count | X | X | X
others.sum(*args) | X | X | X
others.empty? | X | X | X
others.clear | X | X | X
others.delete(other,other,...) | X | X | X
others.delete_all | X | X | X
others.destroy(other,other,...) | X | X | X
others.destroy_all | X | X | X
others.find(*args) | X | X | X
others.exists? | X | X | X
others.distinct | X | X | X
others.reset | X | X | X
others.reload | X | X | X
覆寫產生的方法
關聯方法會產生於包含在模型類別中的模組中,讓覆寫變得容易。因此,可使用 super
來呼叫原始產生的方法。
class Car < ActiveRecord::Base
belongs_to :owner
belongs_to :old_owner
def owner=(new_owner)
self.old_owner = self.owner
super
end
end
關聯方法模組會在產生屬性方法模組後立即包含進來,意即關聯會覆寫帶有相同名稱的屬性方法。
主從性和關聯性
Active Record 關聯可用於描述模型之間的一對一、一對多和多對多關聯性。每個模型會使用關聯來描述其在關聯性中的角色。
一對一
在基礎部分使用 has_one
,在關聯模型中使用 belongs_to
。
class Employee < ActiveRecord::Base
has_one :office
end
class Office < ActiveRecord::Base
belongs_to :employee # foreign key - employee_id
end
一對多
在基礎部分使用 has_many
,在關聯模型中使用 belongs_to
。
class Manager < ActiveRecord::Base
has_many :employees
end
class Employee < ActiveRecord::Base
belongs_to :manager # foreign key - manager_id
end
多對多
建立多對多關聯性的方法有兩種。
第一種方式是使用帶有 :through
選項和聯結模型的 has_many
關聯,因此會有兩個關聯階段。
class Assignment < ActiveRecord::Base
belongs_to :programmer # foreign key - programmer_id
belongs_to :project # foreign key - project_id
end
class Programmer < ActiveRecord::Base
has_many :assignments
has_many :projects, through: :assignments
end
class Project < ActiveRecord::Base
has_many :assignments
has_many :programmers, through: :assignments
end
對於第二種方式,在兩個模型中使用 has_and_belongs_to_many
。這需要無對應模型或主鍵的聯結表格。
class Programmer < ActiveRecord::Base
has_and_belongs_to_many :projects # foreign keys in the join table
end
class Project < ActiveRecord::Base
has_and_belongs_to_many :programmers # foreign keys in the join table
end
選擇建立多對多關聯性的方式並非總是簡單。如果您需要將關聯模型當成其自己的實體處理,請使用 has_many
:through
。如果您使用舊有模式,或從未直接處理關聯本身,請使用 has_and_belongs_to_many
。
它是 belongs_to
還是 has_one
關聯?
這兩個都表達 1-1 的關係。差異主要在於外來鍵放置的位置,外來鍵會出現在宣告 belongs_to
關係的類別中。
class User < ActiveRecord::Base
# I reference an account.
belongs_to :account
end
class Account < ActiveRecord::Base
# One user references me.
has_one :user
end
這些類別的資料表可能如下所示
CREATE TABLE users (
id bigint NOT NULL auto_increment,
account_id bigint default NULL,
name varchar default NULL,
PRIMARY KEY (id)
)
CREATE TABLE accounts (
id bigint NOT NULL auto_increment,
name varchar default NULL,
PRIMARY KEY (id)
)
未儲存的物件和相關性
可以在物件和相關性儲存至資料庫前對其進行操作,但有一些特別的行為您應該知道,主要與關聯物件的儲存有關。
您可以設定 has_one
、belongs_to
、has_many
或 has_and_belongs_to_many
相關性的 :autosave
選項。將其設為 true
將永遠儲存成員,而將其設為 false
將永遠不會儲存成員。有關 :autosave
選項的更多詳細資訊,請參閱 AutosaveAssociation
。
一對一相關性
-
將物件指派給
has_one
相關性會自動儲存該物件和被取代的物件(如果存在),以更新它們的外來鍵(除非父物件未儲存(new_record? == true
))。 -
如果上述任何一個儲存失敗(因為其中一個物件無效),則會引發
ActiveRecord::RecordNotSaved
例外,並且指派會被取消。 -
如果您希望將物件指派給
has_one
相關性而不儲存它,請使用#build_association
方法(文件如下)。被取代的物件仍會儲存以更新其外來鍵。 -
將物件指派給
belongs_to
相關性不會儲存該物件,因為外來鍵欄位屬於父物件。它也不會儲存父物件。
集合
-
將物件新增至集合(
has_many
或has_and_belongs_to_many
)會自動儲存該物件,除非父物件(集合的所有者)尚未儲存在資料庫中。 -
如果將任何物件新增至集合(透過
push
或類似方式)時儲存失敗,則push
會傳回false
。 -
如果在取代集合時(透過
association=
)儲存失敗,則會引發ActiveRecord::RecordNotSaved
例外,並且指派會被取消。 -
您可以使用
collection.build
方法(文件如下)將物件新增至集合而不自動儲存它。 -
集合的所有未儲存成員(
new_record? == true
)會在儲存父項目時自動儲存。
自訂查詢
相關性由 Relation
物件建立,您可以使用 Relation
句法來自訂它們。例如,要新增條件
class Blog < ActiveRecord::Base
has_many :published_posts, -> { where(published: true) }, class_name: 'Post'
end
在 -> { ... }
區塊中,您可以使用所有典型的 Relation
方法。
存取所有者物件
建立查詢時,有時需要存取所有者物件。所有者會作為參數傳遞至區塊。例如,以下相關性會找出發生於使用者生日的所有活動
class User < ActiveRecord::Base
has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event'
end
注意:無法聯結或熱切載入此類相關性,因為這些操作會在執行個體建立前發生。此類相關性可以預先載入,但這麼做會執行 N+1 查詢,因為每筆記錄都會有不同的範圍(類似於預先載入多型態範圍)。
相關性回呼
與連入 Active Record 物件生命週期的正常回呼類似,您也可以定義在將物件新增至相關性集合或從中移除物件時觸發的回呼。
class Firm < ActiveRecord::Base
has_many :clients,
dependent: :destroy,
after_add: :congratulate_client,
after_remove: :log_after_remove
def congratulate_client(client)
# ...
end
def log_after_remove(client)
# ...
end
end
Callbacks
可用三種方式定義
-
引用關聯集合定義在類別上的方法的符號。例如,
after_add: :congratulate_client
呼叫Firm#congratulate_client(client)
。 -
可呼叫且含有特徵,可接受關聯集合中的記錄和正在新增或移除的記錄。例如,
after_add: ->(firm, client) { ... }
。 -
響應呼叫回名稱的物件。例如,傳遞
after_add: CallbackObject.new
將呼叫CallbackObject#after_add(firm, client)
。
可以透過將呼叫回傳遞為陣列堆疊呼叫回。範例
class CallbackObject
def after_add(firm, client)
firm.log << "after_adding #{client.id}"
end
end
class Firm < ActiveRecord::Base
has_many :clients,
dependent: :destroy,
after_add: [
:congratulate_client,
-> (firm, client) { firm.log << "after_adding #{client.id}" },
CallbackObject.new
],
after_remove: :log_after_remove
end
可能的呼叫回為:before_add
、after_add
、before_remove
和 after_remove
。
如果任何 before_add
呼叫回擲回例外,則不會將物件新增至集合。
類似地,如果任何 before_remove
呼叫回擲回例外,則不會從集合中移除物件。
注意:若要引發移除呼叫回,您必須使用 destroy
/ destroy_all
方法。例如
-
firm.clients.destroy(client)
-
firm.clients.destroy(*clients)
-
firm.clients.destroy_all
delete
/ delete_all
方法如下不會引發移除呼叫回
-
firm.clients.delete(client)
-
firm.clients.delete(*clients)
-
firm.clients.delete_all
關聯延伸模組
控制對關聯存取的代理程式物件可以透過匿名的模組延伸。這對於新增僅用在這個關聯中的新尋找器、建立器和其他製造工廠類型的方法特別有幫助。
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
如果您需要在許多關聯之間共用相同的延伸模組,您可以使用命名延伸模組。
module FindOrCreateByNameExtension
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
class Account < ActiveRecord::Base
has_many :people, -> { extending FindOrCreateByNameExtension }
end
class Company < ActiveRecord::Base
has_many :people, -> { extending FindOrCreateByNameExtension }
end
一些延伸模組僅能透過了解關聯內部運作才有作用。延伸模組可以使用下列方法存取相關狀態(其中 items
為關聯的名稱)
-
record.association(:items).owner
- 傳回關聯中的物件。 -
record.association(:items).reflection
- 傳回描述關聯的反射物件。 -
record.association(:items).target
- 傳回belongs_to
和has_one
的關聯物件,或has_many
和has_and_belongs_to_many
關聯物件的集合。
不過,在實際延伸模組程式碼中,您無法存取上述的 record
。在这种情况下,您可以访问 `proxy_association`。例如,`record.association(:items)` 和 `record.items.proxy_association` 將傳回同一個物件,讓您可以在關聯延伸模組中呼叫 `proxy_association.owner` 的函式。
關聯加入模型
:through
選項可以設定 Has Many 關聯,以便使用明確的加入模型來擷取資料。這與 has_and_belongs_to_many
關聯的運作方式類似。好處是可以針對加入模型新增驗證、呼叫回和額外屬性。請考量下列結構
class Author < ActiveRecord::Base
has_many :authorships
has_many :books, through: :authorships
end
class Authorship < ActiveRecord::Base
belongs_to :author
belongs_to :book
end
@author = Author.first
@author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
@author.books # selects all books by using the Authorship join model
您也可以透過加入模型的 has_many
關聯
class Firm < ActiveRecord::Base
has_many :clients
has_many :invoices, through: :clients
end
class Client < ActiveRecord::Base
belongs_to :firm
has_many :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :client
end
@firm = Firm.first
@firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
@firm.invoices # selects all invoices by going through the Client join model
類似地,您可以透過加入模型的 has_one
關聯
class Group < ActiveRecord::Base
has_many :users
has_many :avatars, through: :users
end
class User < ActiveRecord::Base
belongs_to :group
has_one :avatar
end
class Avatar < ActiveRecord::Base
belongs_to :user
end
@group = Group.first
@group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
@group.avatars # selects all avatars by going through the User join model.
在加入模型中使用 has_one
或 has_many
關聯時,一個重要的注意事項是這些關聯為**唯讀**。例如,在先前的範例中,以下方法將無法使用
@group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
@group.avatars.delete(@group.avatars.last) # so would this
設定反向
如果使用加入模型中的 belongs_to
,最好是在 belongs_to
中設定 :inverse_of
選項,代表以下範例能正常運作(而其中 tags
是 has_many
:through
關聯)
@post = Post.first
@tag = @post.tags.build name: "ruby"
@tag.save
最後一行應該可以儲存加入記錄(Tagging
)。這只會在設定 :inverse_of
時運作
class Tagging < ActiveRecord::Base
belongs_to :post
belongs_to :tag, inverse_of: :taggings
end
如果沒有設定 :inverse_of
記錄,關聯會盡量比對正確的反向關聯。自動反向偵測只會作用於 has_many
、has_one
及 belongs_to
關聯。
關聯中的 :foreign_key
及 :through
選項,以及在某些情況下的自訂範圍都會阻止自動尋找關聯中的反向關聯。進一步的詳細資料請參閱 Active Record 關聯指南。
自動猜測反向關聯使用的是根據類別名稱的啟發方式,因此可能無法對所有的關聯運作,特別是非標準名稱的那些關聯。
您可以透過將 :inverse_of
選項設為 false
方法關閉反向關聯的自動偵測,如下所示
class Tagging < ActiveRecord::Base
belongs_to :tag, inverse_of: false
end
巢狀關聯
您實際上可以使用 :through
選項指定任何關聯,包括本身有 :through
選項的關聯。例如
class Author < ActiveRecord::Base
has_many :posts
has_many :comments, through: :posts
has_many :commenters, through: :comments
end
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :commenter
end
@author = Author.first
@author.commenters # => People who commented on posts written by the author
設定此關聯的等價方式如下
class Author < ActiveRecord::Base
has_many :posts
has_many :commenters, through: :posts
end
class Post < ActiveRecord::Base
has_many :comments
has_many :commenters, through: :comments
end
class Comment < ActiveRecord::Base
belongs_to :commenter
end
使用巢狀關聯時,您將無法修改關聯,因為沒有足夠的資訊知道要進行哪一種修改。例如,如果您嘗試在上面的範例中新增 Commenter
,便無法告知系統要如何設定中介 Post
及 Comment
物件。
多型關聯
模型上的多型關聯並未限制其能關聯的模型類型。反之,它們指定 has_many
關聯必須遵守的介面。
class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
end
class Post < ActiveRecord::Base
has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use.
end
@asset.attachable = @post
此方法使用類型欄位搭配外來金鑰來指定關聯記錄。在 Asset 範例中,您需要 attachable_id
整數欄位及 attachable_type
字串欄位。
將多型關聯與單一表格繼承(STI)搭配使用時會稍微 tricky 一些。為了讓關聯如預期運作,請務必在多型關聯的類型欄位中儲存 STI 模型的基礎模型。繼續使用上面的資產範例,假設我們有使用 post 表格 for STI 的客座貼文及會員貼文。在這種情況下,post 表格中必須有一個 type
欄位。
注意:在指派 attachable
時會呼叫 attachable_type=
方法。attachable
的 class_name
會作為 String
傳遞。
class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
def attachable_type=(class_name)
super(class_name.constantize.base_class.to_s)
end
end
class Post < ActiveRecord::Base
# because we store "Post" in attachable_type now dependent: :destroy will work
has_many :assets, as: :attachable, dependent: :destroy
end
class GuestPost < Post
end
class MemberPost < Post
end
快取
所有方法都建立於一個簡單的快取原則,除非特別指示不要快取,否則它們會保留上一次查詢的結果。此快取甚至跨方法共用,讓您在使用巨集新增的方法時更輕輕鬆鬆,不用太擔心第一次執行的效能。
project.milestones # fetches milestones from the database
project.milestones.size # uses the milestone cache
project.milestones.empty? # uses the milestone cache
project.milestones.reload.size # fetches milestones from the database
project.milestones # uses the milestone cache
關聯關係的即時載入
即時載入是尋找特定類別物件和多個命名關聯關係的方法。這是避免可怕的 N+1 問題最簡便的方法之一,此問題在擷取每個需要顯示作者的 100 篇文章時會觸發 101 個資料庫查詢。透過使用即時載入,查詢的數量將從 101 減少到 2。
class Post < ActiveRecord::Base
belongs_to :author
has_many :comments
end
考量在上述類別中使用下列迴圈
Post.all.each do |post|
puts "Post: " + post.title
puts "Written by: " + post.author.name
puts "Last comment on: " + post.comments.first.created_on
end
若要反覆執行這一百篇文章,我們會產生 201 個資料庫查詢。讓我們先針對擷取作者進行最佳化
Post.includes(:author).each do |post|
這是指也使用 :author
符號的 belongs_to
關聯關係的名稱。在載入文章後,find
會從每篇文章中收集 author_id
,並使用一個查詢載入所有參考到的作者。這樣做會將查詢數量從 201 減少到 102。
我們可透過在 finder 中同時參考兩個關聯關係讓情況更進一步,使用
Post.includes(:author, :comments).each do |post|
這樣會使用單一查詢載入所有留言。這將總查詢數量減少到 3。通常,查詢數量會等於 1 加上命名的關聯關係數量(除非某些關聯關係是多型 belongs_to
- 請參閱下方)。
若要包含多層階的關聯關係,請使用 hash
Post.includes(:author, { comments: { author: :gravatar } }).each do |post|
上述程式碼會載入所有留言及其所有相關的作者與大頭貼。您可以混合和搭配任何符號、陣列和 hash 組合,以擷取您想載入的關聯關係。
所有這些功能不應讓您誤以為可以因為減少了查詢數量,而毫不費力地擷取大量資料。資料庫仍需要將所有資料傳送給 Active Record,且仍需要進行處理。因此,這並非對於效能問題的萬靈丹,但它是在上方所述的情況下減少查詢數量的好方法。
由於一次只載入一個表格,因此條件或順序無法參考主要表格以外的表格。如果是這樣的話,Active Record 會改用先前所使用,基於 LEFT OUTER JOIN
的策略。例如
Post.includes([:author, :comments]).where(['comments.approved = ?', true])
這會產生一個具有下列連接線的單一 SQL 查詢:LEFT OUTER JOIN comments ON comments.post_id = posts.id
和 LEFT OUTER JOIN authors ON authors.id = posts.author_id
。請注意,使用此類條件可能會造成意料之外的後果。在上方的範例中,完全沒有已核准留言的文章不會被傳回,這是因為條件套用至整個 SQL 陳述式,而不僅是關聯關係。
您必須消除欄位參考的歧義,此回退才會發生,例如 order: "author.name DESC"
會運作,但 order: "name DESC"
則不會。
如果您想要載入所有文章(包括沒有已核准留言的文章),請使用 ON
撰寫自己的 LEFT OUTER JOIN
查詢
Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")
在這情況下,包含有定義條件的關聯關係通常比較自然
class Post < ActiveRecord::Base
has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment'
end
Post.includes(:approved_comments)
這會載入文章,並即時載入 approved_comments
關聯關係,其中僅包含已核准的留言。
如果您對具有特定 :limit
選項的關聯關係進行即時載入,該選項將會被忽略,傳回所有相關物件
class Picture < ActiveRecord::Base
has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
end
Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
即時載入支援多型關聯關係。
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
end
嘗試即時載入可定址模型的呼叫
Address.includes(:addressable)
這會執行一個查詢,用每個對應類型的一個查詢來載入地址和對應對象。例如,如果所有對應對象都是 Person 或 Company 類別,接著總共將執行 3 個查詢。要載入的對應對象類型清單會在載入的地址之後決定。如果 Activa Record 必須降回使用過往的 eager 載入實作,這將不受支援,並且會產生 ActiveRecord::EagerLoadPolymorphicError
。原因在於父模型的類型是一欄位值,因此其對應的表格名稱無法放置在該查詢的 FROM
/JOIN
子句中。
表格別名
在表格在加入中被多次引用的情況下,Active Record 會使用表格別名。如果一個表格只被引用一次,將會使用標準表格名稱。第二次時,表格會以 #{reflection_name}_#{parent_table_name}
的方式建立別名。對於表格名稱的更多連續使用,將附加索引。
Post.joins(:comments)
# SELECT ... FROM posts INNER JOIN comments ON ...
Post.joins(:special_comments) # STI
# SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
# SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
作用為樹狀範例
TreeMixin.joins(:children)
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
TreeMixin.joins(children: :parent)
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
TreeMixin.joins(children: {parent: :children})
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
# INNER JOIN mixins childrens_mixins_2
Has and Belongs to Many 加入表格使用相同的點子,但是加上 _join
字尾
Post.joins(:categories)
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
Post.joins(categories: :posts)
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
Post.joins(categories: {posts: :categories})
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
如果您希望使用 ActiveRecord::QueryMethods#joins
方法來指定您自己的自訂加入,這些表格名稱將優先於 eager 關聯
Post.joins(:comments).joins("inner join comments ...")
# SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
Post.joins(:comments, :special_comments).joins("inner join comments ...")
# SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
# INNER JOIN comments special_comments_posts ...
# INNER JOIN comments ...
表格別名依照特定資料庫表格識別碼的最大長度自動縮短。
模組
預設上,關聯會在目前模組範圍內尋找物件。考慮以下範例
module MyApplication
module Business
class Firm < ActiveRecord::Base
has_many :clients
end
class Client < ActiveRecord::Base; end
end
end
當呼叫 Firm#clients
時,它會依序呼叫 MyApplication::Business::Client.find_all_by_firm_id(firm.id)
。如果您想與另一個模組範圍類別關聯,可以透過指定完整的類別名稱來完成。
module MyApplication
module Business
class Firm < ActiveRecord::Base; end
end
module Billing
class Account < ActiveRecord::Base
belongs_to :firm, class_name: "MyApplication::Business::Firm"
end
end
end
雙向關聯
在您指定關聯時,通常在已關聯模型中會有一個以相反關係指定相同關聯的關聯。例如,使用以下模型
class Dungeon < ActiveRecord::Base
has_many :traps
has_one :evil_wizard
end
class Trap < ActiveRecord::Base
belongs_to :dungeon
end
class EvilWizard < ActiveRecord::Base
belongs_to :dungeon
end
Dungeon
上的 traps
關聯和 Trap
上的 dungeon
關聯是彼此的相反,而 EvilWizard
上的 dungeon
關聯的相反則是 Dungeon
上的 evil_wizard
關聯 (反之亦然)。預設上,Active Record 可以根據類別名稱猜測關聯。結果如下
d = Dungeon.first
t = d.traps.first
d.object_id == t.dungeon.object_id # => true
上述範例中的 Dungeon
實例 d
和 t.dungeon
參考相同的記憶體內實例,因為關聯與類別的名稱相符。如果我們將 :inverse_of
加入到我們的模型定義中,結果會相同
class Dungeon < ActiveRecord::Base
has_many :traps, inverse_of: :dungeon
has_one :evil_wizard, inverse_of: :dungeon
end
class Trap < ActiveRecord::Base
belongs_to :dungeon, inverse_of: :traps
end
class EvilWizard < ActiveRecord::Base
belongs_to :dungeon, inverse_of: :evil_wizard
end
有關更多資訊,請參閱 :inverse_of
選項的文件和 Active Record 關聯指南。
從關聯中刪除
依賴關聯
has_many
、has_one
和 belongs_to
關聯支援 :dependent
選項。這讓您可以指定在擁有者被刪除時應刪除已關聯記錄。
例如
class Author
has_many :posts, dependent: :destroy
end
Author.find(1).destroy # => Will destroy all of the author's posts, too
:dependent
選項可以有不同的值,指定如何刪除。有關更多資訊,請參考不同特定關聯類型中這個選項的文件。當沒有給定選項時,行為是在摧毀記錄時不對已關聯記錄有任何動作。
請注意 :dependent
是使用 Rails 的回呼系統實作,其運作方式是依序處理回呼。因此,在 :dependent
選項前或後宣告的其他回呼會影響它執行動作。
請注意,:dependent
選項會被 has_one
:through
關聯忽略。
刪除或銷毀?
has_many
和 has_and_belongs_to_many
關聯具有 destroy
、delete
、destroy_all
和 delete_all
方法。
對於 has_and_belongs_to_many
,delete
和 destroy
是相同的:它們會導致關聯資料表中的記錄遭到移除。
對於 has_many
,destroy
和 destroy_all
會永遠呼叫正在移除的記錄的 destroy
方法,以執行 callback。然而,delete
和 delete_all
會根據 :dependent
選項指定的策略執行刪除,或是在沒有提供 :dependent
選項的情況下,遵循預設策略。預設策略是除了 has_many
:through
外什麼都不做(讓外來鍵保持設定為父類型的識別碼),delete_all
為 has_many
:through
的預設策略(刪除關聯記錄,而不執行它們的 callback)。
還有一個 clear
方法,它與 delete_all
相同,只是它傳回關聯而不是已刪除的記錄。
會刪除什麼?
這裡有一個潛藏的陷阱:has_and_belongs_to_many
和 has_many
:through
關聯在關聯資料表和關聯記錄中都有記錄。因此,當我們呼叫其中一個刪除方法時,到底會刪除什麼?
答案是在關聯上執行的刪除,假設是移除擁有者和關聯物件之間的連結,而不是必要的關聯物件本身。因此針對 has_and_belongs_to_many
和 has_many
:through
,關聯記錄會被刪除,但關聯記錄不會。
如果你想一想的話,這是有道理的:假設你要呼叫 post.tags.delete(Tag.find_by(name: 'food'))
,你會想要從文章中取消和「food」標籤的連結,而不是從資料庫中移除標籤本身。
不過,在某些案例中,此策略並不明智。例如,假設一個人有很多專案,而且每個專案都有很多任務。如果我們刪除某個人的其中一個任務,我們或許不會想要刪除這個專案。在此情況下,刪除方法根本不起作用:它只能在關聯模型上的關聯是 belongs_to
時使用。在其他情況下,你應該直接對關聯記錄或 :through
關聯執行操作。
對於一般的 has_many
,不會區分「關聯記錄」和「連結」,因此只有一個選項可以刪除哪些項目。
對於 has_and_belongs_to_many
和 has_many
:through
,如果你想自行刪除關聯記錄,永遠可以執行類似 person.tasks.each(&:destroy)
的操作。
具備 ActiveRecord::AssociationTypeMismatch
的類型安全
如果你嘗試將物件指定給與推論或指定的 :class_name
不相符的關聯,會收到 ActiveRecord::AssociationTypeMismatch
。
選項
可以使用選項調整所有關聯巨集。這讓比簡單和可預測的案例更加複雜的案例得以實現。
執行個體公開方法
belongs_to(name, scope = nil, **options) 連結
指定與另一個類別的一對一關聯。此方法僅在這個類別包含外來鍵時使用。如果另一個類別包含外來鍵,則應該使用 has_one
代替。請參閱 Is it a belongs_to or has_one association?,進一步了解 has_one
與 belongs_to
的使用時機。
方法將新增用於擷取並查詢關聯的單一物件,且這個物件包含 id
association
是用作傳遞作為 name
參數符號的保留標記位置,因此 belongs_to :author
會新增 author.nil?
及其他項目。
關聯
-
傳回關聯的物件。如果沒有找到物件,則傳回
nil
。 association=(associate)
-
指定關聯的物件,擷取主鍵並將其設為外來鍵。不會修改或刪除現有的記錄。
build_association(attributes = {})
-
傳回關聯類型的新的物件,該物件搭配
attributes
執行實例化,並透過外來鍵與這個物件進行連結,但尚未儲存。 create_association(attributes = {})
-
傳回關聯類型的新的物件,該物件搭配
attributes
執行實例化,透過外來鍵與這個物件進行連結,且已經儲存(如果驗證通過)。 create_association!(attributes = {})
-
執行與
create_association
相同的動作,但在記錄無效時會引發ActiveRecord::RecordInvalid
。 reload_association
-
傳回關聯的物件,強制執行資料庫讀取。
reset_association
-
解除載入關聯的物件。下次存取會從資料庫中查詢該物件。
association_changed?
-
如果已經指定新的關聯物件,且接下來的儲存動作會更新外來鍵,則傳回 true。
association_previously_changed?
-
如果先前的儲存動作更新關聯,以參照新的關聯物件,則傳回 true。
範例
class Post < ActiveRecord::Base
belongs_to :author
end
宣告 belongs_to :author
會新增以下方法 (及其他方法)
post = Post.find(7)
author = Author.find(19)
post.author # similar to Author.find(post.author_id)
post.author = author # similar to post.author_id = author.id
post.build_author # similar to post.author = Author.new
post.create_author # similar to post.author = Author.new; post.author.save; post.author
post.create_author! # similar to post.author = Author.new; post.author.save!; post.author
post.reload_author
post.reset_author
post.author_changed?
post.author_previously_changed?
範圍
您可以傳遞第二個引數 scope
作為可呼叫項目 (也即過程或 lambda),以在存取關聯的物件時取得特定的記錄或自訂產生的查詢。
範圍範例
belongs_to :firm, -> { where(id: 2) }
belongs_to :user, -> { joins(:friends) }
belongs_to :level, ->(game) { where("game_level > ?", game.current_level) }
選項
宣告也可以包含 options
hash,以指定關聯的行為類型。
:class_name
-
指定關聯的類別名稱。只有在無法從關聯名稱推論出名稱時才使用它。因此
belongs_to :author
預設會連結到 Author 類別,但如果實際的類別名稱為 Person,則必須使用此選項指定。 :foreign_key
-
指定用於關聯的外來鍵。預設情況下,這會猜認為關聯名稱加上「_id」後綴。因此,定義
belongs_to :person
關聯的類別會使用「person_id」為預設:foreign_key
。類似地,belongs_to :favorite_person, class_name: "Person"
會使用外來鍵「favorite_person_id」。設定
:foreign_key
選項會防止自動偵測關聯的反向,因此通常最好同時設定:inverse_of
選項。 :foreign_type
-
如果這是多形關聯,請指定用來儲存關聯物件類型的欄位,預設會將關聯名稱加上「_type」後綴作為此欄位名稱。因此,定義
belongs_to :taggable, polymorphic: true
關聯的類別會將「taggable_type」用作預設的:foreign_type
。 :primary_key
-
指定用於取得關聯中使用的關聯物件主鍵的方法。預設為
id
。 :dependent
-
若設為
:destroy
,關聯物件會在刪除這個物件時被刪除。若設為:delete
,關聯物件在不呼叫其刪除方法時就會被刪除。若設為:destroy_async
,關聯物件會被排程在背景工作中刪除。由於可能會留下孤立的記錄,如果belongs_to
另一個類別時與has_many
關聯一起使用,就不應指定此選項。 :counter_cache
-
透過使用
CounterCache::ClassMethods#increment_counter
和CounterCache::ClassMethods#decrement_counter
快取關聯類別中屬於物件的數量。當此類別的物件建立時,會增加快取計數;當其被刪除時,則會減少快取計數。這需要在關聯類別(例如 Post 類別)中使用名為#{table_name}_count
的欄位(例如對於屬於 Comment 類別,則是comments_count
) - 也就是要在關聯類別上建立#{table_name}_count
的遷移(使Post.comments_count
會傳回快取的計數)。您也可以透過提供欄位名稱(而非true
/false
值)此選項,來指定自訂快取計數欄位 (例如:counter_cache: :my_custom_counter
)。在現有的大型資料表上開始使用快取計數會很麻煩,因為在新增欄位時,必須分別填充欄位值(以免鎖定資料表時間太長),而且必須在使用
:counter_cache
之前填充(否則如size
/any?
/等等在內部使用快取計數的方法,可能會產生不正確的結果)。若要安全地填入這些值,同時讓快取計數欄位與子記錄的建立/移除保持最新,並且避免上述方法使用可能不正確的快取計數欄位值,並始終從資料庫取得結果,請使用counter_cache: { active: false }
。如果您還需要指定自訂欄位名稱,請使用counter_cache: { active: false, column: :my_custom_counter }
。注意:如果您已啟用快取計數,可能需要將快取計數屬性新增到關聯類別的
attr_readonly
清單中(例如:class Post; attr_readonly :comments_count; end
)。 :polymorphic
-
透過傳遞
true
來指定此關聯為多形關聯。注意:由於多形關聯仰賴在資料庫中儲存類別名稱,所以請務必在對應列的*_type
多形類型欄位中更新類別名稱。 :validate
-
設為
true
時,在儲存父物件時會驗證新增至關聯的新物件。預設為false
。如果您要確保關聯物件在每次更新時都會重新驗證,請使用validates_associated
。 :autosave
-
如果為
true
,在儲存父物件時會永遠儲存關聯物件,或是在標記為刪除時將其刪除。如果為false
,則不會儲存或刪除關聯物件。預設只會在關聯物件是新記錄時將其儲存。請注意,
NestedAttributes::ClassMethods#accepts_nested_attributes_for
將設定:autosave
為true
。 :touch
-
如果為 true,當此記錄儲存或銷毀時,關聯物件會被觸發 (將
updated_at
/updated_on
屬性設定為目前時間)。如果指定符號,會將此屬性與updated_at
/updated_on
屬性一起更新為目前時間。請注意,觸發時不會執行驗證,而且只有after_touch
、after_commit
和after_rollback
回呼會執行。 :inverse_of
-
指定關聯物件上
has_one
或has_many
關聯的名稱,也就是此belongs_to
關聯的反向。有關更多詳細資訊,請參閱 雙向關聯。 :optional
-
設定為
true
時,此關聯不會驗證其存在。 :required
-
設定為
true
時,此關聯也會驗證其存在。這會驗證關聯本身,而不是驗證 id。你可以使用:inverse_of
來避免驗證期間的額外查詢。注意:預設將required
設定為true
,且此設定已棄用。如果您不想驗證關聯存在,請使用optional: true
。 :default
-
提供一個可呼叫式物件 (例如程序或 lambda) 來指定在驗證前,關聯應使用特定記錄進行初始化。請注意,如果記錄存在,不會執行可呼叫式物件。
:strict_loading
-
每次透過這個關聯載入關聯記錄時,強制載入。勁量
:ensuring_owner_was
-
指定要在所有者上呼叫的實體方法。方法必須傳回 true,關聯記錄才會在背景工作刪除。
:query_constraints
-
會作為複合外來金鑰。定義用於查詢關聯物件的欄位清單。這是選用選項。預設情況下,
Rails
會嘗試自動擷取值。設定值時,陣列
大小必須與關聯模型的主鍵或query_constraints
大小相符。
選項範例
belongs_to :firm, foreign_key: "client_of"
belongs_to :person, primary_key: "name", foreign_key: "person_name"
belongs_to :author, class_name: "Person", foreign_key: "author_id"
belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
class_name: "Coupon", foreign_key: "coupon_id"
belongs_to :attachable, polymorphic: true
belongs_to :project, -> { readonly }
belongs_to :post, counter_cache: true
belongs_to :comment, touch: true
belongs_to :company, touch: :employees_last_updated_at
belongs_to :user, optional: true
belongs_to :account, default: -> { company.account }
belongs_to :account, strict_loading: true
belongs_to :note, query_constraints: [:organization_id, :note_id]
來源:顯示 | 在 GitHub 上
# File activerecord/lib/active_record/associations.rb, line 1689 def belongs_to(name, scope = nil, **options) reflection = Builder::BelongsTo.build(self, name, scope, options) Reflection.add_reflection self, name, reflection end
has_and_belongs_to_many(name, scope = nil, **options, &extension) 連結
指定與另一類別的多對多關係。透過中介的連結表格,這會將兩個類別串聯起來。除非明確地將連結表格指定為選項,否則系統會使用類別名稱的字典順序來猜測。因此,開發人員與專案之間的連結將會給出「developers_projects」的預設連結表格名稱,因為「D」在字母順序上出現在「P」前面。請注意這個優先次序是使用 <
算子來計算 String
。這表示如果字串長度不同,且這兩個字串在比較到最短長度時相等,那麼較長的字串會被視為具有比較短的字串更高的字典優先次序。例如,你原本預期「paper_boxes」與「papers」表格會產生「papers_paper_boxes」的連結表格名稱,但實際上產生的連結表格名稱是「paper_boxes_papers」。請注意這個警告,若有需要,請使用自訂的 :join_table
選項。如果你的表格共用一個相同的前置詞,該前置詞只會出現在最前面一次。例如,「catalog_categories」與「catalog_products」表格會產生「catalog_categories_products」的連結表格名稱。
連結表格不應該具備主要金鑰,或與它關聯的模型。你必須手動產生連結表格,例如使用遷移
class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[8.0]
def change
create_join_table :developers, :projects
end
end
在這些欄位中加入索引,以加快連結程序,這也是個好主意。然而,在 MySQL 中,建議針對這兩個欄位加入複合索引,因為 MySQL 在查詢期間只會對每個表格使用一個索引。
加入下列檢索與查詢方法
collection
是充當傳遞至 name
引數的符號的替代字,因此在 has_and_belongs_to_many :categories
中,會加入 categories.empty?
。
collection
collection<<(object, ...)
-
透過在連結表格中 (
collection.push
和collection.concat
是此方法的別名) 建立關聯,將一個或多個物件加入集合中。請注意,這個運作會立即啟動 update SQL,不會等到在父物件中儲存或呼叫更新,除非父物件是新的記錄。 collection.delete(object, ...)
-
透過從連結表格中移除它們的關聯,將一個或多個物件從集合中移除。這不會移除物件。
collection.destroy(object, ...)
-
透過對連結表格中的每個關聯執行移除,覆寫任何相關選項,將一個或多個物件從集合中移除。這不會移除物件。
collection=objects
-
透過適當地刪除與加入物件,取代集合的內容。
collection_singular_ids
-
傳回關聯物件的 ID 陣列。
collection_singular_ids=ids
-
使用
ids
中主要金鑰所識別出的物件取代集合。 collection.clear
-
從集合中移除每個物件。這不會移除物件。
collection.empty?
-
如果沒有關聯物件,則傳回
true
。 collection.size
-
傳回關聯物件的數目。
collection.find(id)
-
尋找與
id
相符,且符合它必須與這個物件關聯的條件的關聯物件。使用與ActiveRecord::FinderMethods#find
相同的規則。 collection.exists?(...)
-
確認是否存在具有指定條件的關聯物件。使用與
ActiveRecord::FinderMethods#exists?
相同的規則。 collection.build(attributes = {})
-
傳回已使用
attributes
指示的集合類型新物件,並已透過連結表格連結至這個物件,但尚未儲存。 collection.create(attributes = {})
-
傳回已實例化的收集類型的新物件,透過連結表格連結至物件,並於儲存 (通過驗證) 後將儲存。
collection.reload
範例
class Developer < ActiveRecord::Base
has_and_belongs_to_many :projects
end
宣告 has_and_belongs_to_many :projects
將加入下列方法 (及更多)
developer = Developer.find(11)
project = Project.find(9)
developer.projects
developer.projects << project
developer.projects.delete(project)
developer.projects.destroy(project)
developer.projects = [project]
developer.project_ids
developer.project_ids = [9]
developer.projects.clear
developer.projects.empty?
developer.projects.size
developer.projects.find(9)
developer.projects.exists?(9)
developer.projects.build # similar to Project.new(developer_id: 11)
developer.projects.create # similar to Project.create(developer_id: 11)
developer.projects.reload
宣告中可能會包含一個 options
hash,用來指定關聯行為。
範圍
您可以傳遞第二個引數 scope
作為可呼叫的函式 (即程序或 lambda),以便在存取關聯收集時,擷取特定組的記錄,或自訂產生的查詢。
範圍範例
has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
has_and_belongs_to_many :categories, ->(post) {
where("default_category = ?", post.default_category)
}
延伸模組
extension
引數允許您傳遞區塊至 has_and_belongs_to_many
關聯中。這對於將新的尋找器、建立器及其他作為關聯一部分的工廠型態方法一併加入,非常有用。
延伸範例
has_and_belongs_to_many :contractors do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
選項
:class_name
-
指定關聯的類別名稱。僅在無法從關聯名稱推斷得知名稱,才使用此選項。例如
has_and_belongs_to_many :projects
預設會連結至 Project 類別,但實際類別名稱為 SuperProject 的話,就必須使用此選項來指定名稱。 :join_table
-
指定連結表格名稱 (若不想使用預設的語彙順序)。警告:如果您覆寫了任一類別的表格名稱,那麼為了讓
table_name
方法能夠作用,必須在任何has_and_belongs_to_many
宣告下方宣告該方法。 :foreign_key
-
指定用於關聯的外來金鑰。預設值會猜測為小寫此類別名稱,並加上「_id」後綴。因此,將 Person 類別與 Project 建立
has_and_belongs_to_many
關聯,則預設的:foreign_key
為「person_id」。設定
:foreign_key
選項會防止自動偵測關聯的反向,因此通常最好同時設定:inverse_of
選項。 :association_foreign_key
-
指定用於關聯中接收端關聯的外來金鑰。預設值會猜測為小寫關聯類別名稱,並加上「_id」後綴。因此,若 Person 類別對 Project 建立
has_and_belongs_to_many
關聯,則預設的:association_foreign_key
為「project_id」。 :validate
-
設為
true
時,會在儲存父物件時,驗證新增至關聯的新物件。預設值為true
。若希望確保關聯物件會在每次更新時重新驗證,請使用validates_associated
。 :autosave
-
若為
true
,儲存父物件時,會永遠儲存關聯物件,或在標示為毀損時毀損關聯物件。若為false
,則絕不儲存或毀損關聯物件。預設只儲存為新增記錄的關聯物件。請注意,
NestedAttributes::ClassMethods#accepts_nested_attributes_for
將設定:autosave
為true
。 :strict_loading
-
強制在每次透過這個關聯載入關聯記錄時,進行嚴格載入。
選項範例
has_and_belongs_to_many :projects
has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
has_and_belongs_to_many :nations, class_name: "Country"
has_and_belongs_to_many :categories, join_table: "prods_cats"
has_and_belongs_to_many :categories, -> { readonly }
has_and_belongs_to_many :categories, strict_loading: true
圖片來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/associations.rb, line 1870 def has_and_belongs_to_many(name, scope = nil, **options, &extension) habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self) builder = Builder::HasAndBelongsToMany.new name, self, options join_model = builder.through_model const_set join_model.name, join_model private_constant join_model.name middle_reflection = builder.middle_reflection join_model Builder::HasMany.define_callbacks self, middle_reflection Reflection.add_reflection self, middle_reflection.name, middle_reflection middle_reflection.parent_reflection = habtm_reflection include Module.new { class_eval <<-RUBY, __FILE__, __LINE__ + 1 def destroy_associations association(:#{middle_reflection.name}).delete_all(:delete_all) association(:#{name}).reset super end RUBY } hm_options = {} hm_options[:through] = middle_reflection.name hm_options[:source] = join_model.right_reflection.name [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k| hm_options[k] = options[k] if options.key? k end has_many name, scope, **hm_options, &extension _reflections[name].parent_reflection = habtm_reflection end
has_many(name, scope = nil, **options, &extension) 連結
指定一對多關聯。下列用於擷取並查詢關聯物件收集的方法,會一併加入
collection
是作為名稱引數傳遞的符號之佔位符,例如 has_many :clients
會新增 clients.empty?
等方法。
collection
collection<<(object, ...)
-
將一個或多個物件加入集合中,藉由設定其外鍵至集合中的主鍵。請注意,除非母物件為新記錄,否則此操作即刻執行更新 SQL,而不需等到母物件的儲存或更新呼叫。這也會執行關聯物件的驗證和回呼。
collection.delete(object, ...)
-
將一個或多個物件從集合中移除,藉由設定其外鍵為
NULL
。物件除了會被摧毀,如果其與dependent: :destroy
有關聯,也會被刪除,如果其與dependent: :delete_all
有關聯。如果使用
:through
選項,則預設會刪除 (而非將其設為空值) 連接記錄,但您可以指定dependent: :destroy
或dependent: :nullify
來覆寫此設定。 collection.destroy(object, ...)
-
將一個或多個物件從集合中移除,藉由執行
destroy
在每條記錄上,不考慮任何從屬選項,確保回呼會執行。如果使用
:through
選項,則會摧毀連接記錄,而不是物件本身。 collection=objects
-
適當地刪除並新增物件,取代集合內容。如果
:through
選項為 true,則連接模型中的回呼會被觸發,但摧毀回呼除外,因為預設會直接刪除。您可以指定dependent: :destroy
或dependent: :nullify
來覆寫此設定。 collection_singular_ids
-
傳回關聯物件之識別碼的陣列
collection_singular_ids=ids
-
以
ids
中主鍵所識別的物件取代集合。此方法會載入模型並呼叫collection=
。請參閱上方說明。 collection.clear
-
將集合中的每個物件移除。這會摧毀與
dependent: :destroy
有關聯的物件,如果與dependent: :delete_all
關聯,直接從資料庫中刪除,否則將其外鍵設定為NULL
。如果:through
選項為 true,不會在連接模型中呼叫任何摧毀回呼。連接模型會直接被刪除。 collection.empty?
-
如果沒有關聯物件,則傳回
true
。 collection.size
-
傳回關聯物件的數目。
collection.find(...)
-
依據與
ActiveRecord::FinderMethods#find
相同的規則找出關聯物件。 collection.exists?(...)
-
確認是否存在具有指定條件的關聯物件。使用與
ActiveRecord::FinderMethods#exists?
相同的規則。 collection.build(attributes = {}, ...)
-
傳回集合類型中一個或多個新的物件,已使用
attributes
執行實例化,並透過外鍵連結到此物件,但尚未儲存。 collection.create(attributes = {})
-
傳回集合類型中的新物件,已使用
attributes
執行實例化,透過外鍵連結到此物件,並且已經儲存 (如果它通過驗證)。注意:這只適用於資料庫中已存在的基礎模型,不適用於新的 (未儲存的) 記錄! collection.create!(attributes = {})
-
執行與
collection.create
相同的動作,但如果記錄無效會觸發ActiveRecord::RecordInvalid
。 collection.reload
範例
class Firm < ActiveRecord::Base
has_many :clients
end
宣告 has_many :clients
會新增以下方法 (以及更多)
firm = Firm.find(2)
client = Client.find(6)
firm.clients # similar to Client.where(firm_id: 2)
firm.clients << client
firm.clients.delete(client)
firm.clients.destroy(client)
firm.clients = [client]
firm.client_ids
firm.client_ids = [6]
firm.clients.clear
firm.clients.empty? # similar to firm.clients.size == 0
firm.clients.size # similar to Client.count "firm_id = 2"
firm.clients.find # similar to Client.where(firm_id: 2).find(6)
firm.clients.exists?(name: 'ACME') # similar to Client.exists?(name: 'ACME', firm_id: 2)
firm.clients.build # similar to Client.new(firm_id: 2)
firm.clients.create # similar to Client.create(firm_id: 2)
firm.clients.create! # similar to Client.create!(firm_id: 2)
firm.clients.reload
宣告也可以包含 options
hash,以指定關聯的行為類型。
範圍
您可以傳遞第二個引數 scope
作為可呼叫的函式 (即程序或 lambda),以便在存取關聯收集時,擷取特定組的記錄,或自訂產生的查詢。
範圍範例
has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
延伸模組
extension
參數允許您將區塊傳遞到 has_many
關聯。這對於新增新的查詢器、建立器和其他工廠類型的用於該關聯的方法很有用。
延伸範例
has_many :employees do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
選項
:class_name
-
指定關聯的類別名稱。只有在無法從關聯名稱推論出時才用得到。因此,預設
has_many :products
將連結到Product
類別,但如果真正的類別名稱為SpecialProduct
,您必須使用此選項指定它。 :foreign_key
-
指定關聯中使用的外來鍵。預設會猜測這會是小寫的類別名稱,並附加「_id」。因此,建立
has_many
關聯的 Person 類別會將「person_id」當作預設的:foreign_key
。設定
:foreign_key
選項會防止自動偵測關聯的反向,因此通常最好同時設定:inverse_of
選項。 :foreign_type
-
如果這是多型關聯,請指定用於儲存關聯物件類型的欄位。預設會猜測這是「as」選項中指定的「多型關聯」的名稱,並附加「_type」字尾。因此,定義
has_many :tags, as: :taggable
關聯的類別會將「taggable_type」當作預設的:foreign_type
。 :primary_key
-
指定用於關聯的主鍵的欄位名稱。預設為
id
。 :dependent
-
控制當擁有者物件被刪除時會對關聯物件發生什麼事。請注意,這些都是以 callback 實作,而 Rails 會依序執行 callback。因此,其他類似的 callback 可能會影響
:dependent
行為,而:dependent
行為可能會影響其他 callback。-
nil
不執行任何動作(預設)。 -
:destroy
會導致所有關聯物件也會被刪除。 -
:destroy_async
會在背景作業中刪除所有關聯物件。警告:如果關聯有資料庫外來鍵約束,請不要使用此選項。外來鍵約束動作會在與刪除其擁有者相同的交易中進行。 -
:delete_all
會導致所有關聯物件直接從資料庫中刪除(因此不會執行 callback)。 -
:nullify
會將外來鍵設定為NULL
。多型關聯的多型類型也會變為無效。不會執行Callback
。 -
:restrict_with_exception
會在有任何關聯記錄時引發 ActiveRecord::DeleteRestrictionError 例外。 -
:restrict_with_error
會在有任何關聯物件時將錯誤新增到擁有者。
如果與
:through
選項一起使用,則連結模式上的關聯必須是belongs_to
,而且會被刪除的記錄是連結記錄,而不是關聯記錄。如果在範圍關聯上使用
dependent: :destroy
,則只會刪除範圍物件。例如,如果 Post 模型定義has_many :comments, -> { where published: true }, dependent: :destroy
,而且對某個貼文呼叫destroy
,則只會刪除已發表的留言。這表示資料庫中的任何未發表的留言仍會包含指向現在已刪除貼文的外來鍵。 -
:counter_cache
-
此選項可以用於設定自訂名稱的
:counter_cache.
您只在於belongs_to
關聯上自訂:counter_cache
名稱時才需要這個選項。 :as
-
指定多型介面(請參閱
belongs_to
)。 :through
-
指定要透過哪個關聯來執行查詢。這可以是任何其他類型的關聯,包括其他
:through
關聯。:class_name
、:primary_key
和:foreign_key
的選項會被忽略,因為關聯會使用來源反映。如果 join 模型中的關聯是
belongs_to
,那麼集合可以修改,並且:through
模型中的記錄會根據實際情況自動新增或移除。否則集合為唯讀,因此你應該直接控制:through
關聯。如果你想要修改關聯(而不是僅從中讀取),那麼建議在 join 模型的來源關聯上設定
:inverse_of
選項。這樣可以建構關聯記錄,而且當這些記錄儲存後,會自動產生適當的 join 模型記錄。詳情請參閱 關聯加入模型 和 設定反向。 :disable_joins
-
指定是否要在關聯中略過加入。如果設為 true,將會產生多個查詢。請注意,在某些情況下,如果套用排序或限制,則會因為資料庫限制而在記憶體中進行該作業。此選項僅適用於
has_many :through
關聯,因為單獨使用has_many
時不會執行加入。 :source
-
指定
has_many
:through
查詢所使用的來源關聯名稱。僅在無法從關聯中推斷名稱時使用。has_many :subscribers, through: :subscriptions
會在 Subscription 中尋找:subscribers
或:subscriber
,除非有提供:source
。 :source_type
-
指定
has_many
:through
查詢所使用的來源關聯類型,其中來源關聯是多型態belongs_to
。 :validate
-
設為
true
時,會在儲存父物件時,驗證新增至關聯的新物件。預設值為true
。若希望確保關聯物件會在每次更新時重新驗證,請使用validates_associated
。 :autosave
-
如果為 true,則儲存上層物件時,總會儲存關聯物件或在標註為要毀損時毀損它們。如果為 false,則絕不會儲存或毀損關聯物件。預設情況下,只會儲存新的記錄的關聯物件。此選項實作成
before_save
回呼。由於回呼會依定義順序執行,因此可能需要在使用者定義的任何before_save
回呼中明確儲存關聯物件。請注意,
NestedAttributes::ClassMethods#accepts_nested_attributes_for
將設定:autosave
為true
。 :inverse_of
-
指定
belongs_to
關聯的名稱,該關聯在關聯物件上,而且是此has_many
關聯的反向。詳情請參閱 雙向關聯。 :extend
-
指定一個模組或模組陣列,會擴充到傳回的關聯物件中。可用於定義關聯的方法,特別是在它們應該在多個關聯物件之間共用時。
:strict_loading
-
當設為
true
時,會強制在每次透過此關聯載入關聯記錄時進行嚴格載入。 :ensuring_owner_was
-
指定要在所有者上呼叫的實體方法。方法必須傳回 true,關聯記錄才會在背景工作刪除。
:query_constraints
-
會作為複合外來金鑰。定義用於查詢關聯物件的欄位清單。這是選用選項。預設情況下,
Rails
會嘗試自動擷取值。設定值時,陣列
大小必須與關聯模型的主鍵或query_constraints
大小相符。 :index_errors
-
允許區分關聯記錄中的多個驗證錯誤,方法是在錯誤屬性名稱中包含索引,例如
roles[2].level
。當設為true
時,索引會根據關聯順序(也就是資料庫順序)進行,包括置於最後的尚未持續存在的新記錄。當設為:nested_attributes_order
時,索引會根據接受 nested attributes for 時巢狀屬性設定器所接收的記錄順序進行。 - :before_add
-
定義一個 關聯回呼,這個回呼會在物件新增到關聯集合之前觸發。
- :after_add
-
定義一個 關聯回呼,這個回呼會在物件新增到關聯集合之後觸發。
- :before_remove
-
定義一個 關聯回呼,這個回呼會在物件移除到關聯集合之前觸發。
- :after_remove
-
定義 關聯回呼,當一個物件從關聯集合中移除掉時執行。
選項範例
has_many :comments, -> { order("posted_on") }
has_many :comments, -> { includes(:author) }
has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
has_many :tracks, -> { order("position") }, dependent: :destroy
has_many :comments, dependent: :nullify
has_many :tags, as: :taggable
has_many :reports, -> { readonly }
has_many :subscribers, through: :subscriptions, source: :user
has_many :subscribers, through: :subscriptions, disable_joins: true
has_many :comments, strict_loading: true
has_many :comments, query_constraints: [:blog_id, :post_id]
has_many :comments, index_errors: :nested_attributes_order
has_one(name, scope = nil, **options) 連結
指定一對一與另一類別的關聯。只有當另一類別包含外來鍵時才應使用此方法。如果目前的類別包含外來鍵,則您應使用 belongs_to
。請參閱 Is it a belongs_to or has_one association? 以取得使用 has_one
和 belongs_to
時機的更多資訊。
將加入以下方法,以擷取及查詢單一關聯物件
association
是傳遞為 name
引數的符號的佔位符,因此 has_one :manager
將在其他方法中新增 manager.nil?
。
關聯
-
傳回關聯的物件。如果沒有找到物件,則傳回
nil
。 association=(associate)
-
指派關聯物件、擷取主鍵、將其設定為外來鍵並儲存關聯物件。為了避免資料庫不一致,當指派新的關聯物件時,永久刪除現存關聯物件,即使新的關聯物件未儲存至資料庫中。
build_association(attributes = {})
-
傳回關聯類型的新的物件,該物件搭配
attributes
執行實例化,並透過外來鍵與這個物件進行連結,但尚未儲存。 create_association(attributes = {})
-
傳回關聯類型的新的物件,該物件搭配
attributes
執行實例化,透過外來鍵與這個物件進行連結,且已經儲存(如果驗證通過)。 create_association!(attributes = {})
-
執行與
create_association
相同的動作,但在記錄無效時會引發ActiveRecord::RecordInvalid
。 reload_association
-
傳回關聯的物件,強制執行資料庫讀取。
reset_association
-
解除載入關聯的物件。下次存取會從資料庫中查詢該物件。
範例
class Account < ActiveRecord::Base
has_one :beneficiary
end
宣告 has_one :beneficiary
將新增以下方法(以及更多方法)
account = Account.find(5)
beneficiary = Beneficiary.find(8)
account.beneficiary # similar to Beneficiary.find_by(account_id: 5)
account.beneficiary = beneficiary # similar to beneficiary.update(account_id: 5)
account.build_beneficiary # similar to Beneficiary.new(account_id: 5)
account.create_beneficiary # similar to Beneficiary.create(account_id: 5)
account.create_beneficiary! # similar to Beneficiary.create!(account_id: 5)
account.reload_beneficiary
account.reset_beneficiary
範圍
您可以傳遞第二個引數 scope
作為可呼叫項目 (也即過程或 lambda),以在存取關聯的物件時取得特定的記錄或自訂產生的查詢。
範圍範例
has_one :author, -> { where(comment_id: 1) }
has_one :employer, -> { joins(:company) }
has_one :latest_post, ->(blog) { where("created_at > ?", blog.enabled_at) }
選項
宣告也可以包含 options
hash,以指定關聯的行為類型。
選項如下
:class_name
-
指定關聯的類別名稱。僅在無法從關聯名稱推論此名稱時使用。因此
has_one :manager
在預設情況下將連結至經理類別,但如果實際類別名稱為個人,您必須使用此選項指定。 :dependent
-
控制在關聯的擁有者被銷毀時關聯物件發生什麼事
-
nil
不執行任何動作(預設)。 -
:destroy
會導致關聯物件也一併銷毀 -
:destroy_async
會導致關聯物件在背景工作中銷毀。警告:如果關聯在資料庫中受外來鍵約束限制,請勿使用此選項。外來鍵約束動作會在刪除其擁有者的同一個交易中執行。 -
:delete
會導致關聯物件直接從資料庫中刪除(因此不會執行回呼) -
:nullify
會將外來鍵設定為NULL
。多形類型欄位在多形關聯中也會被設定為NULL
。不會執行回呼
。 -
:restrict_with_exception
會在有相關記錄時引發 ActiveRecord::DeleteRestrictionError 例外 -
:restrict_with_error
會在有相關物件時新增錯誤至擁有者
請注意,在使用
:through
選項時會忽略:dependent
選項。 -
:foreign_key
-
指定用於關聯的外來鍵。預設會猜測為小寫的此類別名稱,並附加上字尾 “_id”。因此,建立
has_one
關聯的個人類別會使用 “person_id” 作為預設:foreign_key
。設定
:foreign_key
選項會防止自動偵測關聯的反向,因此通常最好同時設定:inverse_of
選項。 :foreign_type
-
如果這是一個多形關聯,則指定用於儲存關聯物件類型的欄位。預設會猜測為在 “as” 選項中指定的關聯名稱,並附加上字尾 “_type”。因此,定義
has_one :tag, as: :taggable
關聯的類別會使用 “taggable_type” 作為預設:foreign_type
。 :primary_key
-
指定用於傳回用於關聯的主鍵的方法。預設為
id
。 :as
-
指定多型介面(請參閱
belongs_to
)。 :through
-
指定用於執行查詢的連接模型。由於關聯會使用來源反映,因此
:class_name
、:primary_key
和:foreign_key
選項皆被忽略。您只能透過連接模型對has_one
或belongs_to
關聯使用:through
查詢。如果 join 模型中的關聯是
belongs_to
,那麼集合可以修改,並且:through
模型中的記錄會根據實際情況自動新增或移除。否則集合為唯讀,因此你應該直接控制:through
關聯。如果你想要修改關聯(而不是僅從中讀取),那麼建議在 join 模型的來源關聯上設定
:inverse_of
選項。這樣可以建構關聯記錄,而且當這些記錄儲存後,會自動產生適當的 join 模型記錄。詳情請參閱 關聯加入模型 和 設定反向。 :disable_joins
-
指定是否要略過關聯的聯結。若設為 true,將會產生兩個以上的查詢。請注意,有時若套用順序或限制,將會因資料庫限制而在記憶體中進行。這個選項僅適用於
has_one :through
關聯,因為has_one
本身不會執行聯結。 :source
-
指定
has_one
:through
查詢使用的來源關聯名稱。僅在無法根據關聯推出名稱時使用。has_one :favorite, through: :favorites
將會尋找 Favorite 上的:favorite
,除非已提供:source
。 :source_type
-
指定
has_one
:through
查詢使用的來源關聯類型,其中來源關聯為多型態的belongs_to
。 :validate
-
設為
true
時,在儲存父物件時會驗證新增至關聯的新物件。預設為false
。如果您要確保關聯物件在每次更新時都會重新驗證,請使用validates_associated
。 :autosave
-
如果為
true
,在儲存父物件時,請務必儲存關聯物件或在標記為要銷毀時銷毀關聯物件。如果為false
,請勿儲存或銷毀關聯物件。預設情況下,僅在關聯物件是新記錄時才儲存。將這個選項設定為
true
時,除非使用validate: false
明確停用,否則也會對關聯物件啟用驗證。這是因為儲存具有無效關聯物件的物件將會失敗,因此任何關聯物件都會歷經驗證檢查。請注意,
NestedAttributes::ClassMethods#accepts_nested_attributes_for
將設定:autosave
為true
。 :touch
-
如果為 true,當此記錄儲存或銷毀時,關聯物件會被觸發 (將
updated_at
/updated_on
屬性設定為目前時間)。如果指定符號,會將此屬性與updated_at
/updated_on
屬性一起更新為目前時間。請注意,觸發時不會執行驗證,而且只有after_touch
、after_commit
和after_rollback
回呼會執行。 :inverse_of
-
指定關聯物件上
belongs_to
關聯的名稱,其為這個has_one
關聯的反向。請參閱 雙向關聯 以取得更多詳細資料。 :required
-
將這個設定設為
true
時,關聯也會經過存在驗證。這會驗證關聯本身,而非 ID。您可以使用:inverse_of
來避免在驗證期間執行額外的查詢。 :strict_loading
-
每次透過這個關聯載入關聯記錄時,強制載入。勁量
:ensuring_owner_was
-
指定要在所有者上呼叫的實體方法。方法必須傳回 true,關聯記錄才會在背景工作刪除。
:query_constraints
-
會作為複合外來金鑰。定義用於查詢關聯物件的欄位清單。這是選用選項。預設情況下,
Rails
會嘗試自動擷取值。設定值時,陣列
大小必須與關聯模型的主鍵或query_constraints
大小相符。
選項範例
has_one :credit_card, dependent: :destroy # destroys the associated credit card
has_one :credit_card, dependent: :nullify # updates the associated records foreign
# key value to NULL rather than destroying it
has_one :last_comment, -> { order('posted_on') }, class_name: "Comment"
has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person"
has_one :attachment, as: :attachable
has_one :boss, -> { readonly }
has_one :club, through: :membership
has_one :club, through: :membership, disable_joins: true
has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
has_one :credit_card, required: true
has_one :credit_card, strict_loading: true
has_one :employment_record_book, query_constraints: [:organization_id, :employee_id]
來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/associations.rb, line 1498 def has_one(name, scope = nil, **options) reflection = Builder::HasOne.build(self, name, scope, options) Reflection.add_reflection self, name, reflection end