money-rails 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -3
- data/lib/money-rails/active_model/validator.rb +5 -8
- data/lib/money-rails/active_record/migration_extensions/schema_statements_pg_rails4.rb +21 -0
- data/lib/money-rails/active_record/migration_extensions/table_pg_rails4.rb +21 -0
- data/lib/money-rails/active_record/monetizable.rb +194 -186
- data/lib/money-rails/helpers/action_view_extension.rb +1 -0
- data/lib/money-rails/hooks.rb +19 -1
- data/lib/money-rails/mongoid/money.rb +10 -2
- data/lib/money-rails/version.rb +1 -1
- data/money-rails.gemspec +1 -1
- data/spec/active_record/monetizable_spec.rb +31 -19
- data/spec/dummy/app/models/product.rb +10 -4
- data/spec/dummy/db/migrate/20150107061030_add_delivery_fee_cents_and_restock_fee_cents_to_product.rb +6 -0
- data/spec/dummy/db/migrate/20150126231442_add_reduced_price_to_products.rb +6 -0
- data/spec/dummy/db/schema.rb +5 -1
- data/spec/helpers/action_view_extension_spec.rb +16 -1
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6d1b9abaf2dfb8fa5d9ede9509fe5dd3ef323a5
|
4
|
+
data.tar.gz: 34b2f71bba5a7773e2b85571fda0892f643cb0c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8038c315b04d1d026b2582c1ac76a1c904e4e9ec5a2462fcbfcdfcca283475e1ee778621a34dbd5acc7d28b608350f4a216cf1805bb669f99134cb340bca4b5
|
7
|
+
data.tar.gz: 9109fd349499fe439531743e309a6babd40e7bc1ca3a218a3955e4f87985e4046caeaef5f46ce705293f48a2b0544bb0865e5c9a81f8e072ce852f27f7bfd415
|
data/README.md
CHANGED
@@ -107,10 +107,17 @@ class MonetizeItem < ActiveRecord::Migration
|
|
107
107
|
end
|
108
108
|
```
|
109
109
|
|
110
|
+
Notice. Default value of currency field, generated by migration's helper, is USD. To override these defaults, you need change default_currency in money initializer and run migrations.
|
111
|
+
|
110
112
|
The ```add_money``` helper is reversible, so you an use it inside ```change```
|
111
113
|
migrations. If you're writing separate ```up``` and ```down``` methods, you
|
112
114
|
can use the ```remove_money``` helper.
|
113
115
|
|
116
|
+
##### Notice for Rails >= 4.2 and PG adapter
|
117
|
+
|
118
|
+
Due the adding `money` column type for postgres in Rails 4.2 you will need to use `add_monetize` for add money column,
|
119
|
+
`t.monetize` for add column in `create_table` or `change_table` block and `remove_monetize` for removing column.
|
120
|
+
|
114
121
|
#### Allow nil values
|
115
122
|
|
116
123
|
If you want to allow nil and/or blank values to a specific
|
@@ -377,7 +384,7 @@ MoneyRails.configure do |config|
|
|
377
384
|
|
378
385
|
# Specify a rounding mode
|
379
386
|
# Any one of:
|
380
|
-
#
|
387
|
+
#
|
381
388
|
# BigDecimal::ROUND_UP,
|
382
389
|
# BigDecimal::ROUND_DOWN,
|
383
390
|
# BigDecimal::ROUND_HALF_UP,
|
@@ -385,9 +392,9 @@ MoneyRails.configure do |config|
|
|
385
392
|
# BigDecimal::ROUND_HALF_EVEN,
|
386
393
|
# BigDecimal::ROUND_CEILING,
|
387
394
|
# BigDecimal::ROUND_FLOOR
|
388
|
-
#
|
395
|
+
#
|
389
396
|
# set to BigDecimal::ROUND_HALF_EVEN by default
|
390
|
-
#
|
397
|
+
#
|
391
398
|
# config.rounding_mode = BigDecimal::ROUND_HALF_UP
|
392
399
|
|
393
400
|
# Set default money format globally.
|
@@ -434,6 +441,8 @@ _For examples below, `@money_object == <Money fractional:650 currency:USD>`_
|
|
434
441
|
#### `no_cents_if_whole`
|
435
442
|
|
436
443
|
`humanized_money` and `humanized_money_with_symbol` will not render the cents part if it contains only zeros, unless `config.no_cents_if_whole` is set to `false` in the `money.rb` configuration (default: true).
|
444
|
+
Note that the `config.default_format` will be overwritten by `config.no_cents_if_whole`.
|
445
|
+
So `humanized_money` will ignore `config.default_format = { no_cents_if_whole: false }` if you don't set `config.no_cents_if_whole = false`.
|
437
446
|
|
438
447
|
### Testing
|
439
448
|
|
@@ -6,21 +6,18 @@ module MoneyRails
|
|
6
6
|
@record = record
|
7
7
|
@attr = attr
|
8
8
|
|
9
|
-
# If subunit is not set then no need to validate as it is an
|
10
|
-
# indicator that no assignment has been done onto the virtual
|
11
|
-
# money field.
|
12
9
|
subunit_attr = @record.class.monetized_attributes[@attr.to_sym]
|
13
|
-
return unless @record.changed_attributes.keys.include? subunit_attr
|
14
10
|
|
15
11
|
# WARNING: Currently this is only defined in ActiveRecord extension!
|
16
12
|
before_type_cast = :"#{@attr}_money_before_type_cast"
|
17
13
|
@raw_value = @record.try(before_type_cast)
|
18
14
|
|
19
15
|
# If raw value is nil and changed subunit is nil, then
|
20
|
-
# nil is a assigned value,
|
16
|
+
# nil is a assigned value, else we should treat the
|
21
17
|
# subunit value as the one assigned.
|
22
|
-
if @raw_value.nil? && @record.
|
23
|
-
|
18
|
+
if @raw_value.nil? && @record.public_send(subunit_attr)
|
19
|
+
subunit_value = @record.public_send(subunit_attr)
|
20
|
+
@raw_value = subunit_value.to_f / currency.subunit_to_unit
|
24
21
|
end
|
25
22
|
|
26
23
|
return if options[:allow_nil] && @raw_value.nil?
|
@@ -51,7 +48,7 @@ module MoneyRails
|
|
51
48
|
end
|
52
49
|
|
53
50
|
def currency
|
54
|
-
@_currency ||= @record.
|
51
|
+
@_currency ||= @record.public_send("currency_for_#{@attr}")
|
55
52
|
end
|
56
53
|
|
57
54
|
def decimal_mark
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module MoneyRails
|
2
|
+
module ActiveRecord
|
3
|
+
module MigrationExtensions
|
4
|
+
module SchemaStatements
|
5
|
+
def add_monetize(table_name, accessor, options={})
|
6
|
+
[:amount, :currency].each do |attribute|
|
7
|
+
column_present, *opts = OptionsExtractor.extract attribute, table_name, accessor, options
|
8
|
+
add_column *opts if column_present
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove_monetize(table_name, accessor, options={})
|
13
|
+
[:amount, :currency].each do |attribute|
|
14
|
+
column_present, table_name, column_name, _, _ = OptionsExtractor.extract attribute, table_name, accessor, options
|
15
|
+
remove_column table_name, column_name if column_present
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module MoneyRails
|
2
|
+
module ActiveRecord
|
3
|
+
module MigrationExtensions
|
4
|
+
module Table
|
5
|
+
def monetize(accessor, options={})
|
6
|
+
[:amount, :currency].each do |attribute|
|
7
|
+
column_present, _, *opts = OptionsExtractor.extract attribute, :no_table, accessor, options
|
8
|
+
column *opts if column_present
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove_monetize(accessor, options={})
|
13
|
+
[:amount, :currency].each do |attribute|
|
14
|
+
column_present, _, column_name, _, _ = OptionsExtractor.extract attribute, :no_table, accessor, options
|
15
|
+
remove column_name if column_present
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -8,224 +8,232 @@ module MoneyRails
|
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
10
|
module ClassMethods
|
11
|
-
def monetize(
|
12
|
-
options =
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
options[:
|
19
|
-
|
20
|
-
"
|
21
|
-
|
22
|
-
|
11
|
+
def monetize(*fields)
|
12
|
+
options = fields.extract_options!
|
13
|
+
|
14
|
+
fields.each do |field|
|
15
|
+
# Stringify model field name
|
16
|
+
subunit_name = field.to_s
|
17
|
+
|
18
|
+
if options[:field_currency] || options[:target_name] ||
|
19
|
+
options[:model_currency]
|
20
|
+
ActiveSupport::Deprecation.warn("You are using the old " \
|
21
|
+
"argument keys of the monetize command! Instead use :as, " \
|
22
|
+
":with_currency or :with_model_currency")
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
25
|
+
# Optional accessor to be run on an instance to detect currency
|
26
|
+
instance_currency_name = options[:with_model_currency] ||
|
27
|
+
options[:model_currency] ||
|
28
|
+
MoneyRails::Configuration.currency_column[:column_name]
|
29
|
+
|
30
|
+
instance_currency_name = instance_currency_name &&
|
31
|
+
instance_currency_name.to_s
|
32
|
+
|
33
|
+
# This attribute allows per column currency values
|
34
|
+
# Overrides row and default currency
|
35
|
+
field_currency_name = options[:with_currency] ||
|
36
|
+
options[:field_currency] || nil
|
37
|
+
|
38
|
+
name = options[:as] || options[:target_name] || nil
|
39
|
+
|
40
|
+
# Form target name for the money backed ActiveModel field:
|
41
|
+
# if a target name is provided then use it
|
42
|
+
# if there is a "{column.postfix}" suffix then just remove it to create the target name
|
43
|
+
# if none of the previous is the case then use a default suffix
|
44
|
+
if name
|
45
|
+
name = name.to_s
|
46
|
+
elsif subunit_name =~ /#{MoneyRails::Configuration.amount_column[:postfix]}$/
|
47
|
+
name = subunit_name.sub(/#{MoneyRails::Configuration.amount_column[:postfix]}$/, "")
|
48
|
+
else
|
49
|
+
# FIXME: provide a better default
|
50
|
+
name = [subunit_name, "money"].join("_")
|
51
|
+
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
@monetized_attributes[name] = subunit_name
|
58
|
-
class << self
|
59
|
-
def monetized_attributes
|
60
|
-
@monetized_attributes || superclass.monetized_attributes
|
53
|
+
# Create a reverse mapping of the monetized attributes
|
54
|
+
@monetized_attributes ||= {}.with_indifferent_access
|
55
|
+
if @monetized_attributes[name].present?
|
56
|
+
raise ArgumentError, "#{self} already has a monetized attribute called '#{name}'"
|
61
57
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
#
|
67
|
-
# 1. Subunit field validation (e.g. cents should be > 100)
|
68
|
-
# 2. Money field validation (e.g. euros should be > 10)
|
69
|
-
#
|
70
|
-
# All the options which are available for Rails numericality
|
71
|
-
# validation, are also available for both types.
|
72
|
-
# E.g.
|
73
|
-
# monetize :price_in_a_range_cents, :allow_nil => true,
|
74
|
-
# :subunit_numericality => {
|
75
|
-
# :greater_than_or_equal_to => 0,
|
76
|
-
# :less_than_or_equal_to => 10000,
|
77
|
-
# },
|
78
|
-
# :numericality => {
|
79
|
-
# :greater_than_or_equal_to => 0,
|
80
|
-
# :less_than_or_equal_to => 100,
|
81
|
-
# :message => "Must be greater than zero and less than $100"
|
82
|
-
# }
|
83
|
-
#
|
84
|
-
# To disable validation entirely, use :disable_validation, E.g:
|
85
|
-
# monetize :price_in_a_range_cents, :disable_validation => true
|
86
|
-
if validation_enabled = MoneyRails.include_validations && !options[:disable_validation]
|
87
|
-
|
88
|
-
subunit_validation_options =
|
89
|
-
unless options.has_key? :subunit_numericality
|
90
|
-
true
|
91
|
-
else
|
92
|
-
options[:subunit_numericality]
|
58
|
+
@monetized_attributes[name] = subunit_name
|
59
|
+
class << self
|
60
|
+
def monetized_attributes
|
61
|
+
@monetized_attributes || superclass.monetized_attributes
|
93
62
|
end
|
63
|
+
end unless respond_to? :monetized_attributes
|
64
|
+
|
65
|
+
# Include numericality validations if needed.
|
66
|
+
# There are two validation options:
|
67
|
+
#
|
68
|
+
# 1. Subunit field validation (e.g. cents should be > 100)
|
69
|
+
# 2. Money field validation (e.g. euros should be > 10)
|
70
|
+
#
|
71
|
+
# All the options which are available for Rails numericality
|
72
|
+
# validation, are also available for both types.
|
73
|
+
# E.g.
|
74
|
+
# monetize :price_in_a_range_cents, :allow_nil => true,
|
75
|
+
# :subunit_numericality => {
|
76
|
+
# :greater_than_or_equal_to => 0,
|
77
|
+
# :less_than_or_equal_to => 10000,
|
78
|
+
# },
|
79
|
+
# :numericality => {
|
80
|
+
# :greater_than_or_equal_to => 0,
|
81
|
+
# :less_than_or_equal_to => 100,
|
82
|
+
# :message => "Must be greater than zero and less than $100"
|
83
|
+
# }
|
84
|
+
#
|
85
|
+
# To disable validation entirely, use :disable_validation, E.g:
|
86
|
+
# monetize :price_in_a_range_cents, :disable_validation => true
|
87
|
+
if validation_enabled = MoneyRails.include_validations && !options[:disable_validation]
|
88
|
+
|
89
|
+
subunit_validation_options =
|
90
|
+
unless options.has_key? :subunit_numericality
|
91
|
+
true
|
92
|
+
else
|
93
|
+
options[:subunit_numericality]
|
94
|
+
end
|
94
95
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
96
|
+
money_validation_options =
|
97
|
+
unless options.has_key? :numericality
|
98
|
+
true
|
99
|
+
else
|
100
|
+
options[:numericality]
|
101
|
+
end
|
101
102
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
103
|
+
# This is a validation for the subunit
|
104
|
+
validates subunit_name, {
|
105
|
+
:allow_nil => options[:allow_nil],
|
106
|
+
:numericality => subunit_validation_options
|
107
|
+
}
|
108
|
+
|
109
|
+
# Allow only Money objects or Numeric values!
|
110
|
+
validates name.to_sym, {
|
111
|
+
:allow_nil => options[:allow_nil],
|
112
|
+
'money_rails/active_model/money' => money_validation_options
|
113
|
+
}
|
114
|
+
end
|
114
115
|
|
115
116
|
|
116
|
-
|
117
|
+
define_method name do |*args|
|
117
118
|
|
118
|
-
|
119
|
-
|
119
|
+
# Get the cents
|
120
|
+
amount = public_send(subunit_name, *args)
|
120
121
|
|
121
|
-
|
122
|
-
|
122
|
+
# Get the currency object
|
123
|
+
attr_currency = public_send("currency_for_#{name}")
|
123
124
|
|
124
|
-
|
125
|
-
|
125
|
+
# Get the cached value
|
126
|
+
memoized = instance_variable_get("@#{name}")
|
126
127
|
|
127
|
-
|
128
|
-
|
129
|
-
|
128
|
+
# Dont create a new Money instance if the values haven't been changed.
|
129
|
+
return memoized if memoized && memoized.cents == amount &&
|
130
|
+
memoized.currency == attr_currency
|
130
131
|
|
131
|
-
|
132
|
-
|
132
|
+
# If amount is NOT nil (or empty string) load the amount in a Money
|
133
|
+
amount = Money.new(amount, attr_currency) unless amount.blank?
|
133
134
|
|
134
|
-
|
135
|
-
|
136
|
-
|
135
|
+
# Cache and return the value (it may be nil)
|
136
|
+
instance_variable_set "@#{name}", amount
|
137
|
+
end
|
137
138
|
|
138
|
-
|
139
|
+
define_method "#{name}=" do |value|
|
139
140
|
|
140
|
-
|
141
|
-
|
141
|
+
# Lets keep the before_type_cast value
|
142
|
+
instance_variable_set "@#{name}_money_before_type_cast", value
|
142
143
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
else
|
147
|
-
if value.is_a?(Money)
|
148
|
-
money = value
|
144
|
+
# Use nil or get a Money object
|
145
|
+
if options[:allow_nil] && value.blank?
|
146
|
+
money = nil
|
149
147
|
else
|
150
|
-
|
151
|
-
money = value
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
148
|
+
if value.is_a?(Money)
|
149
|
+
money = value
|
150
|
+
else
|
151
|
+
begin
|
152
|
+
money = value.to_money(public_send("currency_for_#{name}"))
|
153
|
+
rescue NoMethodError
|
154
|
+
return nil
|
155
|
+
rescue ArgumentError
|
156
|
+
raise if MoneyRails.raise_error_on_money_parsing
|
157
|
+
return nil
|
158
|
+
rescue Money::Currency::UnknownCurrency
|
159
|
+
raise if MoneyRails.raise_error_on_money_parsing
|
160
|
+
return nil
|
161
|
+
end
|
160
162
|
end
|
161
163
|
end
|
162
|
-
end
|
163
164
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
165
|
+
# Update cents
|
166
|
+
if !validation_enabled
|
167
|
+
# We haven't defined our own subunit writer, so we can invoke
|
168
|
+
# the regular writer, which works with store_accessors
|
169
|
+
public_send("#{subunit_name}=", money.try(:cents))
|
170
|
+
elsif self.class.respond_to?(:attribute_aliases) &&
|
171
|
+
self.class.attribute_aliases.key?(subunit_name)
|
172
|
+
# If the attribute is aliased, make sure we write to the original
|
173
|
+
# attribute name or an error will be raised.
|
174
|
+
# (Note: 'attribute_aliases' doesn't exist in Rails 3.x, so we
|
175
|
+
# can't tell if the attribute was aliased.)
|
176
|
+
original_name = self.class.attribute_aliases[subunit_name.to_s]
|
177
|
+
write_attribute(original_name, money.try(:cents))
|
178
|
+
else
|
179
|
+
write_attribute(subunit_name, money.try(:cents))
|
180
|
+
end
|
176
181
|
|
177
|
-
|
182
|
+
money_currency = money.try(:currency)
|
178
183
|
|
179
|
-
|
180
|
-
|
181
|
-
|
184
|
+
# Update currency iso value if there is an instance currency attribute
|
185
|
+
if instance_currency_name.present? &&
|
186
|
+
respond_to?("#{instance_currency_name}=")
|
182
187
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
+
public_send("#{instance_currency_name}=", money_currency.try(:iso_code))
|
189
|
+
else
|
190
|
+
current_currency = public_send("currency_for_#{name}")
|
191
|
+
if money_currency && current_currency != money_currency.id
|
192
|
+
raise "Can't change readonly currency '#{current_currency}' to '#{money_currency}' for field '#{name}'"
|
193
|
+
end
|
188
194
|
end
|
189
|
-
end
|
190
195
|
|
191
|
-
|
192
|
-
|
193
|
-
|
196
|
+
# Save and return the new Money object
|
197
|
+
instance_variable_set "@#{name}", money
|
198
|
+
end
|
194
199
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
200
|
+
if validation_enabled
|
201
|
+
# Ensure that the before_type_cast value is cleared when setting
|
202
|
+
# the subunit value directly
|
203
|
+
define_method "#{subunit_name}=" do |value|
|
204
|
+
instance_variable_set "@#{name}_money_before_type_cast", nil
|
205
|
+
write_attribute(subunit_name, value)
|
206
|
+
end
|
202
207
|
end
|
203
|
-
end
|
204
208
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
209
|
+
define_method "currency_for_#{name}" do
|
210
|
+
instance_currency_name_with_postfix = "#{name}#{MoneyRails::Configuration.currency_column[:postfix]}"
|
211
|
+
|
212
|
+
if instance_currency_name.present? &&
|
213
|
+
respond_to?(instance_currency_name) &&
|
214
|
+
Money::Currency.find(public_send(instance_currency_name))
|
215
|
+
|
216
|
+
Money::Currency.find(public_send(instance_currency_name))
|
217
|
+
elsif field_currency_name
|
218
|
+
Money::Currency.find(field_currency_name)
|
219
|
+
elsif respond_to?(instance_currency_name_with_postfix) &&
|
220
|
+
Money::Currency.find(public_send(instance_currency_name_with_postfix))
|
221
|
+
|
222
|
+
Money::Currency.find(public_send(instance_currency_name_with_postfix))
|
223
|
+
elsif self.class.respond_to?(:currency)
|
224
|
+
self.class.currency
|
225
|
+
else
|
226
|
+
Money.default_currency
|
227
|
+
end
|
218
228
|
end
|
219
|
-
end
|
220
229
|
|
221
|
-
|
222
|
-
instance_variable_get "@#{name}_money_before_type_cast"
|
223
|
-
end
|
230
|
+
attr_reader "#{name}_money_before_type_cast"
|
224
231
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
232
|
+
# Hook to ensure the reset of before_type_cast attr
|
233
|
+
# TODO: think of a better way to avoid this
|
234
|
+
after_save do
|
235
|
+
instance_variable_set "@#{name}_money_before_type_cast", nil
|
236
|
+
end
|
229
237
|
end
|
230
238
|
end
|
231
239
|
|
@@ -15,6 +15,7 @@ module MoneyRails
|
|
15
15
|
:no_cents_if_whole => MoneyRails::Configuration.no_cents_if_whole.nil? ? true : MoneyRails::Configuration.no_cents_if_whole,
|
16
16
|
:symbol => false
|
17
17
|
}.merge(options)
|
18
|
+
options.delete(:symbol) if options[:disambiguate]
|
18
19
|
|
19
20
|
if value.is_a?(Money)
|
20
21
|
value.format(options)
|
data/lib/money-rails/hooks.rb
CHANGED
@@ -6,8 +6,26 @@ module MoneyRails
|
|
6
6
|
require 'money-rails/active_model/validator'
|
7
7
|
require 'money-rails/active_record/monetizable'
|
8
8
|
::ActiveRecord::Base.send :include, MoneyRails::ActiveRecord::Monetizable
|
9
|
+
if ::Rails::VERSION::MAJOR >= 4
|
10
|
+
rails42 = case
|
11
|
+
when ::Rails::VERSION::MAJOR < 5 && ::Rails::VERSION::MINOR >= 2
|
12
|
+
true
|
13
|
+
when ::Rails::VERSION::MAJOR >= 5
|
14
|
+
true
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
18
|
+
postgresql_with_money = rails42 && ['activerecord-jdbcpostgresql-adapter', 'postgresql'].include?(::ActiveRecord::Base.connection.instance_values["config"][:adapter])
|
19
|
+
end
|
9
20
|
|
10
|
-
|
21
|
+
require "money-rails/active_record/migration_extensions/options_extractor"
|
22
|
+
%w{schema_statements table}.each do |file|
|
23
|
+
if postgresql_with_money
|
24
|
+
require "money-rails/active_record/migration_extensions/#{file}_pg_rails4"
|
25
|
+
else
|
26
|
+
require "money-rails/active_record/migration_extensions/#{file}"
|
27
|
+
end
|
28
|
+
end
|
11
29
|
::ActiveRecord::Migration.send :include, MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements
|
12
30
|
::ActiveRecord::ConnectionAdapters::TableDefinition.send :include, MoneyRails::ActiveRecord::MigrationExtensions::Table
|
13
31
|
::ActiveRecord::ConnectionAdapters::Table.send :include, MoneyRails::ActiveRecord::MigrationExtensions::Table
|
@@ -14,7 +14,11 @@ class Money
|
|
14
14
|
# this custom class from it.
|
15
15
|
def demongoize(object)
|
16
16
|
if object.is_a?(Hash)
|
17
|
-
|
17
|
+
if object.respond_to?(:deep_symbolize_keys)
|
18
|
+
object = object.deep_symbolize_keys
|
19
|
+
else
|
20
|
+
object = object.symbolize_keys
|
21
|
+
end
|
18
22
|
object.has_key?(:cents) ? ::Money.new(object[:cents], object[:currency_iso]) : nil
|
19
23
|
else
|
20
24
|
nil
|
@@ -27,7 +31,11 @@ class Money
|
|
27
31
|
case
|
28
32
|
when object.is_a?(Money) then object.mongoize
|
29
33
|
when object.is_a?(Hash) then
|
30
|
-
|
34
|
+
if object.respond_to?(:deep_symbolize_keys!)
|
35
|
+
object.deep_symbolize_keys!
|
36
|
+
elsif object.respond_to?(:symbolize_keys!)
|
37
|
+
object.symbolize_keys!
|
38
|
+
end
|
31
39
|
::Money.new(object[:cents], object[:currency_iso]).mongoize
|
32
40
|
when object.respond_to?(:to_money) then
|
33
41
|
begin
|
data/lib/money-rails/version.rb
CHANGED
data/money-rails.gemspec
CHANGED
@@ -33,5 +33,5 @@ Gem::Specification.new do |s|
|
|
33
33
|
|
34
34
|
s.add_development_dependency "rails", ">= 3.0"
|
35
35
|
s.add_development_dependency "rspec-rails", "~> 3.0"
|
36
|
-
s.add_development_dependency 'database_cleaner', ['>= 0.8.0']
|
36
|
+
s.add_development_dependency 'database_cleaner', ['>= 0.8.0', '< 1.4.0']
|
37
37
|
end
|
@@ -8,7 +8,9 @@ if defined? ActiveRecord
|
|
8
8
|
let(:product) do
|
9
9
|
Product.create(:price_cents => 3000, :discount => 150,
|
10
10
|
:bonus_cents => 200, :optional_price => 100,
|
11
|
-
:sale_price_amount => 1200
|
11
|
+
:sale_price_amount => 1200, :delivery_fee_cents => 100,
|
12
|
+
:restock_fee_cents => 2000,
|
13
|
+
:reduced_price_cents => 1500, :reduced_price_currency => :lvl)
|
12
14
|
end
|
13
15
|
|
14
16
|
let(:service) do
|
@@ -25,6 +27,11 @@ if defined? ActiveRecord
|
|
25
27
|
expect(product.bonus).to be_an_instance_of(Money)
|
26
28
|
end
|
27
29
|
|
30
|
+
it "attaches Money objects to multiple model fields" do
|
31
|
+
expect(product.delivery_fee).to be_an_instance_of(Money)
|
32
|
+
expect(product.restock_fee).to be_an_instance_of(Money)
|
33
|
+
end
|
34
|
+
|
28
35
|
it "returns the expected money amount as a Money object" do
|
29
36
|
expect(product.price).to eq(Money.new(3000, "USD"))
|
30
37
|
end
|
@@ -75,7 +82,7 @@ if defined? ActiveRecord
|
|
75
82
|
end
|
76
83
|
|
77
84
|
it "skips numericality validation when disabled" do
|
78
|
-
product.
|
85
|
+
product.accessor_price_cents = 'not_valid'
|
79
86
|
expect(product.save).to be_truthy
|
80
87
|
end
|
81
88
|
|
@@ -91,13 +98,13 @@ if defined? ActiveRecord
|
|
91
98
|
after { MoneyRails.raise_error_on_money_parsing = false }
|
92
99
|
|
93
100
|
it "raises exception when a String value with hyphen is assigned" do
|
94
|
-
expect { product.
|
101
|
+
expect { product.accessor_price = "10-235" }.to raise_error
|
95
102
|
end
|
96
103
|
end
|
97
104
|
|
98
105
|
context "when MoneyRails.raise_error_on_money_parsing is false (default)" do
|
99
106
|
it "does not raise exception when a String value with hyphen is assigned" do
|
100
|
-
expect { product.
|
107
|
+
expect { product.accessor_price = "10-235" }.not_to raise_error
|
101
108
|
end
|
102
109
|
end
|
103
110
|
|
@@ -193,6 +200,15 @@ if defined? ActiveRecord
|
|
193
200
|
expect(product.errors[:price_in_a_range].first).to match(/Must be greater than zero and less than \$100/)
|
194
201
|
end
|
195
202
|
|
203
|
+
it "fails validation if linked attribute changed" do
|
204
|
+
product = Product.create(:price => Money.new(3210, "USD"), :discount => 150,
|
205
|
+
:validates_method_amount => 100,
|
206
|
+
:bonus_cents => 200, :optional_price => 100)
|
207
|
+
expect(product.valid?).to be_truthy
|
208
|
+
product.optional_price = 50
|
209
|
+
expect(product.valid?).to be_falsey
|
210
|
+
end
|
211
|
+
|
196
212
|
it "fails validation with the proper error message using validates :money" do
|
197
213
|
product.validates_method_amount = "-12"
|
198
214
|
expect(product.valid?).to be_falsey
|
@@ -297,20 +313,6 @@ if defined? ActiveRecord
|
|
297
313
|
I18n.locale = old_locale
|
298
314
|
end
|
299
315
|
|
300
|
-
it "defaults to Money::Currency format when no I18n information is present" do
|
301
|
-
old_locale = I18n.locale
|
302
|
-
|
303
|
-
I18n.locale = "zxsw"
|
304
|
-
Money.default_currency = Money::Currency.find('EUR')
|
305
|
-
expect("12,00".to_money).to eq(Money.new(1200, :eur))
|
306
|
-
transaction = Transaction.new(amount: "12,00", tax: "13,00")
|
307
|
-
expect(transaction.amount_cents).to eq(1200)
|
308
|
-
expect(transaction.valid?).to be_truthy
|
309
|
-
|
310
|
-
# reset locale setting
|
311
|
-
I18n.locale = old_locale
|
312
|
-
end
|
313
|
-
|
314
316
|
it "doesn't allow nil by default" do
|
315
317
|
product.price_cents = nil
|
316
318
|
expect(product.save).to be_falsey
|
@@ -364,6 +366,10 @@ if defined? ActiveRecord
|
|
364
366
|
expect(product.bonus.currency).to eq(Money::Currency.find(:gbp))
|
365
367
|
end
|
366
368
|
|
369
|
+
it "uses currency postfix to determine attribute that stores currency" do
|
370
|
+
expect(product.reduced_price.currency).to eq(Money::Currency.find(:lvl))
|
371
|
+
end
|
372
|
+
|
367
373
|
it "correctly assigns Money objects to the attribute" do
|
368
374
|
product.price = Money.new(2500, :USD)
|
369
375
|
expect(product.save).to be_truthy
|
@@ -395,6 +401,13 @@ if defined? ActiveRecord
|
|
395
401
|
expect(service.discount.currency_as_string).to eq("EUR")
|
396
402
|
end
|
397
403
|
|
404
|
+
it "correctly assigns objects to a accessor attribute" do
|
405
|
+
product.accessor_price = 1.23
|
406
|
+
expect(product.save).to be_truthy
|
407
|
+
expect(product.accessor_price.cents).to eq(123)
|
408
|
+
expect(product.accessor_price_cents).to eq(123)
|
409
|
+
end
|
410
|
+
|
398
411
|
it "overrides default, model currency with the value of :with_currency in fixnum assignments" do
|
399
412
|
product.bonus = 25
|
400
413
|
expect(product.save).to be_truthy
|
@@ -467,7 +480,6 @@ if defined? ActiveRecord
|
|
467
480
|
expect(product.optional_price).to be_nil
|
468
481
|
end
|
469
482
|
|
470
|
-
|
471
483
|
context "when the monetized field is an aliased attribute" do
|
472
484
|
it "writes the subunits to the original (unaliased) column" do
|
473
485
|
pending if Rails::VERSION::MAJOR < 4
|
@@ -5,6 +5,9 @@ class Product < ActiveRecord::Base
|
|
5
5
|
# Use money-rails macros
|
6
6
|
monetize :price_cents
|
7
7
|
|
8
|
+
# Use money-rails macro with multiple fields
|
9
|
+
monetize :delivery_fee_cents, :restock_fee_cents, :allow_nil => true
|
10
|
+
|
8
11
|
# Use a custom name for the Money attribute
|
9
12
|
monetize :discount, :as => "discount_value"
|
10
13
|
|
@@ -29,18 +32,21 @@ class Product < ActiveRecord::Base
|
|
29
32
|
:message => "Must be greater than zero and less than $100"
|
30
33
|
}
|
31
34
|
|
32
|
-
attr_accessor :
|
33
|
-
monetize :
|
35
|
+
attr_accessor :accessor_price_cents
|
36
|
+
monetize :accessor_price_cents, disable_validation: true
|
34
37
|
|
35
38
|
monetize :validates_method_amount_cents, allow_nil: true
|
36
39
|
|
37
40
|
validates :validates_method_amount, :money => {
|
38
41
|
:greater_than => 0,
|
39
|
-
:less_than_or_equal_to =>
|
42
|
+
:less_than_or_equal_to => ->(product) { product.optional_price.to_f },
|
40
43
|
:message => 'Must be greater than zero and less than $100',
|
41
|
-
}
|
44
|
+
}, allow_nil: true
|
42
45
|
|
43
46
|
alias_attribute :renamed_cents, :aliased_cents
|
44
47
|
|
45
48
|
monetize :renamed_cents, allow_nil: true
|
49
|
+
|
50
|
+
# Using postfix to determine currency column (reduced_price_currency)
|
51
|
+
monetize :reduced_price_cents, :allow_nil => true
|
46
52
|
end
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
#
|
12
12
|
# It's strongly recommended that you check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(version:
|
14
|
+
ActiveRecord::Schema.define(version: 20150126231442) do
|
15
15
|
|
16
16
|
create_table "dummy_products", force: true do |t|
|
17
17
|
t.string "currency"
|
@@ -32,6 +32,10 @@ ActiveRecord::Schema.define(version: 20141005075025) do
|
|
32
32
|
t.integer "price_in_a_range_cents"
|
33
33
|
t.integer "validates_method_amount_cents"
|
34
34
|
t.integer "aliased_cents"
|
35
|
+
t.integer "delivery_fee_cents"
|
36
|
+
t.integer "restock_fee_cents"
|
37
|
+
t.integer "reduced_price_cents"
|
38
|
+
t.string "reduced_price_currency"
|
35
39
|
end
|
36
40
|
|
37
41
|
create_table "services", force: true do |t|
|
@@ -8,8 +8,9 @@ describe 'MoneyRails::ActionViewExtension', :type => :helper do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
describe '#humanized_money' do
|
11
|
+
let(:money_object){ Money.new(12500) }
|
11
12
|
let(:options) { {} }
|
12
|
-
subject { helper.humanized_money
|
13
|
+
subject { helper.humanized_money money_object, options }
|
13
14
|
it { is_expected.to be_a String }
|
14
15
|
it { is_expected.not_to include Money.default_currency.symbol }
|
15
16
|
it { is_expected.not_to include Money.default_currency.decimal_mark }
|
@@ -26,6 +27,20 @@ describe 'MoneyRails::ActionViewExtension', :type => :helper do
|
|
26
27
|
end
|
27
28
|
it { is_expected.to include Money.default_currency.symbol }
|
28
29
|
end
|
30
|
+
|
31
|
+
context 'with a currency with an alternate symbol' do
|
32
|
+
let(:money_object) { Money.new(125_00, 'SGD') }
|
33
|
+
|
34
|
+
context 'with symbol options' do
|
35
|
+
let(:options) { { :symbol => true } }
|
36
|
+
it { is_expected.to include Money::Currency.new(:sgd).symbol }
|
37
|
+
|
38
|
+
context 'with disambiguate options' do
|
39
|
+
let(:options) { { :symbol => true, :disambiguate => true } }
|
40
|
+
it { is_expected.to include Money::Currency.new(:sgd).disambiguate_symbol }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
29
44
|
end
|
30
45
|
|
31
46
|
describe '#humanized_money_with_symbol' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: money-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Loupasakis
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2015-01-29 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: money
|
@@ -103,6 +103,9 @@ dependencies:
|
|
103
103
|
- - ">="
|
104
104
|
- !ruby/object:Gem::Version
|
105
105
|
version: 0.8.0
|
106
|
+
- - "<"
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: 1.4.0
|
106
109
|
type: :development
|
107
110
|
prerelease: false
|
108
111
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -110,6 +113,9 @@ dependencies:
|
|
110
113
|
- - ">="
|
111
114
|
- !ruby/object:Gem::Version
|
112
115
|
version: 0.8.0
|
116
|
+
- - "<"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 1.4.0
|
113
119
|
description: This library provides integration of RubyMoney - Money gem with Rails
|
114
120
|
email:
|
115
121
|
- alup.rubymoney@gmail.com
|
@@ -128,7 +134,9 @@ files:
|
|
128
134
|
- lib/money-rails/active_model/validator.rb
|
129
135
|
- lib/money-rails/active_record/migration_extensions/options_extractor.rb
|
130
136
|
- lib/money-rails/active_record/migration_extensions/schema_statements.rb
|
137
|
+
- lib/money-rails/active_record/migration_extensions/schema_statements_pg_rails4.rb
|
131
138
|
- lib/money-rails/active_record/migration_extensions/table.rb
|
139
|
+
- lib/money-rails/active_record/migration_extensions/table_pg_rails4.rb
|
132
140
|
- lib/money-rails/active_record/monetizable.rb
|
133
141
|
- lib/money-rails/configuration.rb
|
134
142
|
- lib/money-rails/engine.rb
|
@@ -187,6 +195,8 @@ files:
|
|
187
195
|
- spec/dummy/db/migrate/20130124023419_add_price_in_a_range_cents_to_products.rb
|
188
196
|
- spec/dummy/db/migrate/20140110194016_add_validates_method_amount_cents_to_products.rb
|
189
197
|
- spec/dummy/db/migrate/20141005075025_add_aliased_attr_to_products.rb
|
198
|
+
- spec/dummy/db/migrate/20150107061030_add_delivery_fee_cents_and_restock_fee_cents_to_product.rb
|
199
|
+
- spec/dummy/db/migrate/20150126231442_add_reduced_price_to_products.rb
|
190
200
|
- spec/dummy/db/schema.rb
|
191
201
|
- spec/dummy/db/structure.sql
|
192
202
|
- spec/dummy/public/404.html
|
@@ -273,6 +283,8 @@ test_files:
|
|
273
283
|
- spec/dummy/db/migrate/20130124023419_add_price_in_a_range_cents_to_products.rb
|
274
284
|
- spec/dummy/db/migrate/20140110194016_add_validates_method_amount_cents_to_products.rb
|
275
285
|
- spec/dummy/db/migrate/20141005075025_add_aliased_attr_to_products.rb
|
286
|
+
- spec/dummy/db/migrate/20150107061030_add_delivery_fee_cents_and_restock_fee_cents_to_product.rb
|
287
|
+
- spec/dummy/db/migrate/20150126231442_add_reduced_price_to_products.rb
|
276
288
|
- spec/dummy/db/schema.rb
|
277
289
|
- spec/dummy/db/structure.sql
|
278
290
|
- spec/dummy/public/404.html
|