2016-04-16 12 views
1

Рассмотрите абстрактную концепцию, где есть различные виды тегов, скажем Topic и Location (среди прочих), которые не связаны друг с другом, кроме тегов. Они имеют одинаковые базовые свойства Tag, но в остальном они отличаются.Может ли концепция Trailblazer наследовать от другого и иметь возможность расширять ее операции (множественное наследование)?

A 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 

первопроходца концепция будет следовать аналогичной конструкции - это 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 потребности быть respecified, потому что Tag::Update не передается по наследству, но, потому что Topic::Create наследуется, свойства добавлены в Topic::Create доступны в Topic::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 метод defined в Tag::Delete.

Каким образом концепция Trailblazer наследует другую и сможет расширять ее операции?

ответ

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, который вызывает super для вызова Tag::Delete::process. Кроме того, контракт будет знать, что это Topic, поэтому такие вещи, как simple_form, будут работать правильно.

1

Использование модуля для совместного использования общих данных - один (правильный) способ наследования.

Однако не следует забывать, что вы также можете использовать композиционного интерфейс TrailBlazer в, где вы могли бы использовать наследование по классам операций, чтобы наследовать общую логику, а затем обратиться к объектам слоя с использованием композиции.

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

Композиционное интерфейс позволяет ссылаться на отдельный класс и объясняется in the docs. Он работает для политик, контрактов, представителей и объектов обратного вызова.