有一个ActiveRecord
模型,它有自己的模型(基本上包含在其他ActiveRecord的模块中)#changed?
和#change
方法。还有一个模块Observable
,它也有自己的changed?
和change
定义。
我需要定义一个自定义模块,它自动包含Observable
模块并执行一些基础逻辑,但问题是,当我尝试alias
和undef
原始{{}时1}}方法,它也是来自其他模块的Observable
方法,这是至关重要的。
有没有优雅的方法来解决这个问题?因为我不想真正实现自定义undefs
模块。
这是一个示例代码:
Observable
https://repl.it/repls/KeyQuickwittedAsiantrumpetfish
谢谢。
答案 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
中实施changed
和changed?
方法,以明确调用SomeModel
中的相应方法。使用某些元编程技术可能有助于缩短代码。
SomeActiveRecordModule
答案 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