Can a Trailblazer concept inherit from another and be able to extend its operations (multiple inheritance)? - trailblazer

Consider an abstract Tag concept where there are different kinds of tags, say Topic and Location (amongst others), that are unrelated apart from being tags. They have the same base Tag properties in common but are otherwise different.
A Topic concept is based on a similar Tag concept. An operation like Topic::Update would usually inherit from Topic::Create, but such an operation also needs to inherit from Tag::Update. Ruby doesn't support multiple inheritance - can Trailblazer support this?
Trailblazer operations support inheritance through a builds block that allows them to instantiate a subclass based on the contents of the supplied params hash. This works where the base class (Tag) is public-facing and operations are invoked through the base class. However, in this example, the public-facing class is the Topic subclass.
Operations need to be invoked through the subclass (Topic) but have its operations be based off a common Tag base class (a reverse builder ?).
Here is one way that this can be achieved through single inheritance (but it illustrates the shortcomings of this approach)...
Each type of tag is stored in its own database table and has ActiveRecord classes like this:
class Tag < ActiveRecord::Base
self.abstract_class = true
end
class Topic < Tag; end
The Trailblazer concept would follow a similar design - a Tag operation would provide the base functionality and be subclassed by more a specific operation (Topic). The Tag operation would not be used directly - a Topic controller, for example, would use the Topic operation.
The Topic operation inherits from Tag but must specify its own Topic model which seems to be only possible within each operation, requiring each to be subclassed explicitly:
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
A problem with this is that the contract, being defined on the base operation, thinks that it is a Tag rather than a Topic and this leads to issues where it's used as a model. An example showing where this is a problem is in a cell's view: the Topic concept has a cell that presents views to manipulate its objects. It renders forms using simple_form_for, like this:
simple_form_for operation.contract
This doesn't work as expected because the contract thinks it is a Tag and this breaks the form:
its parameters are sent as params[:tag] instead of params[:topic]
the submit button's label is Create Tag instead of Create Topic.
The cell can't use operation.model (which would otherwise work) because it won't see any form errors when rendering after a submited operation fails.
A way to solve this is to be explicit with simple_form_for:
simple_form_for operation.contract, as: :topic, url: topics_path ...
Another problem occurs when adding properties to Topic, because this requires extending the Tag contract. The usual way to do this is to add a contract do..end block to the Topic::Create operation. The problem occurs because such a block would not be seen by Topic::Update and Topic::Delete because they inherit from their Tag counterparts and not from Topic::Create.
An alternative would be for a subclassed Topic::Update operation to inherit from Topic::Create. This would remove the need to specify the model (because Topic::Create does it) but would mean that anything added by the Tag::Update operation would be lost:
class Update < Create
action :update
end
The action needs to be respecified because Tag::Update isn't inherited but, because Topic::Create is inherited, properties added in Topic::Create are available in Topic::Update.
Both of these styles work as long as changes are only in one base class. It breaks whe there are changes in both because Ruby does not support multiple inheritance. Consider the Delete operation which usually looks like this:
class Delete < Create
action :find
def process(params)
# validate params and then delete
end
end
If that is Tag::Delete then Topic::Delete could be either
class Delete < Tag::Delete
model Topic
end
or
class Delete < Create
action :find
end
In the former case Topic::Delete would be unaware of properties added by Topic::Create and, in the latter case, Topic::Delete would lacks the process method defned in Tag::Delete.
How can a Trailblazer concept inherit another and be able to extend its operations ?

