money-rails 1.6.2 → 1.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cadb127fbc12e2e400269dc9b0996c02f55c8cd6
4
- data.tar.gz: c5e3dcc6f9d53d512927a91888b78ba996a0553e
3
+ metadata.gz: 248e9756e9e93e82ed71a92426635e8864412a66
4
+ data.tar.gz: b985a0e0926babc02c77bd3ece23927c261ccaab
5
5
  SHA512:
6
- metadata.gz: 9c57f0377d92586e1cb7935cf780fadb16c204230dbc0b2a449bed4d94839e8d0013681aeb03530412cf0da5fc433900711a19378bb0caedfb20b58364a07b90
7
- data.tar.gz: 43cf45749ad6a096ad3d83feb3004cc8d85546f28f0a8846b7eb98ba53313e75d401d0726c0811fd6a3b78798def69f2afdd77ff89b765843e335894159f2ff0
6
+ metadata.gz: ea054a49148d15a90b373c61df52d73e2598cd67d06e9ffc79dea67e515e6038a365d45f924b09133b90c804311cd9050432e39add9259354823f1f6e0950171
7
+ data.tar.gz: b53220b8d3e692f983f556a264a9045b5b09d5361a578fa12ce500df96853d58f71e01b927250528b7447a57b5d60be60fbc0d896eec4b3fc440efb2d839aee4
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.0
4
+
5
+ - Rails 5 support
6
+ - Mongoid 5 support
7
+ - Do not convert Mongoid money fields from nil to zero
8
+ - Refactor `#monetize` method
9
+
3
10
  ## 1.6.2
4
11
 
5
12
  - Fix attribute order possibly affecting the value of monetized attribute
data/Rakefile CHANGED
@@ -28,17 +28,29 @@ task :spec => :prepare_test_env
28
28
  desc "Prepare money-rails engine test environment"
29
29
  task :prepare_test_env do
30
30
  Rake.application['app:db:drop:all'].invoke
31
+ Rake.application['app:db:create'].invoke if Rails::VERSION::MAJOR >= 5
31
32
  Rake.application['app:db:migrate'].invoke
32
33
  Rake.application['app:db:test:prepare'].invoke
33
34
  end
34
35
 
35
36
  def run_with_gemfile(gemfile)
36
- sh "BUNDLE_GEMFILE='#{gemfile}' bundle install --quiet"
37
- sh "BUNDLE_GEMFILE='#{gemfile}' bundle exec rake -t spec"
37
+ Bundler.with_clean_env do
38
+ begin
39
+ sh "BUNDLE_GEMFILE='#{gemfile}' bundle install --quiet"
40
+ Rake.application['app:db:create'].invoke
41
+ Rake.application['app:db:test:prepare'].invoke
42
+ sh "BUNDLE_GEMFILE='#{gemfile}' bundle exec rake spec"
43
+ ensure
44
+ Rake.application['app:db:drop:all'].execute
45
+ end
46
+ end
38
47
  end
39
48
 
40
49
  namespace :spec do
41
50
 
51
+ desc "Run Tests against mongoid (version 5)"
52
+ task(:mongoid5) { run_with_gemfile 'gemfiles/mongoid5.gemfile' }
53
+
42
54
  desc "Run Tests against mongoid (version 4)"
43
55
  task(:mongoid4) { run_with_gemfile 'gemfiles/mongoid4.gemfile' }
44
56
 
@@ -48,6 +60,9 @@ namespace :spec do
48
60
  desc "Run Tests against mongoid (version 2)"
49
61
  task(:mongoid2) { run_with_gemfile 'gemfiles/mongoid2.gemfile' }
50
62
 
63
+ desc "Run Tests against rails 5.0"
64
+ task(:rails50) { run_with_gemfile 'gemfiles/rails50.gemfile' }
65
+
51
66
  desc "Run Tests against rails 4.2"
52
67
  task(:rails42) { run_with_gemfile 'gemfiles/rails42.gemfile' }
53
68
 
@@ -60,11 +75,11 @@ namespace :spec do
60
75
  desc "Run Tests against rails 3"
61
76
  task(:rails3) { run_with_gemfile 'gemfiles/rails3.gemfile' }
62
77
 
63
- desc "Run Tests against mongoid 2 & 3 & 4"
64
- task :mongoid => [:mongoid2, :mongoid3, :mongoid4]
78
+ desc "Run Tests against mongoid 2 & 3 & 4, 5"
79
+ task :mongoid => [:mongoid2, :mongoid3, :mongoid4, :mongoid5]
65
80
 
66
- desc "Run Tests against rails 3 & 4 & 4.1 & 4.2"
67
- task :rails => [:rails3, :rails4, :rails41, :rails42]
81
+ desc "Run Tests against rails 3 & 4 & 4.1 & 4.2 & 5.0"
82
+ task :rails => [:rails3, :rails4, :rails41, :rails42, :rails50]
68
83
 
69
84
  desc "Run Tests against all ORMs"
70
85
  task :all => [:rails, :mongoid]
@@ -116,104 +116,15 @@ module MoneyRails
116
116
  end
117
117
  end
118
118
 
