🚨 Rails5 以后,inverse_of
已经做了不少变更,这篇帖子的内容可能已经不在符合现在的情况
最近在使用 关联 的时候,由于一点手误遇到了些问题,于是花了一下午时间来仔细读了 Guide 中关于 Active Record Associations 的部分。在看到 inverse_of
时,感觉自己突然一下就懵了。
我对 inverse_of
的困惑并不是在实际使用中产生的,即使不了解它也能在项目中愉快的玩耍,这似乎又旁证了 Rails 是一个很智能的框架。
不过它的 Guide 文档里这一部分就有些描述不清了(或说自相矛盾?)。关于 inverse_of
的功用倒是没有什么疑惑,只是被这混乱的文档弄得不知道哪些情况下需要去显式的声明 inverse_of
, 哪些情况下 inverse_of
又是无效果的。
Guide 中关于 inverse_of
的解释:http://guides.rubyonrails.org/association_basics.html#bi-directional-associations
其中有两处说明让我很费解:
其一:
There are a few limitations to inverse_of support:
- They do not work with :through associations.
- They do not work with :polymorphic associations.
- They do not work with :as associations. 4. For belongs_to associations, has_many inverse associations are ignored.
按第四条所说的,has_many 的关联是无效的,但是 Guide 中的栗子便是使用的 has_many, 而且很好的证明了 inverse_of 的效果。
【2016.6.17】这个其实没有疑问,这是指在 has_many / belongs_to 关联的两端都声明 inverse_of,只会按 belongs_to 一方的声明处理
其二:
Every association will attempt to automatically find the inverse association and set the :inverse_of option heuristically (based on the association name). Most associations with standard names will be supported. However, associations that contain the following options will not have their inverses set automatically:
- :conditions
- :through
- :polymorphic
- :foreign_key
按这个说法,只要是按约定命名的 关联 会自动加上 inverse_of, 那么,演示用例是按约定命名的吧,也不属于下面声明的四种情况,为什么加与不加是有差别的?
【2016.6.17】这个其实也没有疑问,这是因为 Rails 版本变迁,我当时看得文档没来得及更新导致的。文章后面部分已经解释了这个问题,这里提前把结论贴一下。
验证环境 Rails 版本:4.2.1
定义模型:
# a.rb
class A < ActiveRecord::Base
has_many :b
end
# b.rb
class B < ActiveRecord::Base
belongs_to :a
end
测试结果:
2.2.1 :001 > a = A.create :name => 'ichou'
=> #<A id: 2, name: "ichou", created_at: "2015-04-04 06:41:41", updated_at: "2015-04-04 06:41:41">
2.2.1 :002 > b1 = B.create :name => 'kindle', :a => a
=> #<B id: 3, name: "kindle", a_id: 2, created_at: "2015-04-04 06:42:45", updated_at: "2015-04-04 06:42:45">
2.2.1 :003 > b2 = B.create :name => 'Air', :a => a
=> #<B id: 4, name: "Air", a_id: 2, created_at: "2015-04-04 06:43:10", updated_at: "2015-04-04 06:43:10">
2.2.1 :004 > a.name.object_id
=> 70264637654120
2.2.1 :005 > b1.a.name.object_id
=> 70264637654120
2.2.1 :006 > b2.a.name.object_id
=> 70264637654120
object_id 全都一样,说明 inverse_of
已经被启用了。事实上,即使严格按照 Guide 的案例来做,你也会发现结果全是 True,而不是 Guide 所说的结果。
结论:在 4.1+ 的 Rails 中,即使不手动声明 inverse_of
,has_many 关联也会自动创建,而且是有效的!
inverse_of
的作用在于关联模型间共用实例,而不是让不同的查询在内存中存在多份 Copies.
实际运用中可以带来两个好处,一是减少数据库查询;二是在对 关联对象 修改数据后写入数据前,保证从任何一方取得的值都是最新的。
从 4.1 开始,基本的关联类型(has_many, has_one, belongs_to),若按约定命名,不需要再手动设定 inverse_of
(当时的)Guide 的相关用例是有问题的,或者说是适用于老版本的 Rails,而不是当前版本。
因为给 basic associations\*
自动添加 inverse_of
是在 Rails 4.1 加入的特性。
事实上,关于 has_many 的那个例子,在 4.1 及以后的版本中已经不能复现了。
inverse_of
已经支持 has_many 是从 3.2.1 开始的,4.1 开始支持自动添加 inverse_of
, 但是 has_many :through 仍然需要显式声明 inverse_of
使用没有持久化的关联对象时,根据需要使用 inverse_of,否则反向调用会得到 nil 或者还未更新的 对象。详见 topics/6426
感谢 社区 和 stackoverflow 上的大大们,感谢微信群 成都 Ruby 群 里的星哥。
比较新手向的帖子,若有纰漏,烦请指正