bean_sprout 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4733d40d0fe67374824dead281510cda6053ec71
4
- data.tar.gz: e8865c4596897622f821e6c379c3d766732def86
3
+ metadata.gz: 8f8703dcac93814b06f78ef5c7f6081d41e3809c
4
+ data.tar.gz: 3d50d400de7ff74305f88db06a0feef6c6816658
5
5
  SHA512:
6
- metadata.gz: d13adb40a9589069865f7c72e217fc92017fc1db491040dc87fba2f30e8a6fa73dff009cb10a63b6a5956243e1a9a7885b5549ac87ff1f1652ba45602d201b39
7
- data.tar.gz: 8a608a9d8d6822c9c04f4b0ae26493eedef5b9ce3c5ceb2ba39a05a35f4532e8295cc92a19486ea94cd8b12bf3192374917969428b04177ef59edfad2f08e87c
6
+ metadata.gz: 73181700e7e1a3e20b552bc4010cdd4820718fc19c7990968183fad0e8f3e4aa99a49b043099e261e5a60556b06a7d5e56de2744516f54babbb0776dcba93c41
7
+ data.tar.gz: 85fb4800498552ed1a3a5f3b491c07456467044f048682ea3198387bef22f1ff407634449f8723ca9991d5646939242f7c58fd9b9a9c7c0edc24559b76e32757
@@ -1,31 +1,46 @@
1
- require 'bean_sprout/struct_from_hash_mixin'
2
- require 'bean_sprout/struct_archive_mixin'
1
+ require 'bean_sprout/package_private'
3
2
 
4
3
  module BeanSprout
5
- class Account < Struct.new(:currency, :external_id, :other_data)
6
- include StructFromHashMixin
7
- include StructArchiveMixin
4
+ # TODO: abstract :id?
5
+ class Bean
6
+ include PackagePrivate::InternalClass
8
7
 
9
- class BalanceHolder < Struct.new(:value)
10
- end
8
+ attr_reader :id, :balance, :currency, :sprouts
9
+
10
+ define_public_interface :Account
11
11
 
12
- def initialize *fields
13
- super *fields
14
- @entries = []
15
- @balance = BalanceHolder.new(0)
12
+ def initialize id, currency
13
+ @id = id
14
+ @currency = currency
15
+
16
+ @sprouts = Set.new
17
+ @balance = 0
16
18
  end
17
19
 
18
- def append_entry entry
19
- @entries.push entry
20
- @balance.value += entry.accurate_amount
20
+ def grow sprout
21
+ @sprouts.add sprout
22
+ @balance += sprout.amount
21
23
  end
22
24
 
23
- def entries
24
- @entries.clone
25
+ def pick sprout
26
+ @sprouts.delete sprout
27
+ @balance -= sprout.amount
25
28
  end
29
+ end
30
+
31
+ # Public interface.
32
+ class Account < PackagePrivate::PublicInterfaceBase
33
+ def_default_delegators :balance, :currency
34
+ def_private_default_delegators :sprouts
26
35
 
27
- def balance
28
- @balance.value
36
+ def entries
37
+ sprouts.map do |sprout|
38
+ if block_given?
39
+ yield sprout.to_entry
40
+ else
41
+ sprout.to_entry
42
+ end
43
+ end
29
44
  end
30
45
  end
31
46
  end
@@ -1,5 +1,4 @@
1
- require 'bean_sprout/struct_from_hash_mixin'
2
- require 'bean_sprout/struct_archive_mixin'
1
+ require 'bean_sprout/package_private'
3
2
  require 'bigdecimal'
4
3
  require 'bigdecimal/util'
5
4
 
@@ -10,16 +9,31 @@ module BeanSprout
10
9
  # 2. The amount to be added to the account balance, in local currency;
11
10
  # 3. Convention rate from local currency to the base currency;
12
11
  # 4. Other arbitrary data.
13
- class Entry < Struct.new(:account, :amount, :rate, :other_data)
14
- include StructFromHashMixin
15
- include StructArchiveMixin
12
+ class Sprout
13
+ include PackagePrivate::InternalClass
14
+ attr_reader :id, :bean, :amount, :rate
16
15
 
17
- def rate_or_one
18
- rate or 1
16
+ define_public_interface :Entry
17
+
18
+ def initialize id, bean, amount, rate = 1
19
+ @id = id
20
+ @bean = bean
21
+ @amount = amount.to_d
22
+ @rate = rate
23
+ end
24
+
25
+ def unified_amount
26
+ amount * rate
19
27
  end
28
+ end
29
+
30
+ # Public Interface.
31
+ class Entry < PackagePrivate::PublicInterfaceBase
32
+ def_default_delegators :amount, :unified_amount, :rate
33
+ def_private_default_delegators :bean
20
34
 
21
- def accurate_amount
22
- @accurate_amount ||= amount.to_d
35
+ def account
36
+ bean.to_account
23
37
  end
24
38
  end
25
39
  end
@@ -0,0 +1,24 @@
1
+ require 'forwardable'
2
+
3
+ module BeanSprout
4
+ class ForwardableDelegate
5
+ extend Forwardable
6
+
7
+ def initialize obj
8
+ @target = obj
9
+ end
10
+
11
+ class << self
12
+ def def_default_delegators *args
13
+ def_delegators :@target, *args
14
+ end
15
+
16
+ def def_private_default_delegators *args
17
+ def_default_delegators *args
18
+ private *args
19
+ end
20
+
21
+ private :def_default_delegators, :def_private_default_delegators
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,129 @@
1
+ require 'bean_sprout/sparse_array'
2
+ require 'bean_sprout/account'
3
+ require 'bean_sprout/entry'
4
+ require 'bean_sprout/transaction'
5
+
6
+ module BeanSprout
7
+ class Ledger
8
+ attr_reader :base_currency
9
+
10
+ def initialize base_currency
11
+ @base_currency = base_currency
12
+
13
+ @beans = SparseArray.new
14
+ @sprout_bunches = SparseArray.new
15
+ @sprouts = SparseArray.new
16
+ end
17
+
18
+ def create_account currency, other_data: nil
19
+ bean = @beans.store do |next_id|
20
+ Bean.new(next_id, currency)
21
+ end
22
+
23
+ Account.new(bean, other_data)
24
+ end
25
+
26
+ def create_entry account, amount, rate = nil, other_data: nil
27
+ bean = get_target account
28
+ if not @beans.has_key? bean.id
29
+ raise "Unkown account #{bean.to_account} refered."
30
+ end
31
+
32
+ if not (rate or bean.currency == base_currency)
33
+ raise "Rate must be specified if account is not in base currency " +
34
+ "#{base_currency}."
35
+ end
36
+ rate ||= 1
37
+
38
+
39
+ sprout = @sprouts.store do |next_id|
40
+ Sprout.new(next_id, bean, amount, rate)
41
+ end
42
+
43
+ Entry.new(sprout, other_data)
44
+ end
45
+
46
+ def create_transaction entries, other_data: nil
47
+ sprouts = entries.map do |entry| get_target entry end
48
+ sprout_bunch = @sprout_bunches.store do |next_id|
49
+ SproutBunch.new(next_id, sprouts)
50
+ end
51
+
52
+ Transaction.new(sprout_bunch, other_data)
53
+ end
54
+
55
+ def transfer from_acc, to_acc, amount
56
+ if from_acc.currency != @base_currency || to_acc.currency != @base_currency
57
+ raise "Cannot transfer between two forex accounts."
58
+ end
59
+
60
+ entry0 = create_entry from_acc, -amount
61
+ entry1 = create_entry to_acc, amount
62
+ commit_entries [entry0, entry1]
63
+ end
64
+
65
+ def base_currency_forex_transfer from_acc, to_acc, from_amount, to_amount
66
+ raise "Amount can't be 0." unless from_amount != 0 && to_amount != 0
67
+
68
+ rate0 = rate1 = nil
69
+ if from_acc.currency == @base_currency
70
+ rate1 = from_amount / to_amount
71
+ elsif to_acc.currency == @base_currency
72
+ rate0 = to_amount / from_amount
73
+ else
74
+ raise "Forex transfer must be to or from an account of base currency."
75
+ end
76
+
77
+ entry0 = create_entry from_acc, -from_amount, rate0
78
+ entry1 = create_entry to_acc, to_amount, rate1
79
+ commit_entries [entry0, entry1]
80
+ end
81
+
82
+ # TODO: clients can't access ID.
83
+ def account id
84
+ @beans.fetch(id).to_account
85
+ end
86
+
87
+ # TODO: clients can't access ID.
88
+ def transaction id
89
+ @sprout_bunches.fetch(id).to_transaction
90
+ end
91
+
92
+ # TODO: test
93
+ def accounts
94
+ @beans.values.map do |bean|
95
+ if block_given?
96
+ yield bean.to_account
97
+ else
98
+ bean.to_account
99
+ end
100
+ end
101
+ end
102
+
103
+ # TODO: test
104
+ def transactions
105
+ @sprout_bunches.values.map do |sprout_bunch|
106
+ if block_given?
107
+ yield sprout_bunch.to_transaction
108
+ else
109
+ sprout_bunch.to_transaction
110
+ end
111
+ end
112
+ end
113
+
114
+ def dummy_account
115
+ @dummy_account ||= create_account @base_currency, other_data: "This is a dummy account."
116
+ end
117
+
118
+ private
119
+ def get_target obj
120
+ obj.instance_variable_get :@target
121
+ end
122
+
123
+ def commit_entries entries
124
+ trans = create_transaction entries
125
+ trans.commit
126
+ trans
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,41 @@
1
+ require 'bean_sprout/forwardable_delegate'
2
+
3
+ module BeanSprout
4
+ # Implements the concept of package private methods.
5
+ module PackagePrivate
6
+ # A module to be included by the delegatee.
7
+ module InternalClass
8
+ # The public interface, set by PublicInterfaceBase.
9
+ attr_reader :public_interface
10
+
11
+ def self.included klass
12
+ klass.extend ClassMethods
13
+ end
14
+
15
+ def bind_public_interface public_interface
16
+ raise "Cannot bind public interface to null." if public_interface.nil?
17
+ raise "Cannot bind public interface twice." unless @public_interface.nil?
18
+ @public_interface = public_interface
19
+ end
20
+
21
+ module ClassMethods
22
+ def define_public_interface klass_name
23
+ define_method "to_#{(klass_name.to_s.split "::").last.downcase}" do
24
+ @public_interface
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ # A base class for delegator classes.
31
+ class PublicInterfaceBase < ForwardableDelegate
32
+ def initialize obj, other_data = nil
33
+ super(obj)
34
+ obj.bind_public_interface self
35
+ @other_data = other_data
36
+ end
37
+
38
+ attr_accessor :other_data
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ require 'forwardable'
2
+
3
+ module BeanSprout
4
+ class SparseArray
5
+ extend Forwardable
6
+
7
+ def_delegators :@entities, :each, :has_key?, :each_value, :values
8
+
9
+ def initialize index_offset = 0
10
+ @entities = {}
11
+ @index = index_offset
12
+ end
13
+
14
+ def store
15
+ index = next_index
16
+ @entities[index] = yield index
17
+ end
18
+
19
+ def fetch index
20
+ @entities[index]
21
+ end
22
+
23
+ def fetch! index
24
+ raise "Unkown index #{index}." unless @entities.has_key? index
25
+ fetch index
26
+ end
27
+
28
+ private
29
+ def next_index
30
+ @index += 1
31
+ end
32
+ end
33
+ end
@@ -1,29 +1,87 @@
1
- require 'bean_sprout/struct_archive_mixin'
1
+ require 'bean_sprout/package_private'
2
2
 
