money 3.5.5 → 3.6.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.
File without changes
@@ -1,3 +1,17 @@
1
+ Money 3.x.x
2
+ ===========
3
+
4
+ Features
5
+ --------
6
+ - Add a symbol position option for Money#format (thanks Romain, Gil and
7
+ Julien)
8
+ - Updated CNY to use "Fen" and subunit_to_unit of 100
9
+ - Updates to work with gem-testers.org
10
+
11
+ Bigfixes
12
+ --------
13
+ - Fixed issue with #format(:no_cents => true) (thanks Romain & Julien)
14
+
1
15
  Money 3.5.5
2
16
  ===========
3
17
 
data/README.md CHANGED
@@ -194,7 +194,8 @@ object in your models. The following example requires a `cents` and a
194
194
  composed_of :price,
195
195
  :class_name => "Money",
196
196
  :mapping => [%w(cents cents), %w(currency currency_as_string)],
197
- :constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) }
197
+ :constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) },
198
+ :converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }
198
199
 
199
200
  For Money 2.2.x and previous versions, simply use the following `composed_of`
200
201
  definition:
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rake/clean'
3
+
4
+ CLOBBER.include('doc', '.yardoc')
5
+
6
+ def gemspec
7
+ @gemspec ||= begin
8
+ file = File.expand_path("../money.gemspec", __FILE__)
9
+ eval(File.read(file), binding, file)
10
+ end
11
+ end
12
+
13
+ begin
14
+ require 'rspec/core/rake_task'
15
+ RSpec::Core::RakeTask.new
16
+ rescue LoadError
17
+ task(:spec){abort "`gem install rspec` to run specs"}
18
+ end
19
+ task :default => :spec
20
+ task :test => :spec
21
+
22
+ begin
23
+ require 'yard'
24
+ YARD::Rake::YardocTask.new do |t|
25
+ t.options << "--files" << "CHANGELOG.md,LICENSE"
26
+ end
27
+ rescue LoadError
28
+ task(:yardoc){abort "`gem install yard` to generate documentation"}
29
+ end
30
+
31
+ begin
32
+ require 'rake/gempackagetask'
33
+ Rake::GemPackageTask.new(gemspec) do |pkg|
34
+ pkg.gem_spec = gemspec
35
+ end
36
+ task :gem => :gemspec
37
+ rescue LoadError
38
+ task(:gem){abort "`gem install rake` to package gems"}
39
+ end
40
+
41
+ desc "Install the gem locally"
42
+ task :install => :gem do
43
+ sh "gem install pkg/#{gemspec.full_name}.gem"
44
+ end
45
+
46
+ desc "Validate the gemspec"
47
+ task :gemspec do
48
+ gemspec.validate
49
+ end
@@ -51,7 +51,7 @@ class Money
51
51
  :cdf => { :priority => 100, :iso_code => "CDF", :name => "Congolese Franc", :symbol => "Fr", :subunit => "Centime", :subunit_to_unit => 100, :symbol_first => false, :html_entity => "", :decimal_mark => ".", :thousands_separator => ","},
52
52
  :chf => { :priority => 100, :iso_code => "CHF", :name => "Swiss Franc", :symbol => "Fr", :subunit => "Rappen", :subunit_to_unit => 100, :symbol_first => true, :html_entity => "", :decimal_mark => ".", :thousands_separator => ","},
53
53
  :clp => { :priority => 100, :iso_code => "CLP", :name => "Chilean Peso", :symbol => "$", :subunit => "Peso", :subunit_to_unit => 1, :symbol_first => true, :html_entity => "&#x20B1;", :decimal_mark => ",", :thousands_separator => "."},
54
- :cny => { :priority => 100, :iso_code => "CNY", :name => "Chinese Renminbi Yuan", :symbol => "¥", :subunit => "Jiao", :subunit_to_unit => 10, :symbol_first => true, :html_entity => "&#x5713;", :decimal_mark => ".", :thousands_separator => ","},
54
+ :cny => { :priority => 100, :iso_code => "CNY", :name => "Chinese Renminbi Yuan", :symbol => "¥", :subunit => "Fen", :subunit_to_unit => 100, :symbol_first => true, :html_entity => "&#x5713;", :decimal_mark => ".", :thousands_separator => ","},
55
55
  :cop => { :priority => 100, :iso_code => "COP", :name => "Colombian Peso", :symbol => "$", :subunit => "Centavo", :subunit_to_unit => 100, :symbol_first => true, :html_entity => "&#x20B1;", :decimal_mark => ",", :thousands_separator => "."},
