steveluscher-money 1.7.4.1

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,84 @@
1
+ == Money
2
+
3
+ This library makes it easier to deal with Money values, storing them as integers to avoid floating-point math errors.
4
+
5
+ == Download
6
+
7
+ Preferred method of installation is gem:
8
+
9
+ gem install --source http://gems.github.com collectiveidea-money
10
+
11
+ You can find the source at:
12
+
13
+ http://github.com/collectiveidea/money
14
+
15
+ == Rails
16
+
17
+ There is a rails extension that makes it easier to store money values in the database.
18
+
19
+ class Product < ActiveRecord::Base
20
+ money :price
21
+ validates_numericality_of :price_in_cents, :greater_than => 0
22
+ end
23
+
24
+ This assumes that there is a price_in_cents (integer) column in the database, which can
25
+ be changed by passing the :cents option. You can also specify the :currency option to
26
+ save the currency to a field in the database.
27
+
28
+ class Room < ActiveRecord::Base
29
+ money :rate, :cents => :rate_cents, :currency => :rate_currency
30
+ money :discount, :cents => :discount_cents
31
+ end
32
+
33
+ You can set the attribute to a String, Fixnum, or Float and it will call #to_money to
34
+ convert it to a Money object. This makes it convenient for using money fields in forms.
35
+
36
+ r = Room.new :rate => "100.00"
37
+ r.rate # returns <Money:0x249ef9c @currency="USD", @cents=10000>
38
+
39
+ By default, money values will be stored with a precision of 2 (cents). If you need to store different precisions, such as to the nearest tenth of a cent, you can specify the +:precision+ option:
40
+
41
+ class Room < ActiveRecord::Base
42
+ money :rate, :precision => 3
43
+ end
44
+
45
+ r = Room.new :rate => "100"
46
+ r.rate.format # returns $100.000
47
+ r.rate = "100.995"
48
+ r.rate.format # returns $100.995
49
+
50
+ To use the Rails functionality, install money as a plugin, or require 'money/rails'.
51
+ This version is compatible with Rails 2.2. For compatibility with previous versions of
52
+ Rails, check out the rails-2.1 branch.
53
+
54
+ == Class configuration
55
+
56
+ Two const class variables are available to tailor Money to your needs.
57
+ If you don't need currency exchange at all, just ignore those.
58
+
59
+ === Default Currency
60
+
61
+ By default Money defaults to USD as its currency. This can be overwritten using
62
+
63
+ Money.default_currency = "CAD"
64
+
65
+ If you use rails, the environment.rb is a very good place to put this.
66
+
67
+ === Currency Exchange
68
+
69
+ The second parameter is a bit more complex. It lets you provide your own implementation of the
70
+ currency exchange service. By default Money throws an exception when trying to call .exchange_to.
71
+
72
+ A second minimalist implementation is provided which lets you supply custom exchange rates:
73
+
74
+ Money.bank = VariableExchangeBank.new
75
+ Money.bank.add_rate("USD", "CAD", 1.24515)
76
+ Money.bank.add_rate("CAD", "USD", 0.803115)
77
+ Money.us_dollar(100).exchange_to("CAD") => Money.ca_dollar(124)
78
+ Money.ca_dollar(100).exchange_to("USD") => Money.us_dollar(80)
79
+
80
+ There is nothing stopping you from creating bank objects which scrape www.xe.com for the current rates or just return rand(2)
81
+
82
+ == Code
83
+
84
+ If you have any improvements please email them to tobi [at] leetsoft.com
@@ -0,0 +1,9 @@
1
+ class NoExchangeBank# :nodoc:
2
+
3
+ def exchange(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 exchange(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,52 @@
1
+ class Numeric
2
+ # Convert the number to a +Money+ object.
3
+ #
4
+ # 100.to_money #=> #<Money @cents=10000>
5
+ #
6
+ # Takes an optional precision, which defaults to 2
7
+ def to_money(precision = 2)
8
+ Money.new(self * 10**precision, Money.default_currency, precision)
9
+ end
10
+ end
11
+
12
+ class Float
13
+ # Convert the float to a +Money+ object.
14
+ #
15
+ # 3.75.to_money #=> #<Money @cents=375>
16
+ #
17
+ # It takes an optional precision, which defaults to 2 or the number of digits
18
+ # after the decimal point if it's more than 2.
19
+ def to_money(precision = nil)
20
+ to_s.to_money(precision)
21
+ end
22
+ end
23
+
24
+ class String
25
+ # Convert the String to a +Money+ object.
26
+ #
27
+ # '100'.to_money #=> #<Money @cents=10000>
28
+ # '100.37'.to_money #=> #<Money @cents=10037>
29
+ # '.37'.to_money #=> #<Money @cents=37>
30
+ #
31
+ # It takes an optional precision argument which defaults to 2 or the number of
32
+ # digits after the decimal point if it's more than 2.
33
+ #
34
+ # '3.479'.to_money # => #<Money @cents=3479 @precision=3>
35
+ #
36
+ def to_money(precision = nil)
37
+ # Get the currency
38
+ matches = scan /([A-Z]{2,3})/
39
+ currency = matches[0] ? matches[0][0] : Money.default_currency
40
+
41
+ if !precision
42
+ precision = scan(/\.(\d+)/).to_s.length
43
+ precision = 2 if precision < 2
44
+ end
45
+
46
+ # Get the cents amount
47
+ str = self =~ /^\./ ? "0#{self}" : self
48
+ matches = str.scan /(\-?[\d,]+(\.(\d+))?)/
49
+ cents = matches[0] ? (matches[0][0].gsub(',', '').to_f * 10**precision) : 0
50
+ Money.new(cents, currency, precision)
51
+ end
52
+ end
@@ -0,0 +1,51 @@
1
+ require 'money'
2
+
3
+ module ActiveRecord #:nodoc:
4
+ module Acts #:nodoc:
5
+ module Money #:nodoc:
6
+ def self.included(base) #:nodoc:
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def money(name, options = {})
13
+ options = {:precision => 2, :cents => "#{name}_in_cents".to_sym }.merge(options)
14
+ mapping = [[options[:cents], 'cents']]
15
+ mapping << [options[:currency].to_s, 'currency'] if options[:currency]
16
+ composed_of name, :class_name => 'Money', :mapping => mapping, :allow_nil => true,
17
+ :constructor => lambda{ |*args|
18
+ cents, currency = args
19
+ ::Money.new(cents, (currency || ::Money.default_currency), options[:precision])
20
+ }
21
+
22
+ attr_accessor :"#{name}_before_type_cast"
23
+
24
+ define_method "#{name}_with_cleanup=" do |amount|
25
+ send "#{name}_before_type_cast=", amount
26
+
27
+ if amount.blank?
28
+ money = nil
29
+ elsif amount.is_a?(::Money)
30
+ # We may have been passed a money object whose currency does not match the
31
+ # current record's currency, in which case we have to perform currency conversion
32
+ if options[:currency] and (send(options[:currency]) != amount.currency)
33
+ raise "Can't convert money object to the #{options[:currency]} of this #{self.class} without a bank." if ::Money.bank.is_a?(NoExchangeBank)
34
+ money = amount.exchange_to(send(options[:currency]) || ::Money.default_currency)
35
+ else
36
+ money = amount
37
+ end
38
+ else
39
+ money = ::Money.new(amount.to_money(options[:precision]).cents, (options[:currency] ? send(options[:currency]) : ::Money.default_currency), options[:precision])
40
+ end
41
+
42
+ send "#{name}_without_cleanup=", money
43
+ end
44
+ alias_method_chain "#{name}=", :cleanup
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ ActiveRecord::Base.send :include, ActiveRecord::Acts::Money
data/lib/money.rb ADDED
@@ -0,0 +1,213 @@
1
+ require 'support/cattr_accessor'
2
+ require 'money/bank/no_exchange_bank'
3
+ require 'money/bank/variable_exchange_bank'
4
+ require 'money/core_extensions'
5
+
6
+ # === Usage with ActiveRecord
7
+ #
8
+ # Use the compose_of helper to let active record deal with embedding the money
9
+ # object in your models. The following example requires a cents and a currency field.
10
+ #
11
+ # class ProductUnit < ActiveRecord::Base
12
+ # belongs_to :product
13
+ # composed_of :price, :class_name => "Money", :mapping => [ %w(cents cents), %w(currency currency) ]
14
+ #
15
+ # private
16
+ # validate :cents_not_zero
17
+ #
18
+ # def cents_not_zero
19
+ # errors.add("cents", "cannot be zero or less") unless cents > 0
20
+ # end
21
+ #
22
+ # validates_presence_of :sku, :currency
23
+ # validates_uniqueness_of :sku
24
+ # end
25
+ #
26
+ class Money
27
+ include Comparable
28
+
29
+ attr_reader :cents, :currency, :precision
30
+
31
+ class MoneyError < StandardError# :nodoc:
32
+ end
33
+
34
+ # Bank lets you exchange the object which is responsible for currency
35
+ # exchange.
36
+ # The default implementation just throws an exception. However money
37
+ # ships with a variable exchange bank implementation which supports
38
+ # custom excahnge rates:
39
+ #
40
+ # Money.bank = VariableExchangeBank.new
41
+ # Money.bank.add_rate("USD", "CAD", 1.24515)
42
+ # Money.bank.add_rate("CAD", "USD", 0.803115)
43
+ # Money.us_dollar(100).exchange_to("CAD") => Money.ca_dollar(124)
44
+ # Money.ca_dollar(100).exchange_to("USD") => Money.us_dollar(80)
45
+ @@bank = NoExchangeBank.new
46
+ cattr_accessor :bank
47
+
48
+ @@default_currency = "USD"
49
+ cattr_accessor :default_currency
50
+
51
+ # String to use when formating zero values
52
+ cattr_accessor :zero
53
+
54
+ # Creates a new money object.
55
+ # Money.new(100)
56
+ #
57
+ # Alternativly you can use the convinience methods like
58
+ # Money.ca_dollar and Money.us_dollar
59
+ def initialize(cents, currency = default_currency, precision = 2)
60
+ @cents, @currency, @precision = cents.round, currency, precision
61
+ end
62
+
63
+ # Do two money objects equal? Only works if both objects are of the same currency
64
+ def eql?(other_money)
65
+ cents == other_money.cents && currency == other_money.currency
66
+ end
67
+
68
+ def <=>(other_money)
69
+ if currency == other_money.currency
70
+ cents <=> other_money.cents
71
+ else
72
+ cents <=> other_money.exchange_to(currency).cents
73
+ end
74
+ end
75
+
76
+ def +(other_money)
77
+ other_money = other_money.exchange_to(currency) unless other_money.currency == currency
78
+
79
+ new_precision = [precision, other_money.precision].max
80
+ Money.new(to_precision(new_precision).cents + other_money.to_precision(new_precision).cents, currency, new_precision)
81
+ end
82
+
83
+ def -(other_money)
84
+ other_money = other_money.exchange_to(currency) unless other_money.currency == currency
85
+
86
+ new_precision = [precision, other_money.precision].max
87
+ Money.new(to_precision(new_precision).cents - other_money.to_precision(new_precision).cents, currency, new_precision)
88
+ end
89
+
90
+ def -@
91
+ Money.new(-cents, currency, precision)
92
+ end
93
+
94
+ # multiply money by fixnum
95
+ def *(fixnum)
96
+ Money.new(cents * fixnum, currency, precision)
97
+ end
98
+
99
+ # divide money by fixnum
100
+ def /(fixnum)
101
+ Money.new(cents / fixnum, currency, precision)
102
+ end
103
+
104
+ # Test if the money amount is zero
105
+ def zero?
106
+ cents == 0
107
+ end
108
+
109
+
110
+ # Format the price according to several rules
111
+ # Currently supported are :with_currency, :no_cents and :html
112
+ #
113
+ # with_currency:
114
+ #
115
+ # Money.ca_dollar(0).format => "free"
116
+ # Money.ca_dollar(100).format => "$1.00"
117
+ # Money.ca_dollar(100).format(:with_currency) => "$1.00 CAD"
118
+ # Money.us_dollar(85).format(:with_currency) => "$0.85 USD"
119
+ #
120
+ # no_cents:
121
+ #
122
+ # Money.ca_dollar(100).format(:no_cents) => "$1"
123
+ # Money.ca_dollar(599).format(:no_cents) => "$5"
124
+ #
125
+ # Money.ca_dollar(570).format(:no_cents, :with_currency) => "$5 CAD"
126
+ # Money.ca_dollar(39000).format(:no_cents) => "$390"
127
+ #
128
+ # html:
129
+ #
130
+ # Money.ca_dollar(570).format(:html, :with_currency) => "$5.70 <span class=\"currency\">CAD</span>"
131
+ def format(*rules)
132
+ return self.class.zero if zero? && self.class.zero
133
+
134
+ rules = rules.flatten
135
+
136
+ formatted = "$" + to_s(rules.include?(:no_cents) ? 0 : 2)
137
+
138
+ if rules.include?(:with_currency)
139
+ formatted << " "
140
+ formatted << '<span class="currency">' if rules.include?(:html)
141
+ formatted << currency
142
+ formatted << '</span>' if rules.include?(:html)
143
+ end
144
+ formatted
145
+ end
146
+
147
+ # Money.ca_dollar(100).to_s => "1.00"
148
+ def to_s(show_precision = precision)
149
+ if show_precision > 0
150
+ sprintf("%.#{show_precision}f", to_f )
151
+ else
152
+ sprintf("%d", cents.to_f / 10 ** (precision - show_precision) )
153
+ end
154
+ end
155
+
156
+ def to_f
157
+ cents.to_f / 10 ** precision
158
+ end
159
+
160
+ # Recieve the amount of this money object in another currency
161
+ def exchange_to(other_currency)
162
+ self.class.bank.exchange(self, other_currency)
163
+ end
164
+
165
+ def to_precision(new_precision)
166
+ difference = new_precision - precision
167
+ new_cents = difference > 0 ? cents * 10**difference : (cents.to_f / 10**difference.abs).round
168
+ Money.new(new_cents, currency, new_precision)
169
+ end
170
+
171
+ # Create a new money object with value 0
172
+ def self.empty(currency = default_currency)
173
+ Money.new(0, currency)
174
+ end
175
+
176
+ # Create a new money object using the Canadian dollar currency
177
+ def self.ca_dollar(num)
178
+ Money.new(num, "CAD")
179
+ end
180
+
181
+ # Create a new money object using the American dollar currency
182
+ def self.us_dollar(num)
183
+ Money.new(num, "USD")
184
+ end
185
+
186
+ # Create a new money object using the Euro currency
187
+ def self.euro(num)
188
+ Money.new(num, "EUR")
189
+ end
190
+
191
+ # Recieve a money object with the same amount as the current Money object
192
+ # in american dollar
193
+ def as_us_dollar
194
+ exchange_to("USD")
195
+ end
196
+
197
+ # Recieve a money object with the same amount as the current Money object
198
+ # in canadian dollar
199
+ def as_ca_dollar
200
+ exchange_to("CAD")
201
+ end
202
+
203
+ # Recieve a money object with the same amount as the current Money object
204
+ # in euro
205
+ def as_ca_euro
206
+ exchange_to("EUR")
207
+ end
208
+
209
+ # Conversation to self
210
+ def to_money(precision = nil)
211
+ precision ? to_precision(precision) : self
212
+ end
213
+ end
@@ -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 unless instance_methods.include?('cattr_reader')
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 unless instance_methods.include?('cattr_writer')
52
+
53
+ def cattr_accessor(*syms)
54
+ cattr_reader(*syms)
55
+ cattr_writer(*syms)
56
+ end unless instance_methods.include?('cattr_accessor')
57
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: steveluscher-money
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.7.4.1
5
+ platform: ruby
6
+ authors:
7
+ - Brandon Keepers
8
+ - Tobias Luetke
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-03-21 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ 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.
18
+ email: brandon@opensoul.org
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - README
27
+ - MIT-LICENSE
28
+ - lib/money.rb
29
+ - lib/money/core_extensions.rb
30
+ - lib/money/bank/no_exchange_bank.rb
31
+ - lib/money/bank/variable_exchange_bank.rb
32
+ - lib/money/rails.rb
33
+ - lib/support/cattr_accessor.rb
34
+ has_rdoc: true
35
+ homepage: http://github.com/collectiveidea/money
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ requirements: []
54
+
55
+ rubyforge_project:
56
+ rubygems_version: 1.2.0
57
+ signing_key:
58
+ specification_version: 2
59
+ summary: Class aiding in the handling of Money.
60
+ test_files: []
61
+