3
3
  module BeanSprout
4
- class Transaction < Struct.new(:entries, :other_data)
5
- include StructFromHashMixin
6
- include StructArchiveMixin
4
+ class SproutBunch
5
+ include PackagePrivate::InternalClass
7
6
 
8
- alias entries_data entries
9
- private :entries_data
7
+ attr_reader :sprouts
8
+
9
+ define_public_interface :Transaction
10
+
11
+ class NotBalancedError < StandardError
12
+ end
13
+
14
+ class IllegalStateError < StandardError
15
+ end
16
+
17
+ def initialize id, sprouts
18
+ @id = id
19
+ @sprouts = sprouts
20
+ end
10
21
 
11
- public
12
22
  def balanced?
13
23
  balance = 0
14
- entries_data.each do |entry|
15
- balance += entry.accurate_amount / entry.rate_or_one
24
+ @sprouts.each do |sprout|
25
+ balance += sprout.unified_amount
16
26
  end
17
27
  balance == 0
18
28
  end
19
29
 
20
30
  def balanced!
21
- raise "#{entries_data} is not balanced." unless balanced?
31
+ raise NotBalancedError.new("#{@sprouts} not balanced.") unless balanced?
32
+ end
33
+
34
+ def plant
35
+ balanced!
36
+ raise IllegalStateError, "Can't plant twice." if @in_place
37
+ sprouts.each do |sprout|
38
+ sprout.bean.grow sprout
39
+ end
40
+ @in_place = true
41
+ end
42
+
43
+ def remove
44
+ balanced!
45
+ raise IllegalStateError, "Must plant before remove." unless @in_place
46
+ sprouts.each do |sprout|
47
+ sprout.bean.pick sprout
48
+ end
49
+ @in_place = false
50
+ end
51
+ end
52
+
53
+ class Transaction < PackagePrivate::PublicInterfaceBase
54
+ def_default_delegators :balanced?
55
+ def_private_default_delegators :sprouts, :plant, :remove
56
+
57
+ def commit
58
+ begin
59
+ plant
60
+ rescue SproutBunch::NotBalancedError
61
+ raise "Cannot commit an imbalance transaction."
62
+ rescue SproutBunch::IllegalStateError
63
+ raise "Cannot commit a transaction more than once."
64
+ end
65
+ end
66
+
67
+ def revert
68
+ begin
69
+ remove
70
+ rescue SproutBunch::NotBalancedError
71
+ raise "Cannot revert an imbalance transaction."
72
+ rescue SproutBunch::IllegalStateError
73
+ raise "Cannot revert a transaction more than once."
74
+ end
22
75
  end
23
76
 
24
77
  def entries
25
- entries_data.clone
78
+ sprouts.map do |sprout|
79
+ if block_given?
80
+ yield sprout.to_entry
81
+ else
82
+ sprout.to_entry
83
+ end
84
+ end
26
85
  end
27
86
  end
28
87
  end
29
-
@@ -1,3 +1,3 @@
1
1
  module BeanSprout
2
- VERSION = '0.0.3'
2
+ VERSION = '0.0.4'
3
3
  end
data/lib/bean_sprout.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  require 'bean_sprout/account'
2
2
  require 'bean_sprout/entry'
3
- require 'bean_sprout/glass_jar'
4
- require 'bean_sprout/struct_archive_mixin'
5
- require 'bean_sprout/struct_from_hash_mixin'
3
+ require 'bean_sprout/ledger'
6
4
  require 'bean_sprout/transaction'
7
5
  require 'bean_sprout/version'
8
6
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bean_sprout
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Liqing Muyi
@@ -64,9 +64,10 @@ files:
64
64
  - lib/bean_sprout.rb
65
65
  - lib/bean_sprout/account.rb
66
66
  - lib/bean_sprout/entry.rb
67
- - lib/bean_sprout/glass_jar.rb
68
- - lib/bean_sprout/struct_archive_mixin.rb
69
- - lib/bean_sprout/struct_from_hash_mixin.rb
67
+ - lib/bean_sprout/forwardable_delegate.rb
68
+ - lib/bean_sprout/ledger.rb
69
+ - lib/bean_sprout/package_private.rb
70
+ - lib/bean_sprout/sparse_array.rb
70
71
  - lib/bean_sprout/transaction.rb
71
72
  - lib/bean_sprout/version.rb
