Skip to content

Instantly share code, notes, and snippets.

@jhbabon
Created November 25, 2012 20:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jhbabon/4145105 to your computer and use it in GitHub Desktop.
Save jhbabon/4145105 to your computer and use it in GitHub Desktop.
Playing with DCI in Ruby: Chaining Contexts

DCI: Chain of contexts example

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 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment