【问题标题】:ActiveSupport::Concern and alias_method_chainActiveSupport::Concern 和 alias_method_chain
【发布时间】:2014-04-25 15:13:18
【问题描述】:

这是我遇到的一个小问题。请注意,这是一个简化的示例。 假设我有一个包含多个实例方法的类,我想使用 ActiveSupport::Concern 记录其中一个实例方法:

class Car
  include LogStartEngine

  def start_engine
    # useful thing
  end

  def check_oil
    # useful thing
  end

  def open_doors
    # useful thing
  end      
end

这是我首先提出的问题:

module LogStartEngine
  extend ActiveSupport::Concern

  included do
    alias_method_chain :start_engine, :logging
  end

  def start_engine_with_logging
    Rails.logger.info("Starting engine!")

    start_engine_without_logging

    Rails.logger.info("Engine started!")
  end
end

但这会导致

  NameError: undefined method `start_engine' for class `Car'
    from /Users/david/.gem/ruby/1.9.3/gems/activesupport-4.0.3/lib/active_support/core_ext/module/aliasing.rb:32:in `alias_method'

这是可以理解的,因为当包含LogStartEngine 时,类Car 没有任何称为start_engine 的方法。

我知道我可以在方法 start_engine 之后放置 include LogStartEngine 来解决这个问题,但我想保留这个语句。

所以约束是:

  • 只记录方法start_engine,而不是所有方法。
  • Car 只需要包含LogStartEngine 关注。我想避免调用关注点添加的任何自定义帮助方法,例如log_method :start_engine
  • 我想将include LogStartEngine 语句保留在原处。我不希望它位于方法 start_engine 下方或课程末尾。
  • 这是使用 Ruby 1.9。所以Module#prepend 不是一个有效的解决方案:)

【问题讨论】:

    标签: ruby-on-rails ruby


    【解决方案1】:

    这是个老问题,但我找到了答案,我为某人写信。

    module LogStartEngine
      extend ActiveSupport::Concern
    
      define_method :start_engine_with_logging do
        Rails.logger.info("Starting engine!")
        start_engine_without_logging
        Rails.logger.info("Engine started!")
      end
    
      included do
        alias_method_chain :start_engine, :logging
      end
    end
    

    define_method 是这种方法的重点,它在包含时动态定义方法(alias_method_chain 之前)

    【讨论】:

      【解决方案2】:

      经过一些试验,这是我的解决方案:

      module LogStartEngine
        extend ActiveSupport::Concern
      
        module ClassMethods
          def method_added(_)
            unless instance_methods.include?(:start_engine_without_logging)
              alias_method_chain :start_engine, :logging
            end
          end
        end
      
        def start_engine_with_logging
          Rails.logger.info("Starting engine!")
      
          start_engine_without_logging
      
          Rails.logger.info("Engine started!")
        end
      end
      

      我的子问题是:还有其他方法可以实现吗?

      【讨论】:

        【解决方案3】:

        另一种方法是使用委托或使用http://www.ruby-doc.org/stdlib-2.0/libdoc/forwardable/rdoc/Forwardable.html - 然后你可以组合你的对象并将它们组合起来,我也假设你可以使用method_missing 或类似的东西来提供一个“自动”记录器。

        class Car
          def start_engine
            # wrooom
          end
        end
        
        class CarWithLogging
          attr_reader :car
        
          def initialize(car)
            @car = car
          end
        
          def start_engine
            Rails.logger.info "starting engine"
            car.start_engine
            Rails.logger.info "engine started"
          end
        end
        
        car = CarWithLogging.new(Car.new)
        car.start_engine
        

        更新:作为替代方案,您可以使用 Ruby 的 prepend(仅从 2.0 起可用),因此实际上不需要 AS::Concern。

        class Car
          prepend CarLogging
          def start_engine; end
        end
        
        module CarLogging
          def start_engine
            puts "before"
            super
            puts "after"
          end
        end
        

        【讨论】:

        • 正如我在约束中所说,我使用的是 Ruby 1.9,所以prepend 不是一个选项,但Forwardable 是一个有趣的选项。
        • 啊,我的错 :) prepend 会很好 - 我想那是 alias_method_chainForwardable/Proxy...
        猜你喜欢
        • 2013-05-26
        • 2012-09-14
        • 2015-12-29
        • 1970-01-01
        • 2013-02-01
        • 2014-07-04
        • 1970-01-01
        • 2020-09-21
        • 2019-07-20
        相关资源
        最近更新 更多