bd_money 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +101 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/bd_money.gemspec +73 -0
- data/lib/bd_money/bd_money.rb +210 -0
- data/lib/bd_money/core_extensions.rb +39 -0
- data/lib/bd_money/rails.rb +44 -0
- data/lib/bd_money.rb +2 -0
- data/spec/bd_money_spec.rb +344 -0
- data/spec/core_extensions_spec.rb +32 -0
- data/spec/rails_spec.rb +77 -0
- data/spec/spec_helper.rb +12 -0
- metadata +189 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
Copyright (c) 2012 North Point Advisors, Inc.
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
5
|
+
copy of this software and associated documentation files (the "Software"),
|
6
|
+
to deal in the Software without restriction, including without limitation
|
7
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
8
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
12
|
+
all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
19
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
20
|
+
DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
= bd_money
|
2
|
+
|
3
|
+
This library makes it easier to deal with Money values, storing them as BigDecimal to avoid floating-point
|
4
|
+
math errors.
|
5
|
+
|
6
|
+
This library makes extensive use of ideas borrowed from the Money[https://github.com/collectiveidea/money] gem
|
7
|
+
from CollectiveIdea so please review their code and decide which gem to use according to your needs. We needed
|
8
|
+
an object that would keep numbers with more precision internally and display them with smaller precision if
|
9
|
+
required. Also we needed more control with regards to rounding methods.
|
10
|
+
|
11
|
+
== Money
|
12
|
+
|
13
|
+
Money objects use internally BigDecimal object to provide exact calculations.
|
14
|
+
|
15
|
+
=== Precision
|
16
|
+
|
17
|
+
You can decide how many decimals you want to display using the second parameter on initialization (nil will
|
18
|
+
use the current Money class default).
|
19
|
+
|
20
|
+
m = Money.new 123.456789, 3 #=> 123.456
|
21
|
+
m.to_s(2) #=> 123.45
|
22
|
+
|
23
|
+
You can change the default precision for display purposes:
|
24
|
+
|
25
|
+
Money.precision = 2
|
26
|
+
m = Money.new 123.456789, 3 #=> 123.456
|
27
|
+
m.precision = 1
|
28
|
+
m.to_s #=> 123.4
|
29
|
+
m.to_s(2) #=> 123.45
|
30
|
+
|
31
|
+
== Rounding
|
32
|
+
|
33
|
+
BigDecimal numbers provide several rounding methods: up, down, half_up, half_down, half_even, ceiling and floor.
|
34
|
+
You can pass an option to decide what rounding method to use with your object.
|
35
|
+
|
36
|
+
m = Money.new 123.456, nil, :half_up
|
37
|
+
m.to_s(2) #=> 123.46
|
38
|
+
m.to_s(2, :floor) #=> 123.45
|
39
|
+
|
40
|
+
== Download
|
41
|
+
|
42
|
+
Preferred method of installation is gem:
|
43
|
+
|
44
|
+
gem install bd_money
|
45
|
+
|
46
|
+
You can find the source at:
|
47
|
+
|
48
|
+
http://github.com/NorthPointAdvisors/bd_money
|
49
|
+
|
50
|
+
== Rails
|
51
|
+
|
52
|
+
There is a rails extension that makes it easier to store money values in the database.
|
53
|
+
|
54
|
+
require 'bd_money/rails'
|
55
|
+
|
56
|
+
class Product < ActiveRecord::Base
|
57
|
+
money :price
|
58
|
+
validates_numericality_of :price, :greater_than => 0
|
59
|
+
end
|
60
|
+
|
61
|
+
This assumes that there is a price (decimal highly recommended) column in the database. You can also specify the
|
62
|
+
:precision and :round_mode option for more fine control of the results.
|
63
|
+
|
64
|
+
class Loan < ActiveRecord::Base
|
65
|
+
money :amount, :round_mode => :half_up
|
66
|
+
money :apr, :precision => 5, :round_mode => :floor
|
67
|
+
end
|
68
|
+
|
69
|
+
You can set the attribute to a String, Fixnum, or Float and it will call #to_money to
|
70
|
+
convert it to a Money object. This makes it convenient for using money fields in forms.
|
71
|
+
|
72
|
+
r = Loan.new :amount => "123.456", :apr => 0.123456
|
73
|
+
r.amount #=> 123.46
|
74
|
+
r.apr #=> 0.12345
|
75
|
+
|
76
|
+
Also notice that operating on a Money object will return another money object to help you maintain the extra
|
77
|
+
information in the BigDecimal amount.
|
78
|
+
|
79
|
+
twice = r.amount * 2 #=> 246.91
|
80
|
+
twice.class.name #=> "Money"
|
81
|
+
|
82
|
+
This has been tested with ActiveRecord 2.3.5. Please proceed with caution in any other environment. If you work out a
|
83
|
+
solution for other versions please let me know.
|
84
|
+
|
85
|
+
== Contributing to bd_money
|
86
|
+
|
87
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
88
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
89
|
+
* Fork the project.
|
90
|
+
* Start a feature/bugfix branch.
|
91
|
+
* Commit and push until you are happy with your contribution.
|
92
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
93
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
94
|
+
|
95
|
+
== Code
|
96
|
+
|
97
|
+
If you have any improvements please email them to aemadrid [at] gmail.com
|
98
|
+
|
99
|
+
== Copyright
|
100
|
+
|
101
|
+
Copyright (c) 2012 North Point Advisors, Inc. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "bd_money"
|
18
|
+
gem.homepage = "http://github.com/aemadrid/bd_money"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Managing money objects sanely}
|
21
|
+
gem.description = %Q{This library makes it easier to deal with Money values, storing them as BigDecimal to avoid floating-point math errors.}
|
22
|
+
gem.email = "aemadrid@gmail.com"
|
23
|
+
gem.authors = ["Adrian Madrid"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rdoc/task'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "bd_money #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bd_money.gemspec
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{bd_money}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Adrian Madrid"]
|
12
|
+
s.date = %q{2012-03-09}
|
13
|
+
s.description = %q{This library makes it easier to deal with Money values, storing them as BigDecimal to avoid floating-point math errors.}
|
14
|
+
s.email = %q{aemadrid@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"bd_money.gemspec",
|
28
|
+
"lib/bd_money.rb",
|
29
|
+
"lib/bd_money/bd_money.rb",
|
30
|
+
"lib/bd_money/core_extensions.rb",
|
31
|
+
"lib/bd_money/rails.rb",
|
32
|
+
"spec/bd_money_spec.rb",
|
33
|
+
"spec/core_extensions_spec.rb",
|
34
|
+
"spec/rails_spec.rb",
|
35
|
+
"spec/spec_helper.rb"
|
36
|
+
]
|
37
|
+
s.homepage = %q{http://github.com/aemadrid/bd_money}
|
38
|
+
s.licenses = ["MIT"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = %q{1.4.2}
|
41
|
+
s.summary = %q{Managing money objects sanely}
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
s.specification_version = 3
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
|
48
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
49
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
50
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
51
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
52
|
+
s.add_development_dependency(%q<activerecord>, ["~> 2.3.5"])
|
53
|
+
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
56
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
57
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
58
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
59
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
60
|
+
s.add_dependency(%q<activerecord>, ["~> 2.3.5"])
|
61
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
62
|
+
end
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<rspec>, ["~> 2.8.0"])
|
65
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
66
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
67
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
68
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
69
|
+
s.add_dependency(%q<activerecord>, ["~> 2.3.5"])
|
70
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
class Money
|
4
|
+
|
5
|
+
ROUND_MODES = {
|
6
|
+
:ceiling => BigDecimal::ROUND_CEILING,
|
7
|
+
:down => BigDecimal::ROUND_DOWN,
|
8
|
+
:floor => BigDecimal::ROUND_FLOOR,
|
9
|
+
:half_down => BigDecimal::ROUND_HALF_DOWN,
|
10
|
+
:half_even => BigDecimal::ROUND_HALF_EVEN,
|
11
|
+
:half_up => BigDecimal::ROUND_HALF_UP,
|
12
|
+
:up => BigDecimal::ROUND_UP,
|
13
|
+
} unless const_defined?(:ROUND_MODES)
|
14
|
+
|
15
|
+
REMOVE_RE = %r{[$,_ ]} unless const_defined?(:REMOVE_RE)
|
16
|
+
VALID_RE = %r{^(-)?(\d)+(\.\d{1,12})?$} unless const_defined?(:VALID_RE)
|
17
|
+
|
18
|
+
include Comparable
|
19
|
+
|
20
|
+
class MoneyError < StandardError # :nodoc:
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(value, precision = nil, round_mode = nil)
|
24
|
+
self.amount = value
|
25
|
+
self.precision = precision if precision
|
26
|
+
self.round_mode = round_mode if round_mode
|
27
|
+
end
|
28
|
+
|
29
|
+
def amount=(value)
|
30
|
+
if value.is_a?(BigDecimal)
|
31
|
+
@amount = value
|
32
|
+
else
|
33
|
+
str = self.class.clean value
|
34
|
+
raise MoneyError, "Invalid value [#{str}] (#{value.class.name})" unless self.class.valid?(str)
|
35
|
+
@amount = BigDecimal.new str.gsub REMOVE_RE, ''
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def amount
|
40
|
+
@amount
|
41
|
+
end
|
42
|
+
|
43
|
+
def precision=(value)
|
44
|
+
raise "Unknown precision [#{value}]" unless value.is_a?(Integer)
|
45
|
+
@precision = value
|
46
|
+
end
|
47
|
+
|
48
|
+
def precision
|
49
|
+
@precision || self.class.precision
|
50
|
+
end
|
51
|
+
|
52
|
+
def round_mode=(value)
|
53
|
+
raise "Unknown rounding mode [#{value}]" unless ROUND_MODES.key?(value)
|
54
|
+
@round_mode = value
|
55
|
+
end
|
56
|
+
|
57
|
+
def round_mode
|
58
|
+
@round_mode || self.class.round_mode
|
59
|
+
end
|
60
|
+
|
61
|
+
def convert(value)
|
62
|
+
self.class.convert value
|
63
|
+
end
|
64
|
+
|
65
|
+
def eql?(other)
|
66
|
+
amount == convert(other).amount
|
67
|
+
end
|
68
|
+
|
69
|
+
def <=>(other)
|
70
|
+
amount <=> convert(other).amount
|
71
|
+
end
|
72
|
+
|
73
|
+
def +(other)
|
74
|
+
convert amount + convert(other).amount
|
75
|
+
end
|
76
|
+
|
77
|
+
def -(other)
|
78
|
+
convert amount - convert(other).amount
|
79
|
+
end
|
80
|
+
|
81
|
+
def *(other)
|
82
|
+
convert amount * convert(other).amount
|
83
|
+
end
|
84
|
+
|
85
|
+
def /(other)
|
86
|
+
convert amount / convert(other).amount
|
87
|
+
end
|
88
|
+
|
89
|
+
def **(other)
|
90
|
+
convert amount ** convert(other).amount.to_i
|
91
|
+
end
|
92
|
+
|
93
|
+
def %(other)
|
94
|
+
convert amount % convert(other).amount
|
95
|
+
end
|
96
|
+
|
97
|
+
def ^(other)
|
98
|
+
convert amount ^ convert(other).amount
|
99
|
+
end
|
100
|
+
|
101
|
+
def round_amount(this_precision = precision, this_round_mode = round_mode)
|
102
|
+
this_round_mode = BigDecimal.const_get("ROUND_#{this_round_mode.to_s.upcase}") if this_round_mode.is_a?(Symbol)
|
103
|
+
amount.round this_precision, this_round_mode
|
104
|
+
end
|
105
|
+
|
106
|
+
def round(this_precision = precision, this_round_mode = round_mode)
|
107
|
+
convert round_amount(this_precision, this_round_mode)
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_i(this_round_mode = round_mode)
|
111
|
+
round_amount(0, this_round_mode).to_i
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_f(this_precision = precision, this_round_mode = round_mode)
|
115
|
+
round_amount(this_precision, this_round_mode).to_i
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_credit
|
119
|
+
convert amount.abs
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_credit!
|
123
|
+
self.amount = amount.abs
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
127
|
+
def credit?
|
128
|
+
amount >= 0
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_debit
|
132
|
+
convert amount.abs * -1
|
133
|
+
end
|
134
|
+
|
135
|
+
def to_debit!
|
136
|
+
self.amount = amount.abs * -1
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
def debit?
|
141
|
+
amount < 0
|
142
|
+
end
|
143
|
+
|
144
|
+
def zero?
|
145
|
+
amount == 0
|
146
|
+
end
|
147
|
+
|
148
|
+
def to_s(this_precision = precision, this_round_mode = round_mode)
|
149
|
+
this_round_mode = BigDecimal.const_get("ROUND_#{this_round_mode.to_s.upcase}") if this_round_mode.is_a?(Symbol)
|
150
|
+
amount_str = amount.round(this_precision, this_round_mode).to_s('F')
|
151
|
+
dollars, cents = amount_str.split('.')
|
152
|
+
return dollars if this_precision == 0
|
153
|
+
if cents.size >= this_precision
|
154
|
+
"#{dollars}.#{cents[0, this_precision]}"
|
155
|
+
else
|
156
|
+
"#{dollars}.#{cents}#{'0' * (this_precision - cents.size)}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
alias :inspect :to_s
|
161
|
+
|
162
|
+
def respond_to?(meth)
|
163
|
+
amount.respond_to?(meth) || super
|
164
|
+
end
|
165
|
+
|
166
|
+
def method_missing(meth, *args, &blk)
|
167
|
+
if amount.respond_to? meth
|
168
|
+
result = amount.send meth, *args, &blk
|
169
|
+
result.is_a?(::BigDecimal) ? convert(result) : result
|
170
|
+
else
|
171
|
+
super
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class << self
|
176
|
+
|
177
|
+
def precision=(value)
|
178
|
+
raise "Unknown precision [#{value}]" unless value.is_a?(Integer)
|
179
|
+
@precision = value
|
180
|
+
end
|
181
|
+
|
182
|
+
def precision
|
183
|
+
@precision || 2
|
184
|
+
end
|
185
|
+
|
186
|
+
def round_mode=(value)
|
187
|
+
raise "Unknown rounding mode [#{value}]" unless ROUND_MODES.key?(value)
|
188
|
+
@round_mode = value
|
189
|
+
end
|
190
|
+
|
191
|
+
def round_mode
|
192
|
+
@round_mode || :half_up
|
193
|
+
end
|
194
|
+
|
195
|
+
def convert(value)
|
196
|
+
return value if value.is_a?(Money)
|
197
|
+
new value
|
198
|
+
end
|
199
|
+
|
200
|
+
def clean(value)
|
201
|
+
value.to_s.gsub REMOVE_RE, ''
|
202
|
+
end
|
203
|
+
|
204
|
+
def valid?(value)
|
205
|
+
!!value.to_s.match(VALID_RE)
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Numeric
|
2
|
+
# Convert the number to a +Money+ object.
|
3
|
+
#
|
4
|
+
# 100.to_money #=> 100.00
|
5
|
+
#
|
6
|
+
# Takes an optional precision, which defaults to 2
|
7
|
+
# Takes an optional round_mode which defaults to :half_up
|
8
|
+
def to_money(precision = nil, round_mode = nil)
|
9
|
+
Money.new self, precision, round_mode
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Float
|
14
|
+
# Convert the float to a +Money+ object.
|
15
|
+
#
|
16
|
+
# 3.75.to_money #=> 3.75
|
17
|
+
#
|
18
|
+
# Takes an optional precision, which defaults to 2
|
19
|
+
# Takes an optional round_mode which defaults to :half_up
|
20
|
+
def to_money(precision = nil, round_mode = nil)
|
21
|
+
Money.new self, precision, round_mode
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class String
|
26
|
+
# Convert the String to a +Money+ object.
|
27
|
+
#
|
28
|
+
# '100'.to_money #=> 100.00
|
29
|
+
# '100.37'.to_money #=> 100.37
|
30
|
+
# '.37'.to_money #=> 0.37
|
31
|
+
# '$ 4.25'.to_money #=> 4.25
|
32
|
+
# '3,550.55'.to_money #=> 3550.55
|
33
|
+
#
|
34
|
+
# Takes an optional precision, which defaults to 2
|
35
|
+
# Takes an optional round_mode which defaults to :half_up
|
36
|
+
def to_money(precision = nil, round_mode = nil)
|
37
|
+
Money.new self, precision, round_mode
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../lib/bd_money')
|
2
|
+
|
3
|
+
class Money
|
4
|
+
|
5
|
+
# Terrible hack to allow to quote money correctly
|
6
|
+
def quoted_id
|
7
|
+
amount
|
8
|
+
end
|
9
|
+
|
10
|
+
# This will help to save money objects correctly
|
11
|
+
def to_d
|
12
|
+
amount
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
module ActiveRecord #:nodoc:
|
18
|
+
module Acts #:nodoc:
|
19
|
+
module Money #:nodoc:
|
20
|
+
def self.included(base) #:nodoc:
|
21
|
+
base.extend ClassMethods
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def money(name, options = {})
|
26
|
+
define_method "#{name}=" do |value|
|
27
|
+
if value.present?
|
28
|
+
self[name] = ::Money.new(value, options[:precision], options[:round_mode]).amount
|
29
|
+
else
|
30
|
+
self[name] = nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
define_method "#{name}" do
|
34
|
+
puts "#{name} : self[#{name}] (1) : #{self[name]} : #{self[name].class}"
|
35
|
+
return nil unless self[name].present?
|
36
|
+
::Money.new self[name], options[:precision], options[:round_mode]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
ActiveRecord::Base.send :include, ActiveRecord::Acts::Money
|
data/lib/bd_money.rb
ADDED
@@ -0,0 +1,344 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Money do
|
4
|
+
|
5
|
+
let(:amt) { '3.53' }
|
6
|
+
let(:neg_amt) { '-3.53' }
|
7
|
+
let(:other_amt) { 1.01 }
|
8
|
+
subject { Money.new amt, 2, :half_down }
|
9
|
+
let(:neg_subject) { Money.new neg_amt, 2, :half_down }
|
10
|
+
|
11
|
+
describe "class precision" do
|
12
|
+
it { Money.precision.should == 2 }
|
13
|
+
it "should support customization" do
|
14
|
+
old_value = Money.instance_variable_get :@precision
|
15
|
+
Money.precision = 3
|
16
|
+
Money.precision.should == 3
|
17
|
+
Money.instance_variable_set :@precision, old_value
|
18
|
+
end
|
19
|
+
it { expect { Money.precision = '3' }.to raise_error(RuntimeError) }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "class round mode" do
|
23
|
+
it { Money.round_mode.should == :half_up }
|
24
|
+
it "should support customization" do
|
25
|
+
old_value = Money.instance_variable_get :@round_mode
|
26
|
+
Money.round_mode = :floor
|
27
|
+
Money.round_mode.should == :floor
|
28
|
+
Money.instance_variable_set :@round_mode, old_value
|
29
|
+
end
|
30
|
+
it { expect { Money.round_mode = :not_there }.to raise_error(RuntimeError) }
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "class convert" do
|
34
|
+
describe "strings" do
|
35
|
+
it { Money.convert('3.53').to_s.should == '3.53' }
|
36
|
+
it { Money.convert('-3.53').to_s.should == '-3.53' }
|
37
|
+
end
|
38
|
+
describe "integers" do
|
39
|
+
it { Money.convert(3).to_s.should == '3.00' }
|
40
|
+
it { Money.convert(-3).to_s.should == '-3.00' }
|
41
|
+
end
|
42
|
+
describe "floats" do
|
43
|
+
it { Money.convert(3.53).to_s.should == '3.53' }
|
44
|
+
it { Money.convert(-3.53).to_s.should == '-3.53' }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "class clean" do
|
49
|
+
it { Money.clean(1).should == '1' }
|
50
|
+
it { Money.clean(1.1).should == '1.1' }
|
51
|
+
it { Money.clean('$ 3,456,789.01').should == '3456789.01' }
|
52
|
+
it { Money.clean('3_456_789.01').should == '3456789.01' }
|
53
|
+
it { Money.clean('[ $ 3,456,789.01 ]').should == '[3456789.01]' }
|
54
|
+
it { Money.clean('You owe me $ 35.50').should == 'Youoweme35.50' }
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "class valid?" do
|
58
|
+
it { Money.valid?(1).should be_true }
|
59
|
+
it { Money.valid?('1').should be_true }
|
60
|
+
it { Money.valid?(1.1).should be_true }
|
61
|
+
it { Money.valid?('3456789.01').should be_true }
|
62
|
+
it { Money.valid?('$ 3.45').should_not be_true }
|
63
|
+
it { Money.valid?('[3.45]').should_not be_true }
|
64
|
+
it { Money.valid?('0.01').should be_true }
|
65
|
+
it { Money.valid?('.01').should_not be_true }
|
66
|
+
it { Money.valid?('-0.01').should be_true }
|
67
|
+
it { Money.valid?('-.01').should_not be_true }
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "initialize" do
|
71
|
+
describe "defaults" do
|
72
|
+
subject { Money.new '3.53' }
|
73
|
+
it { subject.to_s.should == '3.53' }
|
74
|
+
it { subject.precision.should == 2 }
|
75
|
+
it { subject.round_mode.should == :half_up }
|
76
|
+
end
|
77
|
+
describe "custom" do
|
78
|
+
subject { Money.new '3.53', 1, :floor }
|
79
|
+
it { subject.to_s.should == '3.5' }
|
80
|
+
it { subject.precision.should == 1 }
|
81
|
+
it { subject.round_mode.should == :floor }
|
82
|
+
end
|
83
|
+
describe "customize" do
|
84
|
+
describe "amount" do
|
85
|
+
it { subject.amount = '5.35'; subject.to_s.should == '5.35' }
|
86
|
+
end
|
87
|
+
describe "precision" do
|
88
|
+
it { subject.precision = 4; subject.precision.should == 4 }
|
89
|
+
it { expect { Money.precision = '4' }.to raise_error(RuntimeError) }
|
90
|
+
end
|
91
|
+
describe "round_mode" do
|
92
|
+
it { subject.round_mode = :floor; subject.round_mode.should == :floor }
|
93
|
+
it { expect { Money.round_mode = :not_there }.to raise_error(RuntimeError) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "convert" do
|
99
|
+
describe "strings" do
|
100
|
+
it { subject.convert('3.53').to_s.should == '3.53' }
|
101
|
+
it { subject.convert('-3.53').to_s.should == '-3.53' }
|
102
|
+
end
|
103
|
+
describe "integers" do
|
104
|
+
it { subject.convert(3).to_s.should == '3.00' }
|
105
|
+
it { subject.convert(-3).to_s.should == '-3.00' }
|
106
|
+
end
|
107
|
+
describe "floats" do
|
108
|
+
it { subject.convert(3.53).to_s.should == '3.53' }
|
109
|
+
it { subject.convert(-3.53).to_s.should == '-3.53' }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe Comparable do
|
114
|
+
let(:amt) { 5.35 }
|
115
|
+
describe "eql" do
|
116
|
+
it { subject.eql?(Money.new(amt)).should be_true }
|
117
|
+
it { subject.eql?(amt).should be_true }
|
118
|
+
it { subject.eql?(amt.to_s).should be_true }
|
119
|
+
end
|
120
|
+
describe "<=>" do
|
121
|
+
it { subject.<=>(amt).should == 0 }
|
122
|
+
it { subject.<=>(amt + 1).should == -1 }
|
123
|
+
it { subject.<=>(amt - 1).should == 1 }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "Operations" do
|
128
|
+
describe "+" do
|
129
|
+
subject { Money.new(amt) + other_amt }
|
130
|
+
it { should be_a Money }
|
131
|
+
it { should == 4.54 }
|
132
|
+
end
|
133
|
+
describe "-" do
|
134
|
+
subject { Money.new(amt) - other_amt }
|
135
|
+
it { should be_a Money }
|
136
|
+
it { should == 2.52 }
|
137
|
+
end
|
138
|
+
describe "*" do
|
139
|
+
subject { Money.new(amt) * other_amt }
|
140
|
+
it { should be_a Money }
|
141
|
+
it { subject.to_s.should == '3.57' }
|
142
|
+
end
|
143
|
+
describe "/" do
|
144
|
+
subject { Money.new(amt) / other_amt }
|
145
|
+
it { should be_a Money }
|
146
|
+
it { subject.to_s.should == '3.50' }
|
147
|
+
end
|
148
|
+
describe "**" do
|
149
|
+
subject { Money.new(amt) ** other_amt }
|
150
|
+
it { should be_a Money }
|
151
|
+
it { should == 3.53 }
|
152
|
+
end
|
153
|
+
describe "%" do
|
154
|
+
subject { Money.new(amt) % other_amt }
|
155
|
+
it { should be_a Money }
|
156
|
+
it { should == 0.50 }
|
157
|
+
end
|
158
|
+
describe "to_credit" do
|
159
|
+
it { subject.object_id.should_not == subject.to_credit.object_id }
|
160
|
+
it { subject.to_credit.should == 3.53 }
|
161
|
+
it { neg_subject.to_credit.should == 3.53 }
|
162
|
+
end
|
163
|
+
describe "to_credit!" do
|
164
|
+
it { o = Money.new(amt); o.object_id.to_s.should == o.to_credit!.object_id.to_s }
|
165
|
+
it { subject.to_credit!.should == 3.53 }
|
166
|
+
it { neg_subject.to_credit!.should == 3.53 }
|
167
|
+
end
|
168
|
+
describe "credit?" do
|
169
|
+
it { subject.should be_credit }
|
170
|
+
it { neg_subject.should_not be_credit }
|
171
|
+
end
|
172
|
+
describe "to_debit" do
|
173
|
+
it { subject.object_id.should_not == subject.to_credit.object_id }
|
174
|
+
it { subject.to_debit.should == -3.53 }
|
175
|
+
it { neg_subject.to_debit.should == -3.53 }
|
176
|
+
end
|
177
|
+
describe "to_debit!" do
|
178
|
+
it { subject.object_id.to_s.should == subject.to_debit!.object_id.to_s }
|
179
|
+
it { subject.to_debit!.should == -3.53 }
|
180
|
+
it { neg_subject.to_debit!.should == -3.53 }
|
181
|
+
end
|
182
|
+
describe "debit?" do
|
183
|
+
it { subject.should_not be_debit }
|
184
|
+
it { neg_subject.should be_debit }
|
185
|
+
end
|
186
|
+
describe "zero?" do
|
187
|
+
it { subject.should_not be_zero }
|
188
|
+
it { neg_subject.should_not be_zero }
|
189
|
+
it { Money.new('0').should be_zero }
|
190
|
+
it { Money.new('0.0').should be_zero }
|
191
|
+
end
|
192
|
+
end
|
193
|
+
describe "to_s" do
|
194
|
+
describe "no decimals" do
|
195
|
+
let(:amt) { '3' }
|
196
|
+
it { subject.to_s.should == "#{amt}.00" }
|
197
|
+
it { subject.to_s(0).should == amt }
|
198
|
+
it { subject.to_s(1).should == "#{amt}.0" }
|
199
|
+
it { subject.to_s(2).should == "#{amt}.00" }
|
200
|
+
it { subject.to_s(3).should == "#{amt}.000" }
|
201
|
+
it { subject.to_s(4).should == "#{amt}.0000" }
|
202
|
+
it { subject.to_s(5).should == "#{amt}.00000" }
|
203
|
+
it { subject.to_s(6).should == "#{amt}.000000" }
|
204
|
+
end
|
205
|
+
describe "one decimal" do
|
206
|
+
let(:amt) { '3.5' }
|
207
|
+
it { subject.to_s.should == "#{amt}0" }
|
208
|
+
it { subject.to_s(0).should == amt[0, 1] }
|
209
|
+
it { subject.to_s(1).should == amt[0, 3] }
|
210
|
+
it { subject.to_s(2).should == "#{amt}0" }
|
211
|
+
it { subject.to_s(3).should == "#{amt}00" }
|
212
|
+
it { subject.to_s(4).should == "#{amt}000" }
|
213
|
+
it { subject.to_s(5).should == "#{amt}0000" }
|
214
|
+
it { subject.to_s(6).should == "#{amt}00000" }
|
215
|
+
end
|
216
|
+
describe "two decimals" do
|
217
|
+
let(:amt) { '3.53' }
|
218
|
+
it { subject.to_s.should == amt }
|
219
|
+
it { subject.to_s(0).should == amt[0, 1] }
|
220
|
+
it { subject.to_s(1).should == amt[0, 3] }
|
221
|
+
it { subject.to_s(2).should == amt }
|
222
|
+
it { subject.to_s(3).should == "#{amt}0" }
|
223
|
+
it { subject.to_s(4).should == "#{amt}00" }
|
224
|
+
it { subject.to_s(5).should == "#{amt}000" }
|
225
|
+
it { subject.to_s(6).should == "#{amt}0000" }
|
226
|
+
end
|
227
|
+
describe "three decimals" do
|
228
|
+
let(:amt) { '3.534' }
|
229
|
+
it { subject.to_s.should == amt[0, 4] }
|
230
|
+
it { subject.to_s(0).should == amt[0, 1] }
|
231
|
+
it { subject.to_s(1).should == amt[0, 3] }
|
232
|
+
it { subject.to_s(2).should == amt[0, 4] }
|
233
|
+
it { subject.to_s(3).should == amt }
|
234
|
+
it { subject.to_s(4).should == "#{amt}0" }
|
235
|
+
it { subject.to_s(5).should == "#{amt}00" }
|
236
|
+
it { subject.to_s(6).should == "#{amt}000" }
|
237
|
+
end
|
238
|
+
describe "four decimals" do
|
239
|
+
let(:amt) { '3.5343' }
|
240
|
+
it { subject.to_s.should == amt[0, 4] }
|
241
|
+
it { subject.to_s(0).should == amt[0, 1] }
|
242
|
+
it { subject.to_s(1).should == amt[0, 3] }
|
243
|
+
it { subject.to_s(2).should == amt[0, 4] }
|
244
|
+
it { subject.to_s(3).should == amt[0, 5] }
|
245
|
+
it { subject.to_s(4).should == amt }
|
246
|
+
it { subject.to_s(5).should == "#{amt}0" }
|
247
|
+
it { subject.to_s(6).should == "#{amt}00" }
|
248
|
+
end
|
249
|
+
describe "five decimals" do
|
250
|
+
let(:amt) { '3.53434' }
|
251
|
+
it { subject.to_s.should == amt[0, 4] }
|
252
|
+
it { subject.to_s(0).should == amt[0, 1] }
|
253
|
+
it { subject.to_s(1).should == amt[0, 3] }
|
254
|
+
it { subject.to_s(2).should == amt[0, 4] }
|
255
|
+
it { subject.to_s(3).should == amt[0, 5] }
|
256
|
+
it { subject.to_s(4).should == amt[0, 6] }
|
257
|
+
it { subject.to_s(5).should == amt }
|
258
|
+
it { subject.to_s(6).should == "#{amt}0" }
|
259
|
+
end
|
260
|
+
describe "six decimals" do
|
261
|
+
let(:amt) { '3.534343' }
|
262
|
+
it { subject.to_s.should == amt[0, 4] }
|
263
|
+
it { subject.to_s(0).should == amt[0, 1] }
|
264
|
+
it { subject.to_s(1).should == amt[0, 3] }
|
265
|
+
it { subject.to_s(2).should == amt[0, 4] }
|
266
|
+
it { subject.to_s(3).should == amt[0, 5] }
|
267
|
+
it { subject.to_s(4).should == amt[0, 6] }
|
268
|
+
it { subject.to_s(5).should == amt[0, 7] }
|
269
|
+
it { subject.to_s(6).should == amt }
|
270
|
+
end
|
271
|
+
end
|
272
|
+
describe "forwarded" do
|
273
|
+
describe "power" do
|
274
|
+
subject { Money.new(amt).power 2 }
|
275
|
+
it { should be_a Money }
|
276
|
+
it { subject.to_s.should == '12.46' }
|
277
|
+
end
|
278
|
+
end
|
279
|
+
describe "Rounding" do
|
280
|
+
let(:pos_amt1) { Money.new '1.4' }
|
281
|
+
let(:pos_amt2) { Money.new '1.5' }
|
282
|
+
let(:pos_amt3) { Money.new '1.6' }
|
283
|
+
let(:neg_amt1) { Money.new '-1.4' }
|
284
|
+
let(:neg_amt2) { Money.new '-1.5' }
|
285
|
+
let(:neg_amt3) { Money.new '-1.6' }
|
286
|
+
describe "up" do
|
287
|
+
it { pos_amt1.to_i(:up).should == 2 }
|
288
|
+
it { pos_amt2.to_i(:up).should == 2 }
|
289
|
+
it { pos_amt3.to_i(:up).should == 2 }
|
290
|
+
it { neg_amt1.to_i(:up).should == -2 }
|
291
|
+
it { neg_amt2.to_i(:up).should == -2 }
|
292
|
+
it { neg_amt3.to_i(:up).should == -2 }
|
293
|
+
end
|
294
|
+
describe "down" do
|
295
|
+
it { pos_amt1.to_i(:down).should == 1 }
|
296
|
+
it { pos_amt2.to_i(:down).should == 1 }
|
297
|
+
it { pos_amt3.to_i(:down).should == 1 }
|
298
|
+
it { neg_amt1.to_i(:down).should == -1 }
|
299
|
+
it { neg_amt2.to_i(:down).should == -1 }
|
300
|
+
it { neg_amt3.to_i(:down).should == -1 }
|
301
|
+
end
|
302
|
+
describe "half_up" do
|
303
|
+
it { pos_amt1.to_i(:half_up).should == 1 }
|
304
|
+
it { pos_amt2.to_i(:half_up).should == 2 }
|
305
|
+
it { pos_amt3.to_i(:half_up).should == 2 }
|
306
|
+
it { neg_amt1.to_i(:half_up).should == -1 }
|
307
|
+
it { neg_amt2.to_i(:half_up).should == -2 }
|
308
|
+
it { neg_amt3.to_i(:half_up).should == -2 }
|
309
|
+
end
|
310
|
+
describe "half_down" do
|
311
|
+
it { pos_amt1.to_i(:half_down).should == 1 }
|
312
|
+
it { pos_amt2.to_i(:half_down).should == 1 }
|
313
|
+
it { pos_amt3.to_i(:half_down).should == 2 }
|
314
|
+
it { neg_amt1.to_i(:half_down).should == -1 }
|
315
|
+
it { neg_amt2.to_i(:half_down).should == -1 }
|
316
|
+
it { neg_amt3.to_i(:half_down).should == -2 }
|
317
|
+
end
|
318
|
+
describe "half_even" do
|
319
|
+
it { pos_amt1.to_i(:half_even).should == 1 }
|
320
|
+
it { pos_amt2.to_i(:half_even).should == 2 }
|
321
|
+
it { pos_amt3.to_i(:half_even).should == 2 }
|
322
|
+
it { neg_amt1.to_i(:half_even).should == -1 }
|
323
|
+
it { neg_amt2.to_i(:half_even).should == -2 }
|
324
|
+
it { neg_amt3.to_i(:half_even).should == -2 }
|
325
|
+
end
|
326
|
+
describe "ceiling" do
|
327
|
+
it { pos_amt1.to_i(:ceiling).should == 2 }
|
328
|
+
it { pos_amt2.to_i(:ceiling).should == 2 }
|
329
|
+
it { pos_amt3.to_i(:ceiling).should == 2 }
|
330
|
+
it { neg_amt1.to_i(:ceiling).should == -1 }
|
331
|
+
it { neg_amt2.to_i(:ceiling).should == -1 }
|
332
|
+
it { neg_amt3.to_i(:ceiling).should == -1 }
|
333
|
+
end
|
334
|
+
describe "floor" do
|
335
|
+
it { pos_amt1.to_i(:floor).should == 1 }
|
336
|
+
it { pos_amt2.to_i(:floor).should == 1 }
|
337
|
+
it { pos_amt3.to_i(:floor).should == 1 }
|
338
|
+
it { neg_amt1.to_i(:floor).should == -2 }
|
339
|
+
it { neg_amt2.to_i(:floor).should == -2 }
|
340
|
+
it { neg_amt3.to_i(:floor).should == -2 }
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Numeric do
|
4
|
+
describe "to_money" do
|
5
|
+
subject { 3 }
|
6
|
+
it { subject.to_money.should == Money.new('3') }
|
7
|
+
it { (subject * -1).to_money.should == Money.new('-3') }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe Float do
|
12
|
+
describe "to_money" do
|
13
|
+
subject { 3.53 }
|
14
|
+
it { subject.to_money.should == Money.new('3.53') }
|
15
|
+
it { (subject * -1).to_money.should == Money.new('-3.53') }
|
16
|
+
it { subject.to_money.precision.should == Money.precision }
|
17
|
+
it { subject.to_money.round_mode.should == Money.round_mode }
|
18
|
+
it { subject.to_money(3).precision.should == 3 }
|
19
|
+
it { subject.to_money(nil, :up).round_mode.should == :up }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe String do
|
24
|
+
describe "to_money" do
|
25
|
+
subject { '3.53' }
|
26
|
+
it { subject.to_money.should == Money.new('3.53') }
|
27
|
+
it { subject.to_money.precision.should == Money.precision }
|
28
|
+
it { subject.to_money.round_mode.should == Money.round_mode }
|
29
|
+
it { subject.to_money(3).precision.should == 3 }
|
30
|
+
it { subject.to_money(nil, :up).round_mode.should == :up }
|
31
|
+
end
|
32
|
+
end
|
data/spec/rails_spec.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_record'
|
4
|
+
require 'bd_money/rails'
|
5
|
+
|
6
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
7
|
+
ActiveRecord::Migration.verbose = false
|
8
|
+
ActiveRecord::Schema.define do
|
9
|
+
create_table :money_examples, :force => true do |t|
|
10
|
+
t.decimal :amount, :precision => 15, :scale => 2
|
11
|
+
t.decimal :apr, :precision => 7, :scale => 5
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class DefaultLoanExample < ActiveRecord::Base
|
16
|
+
set_table_name "money_examples"
|
17
|
+
|
18
|
+
money :amount
|
19
|
+
money :apr
|
20
|
+
end
|
21
|
+
|
22
|
+
class BetterLoanExample < ActiveRecord::Base
|
23
|
+
set_table_name "money_examples"
|
24
|
+
|
25
|
+
money :amount, :precision => 2, :round_mode => :half_up
|
26
|
+
money :apr, :precision => 5, :round_mode => :floor
|
27
|
+
end
|
28
|
+
|
29
|
+
describe Money do
|
30
|
+
describe "default settings" do
|
31
|
+
it "should allow dynamic finders to work with money objects" do
|
32
|
+
record = DefaultLoanExample.create! :amount => '325.75', :apr => '0.01234'
|
33
|
+
DefaultLoanExample.find_by_amount(0.to_money).should be_nil
|
34
|
+
found = DefaultLoanExample.find_by_amount('325.75'.to_money)
|
35
|
+
found.should == record
|
36
|
+
found.amount.should be_a(Money)
|
37
|
+
found.amount.to_s.should == '325.75'
|
38
|
+
found.apr.should be_a(Money)
|
39
|
+
found.apr.to_s.should == '0.01'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
describe "custom settings" do
|
43
|
+
it "should allow dynamic finders to work with money objects" do
|
44
|
+
record = BetterLoanExample.create! :amount => '123.45', :apr => '0.01234'
|
45
|
+
BetterLoanExample.find_by_amount(0.to_money).should be_nil
|
46
|
+
found = BetterLoanExample.find_by_amount('123.45'.to_money)
|
47
|
+
found.should == record
|
48
|
+
found.amount.should be_a(Money)
|
49
|
+
found.amount.to_s.should == '123.45'
|
50
|
+
found.apr.should be_a(Money)
|
51
|
+
found.apr.to_s.should == '0.01234'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
describe "setter method" do
|
55
|
+
it "should pass on money values" do
|
56
|
+
DefaultLoanExample.new(:amount => 1.to_money).amount.should == 1.to_money
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should convert string values to money objects" do
|
60
|
+
DefaultLoanExample.new(:amount => '2').amount.should == 2.to_money
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should convert numeric values to money objects" do
|
64
|
+
DefaultLoanExample.new(:amount => 3).amount.should == 3.to_money
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should treat blank values as nil" do
|
68
|
+
DefaultLoanExample.new(:amount => '').amount.should be_nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should allow existing amounts to be set to nil with a blank value" do
|
72
|
+
me = DefaultLoanExample.new :amount => 500.to_money
|
73
|
+
me.update_attribute :amount, nil
|
74
|
+
me.amount.should be_nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'bd_money'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bd_money
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Adrian Madrid
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-03-09 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
type: :development
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 47
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 8
|
32
|
+
- 0
|
33
|
+
version: 2.8.0
|
34
|
+
requirement: *id001
|
35
|
+
prerelease: false
|
36
|
+
name: rspec
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
type: :development
|
39
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 31
|
45
|
+
segments:
|
46
|
+
- 3
|
47
|
+
- 12
|
48
|
+
version: "3.12"
|
49
|
+
requirement: *id002
|
50
|
+
prerelease: false
|
51
|
+
name: rdoc
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
type: :development
|
54
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ~>
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 23
|
60
|
+
segments:
|
61
|
+
- 1
|
62
|
+
- 0
|
63
|
+
- 0
|
64
|
+
version: 1.0.0
|
65
|
+
requirement: *id003
|
66
|
+
prerelease: false
|
67
|
+
name: bundler
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
type: :development
|
70
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 49
|
76
|
+
segments:
|
77
|
+
- 1
|
78
|
+
- 8
|
79
|
+
- 3
|
80
|
+
version: 1.8.3
|
81
|
+
requirement: *id004
|
82
|
+
prerelease: false
|
83
|
+
name: jeweler
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
type: :development
|
86
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
requirement: *id005
|
96
|
+
prerelease: false
|
97
|
+
name: rcov
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
type: :development
|
100
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ~>
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
hash: 9
|
106
|
+
segments:
|
107
|
+
- 2
|
108
|
+
- 3
|
109
|
+
- 5
|
110
|
+
version: 2.3.5
|
111
|
+
requirement: *id006
|
112
|
+
prerelease: false
|
113
|
+
name: activerecord
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
type: :development
|
116
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
hash: 3
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
version: "0"
|
125
|
+
requirement: *id007
|
126
|
+
prerelease: false
|
127
|
+
name: sqlite3
|
128
|
+
description: This library makes it easier to deal with Money values, storing them as BigDecimal to avoid floating-point math errors.
|
129
|
+
email: aemadrid@gmail.com
|
130
|
+
executables: []
|
131
|
+
|
132
|
+
extensions: []
|
133
|
+
|
134
|
+
extra_rdoc_files:
|
135
|
+
- LICENSE.txt
|
136
|
+
- README.rdoc
|
137
|
+
files:
|
138
|
+
- .document
|
139
|
+
- .rspec
|
140
|
+
- Gemfile
|
141
|
+
- LICENSE.txt
|
142
|
+
- README.rdoc
|
143
|
+
- Rakefile
|
144
|
+
- VERSION
|
145
|
+
- bd_money.gemspec
|
146
|
+
- lib/bd_money.rb
|
147
|
+
- lib/bd_money/bd_money.rb
|
148
|
+
- lib/bd_money/core_extensions.rb
|
149
|
+
- lib/bd_money/rails.rb
|
150
|
+
- spec/bd_money_spec.rb
|
151
|
+
- spec/core_extensions_spec.rb
|
152
|
+
- spec/rails_spec.rb
|
153
|
+
- spec/spec_helper.rb
|
154
|
+
has_rdoc: true
|
155
|
+
homepage: http://github.com/aemadrid/bd_money
|
156
|
+
licenses:
|
157
|
+
- MIT
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
hash: 3
|
169
|
+
segments:
|
170
|
+
- 0
|
171
|
+
version: "0"
|
172
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
173
|
+
none: false
|
174
|
+
requirements:
|
175
|
+
- - ">="
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
hash: 3
|
178
|
+
segments:
|
179
|
+
- 0
|
180
|
+
version: "0"
|
181
|
+
requirements: []
|
182
|
+
|
183
|
+
rubyforge_project:
|
184
|
+
rubygems_version: 1.4.2
|
185
|
+
signing_key:
|
186
|
+
specification_version: 3
|
187
|
+
summary: Managing money objects sanely
|
188
|
+
test_files: []
|
189
|
+
|