学習めも 書き直し

subject.send_money do
  from("鈴木", 260)
  to("佐藤", 110)
  to("田中", 150)
end

のように書く方法をRackのソースを調べて思い出した。
これにパタン名かイディオム名とか付いてるのかな。

require 'rubygems'
require 'mongo_mapper'
MongoMapper.database = 'accounting-patterns'

# see accounting transaction http://martinfowler.com/apsupp/accounting.pdf
describe "accounting transaction" do
  before(:each) do
    init_db
  end
  
  subject{ AccountService.new }  
  
  context "送金 佐藤さんから田中さんへの例" do
    before(:each) do
      subject.send_money do
        from("佐藤", 99)
        to("田中", 99)
      end
    end
    
    it "移動の総量は0であること" do
      trn = AccountingTransaction.last :order => "timestamps"
      trn.should be_total_zero
    end
    
    it "送り元の balance が減っていること" do
      sato = Account.find_by_name("佐藤")
      sato.balance.should == 200 - 99
    end
    
    it "送り先の balance が増えていること" do
      tanaka = Account.find_by_name("田中")
      tanaka.balance.should == 300 + 99
    end
  end
  
  context "送金 鈴木さんから 佐藤さん,田中さんへの例" do
    before(:each) do
      subject.send_money do
        from("鈴木", 260)
        to("佐藤", 110)
        to("田中", 150)
      end
    end
    
    it "移動の総量は0であること" do
      AccountingTransaction.count.should == 1
      trn = AccountingTransaction.last :order => "timestamps"
      trn.should be_total_zero
    end
    
    it "送り元の balance が減っていること" do
      suzuki = Account.find_by_name("鈴木")
      suzuki.balance.should == 700 - 260
    end
  
    it "送り先1の balance が増えていること" do
      sato = Account.find_by_name("佐藤")
      sato.balance.should == 200 + 110
    end
    
    it "送り先2の balance が増えていること" do
      tanaka = Account.find_by_name("田中")
      tanaka.balance.should == 300 + 150
    end
  end
  
  context "入金出金の総和が 0 にならないケース" do
    it "初期か状態であること" do
      lambda {
        subject.send_money do
          from("鈴木", 260)
          to("田中", 150)
        end
      }.should raise_error("入出の総和が0になりませんでした")
      
      should_init_status
    end
    
    def should_init_status
      AccountingTransaction.count.should == 0
      Entry.count.should == 3
      Account.count.should == 3
      suzuki = Account.find_by_name("鈴木")
      suzuki.balance.should == 700
      sato = Account.find_by_name("佐藤")
      sato.balance.should == 200
      tanaka = Account.find_by_name("田中")
      tanaka.balance.should == 300
    end
  end
  
  def init_db
    Account.delete_all
    Entry.delete_all
    AccountingTransaction.delete_all
    sato = Account.new (:name  => "佐藤")
    sato.entries << Entry.new(:amount  => 200)
  # sato.save saveしていないんだが。。。。 こうゆうもの?
    tanaka = Account.new (:name  => "田中")
    tanaka.entries << Entry.new(:amount  => 300)
  # tanaka.save
    tanaka = Account.new (:name  => "鈴木")
    tanaka.entries << Entry.new(:amount  => 700)
  #tanaka.save
  end
end

class AccountService
  class AcccountingRunner
    attr_accessor :transaction
    
    def initialize
      @transaction = AccountingTransaction.new
    end
    
    def send_money(&block)
      instance_eval(&block)
      if valid?
        # runner.transaction.save! saveしてないんだが。。。
      else
        destory
        raise "入出の総和が0になりませんでした"
      end
    end
    
    def from(name, amount)
      entry = Entry.new(:amount => -amount)
      entry.tmp_account = Account.find_by_name name
      entry.account = Account.find_by_name name
      @transaction.entries << entry
    end
    
    def to(name, amount)
      entry = Entry.new(:amount => amount)
      entry.tmp_account = Account.find_by_name name
      entry.account = Account.find_by_name name
      @transaction.entries << entry
    end
    
    def valid?
      @transaction.total_zero?
    end

    def destory
      @transaction.entries.delete_all
      @transaction.delete
    end
  end

  def send_money(&block)
    runner = AcccountingRunner.new
    runner.send_money(&block)
  end
end

class Account
  include MongoMapper::Document
  key :name, String, :required  => true, :unique => true
  many :entries, :order => 'create_at asc'
  
  def balance
    entries.inject(0) do |result, entry|
      result + entry.amount
    end
  end
end

class Entry
  attr_accessor :tmp_account
  include MongoMapper::Document
  key :amount, Integer
  belongs_to :account
  timestamps!
end

class AccountingTransaction
  include MongoMapper::Document
  many :entries
  timestamps!
  
  def total_zero?
    entries.inject(0) {|result, entry| result + entry.amount } == 0
  end
end