Run each chain:
$ ruby full_chain.rb
$ ruby half_chain.rb
$ ruby no_chain.rb
# encoding: utf-8 | |
# A Data, Context, Interaction (DCI) experiment. | |
# Here we will try to create a DSL to manipulate contexts. | |
class Context | |
# Add all the liteners. A listener is an Object that | |
# responds to the methods `#success` and `#failure`. | |
def initialize(listeners) | |
@listeners = listeners | |
@events = [] | |
end | |
# Executes the use case. When finish, the method `#publish` should | |
# be called to notify to all the listeners. | |
# | |
# The @params parameters is a hash with all the information | |
# that the context needs. Is passed to each listener. | |
def call(params) | |
# Extract params, instances and initiate the use case | |
raise 'Method not implemented' | |
end | |
# Notify to all the listeners the result of the context | |
# execution. | |
def publish(event, params, keywords = []) | |
@listeners.map do |listener| | |
listener.send(event, params, keywords) | |
end | |
end | |
# In order to be able to chain contexts, a context must | |
# act as a listener, thats why it responds to `#success` | |
# and `#failure` methods. | |
# By default, a context is not listening to any event. | |
# The context initiator should indicates the events that each | |
# context should be aware of. | |
def listen(event) | |
@events << event.to_sym | |
end | |
def success(params, keywords = []) | |
trigger(:success, params, keywords) | |
end | |
def failure(params, keywords = []) | |
trigger(:failure, params, keywords) | |
end | |
# If the context is listening to the event, it calls himself. | |
# If not, pass the call to its own listeners, following the chain. | |
def trigger(event, params, keywords = []) | |
if @events.include?(event) | |
# TODO: save keywords in a history | |
call(params.dup) | |
else | |
publish(event, params.dup, keywords) | |
end | |
end | |
end | |
class RegisterUser < Context | |
def call(params) | |
user = UserData.new(params['user_params']) | |
user.extend GuestUser | |
_params = params.dup | |
_params['user'] = user | |
if user.signup | |
publish(:success, _params, ['register_user']) | |
else | |
publish(:failure, _params, ['register_user']) | |
end | |
end | |
end | |
class SavePendingPurchase < Context | |
def call(params) | |
pending_purchase = PendingPurchaseData.new(params['pending_purchase_params']) | |
pending_purchase.extend NewPendingPurchase | |
_params = params.dup | |
_params['pending_purchase'] = pending_purchase | |
if pending_purchase.save_for_user(_params['user']) | |
publish(:success, _params, ['save_pending_purchase']) | |
else | |
publish(:failure, _params, ['save_pending_purchase']) | |
end | |
end | |
end |
# encoding: utf-8 | |
# Dummy data models | |
class UserData < Struct.new(:params) | |
end | |
class PendingPurchaseData < Struct.new(:params) | |
end |
# encoding: utf-8 | |
require './initiator' | |
params = { | |
'user_params' => { 'success' => true }, | |
'pending_purchase_params' => { 'success' => true } | |
} | |
Initiator.call(params) |
# encoding: utf-8 | |
require './initiator' | |
params = { | |
'user_params' => { 'success' => true }, | |
'pending_purchase_params' => { 'success' => false } | |
} | |
Initiator.call(params) |
# encoding: utf-8 | |
require './responder' | |
require './data' | |
require './roles' | |
require './contexts' | |
# Test class that executes a chain of contexts, | |
# showing how to use the them. | |
class Initiator | |
def self.call(params) | |
new.call(params) | |
end | |
def call(params) | |
# Initialize the last element of the chain, the final responder. | |
responder = Responder.new | |
# Then, initialize each context of the chain. | |
# The last one to initialize is the first context to call. | |
# Each context will be a listener of the next one. | |
# SavePendingPurchase is the last context to be executed, thats why | |
# it has the responder as a listener. | |
save_pending_purchase = SavePendingPurchase.new([responder]) | |
# Only acts if the previous context was successful. | |
save_pending_purchase.listen(:success) | |
# RegisterUser is the first context, so it has the SavePendingPurchase | |
# as a listener. | |
register_user = RegisterUser.new([save_pending_purchase]) | |
register_user.call(params) | |
end | |
end |
# encoding: utf-8 | |
require './initiator' | |
params = { | |
'user_params' => { 'success' => false }, | |
'pending_purchase_params' => { 'success' => true } | |
} | |
Initiator.call(params) |
# encoding: utf-8 | |
# Final listener. Shows who was the last caller. | |
class Responder | |
def success(params, keywords = []) | |
notify('success', keywords) | |
end | |
def failure(params, keywords = []) | |
notify('failure', keywords) | |
end | |
def notify(event, keywords) | |
message = "Context: #{keywords.join(', ')}" | |
$stdout.puts "[#{event.upcase}] #{message}" | |
end | |
end |
# encoding: utf-8 | |
# Dummy roles, only for testing purposes. | |
module Notify | |
def notify(label, message) | |
$stdout.puts "[#{label.upcase}] #{message}" | |
end | |
end | |
module GuestUser | |
include Notify | |
def signup | |
if params.fetch('success', false) | |
notify('signup', 'User CAN signup') | |
true | |
else | |
notify('signup', 'User CANNOT signup') | |
false | |
end | |
end | |
end | |
module NewPendingPurchase | |
include Notify | |
def save_for_user(user) | |
if params.fetch('success', false) | |
notify('pending_purchase', 'Pending purchase CAN be saved') | |
true | |
else | |
notify('pending_purchase', 'Pending purchase CANNOT be saved') | |
false | |
end | |
end | |
end |