ActiveSupport :: Concern和alias_method_chain

时间:2014-04-25 15:13:18

标签: ruby-on-rails ruby

这是我遇到的一个小问题。请注意,这是一个简化的示例。 假设我有一个包含多个实例方法的类,我想使用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的方法。

我知道我可以在方法include LogStartEngine之后解决此问题start_engine,但我想将此声明保留在原来的位置。

所以约束是:

  • 仅记录方法start_engine,而不记录所有方法。
  • Car只需要包含LogStartEngine个问题。我想避免因为关注而添加任何自定义帮助方法,例如log_method :start_engine
  • 我想将include LogStartEngine语句保留在原来的位置。我不希望它低于方法start_engine或在课程结束时。
  • 这是使用Ruby 1.9。所以Module#prepend不是有效的解决方案:)

2 个答案:

答案 0 :(得分:1)

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

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

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

答案 1 :(得分:0)

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

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