Skip to content

Instantly share code, notes, and snippets.

@alanq
Created May 16, 2012 15:18
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save alanq/2711153 to your computer and use it in GitHub Desktop.
Save alanq/2711153 to your computer and use it in GitHub Desktop.
DCI in ruby without injection - MoneyTransfer example
-----------------------------------------
Boilerplate code (in rails_app_root/lib)
-----------------------------------------
module ContextAccessor
def context
Thread.current[:context]
end
end
module Context
include ContextAccessor
attr_reader :role_player # allows a role to find its player
# Context setter is defined here so it's not exposed to roles (via ContextAccessor)
def context=(ctx)
Thread.current[:context] = ctx
end
# sets the current global context for access by roles in the interaction
def execute_in_context
old_context = self.context
self.context = self
return_object = yield
self.context = old_context
return_object
end
end # module Context
# A role contains only class methods and cannot be instantiated.
# Although role methods are implemented as public class methods, they only have
# access to their associated object while the role's context is the current context.
class Role
def initialize
raise "A Role should not be instantiated"
end
class << self
protected
include ContextAccessor
# retrieve role object from its (active) context's hash instance variable
def player
context.role_player[self]
end
# allow player object instance methods to be called on the role's self
def method_missing(method, *args, &block)
super unless context && context.is_a?(my_context_class)
if player.respond_to?(method)
player.send(method, *args, &block)
else # Neither a role method nor a valid player instance method
super
end
end
def my_context_class # a role is defined inside its context class
self.to_s.chomp(role_name).constantize
end
def role_name
self.to_s.split("::").last
end
end # Role class methods
end
-----------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Contexts (in rails_app_root/app/contexts)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-----------------------------------------
class Account
include Context
def initialize(ledgers = [])
@role_player = {}
@role_player[Ledgers] = Array(ledgers) # association of the object ledgers (an array) with the role Ledgers
end
def balance()
execute_in_context do
Ledgers.balance
end
end
def increase_balance(amount)
execute_in_context do
Ledgers.add_entry 'depositing', amount
end
end
def decrease_balance(amount)
execute_in_context do
Ledgers.add_entry 'withdrawing', -1 * amount
end
end
# A role can use self or player to reference the obj associated with it
class Ledgers < Role
class << self
def add_entry(msg, amount)
player << LedgerEntry.new(:message => msg, :amount => amount)
end
def balance
player.collect(&:amount).sum
end
end # Role class methods
end # Role
end # Context
class MoneyTransfer
include Context
def initialize(source, destination, amount)
@role_player = {}
@role_player[Source] = source
@role_player[Destination] = destination
@role_player[Amount] = amount
end
def trans
execute_in_context do
Source.transfer @role_player[Amount] # so player not role will go to subcontext
end
end
class Source < Role
class << self
def transfer(amount)
log = Logger.new(STDOUT)
log.info "Source balance is #{Source.balance}"
log.info "Destination balance is #{Destination.balance}"
Destination.deposit amount
Source.withdraw amount
log.info "Source balance is now #{Source.balance}"
log.info "Destination balance is now #{Destination.balance}"
end
def withdraw(amount)
Source.decrease_balance amount
end
end
end
class Destination < Role
class << self
def deposit(amount)
Destination.increase_balance amount
end
end
end
class Amount < Role
end
end
------------------------------------------
Domain Model (app/models/ledger_entry.rb)
DB table has amount:decimal & message:string
------------------------------------------
class LedgerEntry < ActiveRecord::Base
attr_accessible :amount, :message
end
-----------------------------
Program (run in ruby console)
-----------------------------
l1 = LedgerEntry.new(:message=>'lodge',:amount=> 500)
l2 = LedgerEntry.new(:message=>'lodge',:amount=> 420)
source = Account.new([l1,l2])
destination = Account.new()
context = MoneyTransfer.new(source, destination, 700)
context.trans
@loveybot
Copy link

Yay Ruby!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment