brandon-money 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2005 Tobias Lutke
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,75 @@
1
+ == Money class
2
+
3
+ This money class is based on the example from the ActiveRecord doc:
4
+ http://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html
5
+
6
+ Its in production use at http://www.snowdevil.ca and I haven't found any major issues
7
+ so far.
8
+ The main reason to open source it is because It might be useful to other people and
9
+ I hope i'll get some feedback on how to improve the class.
10
+
11
+ I bundled the exporter with the money class since some tests depend on it and I figured
12
+ that most applications which need to deal with Money also need to deal with proper
13
+ exporting.
14
+
15
+ == Download
16
+
17
+ Preferred method of installation is gem:
18
+
19
+ gem install --source http://dist.leetsoft.com money
20
+
21
+ Alternatively you can get the library packed
22
+
23
+ http://dist.leetsoft.com/pkg/
24
+
25
+ == Usage
26
+
27
+ Use the compose_of helper to let active record deal with embedding the money
28
+ object in your models. The following example requires a cents and a currency field.
29
+
30
+ class ProductUnit < ActiveRecord::Base
31
+ belongs_to :product
32
+ composed_of :price, :class_name => "Money", :mapping => [%w(cents cents) %(currency currency)]
33
+
34
+ private
35
+ validate :cents_not_zero
36
+
37
+ def cents_not_zero
38
+ errors.add("cents", "cannot be zero or less") unless cents > 0
39
+ end
40
+
41
+ validates_presence_of :sku, :currency
42
+ validates_uniqueness_of :sku
43
+ end
44
+
45
+ == Class configuration
46
+
47
+ Two const class variables are available to tailor Money to your needs.
48
+ If you don't need currency exchange at all, just ignore those.
49
+
50
+ === Default Currency
51
+
52
+ By default Money defaults to USD as its currency. This can be overwritten using
53
+
54
+ Money.default_currency = "CAD"
55
+
56
+ If you use rails, the environment.rb is a very good place to put this.
57
+
58
+ === Currency Exchange
59
+
60
+ The second parameter is a bit more complex. It lets you provide your own implementation of the
61
+ currency exchange service. By default Money throws an exception when trying to call .exchange_to.
62
+
63
+ A second minimalist implementation is provided which lets you supply custom exchange rates:
64
+
65
+ Money.bank = VariableExchangeBank.new
66
+ Money.bank.add_rate("USD", "CAD", 1.24515)
67
+ Money.bank.add_rate("CAD", "USD", 0.803115)
68
+ Money.us_dollar(100).exchange_to("CAD") => Money.ca_dollar(124)
69
+ Money.ca_dollar(100).exchange_to("USD") => Money.us_dollar(80)
70
+
71
+ There is nothing stopping you from creating bank objects which scrape www.xe.com for the current rates or just return rand(2)
72
+
73
+ == Code
74
+
75
+ If you have any improvements please email them to tobi [at] leetsoft.com
@@ -0,0 +1,9 @@
1
+ class NoExchangeBank# :nodoc:
2
+
3
+ def reduce(money, currency)
4
+ return money if money.currency == currency
5
+ raise Money::MoneyError.new("Current Money::bank does not support money exchange. Please implement a bank object that does and assign it to the Money class.")
6
+ end
7
+
8
+
9
+ end
@@ -0,0 +1,30 @@
1
+ # Example useage:
2
+ #
3
+ # Money.bank = VariableExchangeBank.new
4
+ # Money.bank.add_rate("USD", "CAD", 1.24515)
5
+ # Money.bank.add_rate("CAD", "USD", 0.803115)
6
+ # Money.us_dollar(100).exchange_to("CAD") => Money.ca_dollar(124)
7
+ # Money.ca_dollar(100).exchange_to("USD") => Money.us_dollar(80)
8
+ class VariableExchangeBank
9
+
10
+ def add_rate(from, to, rate)
11
+ rates["#{from}_TO_#{to}".upcase] = rate
12
+ end
13
+
14
+ def get_rate(from, to)
15
+ rates["#{from}_TO_#{to}".upcase]
16
+ end
17
+
18
+ def reduce(money, currency)
19
+ rate = get_rate(money.currency, currency) or raise Money::MoneyError.new("Can't find required exchange rate")
20
+
21
+ Money.new((money.cents * rate).floor, currency, money.precision)
22
+ end
23
+
24
+ private
25
+
26
+ def rates
27
+ @rates ||= {}
28
+ end
29
+
30
+ end
@@ -0,0 +1,26 @@
1
+ # Allows Writing of 100.to_money for +Numeric+ types
2
+ # 100.to_money => #<Money @cents=10000>
3
+ # 100.37.to_money => #<Money @cents=10037>
4
+ class Numeric
5
+ def to_money
6
+ Money.new(self * 100)
7
+ end
8
+ end
9
+
10
+ # Allows Writing of '100'.to_money for +String+ types
11
+ # Excess characters will be discarded
12
+ # '100'.to_money => #<Money @cents=10000>
13
+ # '100.37'.to_money => #<Money @cents=10037>
14
+ class String
15
+ def to_money
16
+ # Get the currency
17
+ matches = scan /([A-Z]{2,3})/
18
+ currency = matches[0] ? matches[0][0] : Money.default_currency
19
+
20
+ # Get the cents amount
21
+ matches = scan /(\-?\d+(\.(\d+))?)/
22
+ cents = matches[0] ? (matches[0][0].to_f * 100) : 0
23
+
24
+ Money.new(cents, currency)
25
+ end
26
+ end
@@ -0,0 +1,202 @@
1
+ # === Usage with ActiveRecord
2
+ #
3
+ # Use the compose_of helper to let active record deal with embedding the money
4
+ # object in your models. The following example requires a cents and a currency field.
5
+ #
6
+ # class ProductUnit < ActiveRecord::Base
7
+ # belongs_to :product
8
+ # composed_of :price, :class_name => "Money", :mapping => [ %w(cents cents), %w(currency currency) ]
9
+ #
10
+ # private
11
+ # validate :cents_not_zero
12
+ #
13
+ # def cents_not_zero
14
+ # errors.add("cents", "cannot be zero or less") unless cents > 0
15
+ # end
16
+ #
17
+ # validates_presence_of :sku, :currency
18
+ # validates_uniqueness_of :sku
19
+ # end
20
+ #
21
+ class Money
22
+ include Comparable
23
+
24
+ attr_reader :cents, :currency, :precision
25
+
26
+ class MoneyError < StandardError# :nodoc:
27
+ end
28
+
29
+ # Bank lets you exchange the object which is responsible for currency
30
+ # exchange.
31
+ # The default implementation just throws an exception. However money
32
+ # ships with a variable exchange bank implementation which supports
33
+ # custom excahnge rates:
34
+ #
35
+ # Money.bank = VariableExchangeBank.new
36
+ # Money.bank.add_rate("USD", "CAD", 1.24515)
37
+ # Money.bank.add_rate("CAD", "USD", 0.803115)
38
+ # Money.us_dollar(100).exchange_to("CAD") => Money.ca_dollar(124)
39
+ # Money.ca_dollar(100).exchange_to("USD") => Money.us_dollar(80)
40
+ @@bank = NoExchangeBank.new
41
+ cattr_accessor :bank
42
+
43
+ @@default_currency = "USD"
44
+ cattr_accessor :default_currency
45
+
46
+ # Creates a new money object.
47
+ # Money.new(100)
48
+ #
49
+ # Alternativly you can use the convinience methods like
50
+ # Money.ca_dollar and Money.us_dollar
51
+ def initialize(cents, currency = default_currency, precision = 2)
52
+ @cents, @currency, @precision = cents.round, currency, precision
53
+ end
54
+
55
+ # Do two money objects equal? Only works if both objects are of the same currency
56
+ def eql?(other_money)
57
+ cents == other_money.cents && currency == other_money.currency
58
+ end
59
+
60
+ def <=>(other_money)
61
+ if currency == other_money.currency
62
+ cents <=> other_money.cents
63
+ else
64
+ cents <=> other_money.exchange_to(currency).cents
65
+ end
66
+ end
67
+
68
+ def +(other_money)
69
+ other_money = other_money.exchange_to(currency) unless other_money.currency == currency
70
+
71
+ new_precision = [precision, other_money.precision].max
72
+ Money.new(to_precision(new_precision).cents + other_money.to_precision(new_precision).cents, currency, new_precision)
73
+ end
74
+
75
+ def -(other_money)
76
+ other_money = other_money.exchange_to(currency) unless other_money.currency == currency
77
+
78
+ new_precision = [precision, other_money.precision].max
79
+ Money.new(to_precision(new_precision).cents - other_money.to_precision(new_precision).cents, currency, new_precision)
80
+ end
81
+
82
+ # get the cents value of the object
83
+ def cents
84
+ @cents.to_i
85
+ end
86
+
87
+ # multiply money by fixnum
88
+ def *(fixnum)
89
+ Money.new(cents * fixnum, currency, precision)
90
+ end
91
+
92
+ # divide money by fixnum
93
+ def /(fixnum)
94
+ Money.new(cents / fixnum, currency, precision)
95
+ end
96
+
97
+ # Test if the money amount is zero
98
+ def zero?
99
+ cents == 0
100
+ end
101
+
102
+
103
+ # Format the price according to several rules
104
+ # Currently supported are :with_currency, :no_cents and :html
105
+ #
106
+ # with_currency:
107
+ #
108
+ # Money.ca_dollar(0).format => "free"
109
+ # Money.ca_dollar(100).format => "$1.00"
110
+ # Money.ca_dollar(100).format(:with_currency) => "$1.00 CAD"
111
+ # Money.us_dollar(85).format(:with_currency) => "$0.85 USD"
112
+ #
113
+ # no_cents:
114
+ #
115
+ # Money.ca_dollar(100).format(:no_cents) => "$1"
116
+ # Money.ca_dollar(599).format(:no_cents) => "$5"
117
+ #
118
+ # Money.ca_dollar(570).format(:no_cents, :with_currency) => "$5 CAD"
119
+ # Money.ca_dollar(39000).format(:no_cents) => "$390"
120
+ #
121
+ # html:
122
+ #
123
+ # Money.ca_dollar(570).format(:html, :with_currency) => "$5.70 <span class=\"currency\">CAD</span>"
124
+ def format(*rules)
125
+ return "free" if cents == 0
126
+
127
+ rules = rules.flatten
128
+
129
+ formatted = "$" + to_s(rules.include?(:no_cents) ? 0 : 2)
130
+
131
+ if rules.include?(:with_currency)
132
+ formatted << " "
133
+ formatted << '<span class="currency">' if rules.include?(:html)
134
+ formatted << currency
135
+ formatted << '</span>' if rules.include?(:html)
136
+ end
137
+ formatted
138
+ end
139
+
140
+ # Money.ca_dollar(100).to_s => "1.00"
141
+ def to_s(show_precision = precision)
142
+ if show_precision > 0
143
+ sprintf("%.#{show_precision}f", cents.to_f / 10 ** precision )
144
+ else
145
+ sprintf("%d", cents.to_f / 10 ** (precision - show_precision) )
146
+ end
147
+ end
148
+
149
+ # Recieve the amount of this money object in another currency
150
+ def exchange_to(other_currency)
151
+ self.class.bank.reduce(self, other_currency)
152
+ end
153
+
154
+ def to_precision(new_precision)
155
+ difference = new_precision - precision
156
+ new_cents = difference > 0 ? cents * 10**difference : (cents.to_f / 10**difference.abs).round
157
+ Money.new(new_cents, 'USD', new_precision)
158
+ end
159
+
160
+ # Create a new money object with value 0
161
+ def self.empty(currency = default_currency)
162
+ Money.new(0, currency)
163
+ end
164
+
165
+ # Create a new money object using the Canadian dollar currency
166
+ def self.ca_dollar(num)
167
+ Money.new(num, "CAD")
168
+ end
169
+
170
+ # Create a new money object using the American dollar currency
171
+ def self.us_dollar(num)
172
+ Money.new(num, "USD")
173
+ end
174
+
175
+ # Create a new money object using the Euro currency
176
+ def self.euro(num)
177
+ Money.new(num, "EUR")
178
+ end
179
+
180
+ # Recieve a money object with the same amount as the current Money object
181
+ # in american dollar
182
+ def as_us_dollar
183
+ exchange_to("USD")
184
+ end
185
+
186
+ # Recieve a money object with the same amount as the current Money object
187
+ # in canadian dollar
188
+ def as_ca_dollar
189
+ exchange_to("CAD")
190
+ end
191
+
192
+ # Recieve a money object with the same amount as the current Money object
193
+ # in euro
194
+ def as_ca_euro
195
+ exchange_to("EUR")
196
+ end
197
+
198
+ # Conversation to self
199
+ def to_money
200
+ self
201
+ end
202
+ end
data/lib/money.rb ADDED
@@ -0,0 +1,29 @@
1
+ #--
2
+ # Copyright (c) 2005 Tobias Luetke
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+
25
+ require File.dirname(__FILE__) + '/support/cattr_accessor'
26
+ require File.dirname(__FILE__) + '/bank/no_exchange_bank'
27
+ require File.dirname(__FILE__) + '/bank/variable_exchange_bank'
28
+ require File.dirname(__FILE__) + '/money/money'
29
+ require File.dirname(__FILE__) + '/money/core_extensions'
@@ -0,0 +1,57 @@
1
+ # Extends the class object with class and instance accessors for class attributes,
2
+ # just like the native attr* accessors for instance attributes.
3
+ class Class # :nodoc:
4
+ def cattr_reader(*syms)
5
+ syms.each do |sym|
6
+ class_eval <<-EOS
7
+ if ! defined? @@#{sym.id2name}
8
+ @@#{sym.id2name} = nil
9
+ end
10
+
11
+ def self.#{sym.id2name}
12
+ @@#{sym}
13
+ end
14
+
15
+ def #{sym.id2name}
16
+ @@#{sym}
17
+ end
18
+
19
+ def call_#{sym.id2name}
20
+ case @@#{sym.id2name}
21
+ when Symbol then send(@@#{sym})
22
+ when Proc then @@#{sym}.call(self)
23
+ when String then @@#{sym}
24
+ else nil
25
+ end
26
+ end
27
+ EOS
28
+ end
29
+ end
30
+
31
+ def cattr_writer(*syms)
32
+ syms.each do |sym|
33
+ class_eval <<-EOS
34
+ if ! defined? @@#{sym.id2name}
35
+ @@#{sym.id2name} = nil
36
+ end
37
+
38
+ def self.#{sym.id2name}=(obj)
39
+ @@#{sym.id2name} = obj
40
+ end
41
+
42
+ def self.set_#{sym.id2name}(obj)
43
+ @@#{sym.id2name} = obj
44
+ end
45
+
46
+ def #{sym.id2name}=(obj)
47
+ @@#{sym} = obj
48
+ end
49
+ EOS
50
+ end
51
+ end
52
+
53
+ def cattr_accessor(*syms)
54
+ cattr_reader(*syms)
55
+ cattr_writer(*syms)
56
+ end
57
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brandon-money
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.7.2
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Luetke
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Class aiding in the handling of Money and Currencies. It supports easy pluggable bank objects for customized exchange strategies. Can be used as composite in ActiveRecord tables.
17
+ email: tobi@leetsoft.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - README
26
+ - MIT-LICENSE
27
+ - lib/bank/no_exchange_bank.rb
28
+ - lib/bank/variable_exchange_bank.rb
29
+ - lib/money
30
+ - lib/money/core_extensions.rb
31
+ - lib/money/money.rb
32
+ - lib/money.rb
33
+ - lib/support
34
+ - lib/support/cattr_accessor.rb
35
+ has_rdoc: true
36
+ homepage: http://leetsoft.com/rails/money
37
+ post_install_message:
38
+ rdoc_options: []
39
+
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements: []
55
+
56
+ rubyforge_project:
57
+ rubygems_version: 1.2.0
58
+ signing_key:
59
+ specification_version: 2
60
+ summary: Class aiding in the handling of Money.
61
+ test_files: []
62
+