money-rails 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
[](http://travis-ci.org/RubyMoney/money-rails)
|
4
4
|
[](https://gemnasium.com/RubyMoney/money-rails)
|
5
|
-
[](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
|