素振り take2

もっと静的に

# -*- 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["佐藤"]
        c.to_account = Account["田中"]
        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["佐藤"] }
      its(:balance) { should == 201 - move_amount }
    end

    describe "田中:to_account " do
      subject { Account["田中"] }
      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 self.[]=key,  value
    @@accounts[key] = value
  end
  
  def self.[] key
    @@accounts[key]
  end
    
  
  module ForEntry
    def add_entiry entry
      @entries << entry
    end
    
    def entries= entries
      @entries = entries
    end
  end
  
  module ExecutionContextAPI
    def amount
      amount = Thread.current[:context].amount
    end
  end

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

  module ToRole
    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
  include ExecutionContextAPI, ForEntry, FromRole, ToRole, BalanceRole
end