56
56
  :crc => { :priority => 100, :iso_code => "CRC", :name => "Costa Rican Colón", :symbol => "₡", :subunit => "Céntimo", :subunit_to_unit => 100, :symbol_first => true, :html_entity => "&#x20A1;", :decimal_mark => ",", :thousands_separator => "."},
57
57
  :cuc => { :priority => 100, :iso_code => "CUC", :name => "Cuban Convertible Peso", :symbol => "$", :subunit => "Centavo", :subunit_to_unit => 100, :symbol_first => false, :html_entity => "", :decimal_mark => ".", :thousands_separator => ","},
@@ -871,7 +871,19 @@ class Money
871
871
  else
872
872
  "#{self.to_s}"
873
873
  end
874
- formatted = (currency.symbol_first? ? "#{symbol_value}#{formatted}" : "#{formatted} #{symbol_value}") unless symbol_value.nil? or symbol_value.empty?
874
+
875
+ symbol_position =
876
+ if rules.has_key?(:symbol_position)
877
+ rules[:symbol_position]
878
+ elsif currency.symbol_first?
879
+ :before
880
+ else
881
+ :after
882
+ end
883
+
884
+ if symbol_value && !symbol_value.empty?
885
+ formatted = (symbol_position == :before ? "#{symbol_value}#{formatted}" : "#{formatted} #{symbol_value}")
886
+ end
875
887
 
876
888
  if rules.has_key?(:decimal_mark) and rules[:decimal_mark] and
877
889
  rules[:decimal_mark] != decimal_mark
@@ -889,7 +901,7 @@ class Money
889
901
  end
890
902
 
891
903
  # Apply thousands_separator
892
- formatted.gsub!(/(\d)(?=(?:\d{3})+(?:\.|,|$))(\d{3}\..*)?/, "\\1#{thousands_separator_value}\\2")
904
+ formatted.gsub!(/(\d)(?=(?:\d{3})+(?:[^\d]|$))/, "\\1#{thousands_separator_value}")
893
905
 
894
906
  if rules[:with_currency]
895
907
  formatted << " "
@@ -994,7 +1006,7 @@ class Money
994
1006
  "#<Money cents:#{cents} currency:#{currency}>"
995
1007
  end
996
1008
 
997
- # Allocates money between different parties without loosing pennies.
1009
+ # Allocates money between different parties without loosing pennies.
998
1010
  # After the mathmatically split has been performed, left over pennies will
999
1011
  # be distributed round-robin amongst the parties. This means that parties
1000
1012
  # listed first will likely recieve more pennies then ones that are listed later
@@ -1004,7 +1016,7 @@ class Money
1004
1016
  # @return [Array<Money, Money, Money>]
1005
1017
  #
1006
1018
  # @example
1007
- # Money.new(5, "USD").allocate([0.3,0.7)) #=> [Money.new(2), Money.new(3)]
1019
+ # Money.new(5, "USD").allocate([0.3,0.7)) #=> [Money.new(2), Money.new(3)]
1008
1020
  # Money.new(100, "USD").allocate([0.33,0.33,0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
1009
1021
  def allocate(splits)
1010
1022
  allocations = splits.inject(0.0) {|sum, i| sum += i }
@@ -1061,7 +1073,7 @@ class Money
1061
1073
  rules = { rules => true } if rules.is_a?(Symbol)
1062
1074
  end
1063
1075
  if not rules.include?(:decimal_mark) and rules.include?(:separator)
1064
- rules[:decimal_mark] = rules[:separator]
1076
+ rules[:decimal_mark] = rules[:separator]
1065
1077
  end
1066
1078
  if not rules.include?(:thousands_separator) and rules.include?(:delimiter)
1067
1079
  rules[:thousands_separator] = rules[:delimiter]