72
73
  homepage: http://github.com/muyiliqing/bean_sprout
@@ -1,123 +0,0 @@
1
- module BeanSprout
2
- class GlassJar
3
- # TODO: maybe each transaction can have its own
4
- # currency.
5
- attr_reader :base_currency
6
-
7
- def initialize base_currency
8
- @base_currency = base_currency
9
-
10
- @accounts = {}
11
- @account_id = 0
12
- @account_external_ids = {}
13
-
14
- @transactions = {}
15
- @transaction_id = 0
16
-
17
- @entries = {}
18
- end
19
-
20
- def open_account account
21
- account.archive_in_glass_jar self, next_account_id
22
- @accounts[account.id] = account
23
- @account_external_ids[account.external_id] = account
24
- end
25
-
26
- def create_account currency, external_id = nil, other_data: nil
27
- account = Account.new currency, external_id, other_data
28
- open_account account
29
- end
30
-
31
- def accounts
32
- @accounts.values
33
- end
34
-
35
- def account id
36
- @accounts[id]
37
- end
38
-
39
- def account_for external_id
40
- @account_external_ids[external_id]
41
- end
42
-
43
- def transactions
44
- @transactions.values
45
- end
46
-
47
- def transaction id
48
- @transactions[id]
49
- end
50
-
51
- def entries
52
- @entries.values
53
- end
54
-
55
- def entry id
56
- @entries[id]
57
- end
58
-
59
- AccountEntitySizeBits = 30
60
- def commit_transaction trans
61
- raise "Creating transaction with no entries." if trans.entries.empty?
62
- if trans.entries.size >= (1 << AccountEntitySizeBits)
63
- raise "Creating transaction with too many entries."
64
- end
65
-
66
- # Validate trans status.
67
- trans.entries.each do |entry|
68
- valid_account! entry.account
69
- valid_rate! entry
70
- end
71
- trans.balanced!
72
-
73
- trans.archive_in_glass_jar self, next_transaction_id
74
- trans.entries.each_with_index do |entry, index|
75
- entry_id = (trans.id << AccountEntitySizeBits) + index
76
- entry.archive_in_glass_jar self, entry_id
77
-
78
- @entries[entry_id] = entry
79
- account(entry.account).append_entry(entry)
80
- end
81
- @transactions[trans.id] = trans
82
- end
83
-
84
- def create_transaction *entries, other_data: nil
85
- trans = Transaction.new(entries, other_data)
86
- commit_transaction(trans)
87
- end
88
-
89
- def new_account *args
90
- Account.new args
91
- end
92
-
93
- def new_transaction *args
94
- Transaction.new args
95
- end
96
-
97
- def new_entry *args
98
- Entry.new args
99
- end
100
-
101
- private
102
- def next_account_id
103
- @account_id += 1
104
- end
105
-
106
- def next_transaction_id
107
- @transaction_id += 1
108
- end
109
-
110
- def valid_account! account
111
- if not @accounts.has_key? account
112
- raise "Unkown account #{account} refered."
113
- end
114
- end
115
-
116
- def valid_rate! entry
117
- if not (entry.rate or account(entry.account).currency == base_currency)
118
- raise "Rate must be specified if entry is not in base currency " +
119
- "#{base_currency}.\n Entry is #{entry}"
120
- end
121
- end
122
- end
123
- end
@@ -1,16 +0,0 @@
1
- module BeanSprout
2
- module StructArchiveMixin
3
- def self.included klass
4
- klass.class_eval do
5
- attr_reader :id
6
- attr_reader :glass_jar
7
- end
8
- end
9
-
10
- def archive_in_glass_jar glass_jar, id
11
- @glass_jar = glass_jar
12
- @id = id
13
- freeze
14
- end
15
- end
16
- end
@@ -1,14 +0,0 @@
1
- module BeanSprout
2
- module StructFromHashMixin
3
- def self.included klass
4
- klass.extend ClassMethods
5
- end
6
-
7
- module ClassMethods
8
- def from_hash hash
9
- new(*hash.values_at(*members))
10
- end
11
- end
12
- end
13
- end
14
-