Trailblazer概念可以继承自另一个并且能够扩展其操作(多重继承)吗?

时间:2016-04-16 18:52:27

标签: trailblazer

考虑一个抽象的Tag概念,其中有不同类型的标签,比如TopicLocation(以及其他标签),除标签外不相关。它们具有相同的基本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::UpdateTopic::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概念如何继承另一个概念并能够扩展其运营?

2 个答案:

答案 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 Modelcontract)内的代码放在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。它适用于策略,合同,代表和回调对象。