class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post
end
测试:
comment = post.comments.first
comment.post
当has_many :comments, inverse_of: false
Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC LIMIT $2 [["post_id", 1], ["LIMIT", 1]]
Post Load (0.2ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
当has_many :comments, inverse_of: :post
Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = $1 ORDER BY "comments"."id" ASC LIMIT $2 [["post_id", 1], ["LIMIT", 1]]
inverse_of: false
时,comment.post
多了一条 sql
上面在测试没有 inverse_of,为啥要加上inverse_of: false
呢
因为 Rails 会去尝试计算出 inverse_of 的值
相关代码 (v5.2.0.rc2):
# activerecord/lib/active_record/reflection.rb
def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
begin
reflection = klass._reflect_on_association(inverse_name)
rescue NameError
# Give up: we couldn't compute the klass type so we won't be able
# to find any associations either.
reflection = false
end
if valid_inverse_reflection?(reflection)
return inverse_name
end
end
end
那什么情况 Rails 不去自动计算出 inverse_of 的值呢?
相关代码 (v5.2.0.rc2):
# activerecord/lib/active_record/reflection.rb
def can_find_inverse_of_automatically?(reflection)
reflection.options[:inverse_of] != false &&
VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
!INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
!reflection.scope
end
有四种情况:
false
,也就是上面我们测试的情况[:has_many, :has_one, :belongs_to]
其中一种,就是去除了has_and_belongs_to_many
[:conditions, :through, :foreign_key]
has_many :comments, -> { order(id: :asc) }
, Rails 的解释如下(不是很明白):Anything with a scope can additionally ruin our attempt at finding an inverse, so we exclude reflections with scopes.
Rails 是根据 class 的 name(去除了 namespace) 或者 as option(多态),去计算 inverse_of
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
最近遇到 Rails 的一个 bug,所以研究了一下 inverse_of
更新
nested attributes
时,touch: true
没有生效
这个 bug 还未解决,但可以通过加 inverse_of 解决,相关 https://github.com/rails/rails/pull/29132
以上研究希望能帮助大家更理解 inverse_of,如有不对,望能指出,共同进步