学習めも

理解が間違ってたら。ごめん。

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

# see accounting transaction http://martinfowler.com/apsupp/accounting.pdf
class AccountService
  class AcccountingRunner
    def initialize
      @transaction = AccountingTransaction.new
    end
    
    attr_accessor :transaction
    
    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
    runner = AcccountingRunner.new
    yield runner
    if runner.valid?
      # runner.transaction.save! saveしてないんだが。。。
    else
      runner.destory
      raise "バランスしません"
    end
  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


describe "accounting transaction" do
  before(:each) do
    init_db
  end
  let(:account_service) { AccountService.new}  
  
  context "送金 佐藤さんから田中さんへの例" do
    before(:each) do
      account_service.send_money do |runner|
        runner.from("佐藤", 99)
        runner.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
      sato = Account.find_by_name("田中")
      sato.balance.should == 300 + 99
    end
  end
  
  context "送金 鈴木さんから 佐藤さん,田中さんへの例" do
    before(:each) do
      account_service.send_money do |runner|
        runner.from("鈴木", 260)
        runner.to("佐藤", 110)
        runner.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 "バランスしない 送金のケース" do
    it "初期か状態であること" do
      AccountingTransaction.count.should == 0
      lambda {
        account_service.send_money do |runner|
          runner.from("鈴木", 260)
          runner.to("田中", 150)
        end
      }.should raise_error("バランスしません")
      
      should_init
    end
    
    def should_init
      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

本当は、DBトランザクションほしくなるから、Mongoは合わないね。