119
- define_method name do |*args|
120
-
121
- # Get the cents
122
- amount = public_send(subunit_name, *args)
123
-
124
- # Get the currency object
125
- attr_currency = public_send("currency_for_#{name}")
126
-
127
- # Get the cached value
128
- memoized = instance_variable_get("@#{name}")
129
-
130
- # Dont create a new Money instance if the values haven't been changed.
131
- if memoized && memoized.cents == amount
132
- if memoized.currency == attr_currency
133
- result = memoized
134
- else
135
- memoized_amount = memoized.amount.to_money(attr_currency)
136
- write_attribute subunit_name, memoized_amount.cents
137
- # Cache the value (it may be nil)
138
- result = instance_variable_set "@#{name}", memoized_amount
139
- end
140
- elsif amount.present?
141
- # If amount is NOT nil (or empty string) load the amount in a Money
142
- amount = Money.new(amount, attr_currency)
143
-
144
- # Cache the value (it may be nil)
145
- result = instance_variable_set "@#{name}", amount
146
- end
147
119
 
148
- if MoneyRails::Configuration.preserve_user_input
149
- value_before_type_cast = instance_variable_get "@#{name}_money_before_type_cast"
150
- unless errors[name.to_sym].blank?
151
- result.define_singleton_method(:to_s) { value_before_type_cast }
152
- result.define_singleton_method(:format) { |_| value_before_type_cast }
153
- end
154
- end
155
-
156
- result
120
+ # Getter for monetized attribute
121
+ define_method name do |*args|
122
+ read_monetized name, subunit_name, *args
157
123
  end
158
124
 
125
+ # Setter for monetized attribute
159
126
  define_method "#{name}=" do |value|
160
-
161
- # Lets keep the before_type_cast value
162
- instance_variable_set "@#{name}_money_before_type_cast", value
163
-
164
- # Use nil or get a Money object
165
- if options[:allow_nil] && value.blank?
166
- money = nil
167
- else
168
- if value.is_a?(Money)
169
- money = value
170
- else
171
- begin
172
- money = value.to_money(public_send("currency_for_#{name}"))
173
- rescue NoMethodError
174
- return nil
175
- rescue ArgumentError
176
- raise if MoneyRails.raise_error_on_money_parsing
177
- return nil
178
- rescue Money::Currency::UnknownCurrency
179
- raise if MoneyRails.raise_error_on_money_parsing
180
- return nil
181
- end
182
- end
183
- end
184
-
185
- # Update cents
186
- if !validation_enabled
187
- # We haven't defined our own subunit writer, so we can invoke
188
- # the regular writer, which works with store_accessors
189
- public_send("#{subunit_name}=", money.try(:cents))
190
- elsif self.class.respond_to?(:attribute_aliases) &&
191
- self.class.attribute_aliases.key?(subunit_name)
192
- # If the attribute is aliased, make sure we write to the original
193
- # attribute name or an error will be raised.
194
- # (Note: 'attribute_aliases' doesn't exist in Rails 3.x, so we
195
- # can't tell if the attribute was aliased.)
196
- original_name = self.class.attribute_aliases[subunit_name.to_s]
197
- write_attribute(original_name, money.try(:cents))
198
- else
199
- write_attribute(subunit_name, money.try(:cents))
200
- end
201
-
202
- money_currency = money.try(:currency)
203
-
204
- # Update currency iso value if there is an instance currency attribute
205
- if instance_currency_name.present? && respond_to?("#{instance_currency_name}=") && money_currency
206
- public_send("#{instance_currency_name}=", money_currency.try(:iso_code))
207
- else
208
- current_currency = public_send("currency_for_#{name}")
209
- if money_currency && current_currency != money_currency.id
210
- raise ReadOnlyCurrencyException.new("Can't change readonly currency '#{current_currency}' to '#{money_currency}' for field '#{name}'") if MoneyRails.raise_error_on_money_parsing
211
- return nil
212
- end
213
- end
214
-
215
- # Save and return the new Money object
216
- instance_variable_set "@#{name}", money
127
+ write_monetized name, subunit_name, value, validation_enabled, instance_currency_name, options
217
128
  end
218
129
 
219
130
  if validation_enabled
@@ -225,29 +136,9 @@ module MoneyRails
225
136
  end
226
137
  end
227
138
 
139
+ # Currency getter
228
140
  define_method "currency_for_#{name}" do
229
- if MoneyRails::Configuration.currency_column[:postfix].present?
230
- instance_currency_name_with_postfix = "#{name}#{MoneyRails::Configuration.currency_column[:postfix]}"
231
- end
232
-
233
- if instance_currency_name.present? && respond_to?(instance_currency_name) &&
234
- Money::Currency.find(public_send(instance_currency_name))
235
-
236
- Money::Currency.find(public_send(instance_currency_name))
237
- elsif field_currency_name.respond_to?(:call)
238
- Money::Currency.find(field_currency_name.call(self))
239
- elsif field_currency_name
240
- Money::Currency.find(field_currency_name)
241
- elsif instance_currency_name_with_postfix.present? &&
242
- respond_to?(instance_currency_name_with_postfix) &&
243
- Money::Currency.find(public_send(instance_currency_name_with_postfix))
244
-
245
- Money::Currency.find(public_send(instance_currency_name_with_postfix))
246
- elsif self.class.respond_to?(:currency)
247
- self.class.currency
248
- else
249
- Money.default_currency
250
- end
141
+ currency_for name, instance_currency_name, field_currency_name
251
142
  end
252
143
 
253
144
  attr_reader "#{name}_money_before_type_cast"
@@ -286,6 +177,120 @@ module MoneyRails
286
177
  @monetized_attributes[name] = value
287
178
  end
288
179
  end
180
+
181
+ def read_monetized(name, subunit_name, *args)
182
+ # Get the cents
183
+ amount = public_send(subunit_name, *args)
184
+
185
+ # Get the currency object
186
+ attr_currency = public_send("currency_for_#{name}")
187
+
188
+ # Get the cached value
189
+ memoized = instance_variable_get("@#{name}")
190
+
191
+ # Dont create a new Money instance if the values haven't been changed.
192
+ if memoized && memoized.cents == amount
193
+ if memoized.currency == attr_currency
194
+ result = memoized
195
+ else
196
+ memoized_amount = memoized.amount.to_money(attr_currency)
197
+ write_attribute subunit_name, memoized_amount.cents
198
+ # Cache the value (it may be nil)
199
+ result = instance_variable_set("@#{name}", memoized_amount)
200
+ end
201
+ elsif amount.present?
202
+ # If amount is NOT nil (or empty string) load the amount in a Money
203
+ amount = Money.new(amount, attr_currency)
204
+
205
+ # Cache the value (it may be nil)
206
+ result = instance_variable_set("@#{name}", amount)
207
+ end
208
+
209
+ if MoneyRails::Configuration.preserve_user_input
210
+ value_before_type_cast = instance_variable_get "@#{name}_money_before_type_cast"
211
+ if errors[name.to_sym].present?
212
+ result.define_singleton_method(:to_s) { value_before_type_cast }
213
+ result.define_singleton_method(:format) { |_| value_before_type_cast }
214
+ end
215
+ end
216
+
217
+ result
218
+ end
219
+
220
+ def write_monetized(name, subunit_name, value, validation_enabled, instance_currency_name, options)
221
+ # Keep before_type_cast value as a reference to original input
222
+ instance_variable_set "@#{name}_money_before_type_cast", value
223
+
224
+ # Use nil or get a Money object
225
+ if options[:allow_nil] && value.blank?
226
+ money = nil
227
+ else
228
+ if value.is_a?(Money)
229
+ money = value
230
+ else
231
+ begin
232
+ money = value.to_money(public_send("currency_for_#{name}"))
233
+ rescue NoMethodError
234
+ return nil
235
+ rescue ArgumentError
236
+ raise if MoneyRails.raise_error_on_money_parsing
237
+ return nil
238
+ rescue Money::Currency::UnknownCurrency
239
+ raise if MoneyRails.raise_error_on_money_parsing
240
+ return nil
241
+ end
242
+ end
243
+ end
244
+
245
+ # Update cents
246
+ if !validation_enabled
247
+ # We haven't defined our own subunit writer, so we can invoke
248
+ # the regular writer, which works with store_accessors
249
+ public_send("#{subunit_name}=", money.try(:cents))
250
+ elsif self.class.respond_to?(:attribute_aliases) &&
251
+ self.class.attribute_aliases.key?(subunit_name)
252
+ # If the attribute is aliased, make sure we write to the original
253
+ # attribute name or an error will be raised.
254
+ # (Note: 'attribute_aliases' doesn't exist in Rails 3.x, so we
255
+ # can't tell if the attribute was aliased.)
256
+ original_name = self.class.attribute_aliases[subunit_name.to_s]
257
+ write_attribute(original_name, money.try(:cents))
258
+ else
259
+ write_attribute(subunit_name, money.try(:cents))
260
+ end
261
+
262
+ if money_currency = money.try(:currency)
263
+ # Update currency iso value if there is an instance currency attribute
264
+ if instance_currency_name.present? && respond_to?("#{instance_currency_name}=")
265
+ public_send("#{instance_currency_name}=", money_currency.iso_code)
266
+ else
267
+ current_currency = public_send("currency_for_#{name}")
268
+ if current_currency != money_currency.id
269
+ raise ReadOnlyCurrencyException.new("Can't change readonly currency '#{current_currency}' to '#{money_currency}' for field '#{name}'") if MoneyRails.raise_error_on_money_parsing
270
+ return nil
271
+ end
272
+ end
273
+ end
274
+
275
+ # Save and return the new Money object
276
+ instance_variable_set "@#{name}", money
277
+ end
278
+
279
+ def currency_for(name, instance_currency_name, field_currency_name)
280
+ if instance_currency_name.present? && respond_to?(instance_currency_name) &&
281
+ Money::Currency.find(public_send(instance_currency_name))
282
+
283
+ Money::Currency.find(public_send(instance_currency_name))
284
+ elsif field_currency_name.respond_to?(:call)
285
+ Money::Currency.find(field_currency_name.call(self))
286
+ elsif field_currency_name
287
+ Money::Currency.find(field_currency_name)
288
+ elsif self.class.respond_to?(:currency)
289
+ self.class.currency
290
+ else
291
+ Money.default_currency
292
+ end
293
+ end
289
294
  end
290
295
  end
291
296
  end
@@ -37,6 +37,7 @@ class Money
37
37
  object.symbolize_keys!
38
38
  end
39
39
  ::Money.new(object[:cents], object[:currency_iso]).mongoize
40
+ when object.nil? then nil
40
41
  when object.respond_to?(:to_money) then
41
42
  begin
42
43
  object.to_money.mongoize
@@ -21,6 +21,7 @@ class Money
21
21
  :cents => object.cents.is_a?(BigDecimal) ? object.cents.to_s : object.cents,
22
22
  :currency_iso => object.currency.iso_code
23
23
  }
24
+ when object.nil? then nil
24
25
  when object.respond_to?(:to_money)
25
26
  begin
26
27
  serialize(object.to_money)
@@ -1,3 +1,3 @@
1
1
  module MoneyRails
2
- VERSION = '1.6.2'
2
+ VERSION = '1.7.0'
3
3
  end
@@ -6,21 +6,21 @@ class Sub < Product; end
6
6
 
7
7
  if defined? ActiveRecord
8
8
  describe MoneyRails::ActiveRecord::Monetizable do
9
- describe "monetize" do
10
- let(:product) do
11
- Product.create(:price_cents => 3000, :discount => 150,
12
- :bonus_cents => 200, :optional_price => 100,
13
- :sale_price_amount => 1200, :delivery_fee_cents => 100,
14
- :restock_fee_cents => 2000,
15
- :reduced_price_cents => 1500, :reduced_price_currency => :lvl,
16
- :lambda_price_cents => 4000)
17
- end
9
+ let(:product) do
10
+ Product.create(:price_cents => 3000, :discount => 150,
11
+ :bonus_cents => 200, :optional_price => 100,
12
+ :sale_price_amount => 1200, :delivery_fee_cents => 100,
13
+ :restock_fee_cents => 2000,
14
+ :reduced_price_cents => 1500, :reduced_price_currency => :lvl,
15
+ :lambda_price_cents => 4000)
16
+ end
18
17
 
18
+ describe ".monetize" do
19
19
  let(:service) do
20
20
  Service.create(:charge_cents => 2000, :discount_cents => 120)
21
21
  end
22
22
 
23
- context 'monetized_attributes' do
23
+ context ".monetized_attributes" do
24
24
 
25
25
  class InheritedMonetizeProduct < Product
26
26
  monetize :special_price_cents
@@ -396,21 +396,6 @@ if defined? ActiveRecord
396
396
  expect(product.save).to be_truthy
397
397
  end
398
398
 
399
- describe "with preserve_user_input set" do
400
- around(:each) do |example|
401
- old_value = MoneyRails::Configuration.preserve_user_input
402
- MoneyRails::Configuration.preserve_user_input = true
403
- example.run
404
- MoneyRails::Configuration.preserve_user_input = false
405
- end
406
-
407
- it "preserves user input if validation fails" do
408
- product.price = "14,0"
409
- expect(product.save).to be_falsy
410
- expect(product.price.to_s).to eq("14,0")
411
- end
412
- end
413
-
414
399
  it "respects numericality validation when using update_attributes on money attribute" do
415
400
  expect(product.update_attributes(:price => "some text")).to be_falsey
416
401
  expect(product.update_attributes(:price => Money.new(320, 'USD'))).to be_truthy
@@ -798,11 +783,228 @@ if defined? ActiveRecord
798
783
  end
799
784
  end
800
785
 
801
- describe "register_currency" do
786
+ describe ".register_currency" do
802
787
  it "attaches currency at model level" do
803
788
  expect(Product.currency).to eq(Money::Currency.find(:usd))
804
789
  expect(DummyProduct.currency).to eq(Money::Currency.find(:gbp))
805
790
  end
806
791
  end
792
+
793
+ describe "#read_monetized" do
794
+ it "returns monetized attribute's value" do
795
+ reduced_price = product.read_monetized(:reduced_price, :reduced_price_cents)
796
+
797
+ expect(reduced_price).to be_an_instance_of(Money)
798
+ expect(reduced_price).to eq(Money.new(product.reduced_price_cents, product.reduced_price_currency))
799
+ end
800
+
801
+ context "memoize" do
802
+ it "memoizes monetized attribute's value" do
803
+ product.instance_variable_set '@reduced_price', nil
804
+ reduced_price = product.read_monetized(:reduced_price, :reduced_price_cents)
805
+
806
+ expect(product.instance_variable_get('@reduced_price')).to eq(reduced_price)
807
+ end
808
+
809
+ it "resets memoized attribute's value if amount has changed" do
810
+ reduced_price = product.read_monetized(:reduced_price, :reduced_price_cents)
811
+ product.reduced_price_cents = 100
812
+
813
+ expect(product.read_monetized(:reduced_price, :reduced_price_cents)).not_to eq(reduced_price)
814
+ end
815
+
816
+ it "resets memoized attribute's value if currency has changed" do
817
+ reduced_price = product.read_monetized(:reduced_price, :reduced_price_cents)
818
+ product.reduced_price_currency = 'CAD'
819
+
820
+ expect(product.read_monetized(:reduced_price, :reduced_price_cents)).not_to eq(reduced_price)
821
+ end
822
+ end
823
+
824
+ context "with preserve_user_input set" do
825
+ around(:each) do |example|
826
+ MoneyRails::Configuration.preserve_user_input = true
827
+ example.run
828
+ MoneyRails::Configuration.preserve_user_input = false
829
+ end
830
+
831
+ it "has no effect if validation passes" do
832
+ product.price = '14'
833
+
834
+ expect(product.save).to be_truthy
835
+ expect(product.read_monetized(:price, :price_cents).to_s).to eq('14.00')
836
+ end
837
+
838
+ it "preserves user input if validation fails" do
839
+ product.price = '14,0'
840
+
841
+ expect(product.save).to be_falsy
842
+ expect(product.read_monetized(:price, :price_cents).to_s).to eq('14,0')
843
+ end
844
+ end
845
+ end
846
+
847
+ describe "#write_monetized" do
848
+ let(:value) { Money.new(1_000, 'LVL') }
849
+
850
+ it "sets monetized attribute's value to Money object" do
851
+ product.write_monetized :price, :price_cents, value, false, nil, {}
852
+
853
+ expect(product.price).to be_an_instance_of(Money)
854
+ expect(product.price_cents).to eq(value.cents)
855
+ # Because :price does not have a column for currency
856
+ expect(product.price.currency).to eq(Product.currency)
857
+ end
858
+
859
+ it "sets monetized attribute's value from a given Fixnum" do
860
+ product.write_monetized :price, :price_cents, 10, false, nil, {}
861
+
862
+ expect(product.price).to be_an_instance_of(Money)
863
+ expect(product.price_cents).to eq(1000)
864
+ end
865
+
866
+ it "sets monetized attribute's value from a given Float" do
867
+ product.write_monetized :price, :price_cents, 10.5, false, nil, {}
868
+
869
+ expect(product.price).to be_an_instance_of(Money)
870
+ expect(product.price_cents).to eq(1050)
871
+ end
872
+
873
+ it "resets monetized attribute when given blank input" do
874
+ product.write_monetized :price, :price_cents, nil, false, nil, { :allow_nil => true }
875
+
876
+ expect(product.price).to eq(nil)
877
+ end
878
+
879
+ it "sets monetized attribute to 0 when given a blank value" do
880
+ currency = product.price.currency
881
+ product.write_monetized :price, :price_cents, nil, false, nil, {}
882
+
883
+ expect(product.price.amount).to eq(0)
884
+ expect(product.price.currency).to eq(currency)
885
+ end
886
+
887
+ it "does not memoize monetized attribute's value if currency is read-only" do
888
+ product.write_monetized :price, :price_cents, value, false, nil, {}
889
+
890
+ price = product.instance_variable_get('@price')
891
+
892
+ expect(price).to be_an_instance_of(Money)
893
+ expect(price.amount).not_to eq(value.amount)
894
+ end
895
+
896
+ describe "instance_currency_name" do
897
+ it "updates instance_currency_name attribute" do
898
+ product.write_monetized :sale_price, :sale_price_amount, value, false, :sale_price_currency_code, {}
899
+
900
+ expect(product.sale_price).to eq(value)
901
+ expect(product.sale_price_currency_code).to eq('LVL')
902
+ end
903
+
904
+ it "memoizes monetized attribute's value with currency" do
905
+ product.write_monetized :sale_price, :sale_price_amount, value, false, :sale_price_currency_code, {}
906
+
907
+ expect(product.instance_variable_get('@sale_price')).to eq(value)
908
+ end
909
+
910
+ it "ignores empty instance_currency_name" do
911
+ product.write_monetized :sale_price, :sale_price_amount, value, false, '', {}
912
+
913
+ expect(product.sale_price.amount).to eq(value.amount)
914
+ expect(product.sale_price.currency).to eq(Product.currency)
915
+ end
916
+
917
+ it "ignores instance_currency_name that model does not respond to" do
918
+ product.write_monetized :sale_price, :sale_price_amount, value, false, :non_existing_currency, {}
919
+
920
+ expect(product.sale_price.amount).to eq(value.amount)
921
+ expect(product.sale_price.currency).to eq(Product.currency)
922
+ end
923
+ end
924
+
925
+ describe "error handling" do
926
+ let!(:old_price_value) { product.price }
927
+
928
+ it "ignores values that do not implement to_money method" do
929
+ product.write_monetized :price, :price_cents, [10], false, nil, {}
930
+
931
+ expect(product.price).to eq(old_price_value)
932
+ end
933
+
934
+ context "raise_error_on_money_parsing enabled" do
935
+ before { MoneyRails.raise_error_on_money_parsing = true }
936
+ after { MoneyRails.raise_error_on_money_parsing = false }
937
+
938
+ it "raises an ArgumentError when given an invalid value" do
939
+ expect {
940
+ product.write_monetized :price, :price_cents, '10-50', false, nil, {}
941
+ }.to raise_error(ArgumentError)
942
+ end
943
+
944
+ it "raises a Money::Currency::UnknownCurrency error when trying to set invalid currency" do
945
+ allow(product).to receive(:currency_for_price).and_return('INVALID_CURRENCY')
946
+ expect {
947
+ product.write_monetized :price, :price_cents, 10, false, nil, {}
948
+ }.to raise_error(Money::Currency::UnknownCurrency)
949
+ end
950
+ end
951
+
952
+ context "raise_error_on_money_parsing disabled" do
953
+ it "ignores when given invalid value" do
954
+ product.write_monetized :price, :price_cents, '10-50', false, nil, {}
955
+
956
+ expect(product.price).to eq(old_price_value)
957
+ end
958
+
959
+ it "raises a Money::Currency::UnknownCurrency error when trying to set invalid currency" do
960
+ allow(product).to receive(:currency_for_price).and_return('INVALID_CURRENCY')
961
+ product.write_monetized :price, :price_cents, 10, false, nil, {}
962
+
963
+ # Can not use public accessor here because currency_for_price is stubbed
964
+ expect(product.instance_variable_get('@price')).to eq(old_price_value)
965
+ end
966
+ end
967
+ end
968
+ end
969
+
970
+ describe "#currency_for" do
971
+ it "detects currency based on instance currency name" do
972
+ product = Product.new(:sale_price_currency_code => 'CAD')
973
+ currency = product.send(:currency_for, :sale_price, :sale_price_currency_code, nil)
974
+
975
+ expect(currency).to be_an_instance_of(Money::Currency)
976
+ expect(currency.iso_code).to eq('CAD')
977
+ end
978
+
979
+ it "detects currency based on currency passed as a block" do
980
+ product = Product.new
981
+ currency = product.send(:currency_for, :lambda_price, nil, ->(_) { 'CAD' })
982
+
983
+ expect(currency).to be_an_instance_of(Money::Currency)
984
+ expect(currency.iso_code).to eq('CAD')
985
+ end
986
+
987
+ it "detects currency based on currency passed explicitly" do
988
+ product = Product.new
989
+ currency = product.send(:currency_for, :bonus, nil, 'CAD')
990
+
991
+ expect(currency).to be_an_instance_of(Money::Currency)
992
+ expect(currency.iso_code).to eq('CAD')
993
+ end
994
+
995
+ it "falls back to a registered currency" do
996
+ product = Product.new
997
+ currency = product.send(:currency_for, :amount, nil, nil)
998
+
999
+ expect(currency).to eq(Product.currency)
1000
+ end
1001
+
1002
+ it "falls back to a default currency" do
1003
+ transaction = Transaction.new
1004
+ currency = transaction.send(:currency_for, :amount, nil, nil)
1005
+
1006
+ expect(currency).to eq(Money.default_currency)
1007
+ end
1008
+ end
807
1009
  end
