-
Notifications
You must be signed in to change notification settings - Fork 0
Rail
If you have a computer science background you familiar with the concept of this data structure. It has nodes, links between them and a pointer to head element. We are going to extend the linked list concept for data flow control
Please see the section "Cascade wrapping with a delegation "
This is a composition of objects inspired by biological cells. As you understand this very naive naming approach is just a metaphor intended to help you understand the essence of this pattern.
First of all, each cell has a membrane that separates internal and external environments. Secondly, the membrane has many inputs and outputs, that help a cell to adapt to the external environment. A cell itself is represented by an internal state and a purpose to serve. Besides, it has different strategies to achieve the purpose. Finally, a cell can give off something into an external environment and get something from it. All these characteristics give us basic design rules for the new pattern.
Resuming everything above we can create a class that will implement the Railway oriented programming approach in OO fashion based on Cell Pattern.
class Rail
include SteelWheel::Composite[:controllers]
include SteelWheel::Composite[:inputs]
include SteelWheel::Composite[:outputs]
end
Please check rail_spec.rb for more examples
Are the parts of the external interface, their role is an adaption to external factors by introducing correspond objects that do data preparation job before and after operation execution. The current implementation allows switching between different combinations of input/output and to set way to instantiate those objects.
class MyOperation < SteelWheel::Rail
input :mash, init: ->(klass, value) { klass.new(value.merge(my_injection: some_value)) }
input :array, init: ->(klass, n1, n2) { klass.new(pass: n1, log: n2) }
output :json
output :xml
end
The init
procs will be called in the moments when the flow requires creating new instance of input
class. Another option to specify this is during the component activation
class MyOperation < SteelWheel::Rail
json_output init: ->(klass) { klass.new(serializer: MySerializer) } do
# Your customizations
end
end
To specify desired input/output methods from
and to
are used
MyOperation.from(:mash).to(:json)
Input gets inside via the accept
method
MyOperation.from(:mash).to(:json).accept(some_data).call
The output is returned in result
property of operation instance
MyOperation.from(:mash).to(:json).accept(some_data).tap(&:call).result
Also you can use this setup
class MyOperation < SteelWheel::Rail
from :mash
to :json
end
Class.new(MyOperation).from(:array).to(:xml)
More exaples you can find in rail/rail_inputs_spec.rb and rail/rail_outputs_spec.rb
Are part of internal programming interface and provide the way to handle success/failure cases.
class MyOperation < SteelWheel::Rail
controller :preparation
preparation_controller do
include ActiveMolel::Validations # Only this is supported for now
validate { errors.add(:field, 'missing') }
end
def on_failure(step)
step #=> :preparation
# given is a reader for the current context
object = given.errors
result.text = object.to_json
end
def on_success
# given is a reader for the current context
object = given.call
result.text = object.to_json
end
end
Alternative callback looks like this
class MyOperation < SteelWheel::Rail
def on_mash_failure # Priority 0
# Handle this
end
def on_preparation_failure # Priority 0
# Handle this
end
def on_failure(step) # Priority 1
end
end
In case when output logic requires specific procedures, this sort of callbacks should be defined on output class. If they are not defined then the operation's callbacks are triggered (chain of responsibility).
class MyOperation < SteelWheel::Rail
json_output do
def on_authorize_failure(given) # Priority 0
# Handle this
end
def on_failure(given, step) # Priority 1
# Handle this
end
def on_success(given) # Priority 0
# Handle this
end
end
end