money-rails 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +19 -3
- data/README.md +28 -3
- data/Rakefile +19 -3
- data/lib/money-rails/active_model/validator.rb +23 -7
- data/lib/money-rails/active_record/monetizable.rb +77 -16
- data/lib/money-rails/configuration.rb +4 -1
- data/lib/money-rails/helpers/action_view_extension.rb +2 -2
- data/lib/money-rails/money.rb +2 -1
- data/lib/money-rails/mongoid/money.rb +2 -2
- data/lib/money-rails/mongoid/two.rb +5 -5
- data/lib/money-rails/test_helpers.rb +7 -7
- data/lib/money-rails/version.rb +1 -1
- data/money-rails.gemspec +1 -1
- data/spec/active_record/monetizable_spec.rb +250 -165
- data/spec/configuration_spec.rb +27 -0
- data/spec/dummy/app/models/dummy_product.rb +1 -1
- data/spec/dummy/app/models/priceable.rb +1 -0
- data/spec/dummy/app/models/product.rb +12 -4
- data/spec/dummy/app/models/transaction.rb +3 -3
- data/spec/dummy/config/application.rb +3 -1
- data/spec/dummy/config/environments/test.rb +0 -3
- data/spec/mongoid/three_spec.rb +29 -0
- data/spec/mongoid/two_spec.rb +16 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/test_helpers_spec.rb +21 -4
- metadata +4 -4
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,24 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## master
|
4
|
-
|
5
|
-
|
3
|
+
## master (next release)
|
4
|
+
- Add testing tasks for rails 4.x
|
5
|
+
- Fix issue with Numeric values in MoneyValidator (GH-83).
|
6
|
+
- Fix test helper
|
7
|
+
- Fix issue with money validator (GH-102).
|
8
|
+
- Change validation logic. Support Subunit and Money field
|
9
|
+
validation with NumericalityValidator options.
|
10
|
+
Moreover, now Money objects are normalized and pass through
|
11
|
+
all validation steps.
|
12
|
+
- Add support for the global configuration of the sign_before_setting formatting option.
|
13
|
+
|
14
|
+
## 0.8.1
|
15
|
+
- Remove unnecessary files from gem build.
|
16
|
+
- Add options to ActionView helpers that enable the usage of any of the rules ::Money.format allows.
|
17
|
+
- Fix "setting amount_column for default_currency" to only accept
|
18
|
+
postfix for default_currency with subunit.
|
19
|
+
- Add mongoid 4 support.
|
20
|
+
|
21
|
+
## 0.8.0 (yanked)
|
6
22
|
- Added defaults for amount and currency columns in database schema, based on the default currency.
|
7
23
|
- Use a better default subunit_unit name (choose the value of column.postfix set in the config).
|
8
24
|
- Began support of Rails 4.
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Build Status](https://secure.travis-ci.org/RubyMoney/money-rails.png?branch=master)](http://travis-ci.org/RubyMoney/money-rails)
|
4
4
|
[![Dependency Status](https://gemnasium.com/RubyMoney/money-rails.png)](https://gemnasium.com/RubyMoney/money-rails)
|
5
|
-
[![Code Climate](https://codeclimate.com/
|
5
|
+
[![Code Climate](https://codeclimate.com/github/RubyMoney/money-rails.png)](https://codeclimate.com/github/RubyMoney/money-rails)
|
6
6
|
|
7
7
|
## Introduction
|
8
8
|
|
@@ -40,6 +40,17 @@ $ rails g money_rails:initializer
|
|
40
40
|
There, you can define the default currency value and set other
|
41
41
|
configuration parameters for the rails app.
|
42
42
|
|
43
|
+
### Setup for money-rails development
|
44
|
+
|
45
|
+
Our tests are executed with several ORMs - see `Rakefile` for details. To install all required gems run these:
|
46
|
+
|
47
|
+
bundle install --gemfile=gemfiles/mongoid2.gemfile
|
48
|
+
bundle install --gemfile=gemfiles/mongoid3.gemfile
|
49
|
+
bundle install --gemfile=gemfiles/rails3.gemfile
|
50
|
+
bundle install --gemfile=gemfiles/rails4.gemfile
|
51
|
+
|
52
|
+
Then you can run the test suite with `rake`,
|
53
|
+
|
43
54
|
## Usage
|
44
55
|
|
45
56
|
### ActiveRecord
|
@@ -117,6 +128,11 @@ monetized field, you can use the `:allow_nil` parameter like this:
|
|
117
128
|
# in Product model
|
118
129
|
monetize :optional_price_cents, :allow_nil => true
|
119
130
|
|
131
|
+
# in Migration
|
132
|
+
def change
|
133
|
+
add_money :products, :optional_price, amount: { null: true, default: nil }
|
134
|
+
end
|
135
|
+
|
120
136
|
# then blank assignments are permitted
|
121
137
|
product.optional_price = nil
|
122
138
|
product.save # returns without errors
|
@@ -127,7 +143,7 @@ product.optional_price_cents # => nil
|
|
127
143
|
#### Numericality validation options
|
128
144
|
|
129
145
|
You can also pass along
|
130
|
-
[numericality validation options](http://guides.rubyonrails.org/
|
146
|
+
[numericality validation options](http://guides.rubyonrails.org/active_record_validations.html#numericality)
|
131
147
|
such as this:
|
132
148
|
|
133
149
|
```ruby
|
@@ -138,6 +154,13 @@ monetize :price_in_a_range_cents, :allow_nil => true,
|
|
138
154
|
}
|
139
155
|
```
|
140
156
|
|
157
|
+
Or, if you prefer, you can skip validations entirely for the attribute. This is useful if chosen attributes
|
158
|
+
are aggregate methods and you wish to avoid executing them on every record save.
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
monetize :price_in_a_range_cents, :disable_validation => true
|
162
|
+
```
|
163
|
+
|
141
164
|
### Mongoid 2.x and 3.x
|
142
165
|
|
143
166
|
`Money` is available as a field type to supply during a field definition:
|
@@ -354,6 +377,7 @@ MoneyRails.configure do |config|
|
|
354
377
|
#
|
355
378
|
# config.no_cents_if_whole = nil
|
356
379
|
# config.symbol = nil
|
380
|
+
# config.sign_before_symbol = nil
|
357
381
|
end
|
358
382
|
```
|
359
383
|
|
@@ -372,6 +396,7 @@ end
|
|
372
396
|
* ```no_cents_if_whole```: Force `Money#format` method to use its value as the default for ```no_cents_if_whole``` key.
|
373
397
|
* ```symbol```: Use its value as the default for ```symbol``` key in
|
374
398
|
`Money#format` method.
|
399
|
+
* ```sign_before_symbol```: Force `Money#format` to place the negative sign before the currency symbol.
|
375
400
|
* ```amount_column```: Provide values for the amount column (holding the fractional part of a money object).
|
376
401
|
* ```currency_column```: Provide default values or even disable (`present: false`) the currency column.
|
377
402
|
|
@@ -467,4 +492,4 @@ You can see a full list of the currently supported interpreters in [travis.yml](
|
|
467
492
|
|
468
493
|
## License
|
469
494
|
|
470
|
-
MIT License. Copyright 2012 RubyMoney.
|
495
|
+
MIT License. Copyright 2012-2013 RubyMoney.
|
data/Rakefile
CHANGED
@@ -32,6 +32,18 @@ namespace :spec do
|
|
32
32
|
sh "BUNDLE_GEMFILE='gemfiles/mongoid2.gemfile' bundle exec rake -t spec"
|
33
33
|
end
|
34
34
|
|
35
|
+
desc "Run Tests against rails 4"
|
36
|
+
task :rails4 do
|
37
|
+
sh "BUNDLE_GEMFILE='gemfiles/rails4.gemfile' bundle --quiet"
|
38
|
+
sh "BUNDLE_GEMFILE='gemfiles/rails4.gemfile' bundle exec rake -t spec"
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Run Tests against rails 3"
|
42
|
+
task :rails3 do
|
43
|
+
sh "BUNDLE_GEMFILE='gemfiles/rails3.gemfile' bundle --quiet"
|
44
|
+
sh "BUNDLE_GEMFILE='gemfiles/rails3.gemfile' bundle exec rake -t spec"
|
45
|
+
end
|
46
|
+
|
35
47
|
desc "Run Tests against activerecord"
|
36
48
|
task :activerecord do
|
37
49
|
sh "bundle --quiet"
|
@@ -48,9 +60,13 @@ namespace :spec do
|
|
48
60
|
sh "BUNDLE_GEMFILE='gemfiles/mongoid2.gemfile' bundle --quiet"
|
49
61
|
sh "BUNDLE_GEMFILE='gemfiles/mongoid2.gemfile' bundle exec rake -t spec"
|
50
62
|
|
51
|
-
#
|
52
|
-
sh "bundle --quiet"
|
53
|
-
sh "bundle exec rake -t spec"
|
63
|
+
# rails 4
|
64
|
+
sh "BUNDLE_GEMFILE='gemfiles/rails4.gemfile' bundle --quiet"
|
65
|
+
sh "BUNDLE_GEMFILE='gemfiles/rails4.gemfile' bundle exec rake -t spec"
|
66
|
+
|
67
|
+
# rails 3
|
68
|
+
sh "BUNDLE_GEMFILE='gemfiles/rails3.gemfile' bundle --quiet"
|
69
|
+
sh "BUNDLE_GEMFILE='gemfiles/rails3.gemfile' bundle exec rake -t spec"
|
54
70
|
end
|
55
71
|
end
|
56
72
|
|
@@ -9,21 +9,36 @@ module MoneyRails
|
|
9
9
|
subunit_attr = record.class.monetized_attributes[attr.to_sym]
|
10
10
|
return unless record.changed_attributes.keys.include? subunit_attr
|
11
11
|
|
12
|
+
raw_value = nil
|
13
|
+
|
12
14
|
# WARNING: Currently this is only defined in ActiveRecord extension!
|
13
15
|
before_type_cast = "#{attr}_money_before_type_cast"
|
14
|
-
raw_value = record.send(before_type_cast) if record.respond_to?(
|
16
|
+
raw_value = record.send(before_type_cast) if record.respond_to?(
|
17
|
+
before_type_cast.to_sym)
|
15
18
|
|
16
|
-
#
|
17
|
-
|
19
|
+
# If raw value is nil and changed subunit is nil, then
|
20
|
+
# nil is a assigned value, elsewhere we should treat the
|
21
|
+
# subunit value as the one assigned.
|
22
|
+
if raw_value.nil?
|
23
|
+
if record.send(subunit_attr)
|
24
|
+
raw_value = record.send(subunit_attr)
|
25
|
+
end
|
26
|
+
end
|
18
27
|
|
19
|
-
if
|
28
|
+
return if options[:allow_nil] && raw_value.nil?
|
29
|
+
|
30
|
+
# Skip normalization for Numeric values
|
31
|
+
# which can directly be handled by NumericalityValidator
|
32
|
+
if raw_value.present? && !raw_value.is_a?(Numeric)
|
20
33
|
# remove currency symbol, and negative sign
|
21
34
|
currency = record.send("currency_for_#{attr}")
|
22
|
-
decimal_mark = I18n.t('number.currency.format.separator',
|
23
|
-
|
35
|
+
decimal_mark = I18n.t('number.currency.format.separator',
|
36
|
+
default: currency.decimal_mark)
|
37
|
+
thousands_separator = I18n.t('number.currency.format.delimiter',
|
38
|
+
default: currency.thousands_separator)
|
24
39
|
symbol = I18n.t('number.currency.format.unit', default: currency.symbol)
|
25
40
|
|
26
|
-
raw_value = raw_value.to_s.gsub(symbol, "")
|
41
|
+
raw_value = raw_value.to_s.strip.gsub(symbol, "")
|
27
42
|
abs_raw_value = raw_value.gsub(/^-/, "")
|
28
43
|
|
29
44
|
decimal_pieces = abs_raw_value.split(decimal_mark)
|
@@ -57,6 +72,7 @@ module MoneyRails
|
|
57
72
|
.gsub(decimal_mark, '.')
|
58
73
|
.gsub(/[\s_]/, '')
|
59
74
|
end
|
75
|
+
|
60
76
|
super(record, attr, raw_value)
|
61
77
|
end
|
62
78
|
end
|
@@ -21,10 +21,13 @@ module MoneyRails
|
|
21
21
|
":with_currency or :with_model_currency")
|
22
22
|
end
|
23
23
|
|
24
|
-
# Optional accessor to be run on an instance to detect currency
|
24
|
+
# Optional accessor to be run on an instance to detect currency
|
25
25
|
instance_currency_name = options[:with_model_currency] ||
|
26
|
-
options[:model_currency] ||
|
27
|
-
|
26
|
+
options[:model_currency] ||
|
27
|
+
MoneyRails::Configuration.currency_column[:column_name]
|
28
|
+
|
29
|
+
instance_currency_name = instance_currency_name &&
|
30
|
+
instance_currency_name.to_s
|
28
31
|
|
29
32
|
# This attribute allows per column currency values
|
30
33
|
# Overrides row and default currency
|
@@ -55,32 +58,76 @@ module MoneyRails
|
|
55
58
|
end
|
56
59
|
end unless respond_to? :monetized_attributes
|
57
60
|
|
58
|
-
# Include numericality
|
59
|
-
|
60
|
-
|
61
|
+
# Include numericality validations if needed.
|
62
|
+
# There are two validation options:
|
63
|
+
#
|
64
|
+
# 1. Subunit field validation (e.g. cents should be > 100)
|
65
|
+
# 2. Money field validation (e.g. euros should be > 10)
|
66
|
+
#
|
67
|
+
# All the options which are available for Rails numericality
|
68
|
+
# validation, are also available for both types.
|
69
|
+
# E.g.
|
70
|
+
# monetize :price_in_a_range_cents, :allow_nil => true,
|
71
|
+
# :subunit_numericality => {
|
72
|
+
# :greater_than_or_equal_to => 0,
|
73
|
+
# :less_than_or_equal_to => 10000,
|
74
|
+
# },
|
75
|
+
# :numericality => {
|
76
|
+
# :greater_than_or_equal_to => 0,
|
77
|
+
# :less_than_or_equal_to => 100,
|
78
|
+
# :message => "Must be greater than zero and less than $100"
|
79
|
+
# }
|
80
|
+
#
|
81
|
+
# To disable validation entirely, use :disable_validation, E.g:
|
82
|
+
# monetize :price_in_a_range_cents, :disable_validation => true
|
83
|
+
if MoneyRails.include_validations && !options[:disable_validation]
|
84
|
+
|
85
|
+
subunit_validation_options =
|
86
|
+
unless options.has_key? :subunit_numericality
|
87
|
+
true
|
88
|
+
else
|
89
|
+
options[:subunit_numericality]
|
90
|
+
end
|
91
|
+
|
92
|
+
money_validation_options =
|
93
|
+
unless options.has_key? :numericality
|
94
|
+
true
|
95
|
+
else
|
96
|
+
options[:numericality]
|
97
|
+
end
|
98
|
+
|
99
|
+
# This is a validation for the subunit
|
100
|
+
validates subunit_name, {
|
61
101
|
:allow_nil => options[:allow_nil],
|
62
|
-
:numericality =>
|
102
|
+
:numericality => subunit_validation_options
|
63
103
|
}
|
64
|
-
validates subunit_name, validation_options
|
65
104
|
|
66
|
-
validation_options = { :allow_nil => options[:allow_nil] }
|
67
|
-
validation_options = options[:numericality].merge(validation_options) if options[:numericality]
|
68
|
-
|
69
105
|
# Allow only Money objects or Numeric values!
|
70
|
-
validates name.to_sym,
|
106
|
+
validates name.to_sym, {
|
107
|
+
:allow_nil => options[:allow_nil],
|
108
|
+
'money_rails/active_model/money' => money_validation_options
|
109
|
+
}
|
71
110
|
end
|
72
111
|
|
73
112
|
define_method name do |*args|
|
113
|
+
|
114
|
+
# Get the cents
|
74
115
|
amount = send(subunit_name, *args)
|
116
|
+
|
117
|
+
# Get the currency object
|
75
118
|
attr_currency = send("currency_for_#{name}")
|
76
119
|
|
77
|
-
#
|
120
|
+
# Get the cached value
|
78
121
|
memoized = instance_variable_get("@#{name}")
|
122
|
+
|
123
|
+
# Dont create a new Money instance if the values haven't been changed.
|
79
124
|
return memoized if memoized && memoized.cents == amount &&
|
80
125
|
memoized.currency == attr_currency
|
81
126
|
|
127
|
+
# If amount is NOT nil (or empty string) load the amount in a Money
|
82
128
|
amount = Money.new(amount, attr_currency) unless amount.blank?
|
83
129
|
|
130
|
+
# Cache and return the value (it may be nil)
|
84
131
|
instance_variable_set "@#{name}", amount
|
85
132
|
end
|
86
133
|
|
@@ -89,6 +136,7 @@ module MoneyRails
|
|
89
136
|
# Lets keep the before_type_cast value
|
90
137
|
instance_variable_set "@#{name}_money_before_type_cast", value
|
91
138
|
|
139
|
+
# Use nil or get a Money object
|
92
140
|
if options[:allow_nil] && value.blank?
|
93
141
|
money = nil
|
94
142
|
else
|
@@ -99,14 +147,27 @@ module MoneyRails
|
|
99
147
|
end
|
100
148
|
end
|
101
149
|
|
150
|
+
# Update cents
|
102
151
|
send("#{subunit_name}=", money.try(:cents))
|
103
|
-
send("#{instance_currency_name}=", money.try(:currency).try(:iso_code)) if self.respond_to?("#{instance_currency_name}=")
|
104
152
|
|
153
|
+
# Update currency iso value if there is an instance currency attribute
|
154
|
+
if instance_currency_name.present? &&
|
155
|
+
self.respond_to?("#{instance_currency_name}=")
|
156
|
+
|
157
|
+
send("#{instance_currency_name}=",
|
158
|
+
money.try(:currency).try(:iso_code))
|
159
|
+
end
|
160
|
+
|
161
|
+
# Save and return the new Money object
|
105
162
|
instance_variable_set "@#{name}", money
|
106
163
|
end
|
107
164
|
|
108
165
|
define_method "currency_for_#{name}" do
|
109
|
-
if
|
166
|
+
if instance_currency_name.present? &&
|
167
|
+
self.respond_to?(instance_currency_name) &&
|
168
|
+
send(instance_currency_name).present? &&
|
169
|
+
Money::Currency.find(send(instance_currency_name))
|
170
|
+
|
110
171
|
Money::Currency.find(send(instance_currency_name))
|
111
172
|
elsif field_currency_name
|
112
173
|
Money::Currency.find(field_currency_name)
|
@@ -123,7 +184,7 @@ module MoneyRails
|
|
123
184
|
|
124
185
|
# Hook to ensure the reset of before_type_cast attr
|
125
186
|
# TODO: think of a better way to avoid this
|
126
|
-
|
187
|
+
after_save do
|
127
188
|
instance_variable_set "@#{name}_money_before_type_cast", nil
|
128
189
|
end
|
129
190
|
end
|
@@ -51,7 +51,7 @@ module MoneyRails
|
|
51
51
|
# MoneyRails.configure do |config|
|
52
52
|
# config.default_bank = EuCentralBank.new
|
53
53
|
# end
|
54
|
-
delegate :default_bank=, :to => :Money
|
54
|
+
delegate :default_bank=, :default_bank, :to => :Money
|
55
55
|
|
56
56
|
# Provide exchange rates
|
57
57
|
delegate :add_rate, :to => :Money
|
@@ -73,5 +73,8 @@ module MoneyRails
|
|
73
73
|
|
74
74
|
mattr_accessor :symbol
|
75
75
|
@@symbol = nil
|
76
|
+
|
77
|
+
mattr_accessor :sign_before_symbol
|
78
|
+
@@sign_before_symbol = nil
|
76
79
|
end
|
77
80
|
end
|
@@ -25,8 +25,8 @@ module MoneyRails
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def humanized_money_with_symbol(value)
|
29
|
-
humanized_money(value, :symbol => true)
|
28
|
+
def humanized_money_with_symbol(value, options={})
|
29
|
+
humanized_money(value, options.merge(:symbol => true))
|
30
30
|
end
|
31
31
|
|
32
32
|
def money_without_cents(value, options={})
|
data/lib/money-rails/money.rb
CHANGED
@@ -10,7 +10,8 @@ class Money
|
|
10
10
|
# TODO: Add here more setting options
|
11
11
|
defaults = {
|
12
12
|
no_cents_if_whole: MoneyRails::Configuration.no_cents_if_whole,
|
13
|
-
symbol: MoneyRails::Configuration.symbol
|
13
|
+
symbol: MoneyRails::Configuration.symbol,
|
14
|
+
sign_before_symbol: MoneyRails::Configuration.sign_before_symbol
|
14
15
|
}.reject { |k,v| v.nil? }
|
15
16
|
|
16
17
|
rules.reverse_merge!(defaults)
|
@@ -17,12 +17,12 @@ class Money
|
|
17
17
|
def serialize(object)
|
18
18
|
case
|
19
19
|
when object.is_a?(Money)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
{
|
21
|
+
:cents => object.cents.is_a?(BigDecimal) ? object.cents.to_s : object.cents,
|
22
|
+
:currency_iso => object.currency.iso_code
|
23
|
+
}
|
24
24
|
when object.respond_to?(:to_money)
|
25
|
-
|
25
|
+
serialize(object.to_money)
|
26
26
|
else nil
|
27
27
|
end
|
28
28
|
end
|
@@ -17,29 +17,29 @@ module MoneyRails
|
|
17
17
|
match do |target|
|
18
18
|
matched = true
|
19
19
|
money_attr = @as.presence || attr.to_s.sub(/_cents$/, "")
|
20
|
-
matched = false
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
matched = false if !target.respond_to?(money_attr) ||
|
21
|
+
!target.send(money_attr).instance_of?(Money) ||
|
22
|
+
(@currency_iso &&
|
23
|
+
target.send(money_attr.to_sym).currency.id != @currency_iso)
|
24
24
|
matched
|
25
25
|
end
|
26
26
|
|
27
27
|
description do
|
28
|
-
description = "monetize #{
|
28
|
+
description = "monetize #{attr}"
|
29
29
|
description << " as #{@as}" if @as
|
30
30
|
description << " with currency #{@currency_iso}" if @currency_iso
|
31
31
|
description
|
32
32
|
end
|
33
33
|
|
34
34
|
failure_message_for_should do |actual|
|
35
|
-
msg = "expected that #{actual} would be monetized"
|
35
|
+
msg = "expected that #{attr} of #{actual} would be monetized"
|
36
36
|
msg << " as #{@as}" if @as
|
37
37
|
msg << " with currency #{@currency_iso}" if @currency_iso
|
38
38
|
msg
|
39
39
|
end
|
40
40
|
|
41
41
|
failure_message_for_should_not do |actual|
|
42
|
-
msg = "expected that #{actual} would not be monetized"
|
42
|
+
msg = "expected that #{attr} of #{actual} would not be monetized"
|
43
43
|
msg << " as #{@as}" if @as
|
44
44
|
msg << " with currency #{@currency_iso}" if @currency_iso
|
45
45
|
msg
|