重命名重叠方法,由observable添加,而不影响其他方法

时间:2018-01-29 13:28:52

标签: ruby-on-rails ruby

有一个ActiveRecord模型,它有自己的模型(基本上包含在其他ActiveRecord的模块中)#changed?#change方法。还有一个模块Observable,它也有自己的changed?change定义。

我需要定义一个自定义模块,它自动包含Observable模块并执行一些基础逻辑,但问题是,当我尝试aliasundef原始{{}时1}}方法,它也是来自其他模块的Observable方法,这是至关重要的。

有没有优雅的方法来解决这个问题?因为我不想真正实现自定义undefs模块。

这是一个示例代码:

Observable

https://repl.it/repls/KeyQuickwittedAsiantrumpetfish

谢谢。

2 个答案:

答案 0 :(得分:1)

你试图打印模型的祖先,它会显示

SomeModel.ancestors # [SomeModel, TryingToRewriteChanged, Observable, Triggerable, ActiveRecord::Base, ActiveRecord::SomeActiveRecordModule, Object, JSON::Ext::Generator::GeneratorMethods::Object, Kernel, BasicObject]

因此,在致电SomeModel.new.changed时,它会调用changed的{​​{1}}。这个方法已经Observable,它会抛出异常作为文档:https://apidock.com/ruby/Module/undef_method

  

阻止当前类响应对named的调用   方法。将其与删除方法的remove_method进行对比   来自特定班级; Ruby仍然会搜索超类和   用于可能的接收器的混合模块。

您可以使用两种方法来解决此问题:

1 - 在继承链undef_method之前添加ActiveRecord::SomeActiveRecordModule

TryingToRewriteChanged

参考:https://repl.it/repls/ProudGuiltyMorpho

但是使用这种方式,你必须接受# Mock for ActiveRecord::Base class module ActiveRecord class Base include Triggerable include SomeActiveRecordModule end end # Example model, which need to include Triggerable module class SomeModel < ActiveRecord::Base end 将被包含在所有可能比你期望的范围更大的ActiveRecord子类中。

2 - 在Triggerable中实施changedchanged?方法,以明确调用SomeModel中的相应方法。使用某些元编程技术可能有助于缩短代码。

SomeActiveRecordModule

参考:https://repl.it/repls/ContentTameIsabellinewheatear

答案 1 :(得分:0)

发现非常丑陋但有效的解决方案,主要的魔法发生在TryingToRewriteChanged模块下:

require 'observer'

# Create aliases for #changed and changed? methods from 
# Observable - basically, renaming them.
# And then call top-level methods of the first includer when
# receiving #changed / changed?
module TryingToRewriteChanged
  include ::Observable

  alias triggable_changed changed
  alias triggable_changed? changed?

  [:changed, :changed].each do |method|
    define_method(method) do |*args|
      return super(*args) unless origin_method_present?(method)
      call_origin_method(method, *args)
    end
  end

  private

  def call_origin_method(name, *args)
    method(name).super_method.super_method.call(*args)
  end

  def origin_method_present?(name)
    method(name).super_method&.super_method&.name == name
  end
end

# Custom module which has some logic in .included
module Triggerable
  def self.included(obj)
    obj.class_eval do
      include TryingToRewriteChanged
    end
  end
end

# Mock for some ActiveRecord module with 
# #changed and changed? definitions
module ActiveRecord
  module SomeActiveRecordModule
    def changed
      puts 'original changed'
    end

    def changed?
      puts 'original changed?'
    end
  end
end

# Mock for ActiveRecord::Base class
module ActiveRecord
  class Base
    include SomeActiveRecordModule
  end
end

# Example model, which need to include Triggerable module
class SomeModel < ActiveRecord::Base
  include Triggerable
end

# ActiveRecord's #changed is no more available
SomeModel.new.changed

https://repl.it/repls/ThirstyFlawedXanthareel