money 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ == Useage
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,8 @@
1
+ class NoExchangeBank# :nodoc:
2
+
3
+ def reduce(money, currency)
4
+ 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.")
5
+ end
6
+
7
+
8
+ 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)
22
+ end
23
+
24
+ private
25
+
26
+ def rates
27
+ @rates ||= {}
28
+ end
29
+
30
+ end
@@ -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,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 = strip.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,193 @@
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
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)
52
+ @cents, @currency = cents, currency
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
+ if currency == other_money.currency
70
+ Money.new(cents + other_money.cents,currency)
71
+ else
72
+ Money.new(cents + other_money.exchange_to(currency).cents,currency)
73
+ end
74
+ end
75
+
76
+ def -(other_money)
77
+ if currency == other_money.currency
78
+ Money.new(cents - other_money.cents, currency)
79
+ else
80
+ Money.new(cents - other_money.exchange_to(currency).cents, currency)
81
+ end
82
+ end
83
+
84
+ # get the cents value of the object
85
+ def cents
86
+ @cents.to_i
87
+ end
88
+
89
+ # multiply money by fixnum
90
+ def *(fixnum)
91
+ Money.new(cents * fixnum, currency)
92
+ end
93
+
94
+ # divide money by fixnum
95
+ def /(fixnum)
96
+ Money.new(cents / fixnum, currency)
97
+ end
98
+
99
+
100
+ # Format the price according to several rules
101
+ # Currently supported are :with_currency, :no_cents and :html
102
+ #
103
+ # with_currency:
104
+ #
105
+ # Money.ca_dollar(0).format => "free"
106
+ # Money.ca_dollar(100).format => "$1.00"
107
+ # Money.ca_dollar(100).format(:with_currency) => "$1.00 CAD"
108
+ # Money.us_dollar(85).format(:with_currency) => "$0.85 USD"
109
+ #
110
+ # no_cents:
111
+ #
112
+ # Money.ca_dollar(100).format(:no_cents) => "$1"
113
+ # Money.ca_dollar(599).format(:no_cents) => "$5"
114
+ #
115
+ # Money.ca_dollar(570).format(:no_cents, :with_currency) => "$5 CAD"
116
+ # Money.ca_dollar(39000).format(:no_cents) => "$390"
117
+ #
118
+ # html:
119
+ #
120
+ # Money.ca_dollar(570).format(:html, :with_currency) => "$5.70 <span class=\"currency\">CAD</span>"
121
+ def format(*rules)
122
+ return "free" if cents == 0
123
+
124
+ rules = rules.flatten
125
+
126
+ if rules.include?(:no_cents)
127
+ formatted = sprintf("$%d", cents.to_f / 100 )
128
+ else
129
+ formatted = sprintf("$%.2f", cents.to_f / 100 )
130
+ end
131
+
132
+ if rules.include?(:with_currency)
133
+ formatted << " "
134
+ formatted << '<span class="currency">' if rules.include?(:html)
135
+ formatted << currency
136
+ formatted << '</span>' if rules.include?(:html)
137
+ end
138
+ formatted
139
+ end
140
+
141
+ # Money.ca_dollar(100).to_s => "$1.00 CAD"
142
+ def to_s
143
+ format(:with_currency)
144
+ end
145
+
146
+ # Recieve the amount of this money object in another currency
147
+ def exchange_to(other_currency)
148
+ self.class.bank.reduce(self, other_currency)
149
+ end
150
+
151
+ # Create a new money object with value 0
152
+ def self.empty(currency = default_currency)
153
+ Money.new(0, currency)
154
+ end
155
+
156
+ # Create a new money object using the Canadian dollar currency
157
+ def self.ca_dollar(num)
158
+ Money.new(num, "CAD")
159
+ end
160
+
161
+ # Create a new money object using the American dollar currency
162
+ def self.us_dollar(num)
163
+ Money.new(num, "USD")
164
+ end
165
+
166
+ # Create a new money object using the Euro currency
167
+ def self.euro(num)
168
+ Money.new(num, "EUR")
169
+ end
170
+
171
+ # Recieve a money object with the same amount as the current Money object
172
+ # in american dollar
173
+ def as_us_dollar
174
+ exchange_to("USD")
175
+ end
176
+
177
+ # Recieve a money object with the same amount as the current Money object
178
+ # in canadian dollar
179
+ def as_ca_dollar
180
+ exchange_to("CAD")
181
+ end
182
+
183
+ # Recieve a money object with the same amount as the current Money object
184
+ # in euro
185
+ def as_ca_euro
186
+ exchange_to("EUR")
187
+ end
188
+
189
+ # Conversation to self
190
+ def to_money
191
+ self
192
+ end
193
+ 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
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,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.10
3
+ specification_version: 1
4
+ name: money
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.3.2
7
+ date: 2005-06-08
8
+ summary: Class aiding in the handling of Money.
9
+ require_paths:
10
+ - lib
11
+ email: tobi@leetsoft.com
12
+ homepage: http://leetsoft.com/rails/money
13
+ rubyforge_project:
14
+ description: Can be used as composite in ActiveRecord tables.
15
+ autorequire: money
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ authors:
28
+ - Tobias Luetke
29
+ files:
30
+ - README
31
+ - MIT-LICENSE
32
+ - lib/bank
33
+ - lib/money
34
+ - lib/money.rb
35
+ - lib/support
36
+ - lib/bank/no_exchange_bank.rb
37
+ - lib/bank/variable_exchange_bank.rb
38
+ - lib/money/core_extensions.rb
39
+ - lib/money/money.rb
40
+ - lib/support/cattr_accessor.rb
41
+ test_files: []
42
+ rdoc_options: []
43
+ extra_rdoc_files: []
44
+ executables: []
45
+ extensions: []
46
+ requirements: []
47
+ dependencies: []