考虑一个抽象的Tag
概念,其中有不同类型的标签,比如Topic
和Location
(以及其他标签),除标签外不相关。它们具有相同的基本Tag
属性,但不同。
Topic
概念基于类似的Tag
概念。类似Topic::Update
的操作通常会从Topic::Create
继承,但此类操作也需要从Tag::Update
继承。 Ruby不支持多重继承 - Trailblazer可以支持吗?
Trailblazer操作支持通过builds
块继承,允许它们根据提供的params
哈希的内容实例化子类。这适用于基类(Tag
)面向公众并且通过基类调用操作的情况。但是,在此示例中,面向公众的类是Topic
子类。
需要通过子类(Topic
)调用操作,但是其操作是基于公共Tag
基类(反向构建器?)
这是通过单一继承实现这一目标的一种方式(但它说明了这种方法的缺点)......
每种类型的标记都存储在自己的数据库表中,并具有如下的ActiveRecord类:
class Tag < ActiveRecord::Base
self.abstract_class = true
end
class Topic < Tag; end
Trailblazer概念遵循类似的设计 - Tag
操作将提供基本功能,并通过更多特定操作(Topic
)进行子类化。 Tag
操作不会直接使用 - 例如,Topic
控制器将使用Topic
操作。
Topic
操作继承自Tag
,但必须指定自己的Topic
模型,这似乎只能在每个操作中进行,要求每个操作都明确地进行子类化:
class Topic < Tag
class Create < Tag::Create
model Topic
end
class Update < Tag::Update
model Topic
end
class Delete < Tag::Delete
model Topic
end
end
这样做的一个问题是,在基本操作上定义的合同认为它是Tag
而不是Topic
,这会导致问题被用作一个模型。显示这是一个问题的示例在单元格视图中:Topic
概念有一个单元格,用于显示操作其对象的视图。它使用simple_form_for
呈现表单,如下所示:
simple_form_for operation.contract
这并不像预期的那样有效,因为合同认为它是Tag
,这打破了形式:
params[:tag]
而非params[:topic]
单元格不能使用operation.model
(否则会起作用),因为在提交操作失败后渲染时,它不会看到任何表单错误。
解决此问题的方法是明确simple_form_for
:
simple_form_for operation.contract, as: :topic, url: topics_path ...
向Topic
添加属性时会出现另一个问题,因为这需要扩展Tag
合同。通常的方法是在contract do..end
操作中添加Topic::Create
块。出现此问题的原因是Topic::Update
和Topic::Delete
无法看到此类块,因为它们是从Tag
对应方继承而不是从Topic::Create
继承。
替代方法是将子类Topic::Update
操作从Topic::Create
继承。这将消除指定模型的需要(因为Topic::Create
会这样做),但这意味着Tag::Update
操作添加的任何内容都将丢失:
class Update < Create
action :update
end
action
需要重新指定,因为Tag::Update
未被继承,但由于Topic::Create
是继承的,Topic::Create
中添加的属性可在{{1}中使用}}
只要更改仅在一个基类中,这两种样式都可以正常工作。当两者都发生变化时,它会中断,因为Ruby不支持多重继承。考虑通常如下所示的Topic::Update
操作:
Delete
如果是class Delete < Create
action :find
def process(params)
# validate params and then delete
end
end
,那么Tag::Delete
可能是
Topic::Delete
或
class Delete < Tag::Delete
model Topic
end
在前一种情况下,class Delete < Create
action :find
end
不会意识到Topic::Delete
添加的属性,而在后一种情况下,Topic::Create
将缺少Topic::Delete
中定义的process
方法}}
Trailblazer概念如何继承另一个概念并能够扩展其运营?
答案 0 :(得分:1)
使用模块可以实现多重继承的效果。
首先定义ActiveRecord对象,如下所示:
class Topic < ActiveRecord::Base; end
class Location < ActiveRecord::Base; end
不再有基础Tag
抽象类,允许将Tag
定义为这样的模块(app/concepts/tag/crud.rb
):
module Tag
module Create
def self.included(base)
base.send :include, Trailblazer::Operation::Model
base.send :model, base.parent # e.g. Thing::Tag => Thing
base.send :contract, Form
end
class Form < Reform::Form
property ...
end
def process(params)
...
end
end
module Update
def self.included(base)
base.send :action, :update
end
end
module Delete
def self.included(base)
base.send :action, :find
end
def process(params)
...
end
end
end
通常放在操作类(例如include Model
和contract
)内的代码放在self.included
方法中,以便它们在包含类的范围内执行。需要使用ruby send
方法在模块的self.included
方法中调用包含类的方法。
使用此Tag
模块,Topic
标记看起来像这样(app/concepts/tag/topic/crud.rb
)
class Topic
class Create < Trailblazer::Operation
include Tag::Create
contract do
property ...
end
end
class Update < Create
include Tag::Update
end
class Delete < Create
include Tag::Delete
def process(params)
....
super
end
end
end
这允许通过Tag
扩展Topic::Create
合同,这会为合同添加属性,并进一步自定义Tag
方法,例如调用{Delete::process
示例1}}调用super
。另外,合同会知道它是Tag::Delete::process
,所以像Topic
这样的东西会正常工作。
答案 1 :(得分:1)
使用模块共享公共数据是一种(正确的)继承方式。
但是,您不应该忘记您也可以使用Trailblazer的组合接口,您可以在操作类中使用继承来继承通用逻辑,然后参考使用合成的图层对象。
module Location
class Create < Tag::Create # inheritance.
contract Tag::Contract::Create # compositional API.
end
end
组合界面允许您引用单独的类,并解释in the docs。它适用于策略,合同,代表和回调对象。