808
1010
  end
@@ -4,7 +4,7 @@ require File.expand_path('../boot', __FILE__)
4
4
  require "active_record/railtie"
5
5
  require "action_controller/railtie"
6
6
  require "action_mailer/railtie"
7
- unless Rails::VERSION::MAJOR == 4
7
+ unless Rails::VERSION::MAJOR >= 4
8
8
  require "active_resource/railtie"
9
9
  end
10
10
  require "sprockets/railtie"
@@ -21,3 +21,9 @@ test:
21
21
  database: dummy_test
22
22
  hosts:
23
23
  - localhost:27017
24
+
25
+ clients:
26
+ default:
27
+ database: dummy_test
28
+ hosts:
29
+ - localhost:27017
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^5(.*)/
4
+
5
+ describe Money do
6
+ let!(:priceable) { Priceable.create(:price => Money.new(100, 'EUR')) }
7
+ let!(:priceable_from_num) { Priceable.create(:price => 1) }
8
+ let!(:priceable_from_string) { Priceable.create(:price => '1 EUR' )}
9
+ let!(:priceable_from_hash) { Priceable.create(:price => {:cents=>100, :currency_iso=>"EUR"} )}
10
+ let!(:priceable_from_hash_with_indifferent_access) {
11
+ Priceable.create(:price => {:cents=>100, :currency_iso=>"EUR"}.with_indifferent_access)
12
+ }
13
+ let(:priceable_from_string_with_hyphen) { Priceable.create(:price => '1-2 EUR' )}
14
+ let(:priceable_from_string_with_unknown_currency) { Priceable.create(:price => '1 TLDR') }
15
+ let(:priceable_with_infinite_precision) { Priceable.create(:price => Money.new(BigDecimal.new('100.1'), 'EUR')) }
16
+ let(:priceable_with_hash_field) {
17
+ Priceable.create(:price_hash => {
18
+ :key1 => Money.new(100, "EUR"),
19
+ :key2 => Money.new(200, "USD")
20
+ })
21
+ }
22
+
23
+ context "mongoize" do
24
+ it "correctly mongoizes a Money object to a hash of cents and currency" do
25
+ expect(priceable.price.cents).to eq(100)
26
+ expect(priceable.price.currency).to eq(Money::Currency.find('EUR'))
27
+ end
28
+
29
+ it "correctly mongoizes a Numeric object to a hash of cents and currency" do
30
+ expect(priceable_from_num.price.cents).to eq(100)
31
+ expect(priceable_from_num.price.currency).to eq(Money.default_currency)
32
+ end
33
+
34
+ it "correctly mongoizes a String object to a hash of cents and currency" do
35
+ expect(priceable_from_string.price.cents).to eq(100)
36
+ expect(priceable_from_string.price.currency).to eq(Money::Currency.find('EUR'))
37
+ end
38
+
39
+ context "when MoneyRails.raise_error_on_money_parsing is true" do
40
+ before { MoneyRails.raise_error_on_money_parsing = true }
41
+ after { MoneyRails.raise_error_on_money_parsing = false }
42
+
43
+ it "raises exception if the mongoized value is a String with a hyphen" do
44
+ expect { priceable_from_string_with_hyphen }.to raise_error ArgumentError
45
+ end
46
+
47
+ it "raises exception if the mongoized value is a String with an unknown currency" do
48
+ expect { priceable_from_string_with_unknown_currency }.to raise_error ArgumentError
49
+ end
50
+ end
51
+
52
+ context "when MoneyRails.raise_error_on_money_parsing is false" do
53
+ it "does not correctly mongoize a String with a hyphen in its middle" do
54
+ expect(priceable_from_string_with_hyphen.price).to eq(nil)
55
+ end
56
+
57
+ it "does not correctly mongoize a String with an unknown currency" do
58
+ expect(priceable_from_string_with_unknown_currency.price).to eq(nil)
59
+ end
60
+ end
61
+
62
+ it "correctly mongoizes a hash of cents and currency" do
63
+ expect(priceable_from_hash.price.cents).to eq(100)
64
+ expect(priceable_from_hash.price.currency).to eq(Money::Currency.find('EUR'))
65
+ end
66
+
67
+ it "correctly mongoizes a HashWithIndifferentAccess of cents and currency" do
68
+ expect(priceable_from_hash_with_indifferent_access.price.cents).to eq(100)
69
+ expect(priceable_from_hash_with_indifferent_access.price.currency).to eq(Money::Currency.find('EUR'))
70
+ end
71
+
72
+ context "infinite_precision = true" do
73
+ before do
74
+ Money.infinite_precision = true
75
+ end
76
+
77
+ after do
78
+ Money.infinite_precision = false
79
+ end
80
+
81
+ it "correctly mongoizes a Money object to a hash of cents and currency" do
82
+ expect(priceable_with_infinite_precision.price.cents).to eq(BigDecimal.new('100.1'))
83
+ expect(priceable_with_infinite_precision.price.currency).to eq(Money::Currency.find('EUR'))
84
+ end
85
+ end
86
+ end
87
+
88
+ it "correctly serializes a Hash field containing Money objects" do
89
+ expect(priceable_with_hash_field.price_hash[:key1][:cents]).to eq(100)
90
+ expect(priceable_with_hash_field.price_hash[:key2][:cents]).to eq(200)
91
+ expect(priceable_with_hash_field.price_hash[:key1][:currency_iso]).to eq('EUR')
92
+ expect(priceable_with_hash_field.price_hash[:key2][:currency_iso]).to eq('USD')
93
+ end
94
+
95
+ context "demongoize" do
96
+ subject { Priceable.first.price }
97
+ it { is_expected.to be_an_instance_of(Money) }
98
+ it { is_expected.to eq(Money.new(100, 'EUR')) }
99
+
100
+ it "returns 0 cents in default_currency if a nil value was stored" do
101
+ nil_priceable = Priceable.create(price: nil)
102
+ expect(nil_priceable.price.cents).to eq(0)
103
+ expect(nil_priceable.price.currency).to eq(Money.default_currency)
104
+ end
105
+ it 'returns nil if an unknown value was stored' do
106
+ zero_priceable = Priceable.create(:price => [])
107
+ expect(zero_priceable.price).to be_nil
108
+ end
109
+ end
110
+
111
+ context "evolve" do
112
+ it "correctly transforms a Money object into a Mongo friendly value" do
113
+ expect(Priceable.where(:price => Money.new(100, 'EUR')).first).to eq(priceable)
114
+ end
115
+ end
116
+ end
117
+ end
@@ -4,6 +4,7 @@ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^4(.*)/
4
4
 
5
5
  describe Money do
6
6
  let!(:priceable) { Priceable.create(:price => Money.new(100, 'EUR')) }
7
+ let!(:priceable_from_nil) { Priceable.create(:price => nil) }
7
8
  let!(:priceable_from_num) { Priceable.create(:price => 1) }
8
9
  let!(:priceable_from_string) { Priceable.create(:price => '1 EUR' )}
9
10
  let!(:priceable_from_hash) { Priceable.create(:price => {:cents=>100, :currency_iso=>"EUR"} )}
@@ -21,6 +22,10 @@ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^4(.*)/
21
22
  }
22
23
 
23
24
  context "mongoize" do
25
+ it "correctly mongoizes nil to nil" do
26
+ expect(priceable_from_nil.price).to be_nil
27
+ end
28
+
24
29
  it "correctly mongoizes a Money object to a hash of cents and currency" do
25
30
  expect(priceable.price.cents).to eq(100)
26
31
  expect(priceable.price.currency).to eq(Money::Currency.find('EUR'))
@@ -97,11 +102,11 @@ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^4(.*)/
97
102
  it { is_expected.to be_an_instance_of(Money) }
98
103
  it { is_expected.to eq(Money.new(100, 'EUR')) }
99
104
 
100
- it "returns 0 cents in default_currency if a nil value was stored" do
105
+ it "returns nil if a nil value was stored" do
101
106
  nil_priceable = Priceable.create(price: nil)
102
- expect(nil_priceable.price.cents).to eq(0)
103
- expect(nil_priceable.price.currency).to eq(Money.default_currency)
107
+ expect(nil_priceable.price).to be_nil
104
108
  end
109
+
105
110
  it 'returns nil if an unknown value was stored' do
106
111
  zero_priceable = Priceable.create(:price => [])
107
112
  expect(zero_priceable.price).to be_nil
@@ -4,6 +4,7 @@ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^3(.*)/
4
4
 
5
5
  describe Money do
6
6
  let!(:priceable) { Priceable.create(:price => Money.new(100, 'EUR')) }
7
+ let!(:priceable_from_nil) { Priceable.create(:price => nil) }
7
8
  let!(:priceable_from_num) { Priceable.create(:price => 1) }
8
9
  let!(:priceable_from_string) { Priceable.create(:price => '1 EUR' )}
9
10
  let!(:priceable_from_hash) { Priceable.create(:price => {:cents=>100, :currency_iso=>"EUR"} )}
@@ -21,6 +22,10 @@ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^3(.*)/
21
22
  }
22
23
 
23
24
  context "mongoize" do
25
+ it "mongoizes correctly nil to nil" do
26
+ expect(priceable_from_nil.price).to be_nil
27
+ end
28
+
24
29
  it "mongoizes correctly a Money object to a hash of cents and currency" do
25
30
  expect(priceable.price.cents).to eq(100)
26
31
  expect(priceable.price.currency).to eq(Money::Currency.find('EUR'))
@@ -96,11 +101,12 @@ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^3(.*)/
96
101
  subject { Priceable.first.price }
97
102
  it { is_expected.to be_an_instance_of(Money) }
98
103
  it { is_expected.to eq(Money.new(100, 'EUR')) }
99
- it "returns 0 cents in default_currency if a nil value was stored" do
104
+
105
+ it "returns nil if a nil value was stored" do
100
106
  nil_priceable = Priceable.create(price: nil)
101
- expect(nil_priceable.price.cents).to eq(0)
102
- expect(nil_priceable.price.currency).to eq(Money.default_currency)
107
+ expect(nil_priceable.price).to be_nil
103
108
  end
109
+
104
110
  it 'returns nil if an unknown value was stored' do
105
111
  zero_priceable = Priceable.create(:price => [])
106
112
  expect(zero_priceable.price).to be_nil
@@ -4,12 +4,17 @@ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^2(.*)/
4
4
 
5
5
  describe Money do
6
6
  let(:priceable) { Priceable.create(:price => Money.new(100, 'EUR')) }
7
+ let(:priceable_from_nil) { Priceable.create(:price => nil) }
7
8
  let(:priceable_from_num) { Priceable.create(:price => 1) }
8
9
  let(:priceable_from_string) { Priceable.create(:price => '1 EUR' )}
9
10
  let(:priceable_with_infinite_precision) { Priceable.create(:price => Money.new(BigDecimal.new('100.1'), 'EUR')) }
10
11
  let(:priceable_from_string_with_hyphen) { Priceable.create(:price => '1-2 EUR' )}
11
12
 
12
13
  context "serialize" do
14
+ it "mongoizes correctly nil to nil" do
15
+ expect(priceable_from_nil.price).to be_nil
16
+ end
17
+
13
18
  it "serializes correctly a Money object to a hash of cents and currency" do
14
19
  expect(priceable.price.cents).to eq(100)
15
20
  expect(priceable.price.currency).to eq(Money::Currency.find('EUR'))
@@ -60,11 +65,12 @@ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^2(.*)/
60
65
  subject { priceable.price }
61
66
  it { is_expected.to be_an_instance_of(Money) }
62
67
  it { is_expected.to eq(Money.new(100, 'EUR')) }
63
- it "returns 0 cents in default_currency if a nil value was stored" do
68
+
69
+ it "returns nil if a nil value was stored" do
64
70
  nil_priceable = Priceable.create(price: nil)
65
- expect(nil_priceable.price.cents).to eq(0)
66
- expect(nil_priceable.price.currency).to eq(Money.default_currency)
71
+ expect(nil_priceable.price).to be_nil
67
72
  end
73
+
68
74
  it 'returns nil if an unknown value was stored' do
69
75
  zero_priceable = Priceable.create(:price => [])
70
76
  expect(zero_priceable.price).to be_nil
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.6.2
4
+ version: 1.7.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: 2016-06-21 00:00:00.000000000 Z
13
+ date: 2016-08-31 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: money
@@ -226,6 +226,7 @@ files:
226
226
  - spec/dummy/script/rails
227
227
  - spec/helpers/action_view_extension_spec.rb
228
228
  - spec/helpers/form_helper_spec.rb
229
+ - spec/mongoid/five_spec.rb
229
230
  - spec/mongoid/four_spec.rb
230
231
  - spec/mongoid/three_spec.rb
231
232
  - spec/mongoid/two_spec.rb
@@ -319,6 +320,7 @@ test_files:
319
320
  - spec/dummy/script/rails
320
321
  - spec/helpers/action_view_extension_spec.rb
321
322
  - spec/helpers/form_helper_spec.rb
323
+ - spec/mongoid/five_spec.rb
322
324
  - spec/mongoid/four_spec.rb
323
325
  - spec/mongoid/three_spec.rb
324
326
  - spec/mongoid/two_spec.rb