【问题标题】:How to use concerns in Rails 4如何在 Rails 4 中使用关注点
【发布时间】:2013-01-10 14:21:45
【问题描述】:

默认的 Rails 4 项目生成器现在在控制器和模型下创建目录“关注”。我找到了一些关于如何使用路由问题的解释,但没有找到关于控制器或模型的解释。

我很确定这与社区当前的“DCI趋势”有关,并想尝试一下。

问题是,我应该如何使用此功能,是否有关于如何定义命名/类层次结构以使其工作的约定?如何在模型或控制器中包含关注点?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 dci


    【解决方案1】:

    所以我自己发现了。它实际上是一个非常简单但功能强大的概念。它与代码重用有关,如下例所示。基本上,这个想法是提取常见和/或特定于上下文的代码块,以清理模型并避免它们变得过于臃肿和混乱。

    作为一个例子,我将举一个众所周知的模式,可标记模式:

    # app/models/product.rb
    class Product
      include Taggable
    
      ...
    end
    
    # app/models/concerns/taggable.rb
    # notice that the file name has to match the module name 
    # (applying Rails conventions for autoloading)
    module Taggable
      extend ActiveSupport::Concern
    
      included do
        has_many :taggings, as: :taggable
        has_many :tags, through: :taggings
    
        class_attribute :tag_limit
      end
    
      def tags_string
        tags.map(&:name).join(', ')
      end
    
      def tags_string=(tag_string)
        tag_names = tag_string.to_s.split(', ')
    
        tag_names.each do |tag_name|
          tags.build(name: tag_name)
        end
      end
    
      # methods defined here are going to extend the class, not the instance of it
      module ClassMethods
    
        def tag_limit(value)
          self.tag_limit_value = value
        end
    
      end
    
    end
    

    因此,按照产品示例,您可以将 Taggable 添加到您想要的任何类并共享其功能。

    DHH 很好地解释了这一点:

    在 Rails 4 中,我们将邀请程序员使用关注点与 默认 app/models/concerns 和 app/controllers/concerns 目录 它们自动成为加载路径的一部分。与 ActiveSupport::Concern 包装器,它的支持就足够了 轻量级保理机制大放异彩。

    【讨论】:

    • DCI 处理上下文,使用角色作为标识符将心智模型/用例映射到代码,并且不需要使用包装器(方法在运行时直接绑定到对象),因此具有真的与 DCI 无关。
    • @yagooar 即使在运行时包含它也不会成为 DCI。如果您希望查看 ruby​​ DCI 示例实现。查看fulloo.infogithub.com/runefs/Moby 中的示例,或了解如何在Ruby 中使用栗色进行DCI 以及DCI 是什么runefs.com(DCI 是什么。是我最近刚开始的一系列帖子)
    • @RuneFS && ciscoheat 你说的都是对的。我只是再次分析了文章和事实。而且,我上周末参加了一个 Ruby 会议,其中一个演讲是关于 DCI 的,最后我对它的哲学有了更多的了解。更改了文本,因此根本没有提及 DCI。
    • 值得一提(并且可能包括在示例中)类方法应该定义在一个特别命名的模块 ClassMethods 中,并且该模块由基类扩展为 ActiveSupport::Concern ,也是。
    • 谢谢你的这个例子,主要是因为我很笨,在 ClassMethods 模块中用 self.whatever 定义了我的类级别方法,这不起作用 =P
    【解决方案2】:

    This post 帮助我理解了担忧。

    # app/models/trader.rb
    class Trader
      include Shared::Schedule
    end
    
    # app/models/concerns/shared/schedule.rb
    module Shared::Schedule
      extend ActiveSupport::Concern
      ...
    end
    

    【讨论】:

    【解决方案3】:

    我一直在阅读有关使用 模型问题 对脂肪模型进行皮肤缩小以及干燥模型代码的文章。以下是举例说明:

    1) 干燥型号代码

    考虑文章模型、事件模型和评论模型。一篇文章或一个事件有许多 cmets。评论属于文章或事件。

    传统上,模型可能如下所示:

    评论模型:

    class Comment < ActiveRecord::Base
      belongs_to :commentable, polymorphic: true
    end
    

    文章型号:

    class Article < ActiveRecord::Base
      has_many :comments, as: :commentable 
    
      def find_first_comment
        comments.first(created_at DESC)
      end
    
      def self.least_commented
       #return the article with least number of comments
      end
    end
    

    事件模型

    class Event < ActiveRecord::Base
      has_many :comments, as: :commentable 
    
      def find_first_comment
        comments.first(created_at DESC)
      end
    
      def self.least_commented
       #returns the event with least number of comments
      end
    end
    

    我们可以注意到,有一段重要的代码是 Event 和 Article 共有的。使用关注点,我们可以在单独的可注释模块中提取此公共代码。

    为此在 app/models/concerns 中创建一个 commentable.rb 文件。

    module Commentable
      extend ActiveSupport::Concern
    
      included do
        has_many :comments, as: :commentable
      end
    
      # for the given article/event returns the first comment
      def find_first_comment
        comments.first(created_at DESC)
      end
    
      module ClassMethods
        def least_commented
          #returns the article/event which has the least number of comments
        end
      end
    end
    

    现在你的模型看起来像这样:

    评论模型:

    class Comment < ActiveRecord::Base
      belongs_to :commentable, polymorphic: true
    end
    

    文章型号:

    class Article < ActiveRecord::Base
      include Commentable
    end
    

    事件模型:

    class Event < ActiveRecord::Base
      include Commentable
    end
    

    2) 修肤脂肪模型。

    考虑一个事件模型。一个活动有很多参加者和 cmets。

    通常,事件模型可能如下所示

    class Event < ActiveRecord::Base   
      has_many :comments
      has_many :attenders
    
    
      def find_first_comment
        # for the given article/event returns the first comment
      end
    
      def find_comments_with_word(word)
        # for the given event returns an array of comments which contain the given word
      end 
    
      def self.least_commented
        # finds the event which has the least number of comments
      end
    
      def self.most_attended
        # returns the event with most number of attendes
      end
    
      def has_attendee(attendee_id)
        # returns true if the event has the mentioned attendee
      end
    end
    

    具有许多关联的模型倾向于积累越来越多的代码并变得难以管理。关注点提供了一种对脂肪模块进行皮肤化的方法,使它们更加模块化和易于理解。

    可以使用以下关注点重构上述模型: 在 app/models/concerns/event 文件夹中创建 attendable.rbcommentable.rb 文件

    attendable.rb

    module Attendable
      extend ActiveSupport::Concern
    
      included do 
        has_many :attenders
      end
    
      def has_attender(attender_id)
        # returns true if the event has the mentioned attendee
      end
    
      module ClassMethods
        def most_attended
          # returns the event with most number of attendes
        end
      end
    end
    

    commentable.rb

    module Commentable
      extend ActiveSupport::Concern
    
      included do 
        has_many :comments
      end
    
      def find_first_comment
        # for the given article/event returns the first comment
      end
    
      def find_comments_with_word(word)
        # for the given event returns an array of comments which contain the given word
      end
    
      module ClassMethods
        def least_commented
          # finds the event which has the least number of comments
        end
      end
    end
    

    现在使用关注,你的事件模型减少到

    class Event < ActiveRecord::Base
      include Commentable
      include Attendable
    end
    

    * 在使用关注点时,建议使用基于“域”的分组而不是“技术”分组。基于域的分组就像“可评论”、“可拍照”、“可参加”。技术分组将意味着“ValidationMethods”、“FinderMethods”等

    【讨论】:

    • 所以关注点只是使用继承或接口或多重继承的一种方式?创建一个公共基类并从该公共基类子类化有什么问题?
    • 确实@Chloe,我有些地方红了,带有“关注”目录的Rails应用程序实际上是“关注”......
    • 您可以使用“包含”块来定义所有方法,包括:类方法(使用def self.my_class_method)、实例方法以及类范围内的方法调用和指令。不需要module ClassMethods
    • 我担心的问题是它们直接向模型添加了功能。因此,如果两个关注点都实现了add_item,例如,你就完蛋了。我记得当一些验证器停止工作时,我认为 Rails 坏了,但有人在关注中实现了any?。我提出了一个不同的解决方案:像使用不同语言的界面一样使用关注点。它没有定义功能,而是定义了对处理该功能的单独类实例的引用。然后你有更小、更整洁的类来做一件事......
    • @aaditi_jain :请更正小改动以避免误解。即“在app/models/concerns/event 文件夹中创建一个attendable.rd 和commentable.rb 文件”-->attendable.rd 必须是attendable.rb 谢谢
    【解决方案4】:

    关注制作文件filename.rb

    例如,我希望在我的应用程序中存在 create_by 属性的地方将值更新为 1,而对于 updated_by 则为 0

    module TestConcern 
      extend ActiveSupport::Concern
    
      def checkattributes   
        if self.has_attribute?(:created_by)
          self.update_attributes(created_by: 1)
        end
        if self.has_attribute?(:updated_by)
          self.update_attributes(updated_by: 0)
        end
      end
    
    end
    

    如果你想在行动中传递参数

    included do
       before_action only: [:create] do
         blaablaa(options)
       end
    end
    

    然后像这样包含在你的模型中:

    class Role < ActiveRecord::Base
      include TestConcern
    end
    

    【讨论】:

      【解决方案5】:

      值得一提的是,许多人认为使用关注点是个坏主意。

      1. like this guy
      2. and this one

      一些原因:

      1. 幕后发生了一些黑暗的魔法 - 问题正在修补 include 方法,有一个完整的依赖处理系统 - 对于琐碎的旧 Ruby 混合模式来说太复杂了。
      2. 你的课也同样枯燥。如果你把 50 个公共方法塞进各个模块并包含它们,你的类仍然有 50 个公共方法,只是你隐藏了代码味道,有点把你的垃圾放在抽屉里。
      3. 代码库实际上更难处理所有这些问题。
      4. 您确定团队中的所有成员都对真正应该用什么来代替关注有相同的理解吗?

      担忧很容易让自己受伤,小心它们。

      【讨论】:

      • 我知道 SO 不是这个讨论的最佳场所,但是还有什么其他类型的 Ruby mixin 可以让你的课程保持干爽?似乎您的论点中的原因#1和#2是相反的,除非您只是为更好的OO设计,服务层或其他我缺少的东西提出理由? (我不反对——我建议添加替代品会有所帮助!)
      • 使用github.com/AndyObtiva/super_module 是一种选择,使用良好的旧ClassMethods 模式是另一种选择。并且使用更多对象(如服务)来清晰地分离关注点绝对是要走的路。
      • 投反对票,因为这不是问题的答案。这是一个意见。我确信这是一种观点,但它不应该是对 StackOverflow 上问题的回答。
      • @Adam 这是一个固执己见的答案。想象一下有人会问如何在 Rails 中使用全局变量,肯定会提到有更好的方法来做事(即 Redis.current 与 $redis)对于主题启动器可能是有用的信息?软件开发本质上是一门固执己见的学科,无法绕过它。事实上,我将意见视为答案和讨论,在 stackoverflow 上哪个答案一直是最好的,这是一件好事
      • 当然,将其与您对问题的答案一起提及似乎没问题。不过,您的回答中没有任何内容实际上回答了 OP 的问题。如果你想做的只是警告某人为什么他们不应该使用关注点或全局变量,那么这将是一个很好的评论,你可以添加到他们的问题中,但这并不是一个很好的答案。
      【解决方案6】:

      我觉得这里的大多数示例都展示了module 的力量,而不是ActiveSupport::Concern 如何为module 增加价值。

      示例 1: 更具可读性的模块。

      所以不用担心典型的module 会是什么样子。

      module M
        def self.included(base)
          base.extend ClassMethods
          base.class_eval do
            scope :disabled, -> { where(disabled: true) }
          end
        end
      
        def instance_method
          ...
        end
      
        module ClassMethods
          ...
        end
      end
      

      使用ActiveSupport::Concern重构之后。

      require 'active_support/concern'
      
      module M
        extend ActiveSupport::Concern
      
        included do
          scope :disabled, -> { where(disabled: true) }
        end
      
        class_methods do
          ...
        end
      
        def instance_method
          ...
        end
      end
      

      您会看到实例方法、类方法和包含块不那么混乱。担忧会为您适当地注入它们。这是使用ActiveSupport::Concern 的优势之一。


      示例 2: 优雅地处理模块依赖关系。

      module Foo
        def self.included(base)
          base.class_eval do
            def self.method_injected_by_foo_to_host_klass
              ...
            end
          end
        end
      end
      
      module Bar
        def self.included(base)
          base.method_injected_by_foo_to_host_klass
        end
      end
      
      class Host
        include Foo # We need to include this dependency for Bar
        include Bar # Bar is the module that Host really needs
      end
      

      在这个例子中BarHost 真正需要的模块。但是由于Bar 依赖于Foo,所以Host 类必须依赖include Foo(但是等等,为什么Host 想知道Foo?可以避免吗?)。

      所以Bar 到处添加依赖。而且包含的顺序在这里也很重要。这给庞大的代码库增加了很多复杂性/依赖性。

      ActiveSupport::Concern重构后

      require 'active_support/concern'
      
      module Foo
        extend ActiveSupport::Concern
        included do
          def self.method_injected_by_foo_to_host_klass
            ...
          end
        end
      end
      
      module Bar
        extend ActiveSupport::Concern
        include Foo
      
        included do
          self.method_injected_by_foo_to_host_klass
        end
      end
      
      class Host
        include Bar # It works, now Bar takes care of its dependencies
      end
      

      现在看起来很简单。

      如果你在想为什么我们不能在Bar 模块本身中添加Foo 依赖?这是行不通的,因为method_injected_by_foo_to_host_klass 必须注入一个包含Bar 而不是Bar 模块本身的类中。

      来源: Rails ActiveSupport::Concern

      【讨论】:

      • 谢谢。我开始想知道他们的优势是什么......
      • FWIW 这大致是从the docs 复制粘贴。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-10
      • 1970-01-01
      • 1970-01-01
      • 2017-05-08
      相关资源
      最近更新 更多