@@ -0,0 +1,27 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "money"
3
+ s.version = "3.6.0"
4
+ s.platform = Gem::Platform::RUBY
5
+ s.authors = ["Tobias Luetke", "Hongli Lai", "Jeremy McNevin", "Shane Emmons", "Simone Carletti"]
6
+ s.email = ["hongli@phusion.nl", "semmons99+RubyMoney@gmail.com"]
7
+ s.homepage = "http://money.rubyforge.org"
8
+ s.summary = "Money and currency exchange support library."
9
+ s.description = "This library aids one in handling money and different currencies."
10
+
11
+ s.required_rubygems_version = ">= 1.3.6"
12
+ s.rubyforge_project = "money"
13
+
14
+ s.add_dependency "i18n", "~> 0.4"
15
+
16
+ s.add_development_dependency "rspec", ">= 2.0.0"
17
+ s.add_development_dependency "yard"
18
+ s.add_development_dependency "json"
19
+
20
+ s.requirements << "json if you plan to import/export rates formatted as json"
21
+
22
+ s.files = Dir.glob("{lib,spec}/**/*")
23
+ s.files += %w(CHANGELOG.md LICENSE README.md)
24
+ s.files += %w(Rakefile .gemtest money.gemspec)
25
+
26
+ s.require_path = "lib"
27
+ end
@@ -0,0 +1,72 @@
1
+ require "spec_helper"
2
+
3
+ describe Money::Bank::Base do
4
+ before :each do
5
+ @bank = Money::Bank::Base.new
6
+ end
7
+
8
+ describe '#new with &block' do
9
+ it 'should store @rounding_method' do
10
+ proc = Proc.new{|n| n.ceil}
11
+ bank = Money::Bank::Base.new(&proc)
12
+ bank.rounding_method.should == proc
13
+ end
14
+ end
15
+
16
+ describe '#setup' do
17
+ it 'should call #setup after #initialize' do
18
+ class MyBank < Money::Bank::Base
19
+ attr_reader :setup_called
20
+
21
+ def setup
22
+ @setup_called = true
23
+ end
24
+ end
25
+
26
+ bank = MyBank.new
27
+ bank.setup_called.should == true
28
+ end
29
+ end
30
+
31
+ describe '#exchange_with' do
32
+ it 'should raise NotImplementedError' do
33
+ lambda { @bank.exchange_with(Money.new(100, 'USD'), 'EUR') }.should raise_exception(NotImplementedError)
34
+ end
35
+ end
36
+
37
+ describe '#same_currency?' do
38
+ it 'should accept str/str' do
39
+ lambda{@bank.send(:same_currency?, 'USD', 'EUR')}.should_not raise_exception
40
+ end
41
+
42
+ it 'should accept currency/str' do
43
+ lambda{@bank.send(:same_currency?, Money::Currency.wrap('USD'), 'EUR')}.should_not raise_exception
44
+ end
45
+
46
+ it 'should accept str/currency' do
47
+ lambda{@bank.send(:same_currency?, 'USD', Money::Currency.wrap('EUR'))}.should_not raise_exception
48
+ end
49
+
50
+ it 'should accept currency/currency' do
51
+ lambda{@bank.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR'))}.should_not raise_exception
52
+ end
53
+
54
+ it 'should return `true` when currencies match' do
55
+ @bank.send(:same_currency?, 'USD', 'USD').should == true
56
+ @bank.send(:same_currency?, Money::Currency.wrap('USD'), 'USD').should == true
57
+ @bank.send(:same_currency?, 'USD', Money::Currency.wrap('USD')).should == true
58
+ @bank.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('USD')).should == true
59
+ end
60
+
61
+ it 'should return `false` when currencies do not match' do
62
+ @bank.send(:same_currency?, 'USD', 'EUR').should == false
63
+ @bank.send(:same_currency?, Money::Currency.wrap('USD'), 'EUR').should == false
64
+ @bank.send(:same_currency?, 'USD', Money::Currency.wrap('EUR')).should == false
65
+ @bank.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR')).should == false
66
+ end
67
+
68
+ it 'should raise an UnknownCurrency exception when an unknown currency is passed' do
69
+ lambda{@bank.send(:same_currency?, 'AAA', 'BBB')}.should raise_exception(Money::Currency::UnknownCurrency)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,222 @@
1
+ require "spec_helper"
2
+ require "json"
3
+ require "yaml"
4
+
5
+ describe Money::Bank::VariableExchange do
6
+
7
+ describe '#new without block' do
8
+ before :each do
9
+ @bank = Money::Bank::VariableExchange.new
10
+ end
11
+
12
+ describe '#exchange_with' do
13
+ before :each do
14
+ @bank.send(:set_rate, 'USD', 'EUR', 1.33)
15
+ end
16
+
17
+ it 'should accept str' do
18
+ lambda{@bank.exchange_with(Money.new(100, 'USD'), 'EUR')}.should_not raise_exception
19
+ end
20
+
21
+ it 'should accept currency' do
22
+ lambda{@bank.exchange_with(Money.new(100, 'USD'), Money::Currency.wrap('EUR'))}.should_not raise_exception
23
+ end
24
+
25
+ it 'should exchange one currency to another' do
26
+ @bank.exchange_with(Money.new(100, 'USD'), 'EUR').should == Money.new(133, 'EUR')
27
+ end
28
+
29
+ it 'should truncate extra digits' do
30
+ @bank.exchange_with(Money.new(10, 'USD'), 'EUR').should == Money.new(13, 'EUR')
31
+ end
32
+
33
+ it 'should raise an UnknownCurrency exception when an unknown currency is requested' do
34
+ lambda{@bank.exchange_with(Money.new(100, 'USD'), 'BBB')}.should raise_exception(Money::Currency::UnknownCurrency)
35
+ end
36
+
37
+ it 'should raise an UnknownRate exception when an unknown rate is requested' do
38
+ lambda{@bank.exchange_with(Money.new(100, 'USD'), 'JPY')}.should raise_exception(Money::Bank::UnknownRate)
39
+ end
40
+
41
+ #it 'should round the exchanged result down' do
42
+ # @bank.add_rate("USD", "EUR", 0.788332676)
43
+ # @bank.add_rate("EUR", "YEN", 122.631477)
44
+ # @bank.exchange_with(Money.new(10_00, "USD"), "EUR").should == Money.new(788, "EUR")
45
+ # @bank.exchange_with(Money.new(500_00, "EUR"), "YEN").should == Money.new(6131573, "YEN")
46
+ #end
47
+
48
+ it 'should accept a custom truncation method' do
49
+ proc = Proc.new { |n| n.ceil }
50
+ @bank.exchange_with(Money.new(10, 'USD'), 'EUR', &proc).should == Money.new(14, 'EUR')
51
+ end
52
+ end
53
+
54
+ describe "#add_rate" do
55
+ it "should add rates correctly" do
56
+ @bank.add_rate("USD", "EUR", 0.788332676)
57
+ @bank.add_rate("EUR", "YEN", 122.631477)
58
+
59
+ @bank.instance_variable_get(:@rates)['USD_TO_EUR'].should == 0.788332676
60
+ @bank.instance_variable_get(:@rates)['EUR_TO_JPY'].should == 122.631477
61
+ end
62
+
63
+ it "should treat currency names case-insensitively" do
64
+ @bank.add_rate("usd", "eur", 1)
65
+ @bank.instance_variable_get(:@rates)['USD_TO_EUR'].should == 1
66
+ end
67
+ end
68
+
69
+ describe '#set_rate' do
70
+ it 'should set a rate' do
71
+ @bank.set_rate('USD', 'EUR', 1.25)
72
+ @bank.instance_variable_get(:@rates)['USD_TO_EUR'].should == 1.25
73
+ end
74
+
75
+ it 'should raise an UnknownCurrency exception when an unknown currency is passed' do
76
+ lambda{ @bank.set_rate('AAA', 'BBB', 1.25) }.should raise_exception(Money::Currency::UnknownCurrency)
77
+ end
78
+ end
79
+
80
+ describe '#get_rate' do
81
+ it 'should return a rate' do
82
+ @bank.set_rate('USD', 'EUR', 1.25)
83
+ @bank.get_rate('USD', 'EUR').should == 1.25
84
+ end
85
+
86
+ it 'should raise an UnknownCurrency exception when an unknown currency is requested' do
87
+ lambda{ @bank.get_rate('AAA', 'BBB') }.should raise_exception(Money::Currency::UnknownCurrency)
88
+ end
89
+ end
90
+
91
+ describe '#export_rates' do
92
+ before :each do
93
+ @bank.set_rate('USD', 'EUR', 1.25)
94
+ @bank.set_rate('USD', 'JPY', 2.55)
95
+
96
+ @rates = {"USD_TO_EUR"=>1.25,"USD_TO_JPY"=>2.55}
97
+ end
98
+
99
+ describe 'with format == :json' do
100
+ it 'should return rates formatted as json' do
101
+ json = @bank.export_rates(:json)
102
+ JSON.load(json).should == @rates
103
+ end
104
+ end
105
+
106
+ describe 'with format == :ruby' do
107
+ it 'should return rates formatted as ruby objects' do
108
+ Marshal.load(@bank.export_rates(:ruby)).should == @rates
109
+ end
110
+ end
111
+
112
+ describe 'with format == :yaml' do
113
+ it 'should return rates formatted as yaml' do
114
+ yaml = @bank.export_rates(:yaml)
115
+ YAML.load(yaml).should == @rates
116
+ end
117
+ end
118
+
119
+ describe 'with unknown format' do
120
+ it 'should raise `UnknownRateFormat`' do
121
+ lambda{@bank.export_rates(:foo)}.should raise_error Money::Bank::UnknownRateFormat
122
+ end
123
+ end
124
+
125
+ describe 'with :file provided' do
126
+ it 'should write rates to file' do
127
+ f = mock('IO')
128
+ File.should_receive(:open).with('null', 'w').and_return(f)
129
+ f.should_receive(:write).with(@rates.to_json)
130
+
131
+ @bank.export_rates(:json, 'null')
132
+ end
133
+ end
134
+ end
135
+
136
+ describe '#import_rates' do
137
+ describe 'with format == :json' do
138
+ it 'should load the rates provided' do
139
+ s = '{"USD_TO_EUR":1.25,"USD_TO_JPY":2.55}'
140
+ @bank.import_rates(:json, s)
141
+ @bank.get_rate('USD', 'EUR').should == 1.25
142
+ @bank.get_rate('USD', 'JPY').should == 2.55
143
+ end
144
+ end
145
+
146
+ describe 'with format == :ruby' do
147
+ it 'should load the rates provided' do
148
+ s = Marshal.dump({"USD_TO_EUR"=>1.25,"USD_TO_JPY"=>2.55})
149
+ @bank.import_rates(:ruby, s)
150
+ @bank.get_rate('USD', 'EUR').should == 1.25
151
+ @bank.get_rate('USD', 'JPY').should == 2.55
152
+ end
153
+ end
154
+
155
+ describe 'with format == :yaml' do
156
+ it 'should load the rates provided' do
157
+ s = "--- \nUSD_TO_EUR: 1.25\nUSD_TO_JPY: 2.55\n"
158
+ @bank.import_rates(:yaml, s)
159
+ @bank.get_rate('USD', 'EUR').should == 1.25
160
+ @bank.get_rate('USD', 'JPY').should == 2.55
161
+ end
162
+ end
163
+
164
+ describe 'with unknown format' do
165
+ it 'should raise `UnknownRateFormat`' do
166
+ lambda{@bank.import_rates(:foo, "")}.should raise_error Money::Bank::UnknownRateFormat
167
+ end
168
+ end
169
+ end
170
+
171
+ describe '#rate_key_for' do
172
+ it 'should accept str/str' do
173
+ lambda{@bank.send(:rate_key_for, 'USD', 'EUR')}.should_not raise_exception
174
+ end
175
+
176
+ it 'should accept currency/str' do
177
+ lambda{@bank.send(:rate_key_for, Money::Currency.wrap('USD'), 'EUR')}.should_not raise_exception
178
+ end
179
+
180
+ it 'should accept str/currency' do
181
+ lambda{@bank.send(:rate_key_for, 'USD', Money::Currency.wrap('EUR'))}.should_not raise_exception
182
+ end
183
+
184
+ it 'should accept currency/currency' do
185
+ lambda{@bank.send(:rate_key_for, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR'))}.should_not raise_exception
186
+ end
187
+
188
+ it 'should return a hashkey based on the passed arguments' do
189
+ @bank.send(:rate_key_for, 'USD', 'EUR').should == 'USD_TO_EUR'
190
+ @bank.send(:rate_key_for, Money::Currency.wrap('USD'), 'EUR').should == 'USD_TO_EUR'
191
+ @bank.send(:rate_key_for, 'USD', Money::Currency.wrap('EUR')).should == 'USD_TO_EUR'
192
+ @bank.send(:rate_key_for, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR')).should == 'USD_TO_EUR'
193
+ end
194
+
195
+ it 'should raise an UnknownCurrency exception when an unknown currency is passed' do
196
+ lambda{@bank.send(:rate_key_for, 'AAA', 'BBB')}.should raise_exception(Money::Currency::UnknownCurrency)
197
+ end
198
+ end
199
+
200
+ end
201
+
202
+
203
+ describe '#new with &block' do
204
+ before :each do
205
+ proc = Proc.new { |n| n.ceil }
206
+ @bank = Money::Bank::VariableExchange.new(&proc)
207
+ @bank.add_rate('USD', 'EUR', 1.33)
208
+ end
209
+
210
+ describe '#exchange_with' do
211
+ it 'should use a stored truncation method' do
212
+ @bank.exchange_with(Money.new(10, 'USD'), 'EUR').should == Money.new(14, 'EUR')
213
+ end
214
+
215
+ it 'should use a custom truncation method over a stored one' do
216
+ proc = Proc.new { |n| n.ceil + 1 }
217
+ @bank.exchange_with(Money.new(10, 'USD'), 'EUR', &proc).should == Money.new(15, 'EUR')
218
+ end
219
+ end
220
+ end
221
+
222
+ end