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 |