1

考虑一个抽象概念Tag,其中有不同种类的标签,比如TopicLocation(以及其他),它们除了作为标签之外是不相关的。它们具有相同的基本Tag属性,但在其他方面有所不同。

一个Topic概念是基于一个类似的Tag概念。likeTopic::Update通常会继承 from Topic::Create,但这样的操作也需要继承 from 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

这样做的一个问题是,在基本操作上定义的合同认为它是 aTag而不是 a Topic,这会导致将其用作模型的问题。一个显示问题所在的示例是在单元格的视图中:该Topic概念有一个单元格,该单元格呈现视图以操纵其对象。它使用 渲染表单simple_form_for,如下所示:

simple_form_for operation.contract

这不能按预期工作,因为合同认为它是 aTag并且这打破了形式:

  • 它的参数被发送为params[:tag]而不是params[:topic]
  • 提交按钮的标签是Create Tag而不是Create 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::CreateTopic::Update.

只要更改仅在一个基类中,这两种样式都可以工作。当两者都发生变化时它会中断,因为 Ruby 不支持多重继承。考虑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方法Tag::Delete

开拓者概念如何继承另一个概念并能够扩展其业务?

4

2 回答 2

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 Modeland contract)放置在self.included方法中,以便它们在包含类的范围内执行。rubysend方法需要用于从模块方法中调用包含类的此类方法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例如调用super调用的示例Tag::Delete::process。此外,合同会知道它是这样的,Topic所以事情simple_form会正常工作。

于 2016-04-16T18:53:40.793 回答
1

使用模块来共享公共数据是一种(正确的)继承方式。

但是,您不要忘记,您也可以使用 Trailblazer 的组合接口,您可以在其中使用跨操作类的继承来继承通用逻辑,然后使用组合来引用层对象。

module Location
  class Create < Tag::Create       # inheritance.
    contract Tag::Contract::Create # compositional API. 
  end
end

组合接口允许您引用单独的类,并在 2.0 文档中进行了说明。它适用于策略、合同、代表和回调对象。

于 2016-04-17T12:03:29.140 回答