phosney-money 0.0.1
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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/lib/money/bank/no_exchange_bank.rb +9 -0
- data/lib/money/bank/variable_exchange_bank.rb +30 -0
- data/lib/money/core_extensions.rb +64 -0
- data/lib/money/rails.rb +41 -0
- data/lib/money.rb +254 -0
- data/lib/support/cattr_accessor.rb +57 -0
- data/test/helper.rb +10 -0
- data/test/test_money.rb +7 -0
- metadata +88 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2009 ishmael
|
|
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.rdoc
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
= money
|
|
2
|
+
|
|
3
|
+
Description goes here.
|
|
4
|
+
|
|
5
|
+
== Note on Patches/Pull Requests
|
|
6
|
+
|
|
7
|
+
* Fork the project.
|
|
8
|
+
* Make your feature addition or bug fix.
|
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
|
10
|
+
future version unintentionally.
|
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
|
14
|
+
|
|
15
|
+
== Copyright
|
|
16
|
+
|
|
17
|
+
Copyright (c) 2010 ishmael. See LICENSE for details.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require 'jeweler'
|
|
6
|
+
Jeweler::Tasks.new do |gem|
|
|
7
|
+
gem.name = "phosney-money"
|
|
8
|
+
gem.summary = %Q{Class aiding in the handling of Money.}
|
|
9
|
+
gem.description = %Q{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.}
|
|
10
|
+
gem.email = "ishmael@blackbalto.com"
|
|
11
|
+
gem.homepage = "http://github.com/ishmael/money"
|
|
12
|
+
gem.authors = ["Manuel Silva"]
|
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
|
14
|
+
gem.files.include 'lib/**/*.rb'
|
|
15
|
+
#gem.files = FileList['lib/**/*.rb']
|
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
|
17
|
+
end
|
|
18
|
+
Jeweler::GemcutterTasks.new
|
|
19
|
+
rescue LoadError
|
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
require 'rake/testtask'
|
|
24
|
+
Rake::TestTask.new(:test) do |test|
|
|
25
|
+
test.libs << 'lib' << 'test'
|
|
26
|
+
test.pattern = 'test/**/test_*.rb'
|
|
27
|
+
test.verbose = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
require 'rcov/rcovtask'
|
|
32
|
+
Rcov::RcovTask.new do |test|
|
|
33
|
+
test.libs << 'test'
|
|
34
|
+
test.pattern = 'test/**/test_*.rb'
|
|
35
|
+
test.verbose = true
|
|
36
|
+
end
|
|
37
|
+
rescue LoadError
|
|
38
|
+
task :rcov do
|
|
39
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
task :test => :check_dependencies
|
|
44
|
+
|
|
45
|
+
task :default => :test
|
|
46
|
+
|
|
47
|
+
require 'rake/rdoctask'
|
|
48
|
+
Rake::RDocTask.new do |rdoc|
|
|
49
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
|
50
|
+
|
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
52
|
+
rdoc.title = "money #{version}"
|
|
53
|
+
rdoc.rdoc_files.include('README*')
|
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
55
|
+
end
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.1
|
|
@@ -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,64 @@
|
|
|
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
|
+
# Locale support:
|
|
37
|
+
# If
|
|
38
|
+
# number.format.separator = ","
|
|
39
|
+
# number.format.separator = "."
|
|
40
|
+
# Then
|
|
41
|
+
# '100,37'.to_money #=> #<Money @cents=10037>
|
|
42
|
+
#
|
|
43
|
+
def to_money(precision = nil)
|
|
44
|
+
# Get the currency
|
|
45
|
+
matches = scan /([A-Z]{2,3})/
|
|
46
|
+
currency = matches[0] ? matches[0][0] : Money.default_currency
|
|
47
|
+
|
|
48
|
+
separator = I18n.translate("number.format.separator", :default => ".")
|
|
49
|
+
delimiter = I18n.translate("number.format.delimiter", :default => ",")
|
|
50
|
+
|
|
51
|
+
if !precision
|
|
52
|
+
matches = scan(Regexp.new("\\#{separator}(\\d+)"))
|
|
53
|
+
precision = matches[0] ? matches[0][0].length : 2
|
|
54
|
+
precision = 2 if precision < 2
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get the cents amount
|
|
58
|
+
str = self =~ /^(\.|,)/ ? "0#{self}" : self
|
|
59
|
+
matches = str.scan(Regexp.new("(\\-?[\\d\\#{delimiter}]+(\\#{separator}(\\d+))?)"))
|
|
60
|
+
cents = matches[0] ? (matches[0][0].gsub(delimiter, '').gsub(',','.').to_f * 10**precision) : 0
|
|
61
|
+
|
|
62
|
+
Money.new(cents, currency, precision)
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/money/rails.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
def money(name, options = {})
|
|
12
|
+
allow_nil = options.has_key?(:allow_nil) ? options.delete(:allow_nil) : true
|
|
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 => allow_nil,
|
|
17
|
+
:converter => lambda{ |m|
|
|
18
|
+
if !allow_nil && m.nil?
|
|
19
|
+
currency = options[:currency] || ::Money.default_currency
|
|
20
|
+
m = ::Money.new(0, currency, options[:precision])
|
|
21
|
+
end
|
|
22
|
+
m.to_money(options[:precision])
|
|
23
|
+
},
|
|
24
|
+
:constructor => lambda{ |*args|
|
|
25
|
+
cents, currency = args
|
|
26
|
+
cents ||= 0
|
|
27
|
+
currency ||= ::Money.default_currency
|
|
28
|
+
::Money.new(cents, currency, options[:precision])
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
define_method "#{name}_with_cleanup=" do |amount|
|
|
32
|
+
send "#{name}_without_cleanup=", amount.blank? ? nil : amount.to_money(options[:precision])
|
|
33
|
+
end
|
|
34
|
+
alias_method_chain "#{name}=", :cleanup
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
ActiveRecord::Base.send :include, ActiveRecord::Acts::Money
|
data/lib/money.rb
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
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
|
+
class Money
|
|
7
|
+
include Comparable
|
|
8
|
+
|
|
9
|
+
attr_reader :cents, :currency, :precision
|
|
10
|
+
|
|
11
|
+
class MoneyError < StandardError# :nodoc:
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Bank lets you exchange the object which is responsible for currency
|
|
15
|
+
# exchange.
|
|
16
|
+
# The default implementation just throws an exception. However money
|
|
17
|
+
# ships with a variable exchange bank implementation which supports
|
|
18
|
+
# custom excahnge rates:
|
|
19
|
+
#
|
|
20
|
+
# Money.bank = VariableExchangeBank.new
|
|
21
|
+
# Money.bank.add_rate("USD", "CAD", 1.24515)
|
|
22
|
+
# Money.bank.add_rate("CAD", "USD", 0.803115)
|
|
23
|
+
# Money.us_dollar(100).exchange_to("CAD") => Money.ca_dollar(124)
|
|
24
|
+
# Money.ca_dollar(100).exchange_to("USD") => Money.us_dollar(80)
|
|
25
|
+
@@bank = NoExchangeBank.new
|
|
26
|
+
cattr_accessor :bank
|
|
27
|
+
|
|
28
|
+
@@default_currency = "USD"
|
|
29
|
+
cattr_accessor :default_currency
|
|
30
|
+
|
|
31
|
+
# String to use when formating zero values
|
|
32
|
+
cattr_accessor :zero
|
|
33
|
+
|
|
34
|
+
# Creates a new money object.
|
|
35
|
+
# Money.new(100)
|
|
36
|
+
#
|
|
37
|
+
# Alternativly you can use the convinience methods like
|
|
38
|
+
# Money.ca_dollar and Money.us_dollar
|
|
39
|
+
def initialize(cents, currency = default_currency, precision = 2)
|
|
40
|
+
@cents, @currency, @precision = cents.round, currency, precision
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Do two money objects equal? Only works if both objects are of the same currency
|
|
44
|
+
def eql?(other_money)
|
|
45
|
+
cents == other_money.cents && currency == other_money.currency
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def <=>(other_money)
|
|
49
|
+
if currency == other_money.currency
|
|
50
|
+
cents <=> other_money.cents
|
|
51
|
+
else
|
|
52
|
+
cents <=> other_money.exchange_to(currency).cents
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def +(other_money)
|
|
57
|
+
other_money = other_money.exchange_to(currency) unless other_money.currency == currency
|
|
58
|
+
|
|
59
|
+
new_precision = [precision, other_money.precision].max
|
|
60
|
+
Money.new(to_precision(new_precision).cents + other_money.to_precision(new_precision).cents, currency, new_precision)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def -(other_money)
|
|
64
|
+
other_money = other_money.exchange_to(currency) unless other_money.currency == currency
|
|
65
|
+
|
|
66
|
+
new_precision = [precision, other_money.precision].max
|
|
67
|
+
Money.new(to_precision(new_precision).cents - other_money.to_precision(new_precision).cents, currency, new_precision)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def -@
|
|
71
|
+
Money.new(-cents, currency, precision)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# multiply money by fixnum
|
|
75
|
+
def *(fixnum)
|
|
76
|
+
Money.new(cents * fixnum, currency, precision)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# divide money by fixnum
|
|
80
|
+
def /(fixnum)
|
|
81
|
+
Money.new(cents / fixnum, currency, precision)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Test if the money amount is zero
|
|
85
|
+
def zero?
|
|
86
|
+
cents == 0
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Format the price according to several rules
|
|
91
|
+
# Currently supported are :with_currency, :no_cents and :html
|
|
92
|
+
#
|
|
93
|
+
# with_currency:
|
|
94
|
+
#
|
|
95
|
+
# Money.ca_dollar(0).format => "free"
|
|
96
|
+
# Money.ca_dollar(100).format => "$1.00"
|
|
97
|
+
# Money.ca_dollar(100).format(:with_currency) => "$1.00 CAD"
|
|
98
|
+
# Money.us_dollar(85).format(:with_currency) => "$0.85 USD"
|
|
99
|
+
#
|
|
100
|
+
# no_cents:
|
|
101
|
+
#
|
|
102
|
+
# Money.ca_dollar(100).format(:no_cents) => "$1"
|
|
103
|
+
# Money.ca_dollar(599).format(:no_cents) => "$5"
|
|
104
|
+
#
|
|
105
|
+
# Money.ca_dollar(570).format(:no_cents, :with_currency) => "$5 CAD"
|
|
106
|
+
# Money.ca_dollar(39000).format(:no_cents) => "$390"
|
|
107
|
+
#
|
|
108
|
+
# html:
|
|
109
|
+
#
|
|
110
|
+
# Money.ca_dollar(570).format(:html, :with_currency) => "$5.70 <span class=\"currency\">CAD</span>"
|
|
111
|
+
def format(*rules)
|
|
112
|
+
return self.class.zero if zero? && self.class.zero
|
|
113
|
+
|
|
114
|
+
rules = rules.flatten
|
|
115
|
+
case self.currency
|
|
116
|
+
when 'EUR'
|
|
117
|
+
formatted = to_format(rules.include?(:no_cents) ? 0 : 2) +"\u20AC"
|
|
118
|
+
when 'GBP'
|
|
119
|
+
formatted = "\u00A3" + to_format(rules.include?(:no_cents) ? 0 : 2)
|
|
120
|
+
when 'USD'
|
|
121
|
+
formatted = "$" + to_format(rules.include?(:no_cents) ? 0 : 2)
|
|
122
|
+
when 'CAD'
|
|
123
|
+
formatted = "$" + to_format(rules.include?(:no_cents) ? 0 : 2)
|
|
124
|
+
else
|
|
125
|
+
formatted = "$" + to_format(rules.include?(:no_cents) ? 0 : 2)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if rules.include?(:with_currency)
|
|
131
|
+
formatted << " "
|
|
132
|
+
formatted << '<span class="currency">' if rules.include?(:html)
|
|
133
|
+
formatted << currency
|
|
134
|
+
formatted << '</span>' if rules.include?(:html)
|
|
135
|
+
end
|
|
136
|
+
formatted
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Money.ca_dollar(100).to_s => "1.00"
|
|
140
|
+
#
|
|
141
|
+
# Locale support:
|
|
142
|
+
# If
|
|
143
|
+
# number.currency.separator = ","
|
|
144
|
+
# number.currency.separator = "."
|
|
145
|
+
# Then
|
|
146
|
+
# Money.ca_dollar(100).to_s => "1,00"
|
|
147
|
+
#
|
|
148
|
+
def to_s(show_precision = precision)
|
|
149
|
+
s_separator = I18n.translate("number.format.separator", :default => ".")
|
|
150
|
+
if show_precision > 0
|
|
151
|
+
sprintf("%.#{show_precision}f", to_f ).gsub('.', s_separator)
|
|
152
|
+
else
|
|
153
|
+
sprintf("%d", cents.to_f / 10 ** (precision - show_precision) ).gsub('.', s_separator)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def to_format(show_precision = precision)
|
|
158
|
+
if show_precision > 0
|
|
159
|
+
sprintf("%.#{show_precision}f", to_f ).gsub('.', separator)
|
|
160
|
+
else
|
|
161
|
+
sprintf("%d", cents.to_f / 10 ** (precision - show_precision) ).gsub('.', separator)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def to_chart(*rules)
|
|
166
|
+
rules = rules.flatten
|
|
167
|
+
case self.currency
|
|
168
|
+
when 'EUR'
|
|
169
|
+
formatted = to_s(rules.include?(:no_cents) ? 0 : 2) +"€"
|
|
170
|
+
when 'GBP'
|
|
171
|
+
formatted = "£" + to_s(rules.include?(:no_cents) ? 0 : 2)
|
|
172
|
+
when 'USD'
|
|
173
|
+
formatted = "$" + to_s(rules.include?(:no_cents) ? 0 : 2)
|
|
174
|
+
when 'CAD'
|
|
175
|
+
formatted = "$" + to_s(rules.include?(:no_cents) ? 0 : 2)
|
|
176
|
+
else
|
|
177
|
+
formatted = "$" + to_s(rules.include?(:no_cents) ? 0 : 2)
|
|
178
|
+
end
|
|
179
|
+
formatted
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def separator
|
|
183
|
+
case self.currency
|
|
184
|
+
when 'EUR'
|
|
185
|
+
','
|
|
186
|
+
when 'GBP'
|
|
187
|
+
','
|
|
188
|
+
when 'USD'
|
|
189
|
+
'.'
|
|
190
|
+
when 'CAD'
|
|
191
|
+
'.'
|
|
192
|
+
else
|
|
193
|
+
'.'
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def to_f
|
|
198
|
+
cents.to_f / 10 ** precision
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Recieve the amount of this money object in another currency
|
|
202
|
+
def exchange_to(other_currency)
|
|
203
|
+
self.class.bank.exchange(self, other_currency)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def to_precision(new_precision)
|
|
207
|
+
difference = new_precision - precision
|
|
208
|
+
new_cents = difference > 0 ? cents * 10**difference : (cents.to_f / 10**difference.abs).round
|
|
209
|
+
Money.new(new_cents, currency, new_precision)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Create a new money object with value 0
|
|
213
|
+
def self.empty(currency = default_currency)
|
|
214
|
+
Money.new(0, currency)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Create a new money object using the Canadian dollar currency
|
|
218
|
+
def self.ca_dollar(num)
|
|
219
|
+
Money.new(num, "CAD")
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Create a new money object using the American dollar currency
|
|
223
|
+
def self.us_dollar(num)
|
|
224
|
+
Money.new(num, "USD")
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Create a new money object using the Euro currency
|
|
228
|
+
def self.euro(num)
|
|
229
|
+
Money.new(num, "EUR")
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Recieve a money object with the same amount as the current Money object
|
|
233
|
+
# in american dollar
|
|
234
|
+
def as_us_dollar
|
|
235
|
+
exchange_to("USD")
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Recieve a money object with the same amount as the current Money object
|
|
239
|
+
# in canadian dollar
|
|
240
|
+
def as_ca_dollar
|
|
241
|
+
exchange_to("CAD")
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Recieve a money object with the same amount as the current Money object
|
|
245
|
+
# in euro
|
|
246
|
+
def as_ca_euro
|
|
247
|
+
exchange_to("EUR")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Conversation to self
|
|
251
|
+
def to_money(precision = nil)
|
|
252
|
+
precision ? to_precision(precision) : self
|
|
253
|
+
end
|
|
254
|
+
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
|
data/test/helper.rb
ADDED
data/test/test_money.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: phosney-money
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 0
|
|
8
|
+
- 1
|
|
9
|
+
version: 0.0.1
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- Manuel Silva
|
|
13
|
+
autorequire:
|
|
14
|
+
bindir: bin
|
|
15
|
+
cert_chain: []
|
|
16
|
+
|
|
17
|
+
date: 2010-05-03 00:00:00 +01:00
|
|
18
|
+
default_executable:
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
name: thoughtbot-shoulda
|
|
22
|
+
prerelease: false
|
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ">="
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
segments:
|
|
28
|
+
- 0
|
|
29
|
+
version: "0"
|
|
30
|
+
type: :development
|
|
31
|
+
version_requirements: *id001
|
|
32
|
+
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.
|
|
33
|
+
email: ishmael@blackbalto.com
|
|
34
|
+
executables: []
|
|
35
|
+
|
|
36
|
+
extensions: []
|
|
37
|
+
|
|
38
|
+
extra_rdoc_files:
|
|
39
|
+
- LICENSE
|
|
40
|
+
- README.rdoc
|
|
41
|
+
files:
|
|
42
|
+
- .document
|
|
43
|
+
- .gitignore
|
|
44
|
+
- LICENSE
|
|
45
|
+
- README.rdoc
|
|
46
|
+
- Rakefile
|
|
47
|
+
- VERSION
|
|
48
|
+
- lib/money.rb
|
|
49
|
+
- lib/money/bank/no_exchange_bank.rb
|
|
50
|
+
- lib/money/bank/variable_exchange_bank.rb
|
|
51
|
+
- lib/money/core_extensions.rb
|
|
52
|
+
- lib/money/rails.rb
|
|
53
|
+
- lib/support/cattr_accessor.rb
|
|
54
|
+
- test/helper.rb
|
|
55
|
+
- test/test_money.rb
|
|
56
|
+
has_rdoc: true
|
|
57
|
+
homepage: http://github.com/ishmael/money
|
|
58
|
+
licenses: []
|
|
59
|
+
|
|
60
|
+
post_install_message:
|
|
61
|
+
rdoc_options:
|
|
62
|
+
- --charset=UTF-8
|
|
63
|
+
require_paths:
|
|
64
|
+
- lib
|
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
segments:
|
|
70
|
+
- 0
|
|
71
|
+
version: "0"
|
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
segments:
|
|
77
|
+
- 0
|
|
78
|
+
version: "0"
|
|
79
|
+
requirements: []
|
|
80
|
+
|
|
81
|
+
rubyforge_project:
|
|
82
|
+
rubygems_version: 1.3.6
|
|
83
|
+
signing_key:
|
|
84
|
+
specification_version: 3
|
|
85
|
+
summary: Class aiding in the handling of Money.
|
|
86
|
+
test_files:
|
|
87
|
+
- test/helper.rb
|
|
88
|
+
- test/test_money.rb
|