【问题标题】:ActiveRecord Associations - Where to put functionality?ActiveRecord 关联 - 在哪里放置功能?
【发布时间】:2013-06-27 22:01:33
【问题描述】:

我正在为以下情况寻找一些最佳实践建议。

我有以下骨架 ActiveRecord 模型:

# user.rb
class User < ActiveRecord::Base
  has_many :country_entries, dependent: destroy
end

# country_entry.rb
class CountryEntry < ActiveRecord::Base
  belongs_to :user
  validates :code, presence: true
end

现在假设我需要为特定用户获取CountryEntry 代码的逗号分隔列表。问题是,我把这个方法放在哪里?有两种选择:

# user.rb
#...
  def country_codes
    self.country_entries.map(&:code)
  end
#...

-或-

# country_entry.rb
#...
  def self.codes_for_user(user)
    where(user_id: user.id).map(&:code)
  end
#...

因此 API 将是:
@current_user.country_codes -或- CountryEntry.codes_for_user(@current_user)


似乎将代码放在country_entry.rb 中会更加解耦所有内容,但它会使 API 更难看。关于这个问题的任何一般或个人经验的最佳做法?

【问题讨论】:

    标签: ruby-on-rails ruby activerecord sinatra


    【解决方案1】:
    1. 实例方法VS类方法:如果方法是针对实例的,当然最好是实例方法。

    2. 在用户模型中 VS 在 Coutry 模型中:用户模型获胜。得墨忒耳法则仅在 Ruby 中建议一个点。如果你有机会这样做,当然更好。

    结论:你的第一种方法获胜。

    # user.rb
    def country_codes
      self.country_entries.map(&:code)
    end
    

    添加:得墨忒耳法则参考

    http://en.wikipedia.org/wiki/Law_of_Demeter

    http://rails-bestpractices.com/posts/15-the-law-of-demeter

    http://devblog.avdi.org/2011/07/05/demeter-its-not-just-a-good-idea-its-the-law/

    【讨论】:

    • 当我看到您添加了答案时,我正要发布完全相同的内容。 +1 是一个伟大的头脑:)
    • 感谢@iain,我只是追随伟大的思想,但不拥有它:) 为共同意见欢呼。
    • Billy,你能补充一下你的原则吗?不确定你到底指的是什么。
    • @EricPlaton,其实原理是“得墨忒耳法则”,虽然基本类似于单一责任原则。检查我的更新以供参考。
    • 我同意你的这个小例子。我唯一的问题是,如果这变得更复杂一点,用户模型会“了解”更多关于 country_entry 模型的信息。
    【解决方案2】:

    现在这确实是一个有趣的问题。它有很多答案;-)

    根据您最初的问题,我建议您将代码放在关联本身中

    class User < ActiveRecord::Base
      has_many :country_entries do
        def codes
          proxy_association.owner.country_entries.map(&:code)
        end
      end
    end
    

    所以你可以做这样的事情

    list_of_codes = a_user.country_entries.codes
    

    现在显然这违反了Law of Demeter。 因此,最好建议您在 User 对象上提供这样的方法

    class User < ActiveRecord::Base
      has_many :country_entries do
        def codes
          proxy_association.owner.country_entries.map(&:code)
        end
      end
    
      def country_codes
        self.country_entries.codes
      end
    end
    

    显然,Rails 世界中没有人关心得墨忒耳法则,所以对此持保留态度。

    至于将代码放入 CountryEntry 类中,我不确定您为什么要这样做。如果您只能与用户一起查找国家代码,我认为不需要创建类方法。无论如何,只有当您手头有用户时,您才能查看该列表。

    如果许多不同的对象可以有一个 country_entries 关联,那么将它作为类方法放入 CountryEntry 是有意义的。

    我最喜欢将 LOD 和用于重用目的的类方法的组合。

    class User < ActiveRecord::Base
      has_many :country_entries
    
      def country_codes
        CountryEntry.codes_for_user(self)
      end
    end
    
    class CountryEntry < ActiveRecord::Base
      belongs_to :user
      validates :code, presence: true
    
      def self.codes_for_user(some_id)
         where(ref_id: some_id).map(&:code)
      end
    end
    

    【讨论】:

      【解决方案3】:

      就 API 开发人员从这两个提案中获得的收益而言,添加到用户模型中似乎非常简单。鉴于问题:

      现在假设我需要获取特定用户的 CountryEntry 代码的逗号分隔列表。

      上下文由一个用户组成,我们要为其获取代码列表。自然的“入口点”似乎是一个用户对象。

      查看问题的另一种方式是从责任角度来看(因此链接到 Demeter 的 @robkuz 条目)。 CountryEntry 实例负责提供其代码(可能还有其他一些东西)。 CountryEntry 类基本上负责提供所有实例共有的属性和方法,仅此而已(好吧)。获取逗号分隔代码列表是 CountryEntry 实例的一种特殊用法,显然只有 User 对象关心。在这种情况下,责任属于当前用户对象。旁观者眼中的价值……

      这与线程上的大多数答案是一致的,尽管在迄今为止的解决方案中,您没有得到一个以逗号分隔的代码列表,而是一个代码数组。


      在性能方面,请注意,由于延迟评估,可能也存在差异。请注意——更熟悉 ActiveRecord 的人可以对此发表评论!

      【讨论】:

        【解决方案4】:

        我认为@current_user.country_codes 在这种情况下是一个更好的选择,因为它会更容易在您的代码中使用。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-07-22
          • 1970-01-01
          相关资源
          最近更新 更多