分享 Rails inverse_of 研究

hw676018683 · 2018年03月31日 · 最后由 xinxin 回复于 2018年04月17日 · 1288 次阅读
本帖已被设为精华帖!

作用

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,为啥要加上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

有四种情况:

  1. 主动设置inverse_of为false,也就是上面我们测试的情况
  2. 关系类型不是 [:has_many, :has_one, :belongs_to] 其中一种,就是去除了has_and_belongs_to_many
  3. 关系类型的options不能包含 [:conditions, :through, :foreign_key]
    • conditions: 老版本的Rails才有的,不用管
    • through: 通过第三表关联的,也不用去操心
    • foreign_key: 指定了外键的
  4. 设置了scope,比如 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,如有不对,望能指出,共同进步

共收到 1 条回复
huacnlee 将本帖设为了精华贴 04月02日 10:15

加了 scope也可以的

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册