这听起来很疯狂,但我正在尝试构建一个基于通用装饰的系统,它允许装饰类用属性做各种疯狂的东西。目标是能够在高级别定义属性,装饰ORM类(例如ActiveRecord,虽然我们的主要情况实际上有点不同),并在应用程序的不同位置使用这些装饰来自动化一些动态“魔术”我们的应用程序需要。例如,我们将使用属性自动生成表单和视图,将复杂的表单哈希转换为更平坦的结构等。
为了适应我们到目前为止已经确定的两个用例,我有一个可混合的模块和一个装饰器(使用Draper,所以Rails形式魔术仍然可以工作,虽然我不是必须与Draper结婚)看起来或多或少像这样(显然省略了很多细节):
class DecoratorThing < Draper::Decorator
include CoreMixinStuff
delegate_all
end
module CoreMixinStuff
extend ActiveSupport::Concern
module ClassMethods
def attribute(stuff, blah)
attribute = AttributeDefinition.new(...)
add_translation_methods(attribute)
...
end
def add_translation_methods(attribute)
name = attribute.name
reader = name
writer = "#{name}="
# In the case of field wrappers, we have to alias the original reader and writer so we
# don't overwrite them completely
if attribute.translation_type == :wrapper
alias_method :"_orig_#{reader}", reader
alias_method :"_orig_#{writer}", writer
# Otherwise, we need to error if the reader or writer would collide
elsif instance_methods.include?(reader) || instance_methods.include?(writer)
raise RuntimeError.new("Cannot define an attribute which overrides existing methods (#{name.inspect})")
end
end
end
end
然后,特定实例的实际装饰器会执行以下操作:
class FooDecorator < DecoratorThing
decorates Foo
attribute :field, multiple: true, serialize: true
attribute :field2, field: :delegation_field
attribute :field3 do |field|
field.subtype ...
field.subtype ...
end
end
意图是允许Foo#字段获取数组并将其内部序列化为字符串,然后将其发送到装饰对象获取它的任何位置。 Foo#field2只会按原样将数据传递给delegation_field。 Foo#field3将采用复杂的数据哈希并将其委托给子类型字段。
后两种情况很痛苦,但我让他们在原型中工作。第一个问题是因为上面的alias_method
- 因为属性方法是在装饰器上运行的,我试图别名的方法实际上还不存在。直到FooDecorator.new(some_foo_instance)
被调用时,其他实例方法才可用。
我认为我的选择仅限于以下内容,但我希望有更好的选择:
attribute :field...
将成为attribute :field_wrapper, multiple: true, serialize: true, field: "field"
)第四个选项可能是最安静的,但我已经做了很多关于能够序列化的假设,所以如果有人知道一个很好的方法来实现这一点,那就会超级膨胀。
答案 0 :(得分:0)
我想我的答案符合这个问题。
基本上,我的解决方案在与活动记录模型关联的现有列的顶部添加了一个代理列。
此代理列会覆盖原始列的访问者方法。除了set之外,基本代理功能并没有做太多事情并且获取原始列的值。
实际代码很长,所以我为它创建了一个要点。 Check it out here
可以对代理列对象进行子类化/重写,以提供所需的任何类型的功能。例如,在您的代码中AttributeDefinition
将子类ProxyColumn
。 AttributeDefinition
将充当不同属性和值之间的代理。它可以根据您认为合适的条件在DecoratorThing
中包装/解包属性值。
在这个阶段,它没有做的事情是提供一种覆盖如何/添加方法的方法。但是,所有这些都可以根据您的需求轻松更改。无论如何,我实际上是根据你的add_translation_methods
方法建立的那部分。
这是定义列的基本用法:
class MyModel
# Add the concern to get the functionality
include DataCollectionElements # concern
# create proxy column and set various options
proxy_column :my_col_1
proxy_column :my_col_2, :alias => [:col1, col2, col3]
proxy_column :my_col_3, :column => :actual_col_name
# define the proxy object using class/string/symbol
proxy_column :my_col_4, :proxy => MyProxy # or 'MyProxy' or :my_proxy
# define the proxy object using a proc/lambda
proxy_column :my_col_5, :proxy => ->(model, code, options) { MyProxy.new(model, code, options) }
proxy_column :my_col_6, :proxy => proc {|model, code, options| MyProxy.new(model, code, options) }
# define the proxy object using a block
proxy_column :my_col_7 do |model, code, options|
MyProxy.new(model, code, options)
end
end