The effect of multiple inheritance can be achieved by using modules.
First define the ActiveRecord objects like this:
class Topic < ActiveRecord::Base; end
class Location < ActiveRecord::Base; end
There is no longer a base Tag abstract class, allowing Tag to be defined as a module like this (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
Code that would normally be placed inside operation classes (such as include Model and contract) are placed inside a self.included method so that they are executed within the scope of the including class. The ruby send method needs to be used to invoke such methods on the including class from within the module's self.included method.
Using this Tag module, a Topic tag would look like this (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
This allows extension of the Tag contract by Topic::Create, which adds properties to the contract, and further customisation of Tag methods like the Delete::process example that calls super to invoke Tag::Delete::process. Aditionally, the contract will know it's a Topic so things like simple_form will work properly.

Using a module to share common data is one (correct) way of inheriting.
However, you shouldn't forget that you can also use Trailblazer's compositional interface, where you could use inheritance across operation classes to inherit generic logic, and then refer to the layer objects using composition.
module Location
class Create < Tag::Create # inheritance.
contract Tag::Contract::Create # compositional API.
end
end
The compositional interface allows you to reference a separate class and is explained in the 2.0 docs. It works for policies, contracts, representers and callback objects.

Related

Ruby class which could take an array of rules

I'm in the process of learning Ruby/Rails. I'm currently learning to create a model/classes.
I have an existing class that has these methods
def duration
(start_on..end_on).count
end
def items
space.available_items
end
def available_items
duration >= 365 ? items.not_including_insurance : items
end
In the class above, I have a method called available_items that checks if the duration is more than 365 days, then item will not be included in the array.
Rather than coupling the duration logic to the existing class, I think it's better to encapsulate it in another class which could take an array of rules to apply to the items.
So instead, in the above class, in the available_items method I can do something like:
policy.apply(items)
which will return all of the items which satisfy the rules.
And in the future, I can append more rules and keeps it flexible.
After includeing you module you still can define available_items method with custom rules (and have a generic one upstream), this is the easy way.
As for "passing" parameters to a strategy - it can be accomplished in a Concern like:
module SomePolicy
extend ActiveSupport::Concern
module ClassMethods
def my_policiable(param)
has_many :prices, class_name: 'Pricing::SimplePrice', as: :priceable
# ...
define_method(:available_items) {
if param == :foo
# ...
end
}
end
end
end
include SomePolicy
my_policiable(:foo)
trick is that the method is run in class context, there based on params you can define methods differently (but note that in example above if param.. will run for each resulting method invocation)

How to inherit class variables from parents included module in Ruby

I am trying to develop a reusable module for Active Record models to be used shared in models. It works well except child classes can't find the variables.
This is easier demonstrated with code
This is the module:
module Scanner
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def scan(*fields)
#scan = fields if fields.present?
#scan
end
end
end
Usage:
class User < ActiveRecord::Base
include Scanner
scan :user_name
end
Calling User.scan returns :user_name. Perfect!
However. This is an issue:
class Admin < User
end
Calling Admin.scan returns nil
How come :user_name is not being set in the parent? I am following the source for AR for methods like primary_key and table_name, they seem to be inherting values from the parent
class_attribute takes care of the required inheritance semantics for you. As you've seen, it's not quite as simple as putting an instance variable on the class, because that leaves it nil on subclasses: they're instance variables, and that's a separate instance.
The examples you mention do other, more specialised and more complicated, things... but you'll find plenty of straightforward examples in the Rails source that do use class_attribute.

Rails - where to put model helper methods

I want to hide certain implementations from the main Model methods, because of clean code reasons. I don't want my Model to contain a lot of huge methods, only the clearest and verbose functionalities.
For example:
class SomeModel
#included stuff
#fields & attrs
def modelMethod
variable = functionality1(functionality2)
if some condition
functionality3
else
functionality4
end
end
Should I put my functionality methods under a private or protected part at the end of the same model file, or should I put them into a helper file?
If I'm right, the codes in the helpers are only used for the View. What is the convention for this?
Having private or protected has nothing to do with the type of cleanup you're trying to do.
This is related to inheritance method visibility/access (though inheritance can obviously be used for reusability).
Methods will depends on reusability. Why not leverage concerns? Say we have SomeModel and want multiple models to implement suspensions.
# app/models/some_model.rb
class SomeModel
include Suspendable
end
Then add your model concern.
# app/models/concerns/suspendable.rb
module Suspendable
extend ActiveSupport::Concern
included do
has_one :suspension
scope :active, -> { joins('LEFT OUTER JOIN suspensions').where(suspension: {id: nil} }
end
end
Or if this only really applies to a single model, but want to keep the model strictly DB manipulations (not Business oriented), then you could have namespaced concerns.
# app/models/concerns/some_model/availability.rb
module SomeModel::Availability
extend ActiveSupport::Concern
module ClassMethods
def availabilities_by_some_logic
end
end
end
http://api.rubyonrails.org/v5.0/classes/ActiveSupport/Concern.html
If you have a method or set of methods that are used in various models:
Rails Concerns
This is different from private/protected and you can have private/protected methods in a concern. This is just how to extract out duplication.
If you have a method that is needed by the model, and only the model (not subclasses of the model, and never called outside the class:
private
If you have a method that is needed by the model and its subclasses but not from outside the model:
protected
If you need to be able to call the method from outside the class:
neither
this answer goes into better detail on those

Wrapping an object with methods from another class

Let's say I have a model called Article:
class Article < ActiveRecord::Base
end
And then I have a class that is intended to add behavior to an article object (a decorator):
class ArticleDecorator
def format_title
end
end
If I wanted to extend behavior of an article object, I could make ArticleDecorator a module and then call article.extend(ArticleDecorator), but I'd prefer something like this:
article = ArticleDecorator.decorate(Article.top_articles.first) # for single object
or
articles = ArticleDecorator.decorate(Article.all) # for collection of objects
How would I go about implementing this decorate method?
What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?
Updated:
Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:
module ArticleDecorator
def format_title
"#{title} [decorated]"
end
def self.decorate(object_or_objects_to_decorate)
object_or_objects_to_decorate.tap do |objects|
Array(objects).each { |obj| obj.extend ArticleDecorator }
end
end
end
It does the same thing, but:
Avoids checking type of the arguments relying on Kernel#Array method.
Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.
Here is my code:
class ArticleDecorator < BasicObject
def self.[](instance_or_array)
if instance_or_array.respond_to?(:to_a)
instance_or_array.map {|instance| new(instance) }
else
new(instance_or_array)
end
end
attr_accessor :wrapped_article
def initialize(wrapped_article)
#wrapped_article = wrapped_article
end
def format_title
#wrapped_article.title.upcase
end
protected
def method_missing(method, *arguments)
#wrapped_article.method(method).call(*arguments)
end
end
You can now extend a single Article by calling
extended_article = ArticleDecorator[article]
or multiple articles by calling
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]
You can regain the original Article by calling
extended_article.wrapped_article
Or you can exchange the wrapped Article inside like this
extended_article = ArticleDecorator[article_a]
extended_article.format_title
# => "FIRST"
extended_article.wrapped_article = article_b
extended_article.format_title
# => "SECOND"
Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:
article.object_id
# => 123
extended_article = ArticleDecorator[article]
extended_article.object_id
# => 123
Notice though that BasicObject exists only in Ruby 1.9 and above.
You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.
In your case, sounds like you want to match up things like "format_.*" to their respective property getters.
Which part is tripping you up?
module ArticleDecorator
def format_title
"Title: #{title}"
end
end
article = Article.top_articles.first.extend(ArticleDecorator) # for single object
Should work fine.
articles = Article.all.extend(ArticleDecorator)
May also work depending on ActiveRecord support for extending a set of objects.
You may also consider using ActiveSupport::Concern.

Inheritance and Polymorphism conflicting in ruby on rails

I have an issue with Ruby on Rails.
I have several model classes that inherit from the same class in order to have some generic behaviour.
The parent class is called CachedElement.
One of the child is called Outcome.
I want an other model, called Flow to belong to any child of CachedElement.
Hence Flow has a polymorphic attributes called element, to which it belongs_to
When I create a new flow, that belongs to an Outcome, the element_type is set to "CachedElement" which is the parent class, instead of "Outcome".
This is confusing because since I have several type of CachedElement which are stored in different tables, the element_id refers to several different element.
In short I would like the element_type field to refer to the child class name and not the parent class name.
How can I do that ?
The field element_type is set to the parent class because ActiveRecord expects you to use single-table inheritance when deriving from other models. The field will reference the base class because it refers to the table that each instance is stored in.
If the children of CachedElement are stored in their own tables, it may be more helpful to replace the use of inheritance with the use of Ruby modules. The standard approach for sharing logic between classes is to use mix-ins instead of inheritance. For example:
module Cacheable
# methods that should be available for all cached models
# ...
end
class Outcome < ActiveRecord::Base
include Cacheable
# ...
end
You can now easily use polymorphic associations as you have been doing already, and element_type will be set to the proper class.
the file should go on you lib folder. but...
you could do the inheritance thing as well.
all you need to do is to tell you parent class to act as an abstract class.
# put this in your parent class then try to save a polymorphic item again.
# and dont forget to reload, (I prefer restart) if your gonna try this in
# your console.
def self.abstract_class?
true
end
and thats pretty much it, this was kinda unespected for me and actually really
hard to find in the documentation and anywhere else.
Kazuyoshi Tlacaelel.
Thanks, that's what I did, but it was kind of tricky to be able to inherits both instance and class methods from the module
Class methods can be done by:
module Cachable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def a_class_method
"I'm a class method!"
end
end
def an_instance_method
"I'm an instance method!"
end
end
class Outcome < ActiveRecord::Base
include Cacheable
end
if you want to add class methods and instance methods through a mixin (Module)
then I recommend you to abstract these in different modules.
module FakeInheritance
def self.included(klass)
klass.extend ClassMethods
klass.send(:include, InstanceMethods)
end
module ClassMethods
def some_static_method
# you dont need to add self's anywhere since they will be merged into the right scope
# and that is very cool because your code is more clean!
end
end
module InstanceMethods
# methods in here will be accessable only when you create an instance
end
end
# fake inheritance with static and instance methods
class CachedElement
include FakeInheritance
end

Resources