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.
- data/MIT-LICENSE +20 -0
- data/README +75 -0
- data/lib/bank/no_exchange_bank.rb +8 -0
- data/lib/bank/variable_exchange_bank.rb +30 -0
- data/lib/money.rb +29 -0
- data/lib/money/core_extensions.rb +26 -0
- data/lib/money/money.rb +193 -0
- data/lib/support/cattr_accessor.rb +57 -0
- metadata +47 -0
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
|
+
== 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,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
|
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,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
|
data/lib/money/money.rb
ADDED
@@ -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: []
|