素振り

読みどころは、

      SendMoneyContext.new do |c|
        c.from_account = Account["佐藤"].extend FromRole
        c.to_account = Account["田中"].extend ToRole
        c.amount = move_amount
      end.run

かな。

# -*- encoding: UTF-8 -*-


describe "Accounting Transactionパターンの素振り" do

  before do
    AccountingTransaction.clear
    @sato = Account.new do |a|
      a.name = "佐藤"
      a.entries = [Entry.new(101), 
                  Entry.new(200), 
                  Entry.new(-100)]
    end

    @tanaka = Account.new do |a|
      a.name = "田中"
      a.entries = [Entry.new(102), 
                  Entry.new(200), 
                  Entry.new(-100)]
    end    
    Account[@sato.name] = @sato
    Account[@tanaka.name] = @tanaka
  end

  describe "success send money. amount:100 from:sato to:tanaka " do
    let(:move_amount) {100}
    before(:each) do
      SendMoneyContext.new do |c|
        c.from_account = Account["佐藤"].extend FromRole
        c.to_account = Account["田中"].extend ToRole
        c.amount = move_amount
      end.run      
    end
    
    describe "transaction"  do
      subject { AccountingTransaction.last }
      it { should_not be_nil}
      it(:from_entry) { should_not be_nil }
      it(:to_entry) { should_not be_nil }
    end

    describe "from_entry"  do
      subject { AccountingTransaction.last.from_entry }
      its(:amount) { should == -move_amount }
    end

    describe "to_entry"  do
      subject { AccountingTransaction.last.to_entry }
      its(:amount) { should == move_amount }
    end
        
    describe "佐藤:from_account " do
      subject { Account["佐藤"].extend BalanceRole }
      its(:balance) { should == 201 - move_amount }
    end

    describe "田中:to_account " do
      subject { Account["田中"].extend BalanceRole }
      its(:balance) { should == 202 + move_amount }
    end
  end
end


class SendMoneyContext
  attr_accessor :from_account, :to_account, :amount
  
  def initialize &block
    yield self
    Thread.current[:context] = self
  end
  
  def run
    t = AccountingTransaction.new do |t|
      t.from_entry = from_account.send_money
      t.to_entry = to_account.receive_money
    end
    AccountingTransaction.add t
  end
end

class AccountingTransaction
  attr_accessor :from_entry, :to_entry
  @@transaction = []
  
  def initialize &block
    yield self
  end

  def self.add node
    @@transaction << node
  end
  
  def self.last
    @@transaction.last
  end
  
  def self.clear
    @@transaction = []  
  end
end

class Entry
  attr_accessor :amount
  def initialize amount
    @amount = amount
  end
end


class Account
  @@accounts = {}
  attr_accessor :name
  def initialize &block
    yield self
  end
    
  def add_entiry entry
    @entries << entry
  end
  
  def entries= entries
    @entries = entries
  end
  
  def self.[]=key,  value
    @@accounts[key] = value
  end
  
  def self.[] key
    @@accounts[key]
  end
end


module CommonContextAPI
  def amount
    amount = Thread.current[:context].amount
  end
end

module FromRole
  include CommonContextAPI
  def send_money
    s = Entry.new(-amount)
    add_entiry s
    s
  end
end

module ToRole
  include CommonContextAPI
  def receive_money
    d = Entry.new(amount)
    add_entiry d
    d
  end
end

module BalanceRole
  def balance
    @entries.inject(0) do |result, e|
      result + e.amount
    end
  end
end