money_extensions 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.travis.yml +30 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +26 -0
- data/Rakefile +12 -0
- data/gemfiles/rails2.gemfile +8 -0
- data/gemfiles/rails3.gemfile +8 -0
- data/gemfiles/rails4.gemfile +8 -0
- data/lib/money_extensions/active_record/extensions.rb +57 -0
- data/lib/money_extensions/array.rb +6 -0
- data/lib/money_extensions/big_decimal.rb +12 -0
- data/lib/money_extensions/extensions.rb +189 -0
- data/lib/money_extensions/fixnum.rb +6 -0
- data/lib/money_extensions/money_field.rb +44 -0
- data/lib/money_extensions/numeric.rb +16 -0
- data/lib/money_extensions/string.rb +14 -0
- data/lib/money_extensions/version.rb +3 -0
- data/lib/money_extensions.rb +17 -0
- data/money_extensions.gemspec +31 -0
- data/rails/init.rb +1 -0
- data/spec/big_decimal_spec.rb +17 -0
- data/spec/money_field_spec.rb +25 -0
- data/spec/money_spec.rb +117 -0
- data/spec/schema.rb +5 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/coverage_loader.rb +26 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f56893765e2c83d2ffea6e7e80ca2cab0963370c
|
4
|
+
data.tar.gz: 799f31640b6dc664c300e7ca4a01b3275e378cfb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a4144e85e7838f52c2cc39adb3e845cc4cbc48fa6c88c2e1836caf861e7592f65712b734e8b1c8306187b9a1040dfa0e7cc2e281c5344694e1b6cbac54c461a4
|
7
|
+
data.tar.gz: c1d03bfdecca1db109625f7bd4f2ace3839f68ea389dfaf1460e48a01bd34802d37e172eb5dbe3404c5fe6ce7540dde68c50cc357bc998af567705660734117a
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 1.9
|
4
|
+
- 2.0
|
5
|
+
- 2.1
|
6
|
+
- 2.2
|
7
|
+
gemfile:
|
8
|
+
- gemfiles/rails2.gemfile
|
9
|
+
- gemfiles/rails3.gemfile
|
10
|
+
- gemfiles/rails4.gemfile
|
11
|
+
matrix:
|
12
|
+
exclude:
|
13
|
+
- rvm: 1.9
|
14
|
+
gemfile: gemfiles/rails4.gemfile
|
15
|
+
- rvm: 1.9
|
16
|
+
gemfile: gemfiles/rails2.gemfile
|
17
|
+
- rvm: 2.0
|
18
|
+
gemfile: gemfiles/rails2.gemfile
|
19
|
+
- rvm: 2.1
|
20
|
+
gemfile: gemfiles/rails2.gemfile
|
21
|
+
- rvm: 2.2
|
22
|
+
gemfile: gemfiles/rails2.gemfile
|
23
|
+
script: "bundle exec rake spec"
|
24
|
+
notifications:
|
25
|
+
email:
|
26
|
+
- support@travellink.com.au
|
27
|
+
flowdock:
|
28
|
+
secure: A0Fr9j0kvKPEWuPbJu/DPMYPvx98UI8k43adKN9UjTB6FRVAQM3kXmKJIL3A495scc2Civrg2KHDkbwNwVThY+HdLeU7q71Bm6obP6ZkCe43MIoc9NiVJymCNguku6+tLEyusdWIceFiBW7P5p/sU1QU66GFj1t4wNTpVq9A4Kw=
|
29
|
+
sudo: false
|
30
|
+
cache: bundler
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) Tom Preston-Werner
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Money Extensions
|
2
|
+
================
|
3
|
+
|
4
|
+
[](https://travis-ci.org/sealink/money_extensions)
|
5
|
+
[](https://coveralls.io/r/sealink/money_extensions)
|
6
|
+
[](https://gemnasium.com/sealink/money_extensions)
|
7
|
+
[](https://codeclimate.com/github/sealink/money_extensions)
|
8
|
+
|
9
|
+
# DESCRIPTION
|
10
|
+
|
11
|
+
Extends the money classes with helpful functions and currency functions
|
12
|
+
|
13
|
+
e.g. in model
|
14
|
+
money_fields :total, :discount
|
15
|
+
|
16
|
+
This will by default map a money object "total" to a database column total_in_cents and the same for discount.
|
17
|
+
|
18
|
+
Various numerical functions for money, e.g. round, split_between, sign, etc.
|
19
|
+
|
20
|
+
See spec directory for examples.
|
21
|
+
|
22
|
+
# INSTALLATION
|
23
|
+
|
24
|
+
Add to your Gemfile:
|
25
|
+
gem 'money_extensions'
|
26
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
desc 'Default: run specs.'
|
4
|
+
task :default => :spec
|
5
|
+
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
|
8
|
+
desc "Run specs"
|
9
|
+
RSpec::Core::RakeTask.new do |t|
|
10
|
+
t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
|
11
|
+
# Put spec opts in a file named .rspec in root
|
12
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Extensions
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Assign a :currency (Money::Currency object) reader/writer for the given
|
10
|
+
# field name.
|
11
|
+
#
|
12
|
+
# NOTES:
|
13
|
+
# * Currency is identified by a 3-letter ISO code,
|
14
|
+
# * Lookup is doen via Money::Currency.find(iso_code)
|
15
|
+
def has_currency(*args)
|
16
|
+
options = args.extract_options!
|
17
|
+
attr_name = args.first || :currency
|
18
|
+
|
19
|
+
field_name = "#{attr_name}_iso_code"
|
20
|
+
|
21
|
+
composed_of attr_name, :class_name => "Money::Currency",
|
22
|
+
:mapping => [field_name, 'iso_code'],
|
23
|
+
:allow_nil => true,
|
24
|
+
:constructor => Proc.new{|value| Money::Currency.new(value) unless value.blank?}
|
25
|
+
|
26
|
+
if Rails.version > '3'
|
27
|
+
scope :for_currency, lambda{ |currency|
|
28
|
+
where(:currency_iso_code => currency.iso_code)
|
29
|
+
}
|
30
|
+
else
|
31
|
+
named_scope :for_currency, lambda{ |currency|
|
32
|
+
{:conditions => {:currency_iso_code => currency.iso_code}}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
if options[:default]
|
37
|
+
before_validation :set_default_currency
|
38
|
+
class_eval <<-METHOD
|
39
|
+
def set_default_currency
|
40
|
+
self.currency ||= EnabledCurrency.base_currency
|
41
|
+
true
|
42
|
+
end
|
43
|
+
METHOD
|
44
|
+
end
|
45
|
+
|
46
|
+
if options[:required]
|
47
|
+
validates_presence_of :currency_iso_code
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
::ActiveRecord::Base.send(:include, MoneyField)
|
57
|
+
::ActiveRecord::Base.send(:include, ActiveRecord::Extensions)
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'money'
|
2
|
+
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
alias_method :to_s_without_currency_hack, :to_s
|
8
|
+
alias_method :to_s, :to_s_with_currency_hack
|
9
|
+
alias_method :to_d_without_currency_hack, :to_d
|
10
|
+
alias_method :to_d, :to_d_with_currency_hack
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Override division -- should not do it, but use split_between
|
15
|
+
def /(*params)
|
16
|
+
raise "Division of money NOT allowed - use 'split_between' to avoid rounding errors"
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_d_with_currency_hack
|
20
|
+
BigDecimal.new("#{cents/100.0}")
|
21
|
+
end
|
22
|
+
|
23
|
+
# Split the money between the specified number - and return an array of money
|
24
|
+
# Remainder in splits are given to the highest value e.g.
|
25
|
+
# $2.00 splits [40, 81, 40] into [49, 102, 49]
|
26
|
+
# $1.01 splits [ 1, 1, 1] into [35, 33, 33]
|
27
|
+
def split_between(params)
|
28
|
+
#if just a number is passed in, then money is split equally
|
29
|
+
if params.is_a?(Fixnum)
|
30
|
+
divisor = params
|
31
|
+
raise ArgumentError, "Can only split up over a positive number" if divisor < 1
|
32
|
+
|
33
|
+
rounded_split = self.lossy_divide(divisor)
|
34
|
+
results = Array.new(divisor, rounded_split) # Create with 'divisor' num elements
|
35
|
+
|
36
|
+
#if an array of monies is passed in, then split in proportions
|
37
|
+
elsif params.is_a?(Array)
|
38
|
+
|
39
|
+
raise ArgumentError, "Can only split up over at least one ration" if params.empty? && !self.zero?
|
40
|
+
|
41
|
+
|
42
|
+
results = params.map { |p| p.is_a?(::Money) ? p.cents : p }
|
43
|
+
|
44
|
+
total = results.inject(:+)
|
45
|
+
if total.zero?
|
46
|
+
return Array.new(results.size, ::Money.new(0)) if self.zero?
|
47
|
+
raise ArgumentError, "Total of ratios should not be zero! You sent in: #{params.inspect}"
|
48
|
+
end
|
49
|
+
|
50
|
+
results.map! do |ratio|
|
51
|
+
::Money.new((self.cents * (ratio.to_f / total)).round)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
raise "Either a fixnum or array has to be passed in for splitting money"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Distribute rounding to max absolute to avoid a $0 amount getting the rounding
|
58
|
+
biggest_value_index = results.index(results.max_by(&:abs))
|
59
|
+
results[biggest_value_index] += self - results.total_money
|
60
|
+
|
61
|
+
return results
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
DEFAULT_FORMAT_RULES = [:html]
|
66
|
+
# HACK COZ WE ARE PRETENDING WE DON'T HAVE CURRENCY!!!
|
67
|
+
#
|
68
|
+
# TODO: Make this not a hack...
|
69
|
+
def to_s_with_currency_hack
|
70
|
+
sprintf("%.2f", cents.to_f / 100) #currency.subunit_to_unit)
|
71
|
+
end
|
72
|
+
|
73
|
+
def inspect
|
74
|
+
inbuilt_inspect_style_id = '%x' % (object_id << 1)
|
75
|
+
"\#<Money:0x#{inbuilt_inspect_style_id} $#{self}>"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Money's default formatted is not flexible enought.
|
79
|
+
# - we don't want it to say 'free' when 0 (not correct for 'discounts' and such)
|
80
|
+
def format(*rules)
|
81
|
+
rules = rules.empty? ? DEFAULT_FORMAT_RULES.dup : rules.flatten
|
82
|
+
|
83
|
+
options = {}
|
84
|
+
rules.each do |rule|
|
85
|
+
if rule.is_a? Hash
|
86
|
+
options = rule
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
html_wrap = rules.include?(:html)
|
91
|
+
|
92
|
+
options[:delimiter] ||= ','
|
93
|
+
options[:separator] ||= '.'
|
94
|
+
|
95
|
+
''.tap do |formatted|
|
96
|
+
formatted << "<span class=\"money #{direction_class}\">" if html_wrap
|
97
|
+
|
98
|
+
rules << :signed if cents < 0
|
99
|
+
|
100
|
+
if rules.include?(:signed)
|
101
|
+
formatted << if cents > 0
|
102
|
+
'+'
|
103
|
+
elsif cents < 0
|
104
|
+
'-'
|
105
|
+
else
|
106
|
+
''
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
no_cents = rules.include?(:no_cents) ||
|
111
|
+
(rules.include?(:hide_zero_cents) && cents % 100 == 0)
|
112
|
+
format_string = no_cents ? "$%d" : "$%.2f"
|
113
|
+
amount = format_string % (cents.abs.to_f / 100)
|
114
|
+
amount.gsub!('.', options[:separator])
|
115
|
+
amount.gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
|
116
|
+
formatted << amount
|
117
|
+
|
118
|
+
formatted << "</span>" if html_wrap
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def direction_class
|
123
|
+
if cents > 0
|
124
|
+
'positive'
|
125
|
+
elsif cents < 0
|
126
|
+
'negative'
|
127
|
+
else
|
128
|
+
'zero'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def mark_up(mark_up_definition)
|
133
|
+
mark_up_cents = mark_up_definition.cents || 0
|
134
|
+
rounding_direction = mark_up_definition.rounding_direction.try(:to_sym)
|
135
|
+
multiplier = ((100 + mark_up_definition.percent)/100.0)
|
136
|
+
(self * multiplier).round(mark_up_definition.rounding_cents, rounding_direction) + ::Money.new(mark_up_cents)
|
137
|
+
end
|
138
|
+
|
139
|
+
# money = Money.new(501)
|
140
|
+
# money.round
|
141
|
+
# => 500
|
142
|
+
# money.round(10)
|
143
|
+
# => 510
|
144
|
+
# money.round(1)
|
145
|
+
# => 511
|
146
|
+
|
147
|
+
# money.round(100, 'up')
|
148
|
+
# => 600
|
149
|
+
# money.round(100, 'down')
|
150
|
+
# => 500
|
151
|
+
# money.round(100, 'nearest')
|
152
|
+
# => 500
|
153
|
+
def round(round_to_cents = 100, direction = :nearest)
|
154
|
+
round_to_cents = 100 if round_to_cents.nil?
|
155
|
+
case direction.to_sym
|
156
|
+
when :nearest
|
157
|
+
rounded_cents = (cents + round_to_cents/2) / round_to_cents * round_to_cents
|
158
|
+
when :up
|
159
|
+
rounded_cents = (cents + round_to_cents) / round_to_cents * round_to_cents
|
160
|
+
when :down
|
161
|
+
rounded_cents = (cents) / round_to_cents * round_to_cents
|
162
|
+
else
|
163
|
+
end
|
164
|
+
::Money.new(rounded_cents)
|
165
|
+
end
|
166
|
+
|
167
|
+
def abs
|
168
|
+
if cents >= 0
|
169
|
+
self.clone
|
170
|
+
else
|
171
|
+
::Money.new(0 - cents)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def positive?
|
176
|
+
cents > 0
|
177
|
+
end
|
178
|
+
|
179
|
+
def negative?
|
180
|
+
cents < 0
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
::Money.class_eval do
|
186
|
+
alias_method :lossy_divide, :/
|
187
|
+
end
|
188
|
+
|
189
|
+
::Money.send(:include, Extensions)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module MoneyField
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
# Add a money field attribute
|
9
|
+
#
|
10
|
+
# By default it will use attribute_in_cents for cents value
|
11
|
+
def money_field(attribute)
|
12
|
+
module_eval <<-METHOD
|
13
|
+
def #{attribute}
|
14
|
+
Money.new(#{attribute}_in_cents) if #{attribute}_in_cents.present?
|
15
|
+
end
|
16
|
+
|
17
|
+
# Allow assigning of non money objects directly
|
18
|
+
# Allows form data to be directly passed through
|
19
|
+
# e.g. object.cost = '5.1'
|
20
|
+
def #{attribute}=(money)
|
21
|
+
self.#{attribute}_in_cents = money.try(:to_money).try(:cents)
|
22
|
+
end
|
23
|
+
METHOD
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def money_fields(*attributes)
|
28
|
+
attributes.each {|a| self.money_field(a)}
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def money(*fields)
|
33
|
+
fields.each do |field|
|
34
|
+
class_eval <<-METHOD
|
35
|
+
def #{field}
|
36
|
+
Money.new(#{field}_in_cents)
|
37
|
+
end
|
38
|
+
METHOD
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,16 @@
|
|
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
|
+
# NOTE!!!
|
6
|
+
# This is overriden as per the default Money .to_money because it assumes
|
7
|
+
# a different default currency...
|
8
|
+
#
|
9
|
+
# 100.to_money => #<Money @cents=10000>
|
10
|
+
# 100.37.to_money => #<Money @cents=10037>
|
11
|
+
# require 'bigdecimal'
|
12
|
+
# BigDecimal.new('100').to_money => #<Money @cents=10000>
|
13
|
+
def to_money(currency = nil)
|
14
|
+
::Money.new((self * 100).round, currency)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class String
|
2
|
+
# Converts this string to a float and then to a Money object in the default currency.
|
3
|
+
# It multiplies the converted numeric value by 100 and treats that as cents.
|
4
|
+
#
|
5
|
+
# NOTE!!!
|
6
|
+
# This is overriden as per the default Money .to_money because it assumes
|
7
|
+
# a different default currency...
|
8
|
+
#
|
9
|
+
# '100'.to_money => #<Money @cents=10000>
|
10
|
+
# '100.37'.to_money => #<Money @cents=10037>
|
11
|
+
def to_money(currency = nil)
|
12
|
+
Money.new((self.to_f * 100).round, currency)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module MoneyExtensions
|
2
|
+
require 'money'
|
3
|
+
require 'money_extensions/array'
|
4
|
+
require 'money_extensions/big_decimal'
|
5
|
+
require 'money_extensions/fixnum'
|
6
|
+
require 'money_extensions/extensions'
|
7
|
+
require 'money_extensions/money_field'
|
8
|
+
require 'money_extensions/numeric'
|
9
|
+
require 'money_extensions/string'
|
10
|
+
|
11
|
+
# TODO: Defer requiring the active record extension to who needs it
|
12
|
+
if Module.const_defined?('ActiveRecord')
|
13
|
+
require 'money_extensions/active_record/extensions'
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'money_extensions/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'money_extensions'
|
8
|
+
spec.version = MoneyExtensions::VERSION
|
9
|
+
spec.authors = ["Michael Noack", "Alessandro Berardi"]
|
10
|
+
spec.email = 'support@travellink.com.au'
|
11
|
+
spec.description = "These are extensions from the money/currency classes."
|
12
|
+
spec.summary = "Set of extensions to the money gem used by TravelLink Technology."
|
13
|
+
spec.homepage = 'http://github.com/sealink/money_extensions'
|
14
|
+
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency 'money', '>= 3.0.0', '< 6.0.0'
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
spec.add_development_dependency 'rspec'
|
25
|
+
spec.add_development_dependency 'simplecov'
|
26
|
+
spec.add_development_dependency 'simplecov-rcov'
|
27
|
+
spec.add_development_dependency 'coveralls'
|
28
|
+
spec.add_development_dependency 'sqlite3'
|
29
|
+
spec.add_development_dependency 'activerecord'
|
30
|
+
spec.add_development_dependency 'travis'
|
31
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
::ActiveRecord::Base.send(:include, ActiveRecord::Extensions)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BigDecimal do
|
4
|
+
|
5
|
+
it "should allow verifying if it's a positive value" do
|
6
|
+
expect(BigDecimal.new('-1')).to_not be_positive
|
7
|
+
expect(BigDecimal.new('0')).to_not be_positive
|
8
|
+
expect(BigDecimal.new('1')).to be_positive
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should allow verifying if it's a negative value" do
|
12
|
+
expect(BigDecimal.new('-1')).to be_negative
|
13
|
+
expect(BigDecimal.new('0')).to_not be_negative
|
14
|
+
expect(BigDecimal.new('1')).to_not be_negative
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
DB_FILE = 'tmp/test_db'
|
4
|
+
FileUtils.mkdir_p File.dirname(DB_FILE)
|
5
|
+
FileUtils.rm_f DB_FILE
|
6
|
+
|
7
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => DB_FILE
|
8
|
+
|
9
|
+
load('spec/schema.rb')
|
10
|
+
|
11
|
+
load('rails/init.rb')
|
12
|
+
|
13
|
+
describe "ActiveRecord::Base" do
|
14
|
+
class Model < ActiveRecord::Base
|
15
|
+
money_fields :price, :cost
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:model) { Model.create!(:price => 5.to_money, :cost_in_cents => 300) }
|
19
|
+
|
20
|
+
it "should convert to from money and underlying cents object" do
|
21
|
+
expect(model.price_in_cents).to eq 500
|
22
|
+
expect(model.cost.cents).to eq 300
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
data/spec/money_spec.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Money do
|
4
|
+
it "should get correct direction class" do
|
5
|
+
expect(Money.new(-1).direction_class).to eq 'negative'
|
6
|
+
expect(Money.new(0).direction_class).to eq 'zero'
|
7
|
+
expect(Money.new(1).direction_class).to eq 'positive'
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should round correctly" do
|
11
|
+
money = Money.new(511)
|
12
|
+
expect(money.round.cents).to eq 500
|
13
|
+
expect(money.round(10).cents).to eq 510
|
14
|
+
expect(money.round(1).cents).to eq 511
|
15
|
+
|
16
|
+
expect(money.round(100, 'up').cents).to eq 600
|
17
|
+
expect(money.round(100, 'down').cents).to eq 500
|
18
|
+
expect(money.round(100, 'nearest').cents).to eq 500
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should deny division of money (to prevent rounding errors)" do
|
22
|
+
expect { Money.new(50)/10 }.to raise_error(RuntimeError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should split money correctly" do
|
26
|
+
money = Money.new(100)
|
27
|
+
|
28
|
+
expect { money.split_between(0) }.to raise_error(ArgumentError)
|
29
|
+
expect { money.split_between(-100) }.to raise_error(ArgumentError)
|
30
|
+
expect { money.split_between(0.1) }.to raise_error(RuntimeError)
|
31
|
+
|
32
|
+
expect(money.split_between(3)).to eq [34,33,33].map{ |i| Money.new(i)}
|
33
|
+
|
34
|
+
expect { money.split_between([]) }.to raise_error(ArgumentError)
|
35
|
+
expect { money.split_between([-1,1]) }.to raise_error(ArgumentError)
|
36
|
+
|
37
|
+
expect(money.split_between([1,2,2,5])).to eq [10,20,20,50].map{ |i| Money.new(i)}
|
38
|
+
expect(money.split_between([1,2])).to eq [33,67].map{ |i| Money.new(i)}
|
39
|
+
|
40
|
+
money_negative = Money.new(-100)
|
41
|
+
expect(money_negative.split_between(3)).to eq [-32,-34,-34].map{ |i| Money.new(i)}
|
42
|
+
expect(money_negative.split_between([1,2,2,5])).to eq [-10,-20,-20,-50].map{ |i| Money.new(i)}
|
43
|
+
expect(money_negative.split_between([1,2])).to eq [-33,-67].map{ |i| Money.new(i)}
|
44
|
+
|
45
|
+
money_zero = Money.new(0)
|
46
|
+
expect(money_zero.split_between([1,2])).to eq Array.new(2,money_zero)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should round split amounts" do
|
50
|
+
expect(Money.new(200).split_between([81, 40, 40])).to eq [100, 50, 50].map{ |i| Money.new(i) }
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should assign rounding to max absolute" do
|
54
|
+
# the -8 is the largest ABSOLUTE number...
|
55
|
+
# -8 / 3 == -2.66666 = 2.67
|
56
|
+
# it receives the rounding of +1c
|
57
|
+
expected_money = [33, -266, 233, 100].map{ |i| Money.new(i) }
|
58
|
+
expect(Money.new(100).split_between([1, -8, 7, 3])).to eq expected_money
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should return a nice, Big Decimal if so converted" do
|
62
|
+
money = Money.new(1428)
|
63
|
+
bigdecimal = BigDecimal.new("14.28")
|
64
|
+
expect(money.to_d).to eq bigdecimal
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should be createable from strings and numbers" do
|
68
|
+
money = Money.new(100)
|
69
|
+
expect("1".to_money.cents).to eq money.cents
|
70
|
+
expect(BigDecimal.new('1').to_money.cents).to eq money.cents
|
71
|
+
expect(100.total_money.cents).to eq money.cents
|
72
|
+
end
|
73
|
+
|
74
|
+
let(:money_positive) { Money.new(100) }
|
75
|
+
let(:money_negative) { Money.new(-100) }
|
76
|
+
let(:money_zero) { Money.new(0) }
|
77
|
+
|
78
|
+
it "should know positives, negatives, and absolutes" do
|
79
|
+
expect(money_positive.positive?).to eq true
|
80
|
+
expect(money_positive.negative?).to eq false
|
81
|
+
|
82
|
+
expect(money_negative.positive?).to eq false
|
83
|
+
expect(money_negative.negative?).to eq true
|
84
|
+
|
85
|
+
expect(money_zero.positive?).to eq false
|
86
|
+
expect(money_zero.negative?).to eq false
|
87
|
+
|
88
|
+
expect(money_positive.abs).to eq money_positive
|
89
|
+
expect(money_negative.abs).to eq money_positive
|
90
|
+
expect(money_zero.abs).to eq money_zero
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should format the output correctly" do
|
94
|
+
expect(money_positive.format).to eq "<span class=\"money positive\">$1.00</span>"
|
95
|
+
expect(money_negative.format).to eq "<span class=\"money negative\">-$1.00</span>"
|
96
|
+
expect(money_zero.format).to eq "<span class=\"money zero\">$0.00</span>"
|
97
|
+
|
98
|
+
expect(money_positive.format(:html)).to eq "<span class=\"money positive\">$1.00</span>"
|
99
|
+
expect(money_negative.format(:html)).to eq "<span class=\"money negative\">-$1.00</span>"
|
100
|
+
expect(money_zero.format(:html)).to eq "<span class=\"money zero\">$0.00</span>"
|
101
|
+
|
102
|
+
expect(money_positive.format(:signed)).to eq "+$1.00"
|
103
|
+
expect(money_negative.format(:signed)).to eq "-$1.00"
|
104
|
+
expect(money_zero.format(:signed)).to eq "$0.00"
|
105
|
+
|
106
|
+
expect("1.50".to_money.format(:separator => '~')).to eq "$1~50"
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should format cents where appropriate' do
|
110
|
+
expect('1.50'.to_money.format(:no_cents)).to eq '$1'
|
111
|
+
expect('1.00'.to_money.format(:no_cents)).to eq '$1'
|
112
|
+
|
113
|
+
expect('1.50'.to_money.format(:hide_zero_cents)).to eq '$1.50'
|
114
|
+
expect('1.00'.to_money.format(:hide_zero_cents)).to eq '$1'
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
data/spec/schema.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'bundler/setup'
|
10
|
+
|
11
|
+
require 'support/coverage_loader'
|
12
|
+
|
13
|
+
require 'active_record'
|
14
|
+
require 'money_extensions'
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.run_all_when_everything_filtered = true
|
18
|
+
config.filter_run :focus
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
MINIMUM_COVERAGE = 80
|
2
|
+
|
3
|
+
unless ENV['COVERAGE'] == 'off'
|
4
|
+
require 'simplecov'
|
5
|
+
require 'simplecov-rcov'
|
6
|
+
require 'coveralls'
|
7
|
+
Coveralls.wear!
|
8
|
+
|
9
|
+
SimpleCov.formatters = [
|
10
|
+
SimpleCov::Formatter::RcovFormatter,
|
11
|
+
Coveralls::SimpleCov::Formatter
|
12
|
+
]
|
13
|
+
SimpleCov.start do
|
14
|
+
add_filter '/vendor/'
|
15
|
+
add_filter '/spec/'
|
16
|
+
add_group 'lib', 'lib'
|
17
|
+
end
|
18
|
+
SimpleCov.at_exit do
|
19
|
+
SimpleCov.result.format!
|
20
|
+
percent = SimpleCov.result.covered_percent
|
21
|
+
unless percent >= MINIMUM_COVERAGE
|
22
|
+
puts "Coverage must be above #{MINIMUM_COVERAGE}%. It is #{"%.2f" % percent}%"
|
23
|
+
Kernel.exit(1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: money_extensions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Noack
|
8
|
+
- Alessandro Berardi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-02-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: money
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 3.0.0
|
21
|
+
- - "<"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 6.0.0
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 3.0.0
|
31
|
+
- - "<"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 6.0.0
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rake
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
type: :development
|
42
|
+
prerelease: false
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: simplecov
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
type: :development
|
70
|
+
prerelease: false
|
71
|
+
version_requirements: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: simplecov-rcov
|
78
|
+
requirement: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
type: :development
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: coveralls
|
92
|
+
requirement: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
type: :development
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: sqlite3
|
106
|
+
requirement: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
name: activerecord
|
120
|
+
requirement: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
type: :development
|
126
|
+
prerelease: false
|
127
|
+
version_requirements: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
- !ruby/object:Gem::Dependency
|
133
|
+
name: travis
|
134
|
+
requirement: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
type: :development
|
140
|
+
prerelease: false
|
141
|
+
version_requirements: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
description: These are extensions from the money/currency classes.
|
147
|
+
email: support@travellink.com.au
|
148
|
+
executables: []
|
149
|
+
extensions: []
|
150
|
+
extra_rdoc_files: []
|
151
|
+
files:
|
152
|
+
- ".gitignore"
|
153
|
+
- ".rspec"
|
154
|
+
- ".travis.yml"
|
155
|
+
- Gemfile
|
156
|
+
- LICENSE
|
157
|
+
- README.md
|
158
|
+
- Rakefile
|
159
|
+
- gemfiles/rails2.gemfile
|
160
|
+
- gemfiles/rails3.gemfile
|
161
|
+
- gemfiles/rails4.gemfile
|
162
|
+
- lib/money_extensions.rb
|
163
|
+
- lib/money_extensions/active_record/extensions.rb
|
164
|
+
- lib/money_extensions/array.rb
|
165
|
+
- lib/money_extensions/big_decimal.rb
|
166
|
+
- lib/money_extensions/extensions.rb
|
167
|
+
- lib/money_extensions/fixnum.rb
|
168
|
+
- lib/money_extensions/money_field.rb
|
169
|
+
- lib/money_extensions/numeric.rb
|
170
|
+
- lib/money_extensions/string.rb
|
171
|
+
- lib/money_extensions/version.rb
|
172
|
+
- money_extensions.gemspec
|
173
|
+
- rails/init.rb
|
174
|
+
- spec/big_decimal_spec.rb
|
175
|
+
- spec/money_field_spec.rb
|
176
|
+
- spec/money_spec.rb
|
177
|
+
- spec/schema.rb
|
178
|
+
- spec/spec_helper.rb
|
179
|
+
- spec/support/coverage_loader.rb
|
180
|
+
homepage: http://github.com/sealink/money_extensions
|
181
|
+
licenses:
|
182
|
+
- MIT
|
183
|
+
metadata: {}
|
184
|
+
post_install_message:
|
185
|
+
rdoc_options: []
|
186
|
+
require_paths:
|
187
|
+
- lib
|
188
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '0'
|
193
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
requirements: []
|
199
|
+
rubyforge_project:
|
200
|
+
rubygems_version: 2.4.8
|
201
|
+
signing_key:
|
202
|
+
specification_version: 4
|
203
|
+
summary: Set of extensions to the money gem used by TravelLink Technology.
|
204
|
+
test_files:
|
205
|
+
- spec/big_decimal_spec.rb
|
206
|
+
- spec/money_field_spec.rb
|
207
|
+
- spec/money_spec.rb
|
208
|
+
- spec/schema.rb
|
209
|
+
- spec/spec_helper.rb
|
210
|
+
- spec/support/coverage_loader.rb
|