money 1.7.1 → 2.0.0
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 +2 -1
- data/README.rdoc +97 -0
- data/Rakefile +18 -0
- data/lib/money.rb +4 -8
- data/lib/money/core_extensions.rb +22 -10
- data/lib/money/errors.rb +4 -0
- data/lib/money/money.rb +83 -89
- data/lib/money/variable_exchange_bank.rb +72 -0
- data/money.gemspec +23 -0
- data/test/core_extensions_spec.rb +33 -0
- data/test/exchange_bank_spec.rb +45 -0
- data/test/money_spec.rb +124 -0
- metadata +56 -40
- data/README +0 -75
- data/lib/bank/no_exchange_bank.rb +0 -9
- data/lib/bank/variable_exchange_bank.rb +0 -30
- data/lib/support/cattr_accessor.rb +0 -57
data/MIT-LICENSE
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
Copyright (c) 2005 Tobias Lutke
|
2
|
+
Copyright (c) 2008 Phusion
|
2
3
|
|
3
4
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
5
|
a copy of this software and associated documentation files (the
|
@@ -17,4 +18,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
18
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
19
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
20
|
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.
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
= Introduction
|
2
|
+
|
3
|
+
This library aids one in handling money and different currencies. Features:
|
4
|
+
|
5
|
+
- Provides a Money class which encapsulates all information about an certain
|
6
|
+
amount of money, such as its value and its currency.
|
7
|
+
- Represents monetary values as integers, in cents. This avoids floating point
|
8
|
+
rounding errors.
|
9
|
+
- Provides APIs for exchanging money from one currency to another.
|
10
|
+
- Has the ability to parse a money string into a Money object.
|
11
|
+
|
12
|
+
Resources:
|
13
|
+
|
14
|
+
- Website: http://money.rubyforge.org
|
15
|
+
- RDoc API: http://money.rubyforge.org
|
16
|
+
- Git repository: http://github.com/FooBarWidget/money/tree/master
|
17
|
+
|
18
|
+
== Download
|
19
|
+
|
20
|
+
Install stable releases with the following command:
|
21
|
+
|
22
|
+
gem install money
|
23
|
+
|
24
|
+
The development version (hosted on Github) can be installed with:
|
25
|
+
|
26
|
+
gem sources -a http://gems.github.com
|
27
|
+
gem install FooBarWidget-money
|
28
|
+
|
29
|
+
== Usage
|
30
|
+
|
31
|
+
=== Synopsis
|
32
|
+
|
33
|
+
require 'money'
|
34
|
+
|
35
|
+
# 10.00 USD
|
36
|
+
money = Money.new(1000, "USD")
|
37
|
+
money.cents # => 1000
|
38
|
+
money.currency # => "USD"
|
39
|
+
|
40
|
+
Money.new(1000, "USD") == Money.new(1000, "USD") # => true
|
41
|
+
Money.new(1000, "USD") == Money.new(100, "USD") # => false
|
42
|
+
Money.new(1000, "USD") == Money.new(1000, "EUR") # => false
|
43
|
+
|
44
|
+
=== Currency Exchange
|
45
|
+
|
46
|
+
Exchanging money is performed through an exchange bank object. The default
|
47
|
+
exchange bank object requires one to manually specify the exchange rate. Here's
|
48
|
+
an example of how it works:
|
49
|
+
|
50
|
+
Money.add_rate("USD", "CAD", 1.24515)
|
51
|
+
Money.add_rate("CAD", "USD", 0.803115)
|
52
|
+
|
53
|
+
Money.us_dollar(100).exchange_to("CAD") # => Money.new(124, "CAD")
|
54
|
+
Money.ca_dollar(100).exchange_to("USD") # => Money.new(80, "USD")
|
55
|
+
|
56
|
+
Comparison and arithmetic operations work as expected:
|
57
|
+
|
58
|
+
Money.new(1000, "USD") <=> Money.new(900, "USD") # => 1; 9.00 USD is smaller
|
59
|
+
Money.new(1000, "EUR") + Money.new(10, "EUR") == Money.new(1010, "EUR")
|
60
|
+
|
61
|
+
Money.add_rate("USD", "EUR", 0.5)
|
62
|
+
Money.new(1000, "EUR") + Money.new(1000, "USD") == Money.new(1500, "EUR")
|
63
|
+
|
64
|
+
There is nothing stopping you from creating bank objects which scrapes
|
65
|
+
www.xe.com for the current rates or just returns <tt>rand(2)</tt>:
|
66
|
+
|
67
|
+
Money.default_bank = ExchangeBankWhichScrapesXeDotCom.new
|
68
|
+
|
69
|
+
=== Ruby on Rails
|
70
|
+
|
71
|
+
Use the +compose_of+ helper to let Active Record deal with embedding the money
|
72
|
+
object in your models. The following example requires a +cents+ and a +currency+
|
73
|
+
field.
|
74
|
+
|
75
|
+
class ProductUnit < ActiveRecord::Base
|
76
|
+
belongs_to :product
|
77
|
+
composed_of :price, :class_name => "Money", :mapping => [%w(cents cents), %w(currency currency)]
|
78
|
+
|
79
|
+
private
|
80
|
+
validate :cents_not_zero
|
81
|
+
|
82
|
+
def cents_not_zero
|
83
|
+
errors.add("cents", "cannot be zero or less") unless cents > 0
|
84
|
+
end
|
85
|
+
|
86
|
+
validates_presence_of :sku, :currency
|
87
|
+
validates_uniqueness_of :sku
|
88
|
+
end
|
89
|
+
|
90
|
+
=== Default Currency
|
91
|
+
|
92
|
+
By default Money defaults to USD as its currency. This can be overwritten using
|
93
|
+
|
94
|
+
Money.default_currency = "CAD"
|
95
|
+
|
96
|
+
If you use Rails, then environment.rb is a very good place to put this.
|
97
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
desc "Build a gem"
|
2
|
+
task :gem do
|
3
|
+
sh "gem build money.gemspec"
|
4
|
+
end
|
5
|
+
|
6
|
+
task "Generate RDoc documentation"
|
7
|
+
task :rdoc do
|
8
|
+
sh "hanna README.rdoc lib -U"
|
9
|
+
end
|
10
|
+
|
11
|
+
task :upload => :rdoc do
|
12
|
+
sh "scp -r doc/* rubyforge.org:/var/www/gforge-projects/money/"
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Run unit tests"
|
16
|
+
task :test do
|
17
|
+
sh "spec -f s -c test/*_spec.rb"
|
18
|
+
end
|
data/lib/money.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
#--
|
2
1
|
# Copyright (c) 2005 Tobias Luetke
|
2
|
+
# Copyright (c) 2008 Phusion
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -19,11 +19,7 @@
|
|
19
19
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
20
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
-
#++
|
23
22
|
|
24
|
-
|
25
|
-
require
|
26
|
-
require
|
27
|
-
require File.dirname(__FILE__) + '/bank/variable_exchange_bank'
|
28
|
-
require File.dirname(__FILE__) + '/money/money'
|
29
|
-
require File.dirname(__FILE__) + '/money/core_extensions'
|
23
|
+
$LOAD_PATH << File.expand_path(File.dirname(__FILE__))
|
24
|
+
require 'money/money'
|
25
|
+
require 'money/core_extensions'
|
@@ -1,25 +1,37 @@
|
|
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
1
|
class Numeric
|
2
|
+
# Converts this numeric to a Money object in the default currency. It
|
3
|
+
# multiplies the numeric value by 100 and treats that as cents.
|
4
|
+
#
|
5
|
+
# 100.to_money => #<Money @cents=10000>
|
6
|
+
# 100.37.to_money => #<Money @cents=10037>
|
5
7
|
def to_money
|
6
8
|
Money.new(self * 100)
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
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
12
|
class String
|
13
|
+
# Parses the current string and converts it to a Money object.
|
14
|
+
# Excess characters will be discarded.
|
15
|
+
#
|
16
|
+
# '100'.to_money # => #<Money @cents=10000>
|
17
|
+
# '100.37'.to_money # => #<Money @cents=10037>
|
18
|
+
# '100 USD'.to_money # => #<Money @cents=10000, @currency="USD">
|
19
|
+
# 'USD 100'.to_money # => #<Money @cents=10000, @currency="USD">
|
20
|
+
# '$100 USD'.to_money # => #<Money @cents=10000, @currency="USD">
|
15
21
|
def to_money
|
16
|
-
# Get the currency
|
22
|
+
# Get the currency.
|
17
23
|
matches = scan /([A-Z]{2,3})/
|
18
24
|
currency = matches[0] ? matches[0][0] : Money.default_currency
|
19
25
|
|
20
26
|
# Get the cents amount
|
21
|
-
matches = scan /(
|
22
|
-
cents =
|
27
|
+
matches = scan /(\-?[\d ]+([\.,](\d+))?)/
|
28
|
+
cents = if matches[0]
|
29
|
+
value = matches[0][0].gsub(/,/, '.')
|
30
|
+
value.gsub!(/ +/, '')
|
31
|
+
value.to_f * 100
|
32
|
+
else
|
33
|
+
0
|
34
|
+
end
|
23
35
|
|
24
36
|
Money.new(cents, currency)
|
25
37
|
end
|
data/lib/money/errors.rb
ADDED
data/lib/money/money.rb
CHANGED
@@ -1,64 +1,89 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
#
|
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
|
-
#
|
1
|
+
require 'money/variable_exchange_bank'
|
2
|
+
|
3
|
+
# Represents an amount of money in a certain currency.
|
21
4
|
class Money
|
22
5
|
include Comparable
|
23
|
-
|
24
|
-
attr_reader :cents, :currency
|
25
|
-
|
26
|
-
class
|
6
|
+
|
7
|
+
attr_reader :cents, :currency, :bank
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Each Money object is associated to a bank object, which is responsible
|
11
|
+
# for currency exchange. This property allows one to specify the default
|
12
|
+
# bank object.
|
13
|
+
#
|
14
|
+
# bank1 = MyBank.new
|
15
|
+
# bank2 = MyOtherBank.new
|
16
|
+
#
|
17
|
+
# Money.default_bank = bank1
|
18
|
+
# money1 = Money.new(10)
|
19
|
+
# money1.bank # => bank1
|
20
|
+
#
|
21
|
+
# Money.default_bank = bank2
|
22
|
+
# money2 = Money.new(10)
|
23
|
+
# money2.bank # => bank2
|
24
|
+
# money1.bank # => bank1
|
25
|
+
#
|
26
|
+
# The default value for this property is an instance if VariableExchangeBank.
|
27
|
+
# It allows one to specify custom exchange rates:
|
28
|
+
#
|
29
|
+
# Money.default_bank.add_rate("USD", "CAD", 1.24515)
|
30
|
+
# Money.default_bank.add_rate("CAD", "USD", 0.803115)
|
31
|
+
# Money.us_dollar(100).exchange_to("CAD") # => Money.ca_dollar(124)
|
32
|
+
# Money.ca_dollar(100).exchange_to("USD") # => Money.us_dollar(80)
|
33
|
+
attr_accessor :default_bank
|
34
|
+
|
35
|
+
# The default currency, which is used when <tt>Money.new</tt> is called
|
36
|
+
# without an explicit currency argument. The default value is "USD".
|
37
|
+
attr_accessor :default_currency
|
38
|
+
end
|
39
|
+
|
40
|
+
self.default_bank = VariableExchangeBank.instance
|
41
|
+
self.default_currency = "USD"
|
42
|
+
|
43
|
+
|
44
|
+
# Create a new money object with value 0.
|
45
|
+
def self.empty(currency = default_currency)
|
46
|
+
Money.new(0, currency)
|
27
47
|
end
|
28
48
|
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
49
|
+
# Creates a new Money object of the given value, using the Canadian dollar currency.
|
50
|
+
def self.ca_dollar(cents)
|
51
|
+
Money.new(cents, "CAD")
|
52
|
+
end
|
45
53
|
|
54
|
+
# Creates a new Money object of the given value, using the American dollar currency.
|
55
|
+
def self.us_dollar(cents)
|
56
|
+
Money.new(cents, "USD")
|
57
|
+
end
|
58
|
+
|
59
|
+
# Creates a new Money object of the given value, using the Euro currency.
|
60
|
+
def self.euro(cents)
|
61
|
+
Money.new(cents, "EUR")
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.add_rate(from_currency, to_currency, rate)
|
65
|
+
Money.default_bank.add_rate(from_currency, to_currency, rate)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
46
69
|
# Creates a new money object.
|
47
70
|
# Money.new(100)
|
48
71
|
#
|
49
72
|
# Alternativly you can use the convinience methods like
|
50
73
|
# Money.ca_dollar and Money.us_dollar
|
51
|
-
def initialize(cents, currency = default_currency)
|
52
|
-
@cents
|
74
|
+
def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
|
75
|
+
@cents = cents.round
|
76
|
+
@currency = currency
|
77
|
+
@bank = bank
|
53
78
|
end
|
54
79
|
|
55
80
|
# Do two money objects equal? Only works if both objects are of the same currency
|
56
|
-
def
|
57
|
-
cents == other_money.cents && currency
|
81
|
+
def ==(other_money)
|
82
|
+
cents == other_money.cents && bank.same_currency?(currency, other_money.currency)
|
58
83
|
end
|
59
84
|
|
60
85
|
def <=>(other_money)
|
61
|
-
if currency
|
86
|
+
if bank.same_currency?(currency, other_money.currency)
|
62
87
|
cents <=> other_money.cents
|
63
88
|
else
|
64
89
|
cents <=> other_money.exchange_to(currency).cents
|
@@ -66,9 +91,6 @@ class Money
|
|
66
91
|
end
|
67
92
|
|
68
93
|
def +(other_money)
|
69
|
-
return other_money.dup if cents.zero?
|
70
|
-
return dup if other_money.cents.zero?
|
71
|
-
|
72
94
|
if currency == other_money.currency
|
73
95
|
Money.new(cents + other_money.cents, other_money.currency)
|
74
96
|
else
|
@@ -77,24 +99,16 @@ class Money
|
|
77
99
|
end
|
78
100
|
|
79
101
|
def -(other_money)
|
80
|
-
|
81
102
|
if currency == other_money.currency
|
82
103
|
Money.new(cents - other_money.cents, other_money.currency)
|
83
104
|
else
|
84
|
-
|
85
|
-
return other_money.dup if self.cents.zero?
|
86
|
-
|
87
|
-
return self.dup if other_money.cents.zero?
|
88
|
-
|
89
|
-
|
90
105
|
Money.new(cents - other_money.exchange_to(currency).cents, currency)
|
91
|
-
|
92
106
|
end
|
93
107
|
end
|
94
108
|
|
95
109
|
# get the cents value of the object
|
96
110
|
def cents
|
97
|
-
@cents
|
111
|
+
@cents
|
98
112
|
end
|
99
113
|
|
100
114
|
# multiply money by fixnum
|
@@ -153,57 +167,37 @@ class Money
|
|
153
167
|
end
|
154
168
|
formatted
|
155
169
|
end
|
156
|
-
|
170
|
+
|
157
171
|
# Money.ca_dollar(100).to_s => "1.00"
|
158
172
|
def to_s
|
159
|
-
sprintf("%.2f", cents
|
173
|
+
sprintf("%.2f", cents / 100.00)
|
160
174
|
end
|
161
|
-
|
162
|
-
# Recieve the amount of this money object in another currency
|
175
|
+
|
176
|
+
# Recieve the amount of this money object in another currency.
|
163
177
|
def exchange_to(other_currency)
|
164
|
-
|
178
|
+
Money.new(@bank.exchange(self.cents, currency, other_currency), other_currency)
|
165
179
|
end
|
166
|
-
|
167
|
-
# Create a new money object with value 0
|
168
|
-
def self.empty(currency = default_currency)
|
169
|
-
Money.new(0, currency)
|
170
|
-
end
|
171
|
-
|
172
|
-
# Create a new money object using the Canadian dollar currency
|
173
|
-
def self.ca_dollar(num)
|
174
|
-
Money.new(num, "CAD")
|
175
|
-
end
|
176
|
-
|
177
|
-
# Create a new money object using the American dollar currency
|
178
|
-
def self.us_dollar(num)
|
179
|
-
Money.new(num, "USD")
|
180
|
-
end
|
181
|
-
|
182
|
-
# Create a new money object using the Euro currency
|
183
|
-
def self.euro(num)
|
184
|
-
Money.new(num, "EUR")
|
185
|
-
end
|
186
|
-
|
180
|
+
|
187
181
|
# Recieve a money object with the same amount as the current Money object
|
188
182
|
# in american dollar
|
189
183
|
def as_us_dollar
|
190
184
|
exchange_to("USD")
|
191
185
|
end
|
192
|
-
|
186
|
+
|
193
187
|
# Recieve a money object with the same amount as the current Money object
|
194
188
|
# in canadian dollar
|
195
189
|
def as_ca_dollar
|
196
190
|
exchange_to("CAD")
|
197
191
|
end
|
198
|
-
|
192
|
+
|
199
193
|
# Recieve a money object with the same amount as the current Money object
|
200
194
|
# in euro
|
201
|
-
def
|
195
|
+
def as_euro
|
202
196
|
exchange_to("EUR")
|
203
|
-
end
|
204
|
-
|
197
|
+
end
|
198
|
+
|
205
199
|
# Conversation to self
|
206
200
|
def to_money
|
207
201
|
self
|
208
202
|
end
|
209
|
-
end
|
203
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'money/errors'
|
3
|
+
|
4
|
+
# Class for aiding in exchanging money between different currencies.
|
5
|
+
# By default, the Money class uses an object of this class (accessible through
|
6
|
+
# Money#bank) for performing currency exchanges.
|
7
|
+
#
|
8
|
+
# By default, VariableExchangeBank has no knowledge about conversion rates.
|
9
|
+
# One must manually specify them with +add_rate+, after which one can perform
|
10
|
+
# exchanges with +exchange+. For example:
|
11
|
+
#
|
12
|
+
# bank = Money::VariableExchangeBank.new
|
13
|
+
# bank.add_rate("USD", "CAD", 1.24515)
|
14
|
+
# bank.add_rate("CAD", "USD", 0.803115)
|
15
|
+
#
|
16
|
+
# # Exchange 100 CAD to USD:
|
17
|
+
# bank.exchange(100_00, "CAD", "USD") # => 124
|
18
|
+
# # Exchange 100 USD to CAD:
|
19
|
+
# bank.exchange(100_00, "USD", "CAD") # => 80
|
20
|
+
class Money
|
21
|
+
class VariableExchangeBank
|
22
|
+
# Returns the singleton instance of VariableExchangeBank.
|
23
|
+
#
|
24
|
+
# By default, <tt>Money.default_bank</tt> returns the same object.
|
25
|
+
def self.instance
|
26
|
+
@@singleton
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@rates = {}
|
31
|
+
@mutex = Mutex.new
|
32
|
+
end
|
33
|
+
|
34
|
+
# Registers a conversion rate. +from+ and +to+ are both currency names.
|
35
|
+
def add_rate(from, to, rate)
|
36
|
+
@mutex.synchronize do
|
37
|
+
@rates["#{from}_TO_#{to}".upcase] = rate
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Gets the rate for exchanging the currency named +from+ to the currency
|
42
|
+
# named +to+. Returns nil if the rate is unknown.
|
43
|
+
def get_rate(from, to)
|
44
|
+
@mutex.synchronize do
|
45
|
+
@rates["#{from}_TO_#{to}".upcase]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Given two currency names, checks whether they're both the same currency.
|
50
|
+
#
|
51
|
+
# bank = VariableExchangeBank.new
|
52
|
+
# bank.same_currency?("usd", "USD") # => true
|
53
|
+
# bank.same_currency?("usd", "EUR") # => false
|
54
|
+
def same_currency?(currency1, currency2)
|
55
|
+
currency1.upcase == currency2.upcase
|
56
|
+
end
|
57
|
+
|
58
|
+
# Exchange the given amount of cents in +from_currency+ to +to_currency+.
|
59
|
+
# Returns the amount of cents in +to_currency+ as an integer, rounded down.
|
60
|
+
#
|
61
|
+
# If the conversion rate is unknown, then Money::UnknownRate will be raised.
|
62
|
+
def exchange(cents, from_currency, to_currency)
|
63
|
+
rate = get_rate(from_currency, to_currency)
|
64
|
+
if !rate
|
65
|
+
raise Money::UnknownRate, "No conversion rate known for '#{from_currency}' -> '#{to_currency}'"
|
66
|
+
end
|
67
|
+
(cents * rate).floor
|
68
|
+
end
|
69
|
+
|
70
|
+
@@singleton = VariableExchangeBank.new
|
71
|
+
end
|
72
|
+
end
|
data/money.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "money"
|
3
|
+
s.version = "2.0.0"
|
4
|
+
s.summary = "Money and currency exchange support library"
|
5
|
+
s.email = "hongli@phusion.nl"
|
6
|
+
s.homepage = "http://money.rubyforge.org/"
|
7
|
+
s.description = "Money and currency exchange support library."
|
8
|
+
s.has_rdoc = true
|
9
|
+
s.rubyforge_project = "money"
|
10
|
+
s.authors = ["Tobias Luetke", "Hongli Lai"]
|
11
|
+
|
12
|
+
s.files = [
|
13
|
+
"README.rdoc", "MIT-LICENSE", "money.gemspec", "Rakefile",
|
14
|
+
"lib/money.rb",
|
15
|
+
"lib/money/core_extensions.rb",
|
16
|
+
"lib/money/errors.rb",
|
17
|
+
"lib/money/money.rb",
|
18
|
+
"lib/money/variable_exchange_bank.rb",
|
19
|
+
"test/core_extensions_spec.rb",
|
20
|
+
"test/exchange_bank_spec.rb",
|
21
|
+
"test/money_spec.rb"
|
22
|
+
]
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path(File.dirname(__FILE__) + "/../lib")
|
2
|
+
require 'money/core_extensions'
|
3
|
+
|
4
|
+
describe "Money core extensions" do
|
5
|
+
specify "Numberic#to_money works" do
|
6
|
+
money = 1234.to_money
|
7
|
+
money.cents.should == 1234_00
|
8
|
+
money.currency.should == Money.default_currency
|
9
|
+
|
10
|
+
money = 100.37.to_money
|
11
|
+
money.cents.should == 100_37
|
12
|
+
money.currency.should == Money.default_currency
|
13
|
+
end
|
14
|
+
|
15
|
+
specify "String#to_money works" do
|
16
|
+
"100".to_money.should == Money.new(100_00)
|
17
|
+
"100.37".to_money.should == Money.new(100_37)
|
18
|
+
"100,37".to_money.should == Money.new(100_37)
|
19
|
+
"100 000".to_money.should == Money.new(100_000_00)
|
20
|
+
|
21
|
+
"100 USD".to_money.should == Money.new(100_00, "USD")
|
22
|
+
"100 EUR".to_money.should == Money.new(100_00, "EUR")
|
23
|
+
"100.37 EUR".to_money.should == Money.new(100_37, "EUR")
|
24
|
+
"100,37 EUR".to_money.should == Money.new(100_37, "EUR")
|
25
|
+
|
26
|
+
"USD 100".to_money.should == Money.new(100_00, "USD")
|
27
|
+
"EUR 100".to_money.should == Money.new(100_00, "EUR")
|
28
|
+
"EUR 100.37".to_money.should == Money.new(100_37, "EUR")
|
29
|
+
"EUR 100,37".to_money.should == Money.new(100_37, "EUR")
|
30
|
+
|
31
|
+
"$100 USD".to_money.should == Money.new(100_00, "USD")
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path(File.dirname(__FILE__) + "/../lib")
|
2
|
+
require 'money/variable_exchange_bank'
|
3
|
+
|
4
|
+
describe Money::VariableExchangeBank do
|
5
|
+
before :each do
|
6
|
+
@bank = Money::VariableExchangeBank.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns the previously specified conversion rate" do
|
10
|
+
@bank.add_rate("USD", "EUR", 0.788332676)
|
11
|
+
@bank.add_rate("EUR", "YEN", 122.631477)
|
12
|
+
@bank.get_rate("USD", "EUR").should == 0.788332676
|
13
|
+
@bank.get_rate("EUR", "YEN").should == 122.631477
|
14
|
+
end
|
15
|
+
|
16
|
+
it "treats currency names case-insensitively" do
|
17
|
+
@bank.add_rate("usd", "eur", 1)
|
18
|
+
@bank.get_rate("USD", "EUR").should == 1
|
19
|
+
@bank.same_currency?("USD", "usd").should be_true
|
20
|
+
@bank.same_currency?("EUR", "usd").should be_false
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns nil if the conversion rate is unknown" do
|
24
|
+
@bank.get_rate("American Pesos", "EUR").should be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "exchanges money from one currency to another according to the specified conversion rates" do
|
28
|
+
@bank.add_rate("USD", "EUR", 0.5)
|
29
|
+
@bank.add_rate("EUR", "YEN", 10)
|
30
|
+
@bank.exchange(10_00, "USD", "EUR").should == 5_00
|
31
|
+
@bank.exchange(500_00, "EUR", "YEN").should == 5000_00
|
32
|
+
end
|
33
|
+
|
34
|
+
it "rounds the exchanged result down" do
|
35
|
+
@bank.add_rate("USD", "EUR", 0.788332676)
|
36
|
+
@bank.add_rate("EUR", "YEN", 122.631477)
|
37
|
+
@bank.exchange(10_00, "USD", "EUR").should == 788
|
38
|
+
@bank.exchange(500_00, "EUR", "YEN").should == 6131573
|
39
|
+
end
|
40
|
+
|
41
|
+
it "raises Money::UnknownRate upon conversion if the conversion rate is unknown" do
|
42
|
+
block = lambda { @bank.exchange(10, "USD", "EUR") }
|
43
|
+
block.should raise_error(Money::UnknownRate)
|
44
|
+
end
|
45
|
+
end
|
data/test/money_spec.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path(File.dirname(__FILE__) + "/../lib")
|
2
|
+
require 'money/money'
|
3
|
+
|
4
|
+
describe Money do
|
5
|
+
it "is associated to the singleton instance of VariableExchangeBank by default" do
|
6
|
+
Money.new(0).bank.object_id.should == Money::VariableExchangeBank.instance.object_id
|
7
|
+
end
|
8
|
+
|
9
|
+
specify "#cents returns the amount of cents passed to the constructor" do
|
10
|
+
Money.new(200_00, "USD").cents.should == 200_00
|
11
|
+
end
|
12
|
+
|
13
|
+
it "rounds the given cents to an integer" do
|
14
|
+
Money.new(1.00, "USD").cents.should == 1
|
15
|
+
Money.new(1.01, "USD").cents.should == 1
|
16
|
+
Money.new(1.50, "USD").cents.should == 2
|
17
|
+
end
|
18
|
+
|
19
|
+
specify "#currency returns the currency passed to the constructor" do
|
20
|
+
Money.new(200_00, "USD").currency.should == "USD"
|
21
|
+
end
|
22
|
+
|
23
|
+
specify "#zero? returns whether the amount is 0" do
|
24
|
+
Money.new(0, "USD").should be_zero
|
25
|
+
Money.new(0, "EUR").should be_zero
|
26
|
+
Money.new(1, "USD").should_not be_zero
|
27
|
+
Money.new(10, "YEN").should_not be_zero
|
28
|
+
Money.new(-1, "EUR").should_not be_zero
|
29
|
+
end
|
30
|
+
|
31
|
+
specify "#exchange_to exchanges the amount via its exchange bank" do
|
32
|
+
money = Money.new(100_00, "USD")
|
33
|
+
money.bank.should_receive(:exchange).with(100_00, "USD", "EUR").and_return(200_00)
|
34
|
+
money.exchange_to("EUR")
|
35
|
+
end
|
36
|
+
|
37
|
+
specify "#exchange_to exchanges the amount properly" do
|
38
|
+
money = Money.new(100_00, "USD")
|
39
|
+
money.bank.should_receive(:exchange).with(100_00, "USD", "EUR").and_return(200_00)
|
40
|
+
money.exchange_to("EUR").should == Money.new(200_00, "EUR")
|
41
|
+
end
|
42
|
+
|
43
|
+
specify "#== returns true if and only if their amount and currency are equal" do
|
44
|
+
Money.new(1_00, "USD").should == Money.new(1_00, "USD")
|
45
|
+
Money.new(1_00, "USD").should_not == Money.new(1_00, "EUR")
|
46
|
+
Money.new(1_00, "USD").should_not == Money.new(2_00, "USD")
|
47
|
+
Money.new(1_00, "USD").should_not == Money.new(99_00, "EUR")
|
48
|
+
end
|
49
|
+
|
50
|
+
specify "#* multiplies the money's amount by the multiplier while retaining the currency" do
|
51
|
+
(Money.new(1_00, "USD") * 10).should == Money.new(10_00, "USD")
|
52
|
+
end
|
53
|
+
|
54
|
+
specify "#* divides the money's amount by the divisor while retaining the currency" do
|
55
|
+
(Money.new(10_00, "USD") / 10).should == Money.new(1_00, "USD")
|
56
|
+
end
|
57
|
+
|
58
|
+
specify "Money.empty creates a new Money object of 0 cents" do
|
59
|
+
Money.empty.should == Money.new(0)
|
60
|
+
end
|
61
|
+
|
62
|
+
specify "Money.ca_dollar creates a new Money object of the given value in CAD" do
|
63
|
+
Money.ca_dollar(50).should == Money.new(50, "CAD")
|
64
|
+
end
|
65
|
+
|
66
|
+
specify "Money.ca_dollar creates a new Money object of the given value in USD" do
|
67
|
+
Money.us_dollar(50).should == Money.new(50, "USD")
|
68
|
+
end
|
69
|
+
|
70
|
+
specify "Money.ca_dollar creates a new Money object of the given value in EUR" do
|
71
|
+
Money.euro(50).should == Money.new(50, "EUR")
|
72
|
+
end
|
73
|
+
|
74
|
+
specify "Money.add_rate works" do
|
75
|
+
Money.add_rate("EUR", "USD", 10)
|
76
|
+
Money.new(10_00, "EUR").exchange_to("USD").should == Money.new(100_00, "USD")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "Actions involving two Money objects" do
|
81
|
+
describe "if the other Money object has the same currency" do
|
82
|
+
specify "#<=> compares the two objects' amounts" do
|
83
|
+
(Money.new(1_00, "USD") <=> Money.new(1_00, "USD")).should == 0
|
84
|
+
(Money.new(1_00, "USD") <=> Money.new(99, "USD")).should > 0
|
85
|
+
(Money.new(1_00, "USD") <=> Money.new(2_00, "USD")).should < 0
|
86
|
+
end
|
87
|
+
|
88
|
+
specify "#+ adds the other object's amount to the current object's amount while retaining the currency" do
|
89
|
+
(Money.new(10_00, "USD") + Money.new(90, "USD")).should == Money.new(10_90, "USD")
|
90
|
+
end
|
91
|
+
|
92
|
+
specify "#- substracts the other object's amount from the current object's amount while retaining the currency" do
|
93
|
+
(Money.new(10_00, "USD") - Money.new(90, "USD")).should == Money.new(9_10, "USD")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "if the other Money object has a different currency" do
|
98
|
+
specify "#<=> compares the two objects' amount after converting the other object's amount to its own currency" do
|
99
|
+
target = Money.new(200_00, "EUR")
|
100
|
+
target.should_receive(:exchange_to).with("USD").and_return(Money.new(300_00, "USD"))
|
101
|
+
(Money.new(100_00, "USD") <=> target).should < 0
|
102
|
+
|
103
|
+
target = Money.new(200_00, "EUR")
|
104
|
+
target.should_receive(:exchange_to).with("USD").and_return(Money.new(100_00, "USD"))
|
105
|
+
(Money.new(100_00, "USD") <=> target).should == 0
|
106
|
+
|
107
|
+
target = Money.new(200_00, "EUR")
|
108
|
+
target.should_receive(:exchange_to).with("USD").and_return(Money.new(99_00, "USD"))
|
109
|
+
(Money.new(100_00, "USD") <=> target).should > 0
|
110
|
+
end
|
111
|
+
|
112
|
+
specify "#+ adds the other object's amount, converted to this object's currency, to this object's amount while retaining its currency" do
|
113
|
+
other = Money.new(90, "EUR")
|
114
|
+
other.should_receive(:exchange_to).with("USD").and_return(Money.new(9_00, "USD"))
|
115
|
+
(Money.new(10_00, "USD") + other).should == Money.new(19_00, "USD")
|
116
|
+
end
|
117
|
+
|
118
|
+
specify "#- substracts the other object's amount, converted to this object's currency, from this object's amount while retaining its currency" do
|
119
|
+
other = Money.new(90, "EUR")
|
120
|
+
other.should_receive(:exchange_to).with("USD").and_return(Money.new(9_00, "USD"))
|
121
|
+
(Money.new(10_00, "USD") - other).should == Money.new(1_00, "USD")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
metadata
CHANGED
@@ -1,49 +1,65 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.8.11
|
3
|
-
specification_version: 1
|
4
2
|
name: money
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version:
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
homepage: http://leetsoft.com/rails/money
|
13
|
-
rubyforge_project:
|
14
|
-
description: Class aiding in the handling of Money and Currencies.\nIt supports easy pluggable bank objects for customized exchange strategies.\nCan be used as composite in ActiveRecord tables.
|
15
|
-
autorequire: money
|
16
|
-
default_executable:
|
4
|
+
version: 2.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tobias Luetke
|
8
|
+
- Hongli Lai
|
9
|
+
autorequire:
|
17
10
|
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2008-10-31 00:00:00 +01:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Money and currency exchange support library.
|
18
|
+
email: hongli@phusion.nl
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- README.rdoc
|
27
|
+
- MIT-LICENSE
|
28
|
+
- money.gemspec
|
29
|
+
- Rakefile
|
30
|
+
- lib/money.rb
|
31
|
+
- lib/money/core_extensions.rb
|
32
|
+
- lib/money/errors.rb
|
33
|
+
- lib/money/money.rb
|
34
|
+
- lib/money/variable_exchange_bank.rb
|
35
|
+
- test/core_extensions_spec.rb
|
36
|
+
- test/exchange_bank_spec.rb
|
37
|
+
- test/money_spec.rb
|
18
38
|
has_rdoc: true
|
19
|
-
|
39
|
+
homepage: http://money.rubyforge.org/
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
20
46
|
requirements:
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
version: 0.0.0
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
25
50
|
version:
|
26
|
-
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project: money
|
60
|
+
rubygems_version: 1.2.0
|
27
61
|
signing_key:
|
28
|
-
|
29
|
-
|
30
|
-
- Tobias Luetke
|
31
|
-
files:
|
32
|
-
- README
|
33
|
-
- MIT-LICENSE
|
34
|
-
- lib/bank
|
35
|
-
- lib/money
|
36
|
-
- lib/money.rb
|
37
|
-
- lib/support
|
38
|
-
- lib/bank/no_exchange_bank.rb
|
39
|
-
- lib/bank/variable_exchange_bank.rb
|
40
|
-
- lib/money/core_extensions.rb
|
41
|
-
- lib/money/money.rb
|
42
|
-
- lib/support/cattr_accessor.rb
|
62
|
+
specification_version: 2
|
63
|
+
summary: Money and currency exchange support library
|
43
64
|
test_files: []
|
44
|
-
|
45
|
-
extra_rdoc_files: []
|
46
|
-
executables: []
|
47
|
-
extensions: []
|
48
|
-
requirements: []
|
49
|
-
dependencies: []
|
65
|
+
|
data/README
DELETED
@@ -1,75 +0,0 @@
|
|
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
|
@@ -1,9 +0,0 @@
|
|
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
|
@@ -1,30 +0,0 @@
|
|
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
|
@@ -1,57 +0,0 @@
|
|
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
|