money-rails 1.15.0 → 2.0.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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -6
  3. data/LICENSE +1 -1
  4. data/README.md +121 -102
  5. data/config/locales/money.pt.yml +4 -0
  6. data/lib/money-rails/active_job/money_serializer.rb +19 -0
  7. data/lib/money-rails/active_record/monetizable.rb +17 -7
  8. data/lib/money-rails/configuration.rb +2 -2
  9. data/lib/money-rails/helpers/action_view_extension.rb +1 -1
  10. data/lib/money-rails/hooks.rb +11 -0
  11. data/lib/money-rails/version.rb +3 -1
  12. data/money-rails.gemspec +13 -26
  13. metadata +16 -225
  14. data/Rakefile +0 -84
  15. data/spec/active_record/migration_extensions/schema_statements_spec.rb +0 -101
  16. data/spec/active_record/migration_extensions/table_spec.rb +0 -104
  17. data/spec/active_record/monetizable_spec.rb +0 -1062
  18. data/spec/configuration_spec.rb +0 -147
  19. data/spec/dummy/README.rdoc +0 -261
  20. data/spec/dummy/Rakefile +0 -7
  21. data/spec/dummy/app/assets/config/manifest.js +0 -0
  22. data/spec/dummy/app/assets/javascripts/application.js +0 -15
  23. data/spec/dummy/app/assets/stylesheets/application.css +0 -13
  24. data/spec/dummy/app/controllers/application_controller.rb +0 -3
  25. data/spec/dummy/app/helpers/application_helper.rb +0 -2
  26. data/spec/dummy/app/models/dummy_product.rb +0 -8
  27. data/spec/dummy/app/models/priceable.rb +0 -8
  28. data/spec/dummy/app/models/product.rb +0 -59
  29. data/spec/dummy/app/models/service.rb +0 -5
  30. data/spec/dummy/app/models/transaction.rb +0 -13
  31. data/spec/dummy/app/views/layouts/application.html.erb +0 -14
  32. data/spec/dummy/config/application.rb +0 -66
  33. data/spec/dummy/config/boot.rb +0 -13
  34. data/spec/dummy/config/database.yml +0 -25
  35. data/spec/dummy/config/environment.rb +0 -5
  36. data/spec/dummy/config/environments/development.rb +0 -30
  37. data/spec/dummy/config/environments/production.rb +0 -69
  38. data/spec/dummy/config/environments/test.rb +0 -36
  39. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  40. data/spec/dummy/config/initializers/inflections.rb +0 -15
  41. data/spec/dummy/config/initializers/mime_types.rb +0 -5
  42. data/spec/dummy/config/initializers/money.rb +0 -31
  43. data/spec/dummy/config/initializers/secret_token.rb +0 -7
  44. data/spec/dummy/config/initializers/session_store.rb +0 -8
  45. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  46. data/spec/dummy/config/locales/en-GB.yml +0 -11
  47. data/spec/dummy/config/locales/en-US.yml +0 -5
  48. data/spec/dummy/config/locales/en.yml +0 -5
  49. data/spec/dummy/config/locales/it.yml +0 -11
  50. data/spec/dummy/config/mongoid.yml +0 -29
  51. data/spec/dummy/config/routes.rb +0 -3
  52. data/spec/dummy/config.ru +0 -4
  53. data/spec/dummy/db/migrate/20120331190108_create_products.rb +0 -10
  54. data/spec/dummy/db/migrate/20120402080348_add_bonus_cents_to_product.rb +0 -6
  55. data/spec/dummy/db/migrate/20120524052716_create_services.rb +0 -10
  56. data/spec/dummy/db/migrate/20120528181002_create_transactions.rb +0 -11
  57. data/spec/dummy/db/migrate/20120528210103_create_dummy_products.rb +0 -10
  58. data/spec/dummy/db/migrate/20120607210247_add_column_that_allows_nil.rb +0 -5
  59. data/spec/dummy/db/migrate/20120712202655_add_sale_price_cents_to_product.rb +0 -7
  60. data/spec/dummy/db/migrate/20130124023419_add_price_in_a_range_cents_to_products.rb +0 -5
  61. data/spec/dummy/db/migrate/20140110194016_add_validates_method_amount_cents_to_products.rb +0 -5
  62. data/spec/dummy/db/migrate/20141005075025_add_aliased_attr_to_products.rb +0 -5
  63. data/spec/dummy/db/migrate/20150107061030_add_delivery_fee_cents_and_restock_fee_cents_to_product.rb +0 -6
  64. data/spec/dummy/db/migrate/20150126231442_add_reduced_price_to_products.rb +0 -6
  65. data/spec/dummy/db/migrate/20150213234410_add_special_price_to_products.rb +0 -5
  66. data/spec/dummy/db/migrate/20150217222612_add_lambda_price_to_products.rb +0 -5
  67. data/spec/dummy/db/migrate/20150303222230_add_skip_validation_price_cents_to_products.rb +0 -5
  68. data/spec/dummy/db/migrate/20151026220420_add_optional_amount_to_transactions.rb +0 -5
  69. data/spec/dummy/db/schema.rb +0 -59
  70. data/spec/dummy/db/structure.sql +0 -21
  71. data/spec/dummy/public/404.html +0 -26
  72. data/spec/dummy/public/422.html +0 -26
  73. data/spec/dummy/public/500.html +0 -25
  74. data/spec/dummy/public/favicon.ico +0 -0
  75. data/spec/dummy/script/rails +0 -6
  76. data/spec/helpers/action_view_extension_spec.rb +0 -189
  77. data/spec/helpers/form_helper_spec.rb +0 -19
  78. data/spec/money_spec.rb +0 -40
  79. data/spec/mongoid/mongoid_spec.rb +0 -130
  80. data/spec/mongoid/two_spec.rb +0 -80
  81. data/spec/spec_helper.rb +0 -23
  82. data/spec/support/database_cleaner.rb +0 -14
  83. data/spec/test_helpers_spec.rb +0 -72
@@ -1,1062 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'spec_helper'
4
-
5
- class Sub < Product; end
6
-
7
- if defined? ActiveRecord
8
- describe MoneyRails::ActiveRecord::Monetizable do
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
17
-
18
- describe ".monetize" do
19
- let(:service) do
20
- Service.create(charge_cents: 2000, discount_cents: 120)
21
- end
22
-
23
- def update_product(*attributes)
24
- if defined?(::ActiveRecord::VERSION) && ::ActiveRecord::VERSION::MAJOR >= 5
25
- product.update(*attributes)
26
- else
27
- product.update_attributes(*attributes)
28
- end
29
- end
30
-
31
- context ".monetized_attributes" do
32
-
33
- class InheritedMonetizeProduct < Product
34
- monetize :special_price_cents
35
- end
36
-
37
- it "should be inherited by subclasses" do
38
- assert_monetized_attributes(Sub.monetized_attributes, Product.monetized_attributes)
39
- end
40
-
41
- it "should be inherited by subclasses with new monetized attribute" do
42
- assert_monetized_attributes(InheritedMonetizeProduct.monetized_attributes, Product.monetized_attributes.merge(special_price: "special_price_cents"))
43
- end
44
-
45
- def assert_monetized_attributes(monetized_attributes, expected_attributes)
46
- expect(monetized_attributes).to include expected_attributes
47
- expect(expected_attributes).to include monetized_attributes
48
- expect(monetized_attributes.size).to eql expected_attributes.size
49
- monetized_attributes.keys.each do |key|
50
- expect(key.is_a? String).to be_truthy
51
- end
52
- end
53
- end
54
-
55
- it "attaches a Money object to model field" do
56
- expect(product.price).to be_an_instance_of(Money)
57
- expect(product.discount_value).to be_an_instance_of(Money)
58
- expect(product.bonus).to be_an_instance_of(Money)
59
- end
60
-
61
- it "attaches Money objects to multiple model fields" do
62
- expect(product.delivery_fee).to be_an_instance_of(Money)
63
- expect(product.restock_fee).to be_an_instance_of(Money)
64
- end
65
-
66
- it "returns the expected money amount as a Money object" do
67
- expect(product.price).to eq(Money.new(3000, "USD"))
68
- end
69
-
70
- it "assigns the correct value from a Money object" do
71
- product.price = Money.new(3210, "USD")
72
- expect(product.save).to be_truthy
73
- expect(product.price_cents).to eq(3210)
74
- end
75
-
76
- it "assigns the correct value from a Money object using create" do
77
- product = Product.create(price: Money.new(3210, "USD"), discount: 150,
78
- bonus_cents: 200, optional_price: 100)
79
- expect(product.valid?).to be_truthy
80
- expect(product.price_cents).to eq(3210)
81
- end
82
-
83
- it "correctly updates from a Money object using update_attributes" do
84
- expect(update_product(price: Money.new(215, "USD"))).to be_truthy
85
- expect(product.price_cents).to eq(215)
86
- end
87
-
88
- it "assigns the correct value from params" do
89
- params_clp = { amount: '20000', tax: '1000', currency: 'CLP' }
90
- product = Transaction.create(params_clp)
91
- expect(product.valid?).to be_truthy
92
- expect(product.amount.currency.subunit_to_unit).to eq(1)
93
- expect(product.amount_cents).to eq(20000)
94
- end
95
-
96
- # TODO: This is a slightly controversial example, btu it reflects the current behaviour
97
- it "re-assigns cents amount when subunit/unit ratio changes preserving amount in units" do
98
- transaction = Transaction.create(amount: '20000', tax: '1000', currency: 'USD')
99
-
100
- expect(transaction.amount).to eq(Money.new(20000_00, 'USD'))
101
-
102
- transaction.currency = 'CLP'
103
-
104
- expect(transaction.amount).to eq(Money.new(20000, 'CLP'))
105
- expect(transaction.amount_cents).to eq(20000)
106
- end
107
-
108
- it "raises an error if trying to create two attributes with the same name" do
109
- expect do
110
- class Product
111
- monetize :discount, as: :price
112
- end
113
- end.to raise_error ArgumentError
114
- end
115
-
116
- it "raises an error if Money object has the same attribute name as the monetizable attribute" do
117
- expect do
118
- class AnotherProduct < Product
119
- monetize :price_cents, as: :price_cents
120
- end
121
- end.to raise_error ArgumentError
122
- end
123
-
124
- it "raises an error when unable to infer attribute name" do
125
- old_postfix = MoneyRails::Configuration.amount_column[:postfix]
126
- MoneyRails::Configuration.amount_column[:postfix] = '_pennies'
127
-
128
- expect do
129
- class AnotherProduct < Product
130
- monetize :price_cents
131
- end
132
- end.to raise_error ArgumentError
133
-
134
- MoneyRails::Configuration.amount_column[:postfix] = old_postfix
135
- end
136
-
137
- it "allows subclass to redefine attribute with the same name" do
138
- class SubProduct < Product
139
- monetize :discount, as: :discount_price, with_currency: :gbp
140
- end
141
-
142
- sub_product = SubProduct.new(discount: 100)
143
-
144
- expect(sub_product.discount_price).to be_an_instance_of(Money)
145
- expect(sub_product.discount_price.currency.id).to equal :gbp
146
- end
147
-
148
- it "respects :as argument" do
149
- expect(product.discount_value).to eq(Money.new(150, "USD"))
150
- end
151
-
152
- it "uses numericality validation" do
153
- product.price_cents = "foo"
154
- expect(product.save).to be_falsey
155
-
156
- product.price_cents = 2000
157
- expect(product.save).to be_truthy
158
- end
159
-
160
- it "skips numericality validation when disabled" do
161
- product.accessor_price_cents = 'not_valid'
162
- expect(product.save).to be_truthy
163
- end
164
-
165
- it "passes validation after updating fractional attribute which was previously invalid" do
166
- product.price_in_a_range = -5
167
- expect(product).not_to be_valid
168
- product.price_in_a_range_cents = 500
169
- expect(product).to be_valid
170
- end
171
-
172
- context "when MoneyRails.raise_error_on_money_parsing is true" do
173
- before { MoneyRails.raise_error_on_money_parsing = true }
174
- after { MoneyRails.raise_error_on_money_parsing = false }
175
-
176
- it "raises exception when a String value with hyphen is assigned" do
177
- expect { product.accessor_price = "10-235" }.to raise_error MoneyRails::Error
178
- end
179
-
180
- it "raises an exception if it can't change currency" do
181
- expect {
182
- Product.new.price = Money.new(10, "RUB")
183
- }.to raise_error(MoneyRails::ActiveRecord::Monetizable::ReadOnlyCurrencyException, "Can't change readonly currency 'USD' to 'RUB' for field 'price'")
184
- end
185
- end
186
-
187
- context "when MoneyRails.raise_error_on_money_parsing is false (default)" do
188
- it "does not raise exception when a String value with hyphen is assigned" do
189
- expect { product.accessor_price = "10-235" }.not_to raise_error
190
- end
191
-
192
- it "does not raise exception if it can't change currency" do
193
- expect { Product.new.price = Money.new(10, "RUB") }.not_to raise_error
194
- end
195
- end
196
-
197
- it "respects numericality validation when using update_attributes" do
198
- expect(update_product(price_cents: "some text")).to be_falsey
199
- expect(update_product(price_cents: 2000)).to be_truthy
200
- end
201
-
202
- it "uses numericality validation on money attribute" do
203
- product.price = "some text"
204
- expect(product.save).to be_falsey
205
-
206
- product.price = Money.new(320, "USD")
207
- expect(product.save).to be_truthy
208
-
209
- product.sale_price = "12.34"
210
- product.sale_price_currency_code = 'EUR'
211
- expect(product.valid?).to be_truthy
212
- end
213
-
214
- it "separately skips price validations" do
215
- product.skip_validation_price = 'hundred thousands'
216
- expect(product.save).to be_truthy
217
- end
218
-
219
- it "separately skips subunit validations" do
220
- product.skip_validation_price_cents = 'ten million'
221
- expect(product.save).to be_truthy
222
- end
223
-
224
- it "shouldn't init empty key in errors" do
225
- product.price = Money.new(320, "USD")
226
- product.valid?
227
- expect(product.errors.has_key?(:price)).to be_falsey
228
- end
229
-
230
- it "fails validation with the proper error message if money value is invalid decimal" do
231
- product.price = "12.23.24"
232
- expect(product.save).to be_falsey
233
- expect(product.errors[:price].size).to eq(1)
234
- expect(product.errors[:price].first).to match(/not a number/)
235
- end
236
-
237
- it "fails validation with the proper error message if money value is nothing but periods" do
238
- product.price = "..."
239
- expect(product.save).to be_falsey
240
- expect(product.errors[:price].size).to eq(1)
241
- expect(product.errors[:price].first).to match(/not a number/)
242
- end
243
-
244
- it "fails validation with the proper error message if money value has invalid thousands part" do
245
- product.price = "12,23.24"
246
- expect(product.save).to be_falsey
247
- expect(product.errors[:price].size).to eq(1)
248
- expect(product.errors[:price].first).to match(/has invalid format/)
249
- expect(product.errors[:price].first).to match(/Got 12,23.24/)
250
- end
251
-
252
- it "fails validation with the proper error message if money value has thousand char after decimal mark" do
253
- product.price = "1.234,56"
254
- expect(product.save).to be_falsey
255
- expect(product.errors[:price].size).to eq(1)
256
- expect(product.errors[:price].first).to match(/has invalid format/)
257
- expect(product.errors[:price].first).to match(/Got 1.234,56/)
258
- end
259
-
260
- it "allows an empty string as the thousands separator" do
261
- begin
262
- I18n.locale = 'en-US'
263
- product.price = '10.00'
264
- expect(product).to be_valid
265
- ensure
266
- I18n.locale = I18n.default_locale
267
- end
268
- end
269
-
270
- it "passes validation if money value is a Float and the currency decimal mark is not period" do
271
- # The corresponding String would be "12,34" euros
272
- service.discount = 12.34
273
- expect(service.save).to be_truthy
274
- end
275
-
276
- it "passes validation if money value is a Float" do
277
- product.price = 12.34
278
- expect(product.save).to be_truthy
279
- end
280
-
281
- it "passes validation if money value is an Integer" do
282
- product.price = 12
283
- expect(product.save).to be_truthy
284
- end
285
-
286
- it "fails validation with the proper error message using numericality validations" do
287
- product.price_in_a_range = "-12"
288
- expect(product.valid?).to be_falsey
289
- expect(product.errors[:price_in_a_range].size).to eq(1)
290
- expect(product.errors[:price_in_a_range].first).to match(/must be greater than zero and less than \$100/)
291
-
292
- product.price_in_a_range = Money.new(-1200, "USD")
293
- expect(product.valid?).to be_falsey
294
- expect(product.errors[:price_in_a_range].size).to eq(1)
295
- expect(product.errors[:price_in_a_range].first).to match(/must be greater than zero and less than \$100/)
296
-
297
- product.price_in_a_range = "0"
298
- expect(product.valid?).to be_falsey
299
- expect(product.errors[:price_in_a_range].size).to eq(1)
300
- expect(product.errors[:price_in_a_range].first).to match(/must be greater than zero and less than \$100/)
301
-
302
- product.price_in_a_range = "12"
303
- expect(product.valid?).to be_truthy
304
-
305
- product.price_in_a_range = Money.new(1200, "USD")
306
- expect(product.valid?).to be_truthy
307
-
308
- product.price_in_a_range = "101"
309
- expect(product.valid?).to be_falsey
310
- expect(product.errors[:price_in_a_range].size).to eq(1)
311
- expect(product.errors[:price_in_a_range].first).to match(/must be greater than zero and less than \$100/)
312
-
313
- product.price_in_a_range = Money.new(10100, "USD")
314
- expect(product.valid?).to be_falsey
315
- expect(product.errors[:price_in_a_range].size).to eq(1)
316
- expect(product.errors[:price_in_a_range].first).to match(/must be greater than zero and less than \$100/)
317
- end
318
-
319
- it "fails validation if linked attribute changed" do
320
- product = Product.create(price: Money.new(3210, "USD"), discount: 150,
321
- validates_method_amount: 100,
322
- bonus_cents: 200, optional_price: 100)
323
- expect(product.valid?).to be_truthy
324
- product.optional_price = 50
325
- expect(product.valid?).to be_falsey
326
- end
327
-
328
- it "fails validation with the proper error message using validates :money" do
329
- product.validates_method_amount = "-12"
330
- expect(product.valid?).to be_falsey
331
- expect(product.errors[:validates_method_amount].size).to eq(1)
332
- expect(product.errors[:validates_method_amount].first).to match(/must be greater than zero and less than \$100/)
333
-
334
- product.validates_method_amount = Money.new(-1200, "USD")
335
- expect(product.valid?).to be_falsey
336
- expect(product.errors[:validates_method_amount].size).to eq(1)
337
- expect(product.errors[:validates_method_amount].first).to match(/must be greater than zero and less than \$100/)
338
-
339
- product.validates_method_amount = "0"
340
- expect(product.valid?).to be_falsey
341
- expect(product.errors[:validates_method_amount].size).to eq(1)
342
- expect(product.errors[:validates_method_amount].first).to match(/must be greater than zero and less than \$100/)
343
-
344
- product.validates_method_amount = "12"
345
- expect(product.valid?).to be_truthy
346
-
347
- product.validates_method_amount = Money.new(1200, "USD")
348
- expect(product.valid?).to be_truthy
349
-
350
- product.validates_method_amount = "101"
351
- expect(product.valid?).to be_falsey
352
- expect(product.errors[:validates_method_amount].size).to eq(1)
353
- expect(product.errors[:validates_method_amount].first).to match(/must be greater than zero and less than \$100/)
354
-
355
- product.validates_method_amount = Money.new(10100, "USD")
356
- expect(product.valid?).to be_falsey
357
- expect(product.errors[:validates_method_amount].size).to eq(1)
358
- expect(product.errors[:validates_method_amount].first).to match(/must be greater than zero and less than \$100/)
359
- end
360
-
361
- it "fails validation with the proper error message on the cents field " do
362
- product.price_in_a_range = "-12"
363
- expect(product.valid?).to be_falsey
364
- expect(product.errors[:price_in_a_range_cents].size).to eq(1)
365
- expect(product.errors[:price_in_a_range_cents].first).to match(/greater than 0/)
366
-
367
- product.price_in_a_range = "0"
368
- expect(product.valid?).to be_falsey
369
- expect(product.errors[:price_in_a_range_cents].size).to eq(1)
370
- expect(product.errors[:price_in_a_range_cents].first).to match(/greater than 0/)
371
-
372
- product.price_in_a_range = "12"
373
- expect(product.valid?).to be_truthy
374
-
375
- product.price_in_a_range = "101"
376
- expect(product.valid?).to be_falsey
377
- expect(product.errors[:price_in_a_range_cents].size).to eq(1)
378
- expect(product.errors[:price_in_a_range_cents].first).to match(/less than or equal to 10000/)
379
- end
380
-
381
- it "fails validation when a non number string is given" do
382
- product = Product.create(price_in_a_range: "asd")
383
- expect(product.valid?).to be_falsey
384
- expect(product.errors[:price_in_a_range].size).to eq(1)
385
- expect(product.errors[:price_in_a_range].first).to match(/greater than zero/)
386
-
387
- product = Product.create(price_in_a_range: "asd23")
388
- expect(product.valid?).to be_falsey
389
- expect(product.errors[:price_in_a_range].size).to eq(1)
390
- expect(product.errors[:price_in_a_range].first).to match(/greater than zero/)
391
-
392
- product = Product.create(price: "asd")
393
- expect(product.valid?).to be_falsey
394
- expect(product.errors[:price].size).to eq(1)
395
- expect(product.errors[:price].first).to match(/is not a number/)
396
-
397
- product = Product.create(price: "asd23")
398
- expect(product.valid?).to be_falsey
399
- expect(product.errors[:price].size).to eq(1)
400
- expect(product.errors[:price].first).to match(/is not a number/)
401
- end
402
-
403
- it "passes validation when amount contains spaces (999 999.99)" do
404
- product.price = "999 999.99"
405
- expect(product).to be_valid
406
- expect(product.price_cents).to eq(99999999)
407
- end
408
-
409
- it "passes validation when amount contains underscores (999_999.99)" do
410
- product.price = "999_999.99"
411
- expect(product).to be_valid
412
- expect(product.price_cents).to eq(99999999)
413
- end
414
-
415
- it "passes validation if money value has correct format" do
416
- product.price = "12,230.24"
417
- expect(product.save).to be_truthy
418
- end
419
-
420
- it "passes validation if there is a whitespace between the currency symbol and amount" do
421
- product.price = "$ 123,456.78"
422
- expect(product.save).to be_truthy
423
- end
424
-
425
- it "respects numericality validation when using update_attributes on money attribute" do
426
- expect(update_product(price: "some text")).to be_falsey
427
- expect(update_product(price: Money.new(320, 'USD'))).to be_truthy
428
- end
429
-
430
- it "uses i18n currency format when validating" do
431
- old_locale = I18n.locale
432
-
433
- I18n.locale = "en-GB"
434
- Money.default_currency = Money::Currency.find('EUR')
435
- expect("12.00".to_money).to eq(Money.new(1200, :eur))
436
- transaction = Transaction.new(amount: "12.00", tax: "13.00")
437
- expect(transaction.amount_cents).to eq(1200)
438
- expect(transaction.valid?).to be_truthy
439
-
440
- # reset locale setting
441
- I18n.locale = old_locale
442
- end
443
-
444
- it "doesn't allow nil by default" do
445
- product.price_cents = nil
446
- expect(product.save).to be_falsey
447
- end
448
-
449
- it "allows nil if optioned" do
450
- product.optional_price = nil
451
- expect(product.save).to be_truthy
452
- expect(product.optional_price).to be_nil
453
- end
454
-
455
- it "doesn't raise exception if validation is used and nil is not allowed" do
456
- expect { product.price = nil }.to_not raise_error
457
- end
458
-
459
- it "doesn't save nil values if validation is used and nil is not allowed" do
460
- product.price = nil
461
- product.save
462
- expect(product.price_cents).not_to be_nil
463
- end
464
-
465
- it "resets money_before_type_cast attr every time a save operation occurs" do
466
- v = Money.new(100, :usd)
467
- product.price = v
468
- expect(product.price_money_before_type_cast).to eq(v)
469
- product.save
470
- expect(product.price_money_before_type_cast).to be_nil
471
- product.price = 10
472
- expect(product.price_money_before_type_cast).to eq(10)
473
- product.save
474
- expect(product.price_money_before_type_cast).to be_nil
475
- end
476
-
477
- it "does not reset money_before_type_cast attr if save operation fails" do
478
- product.bonus = ""
479
- expect(product.bonus_money_before_type_cast).to eq("")
480
- expect(product.save).to be_falsey
481
- expect(product.bonus_money_before_type_cast).to eq("")
482
- end
483
-
484
- it "uses Money default currency if :with_currency has not been used" do
485
- expect(service.discount.currency).to eq(Money::Currency.find(:eur))
486
- end
487
-
488
- it "overrides default currency with the currency registered for the model" do
489
- expect(product.price.currency).to eq(Money::Currency.find(:usd))
490
- end
491
-
492
- it "overrides default currency with the value of :with_currency argument" do
493
- expect(service.charge.currency).to eq(Money::Currency.find(:usd))
494
- expect(product.bonus.currency).to eq(Money::Currency.find(:gbp))
495
- end
496
-
497
- it "uses currency postfix to determine attribute that stores currency" do
498
- expect(product.reduced_price.currency).to eq(Money::Currency.find(:lvl))
499
- end
500
-
501
- it "correctly assigns Money objects to the attribute" do
502
- product.price = Money.new(2500, :USD)
503
- expect(product.save).to be_truthy
504
- expect(product.price.cents).to eq(2500)
505
- expect(product.price.currency.to_s).to eq("USD")
506
- end
507
-
508
- it "correctly assigns Fixnum objects to the attribute" do
509
- product.price = 25
510
- expect(product.save).to be_truthy
511
- expect(product.price.cents).to eq(2500)
512
- expect(product.price.currency.to_s).to eq("USD")
513
-
514
- service.discount = 2
515
- expect(service.save).to be_truthy
516
- expect(service.discount.cents).to eq(200)
517
- expect(service.discount.currency.to_s).to eq("EUR")
518
- end
519
-
520
- it "correctly assigns String objects to the attribute" do
521
- product.price = "25"
522
- expect(product.save).to be_truthy
523
- expect(product.price.cents).to eq(2500)
524
- expect(product.price.currency.to_s).to eq("USD")
525
-
526
- service.discount = "2"
527
- expect(service.save).to be_truthy
528
- expect(service.discount.cents).to eq(200)
529
- expect(service.discount.currency.to_s).to eq("EUR")
530
- end
531
-
532
- it "correctly assigns objects to a accessor attribute" do
533
- product.accessor_price = 1.23
534
- expect(product.save).to be_truthy
535
- expect(product.accessor_price.cents).to eq(123)
536
- expect(product.accessor_price_cents).to eq(123)
537
- end
538
-
539
- it "overrides default, model currency with the value of :with_currency in fixnum assignments" do
540
- product.bonus = 25
541
- expect(product.save).to be_truthy
542
- expect(product.bonus.cents).to eq(2500)
543
- expect(product.bonus.currency.to_s).to eq("GBP")
544
-
545
- service.charge = 2
546
- expect(service.save).to be_truthy
547
- expect(service.charge.cents).to eq(200)
548
- expect(service.charge.currency.to_s).to eq("USD")
549
- end
550
-
551
- it "overrides default, model currency with the value of :with_currency in string assignments" do
552
- product.bonus = "25"
553
- expect(product.save).to be_truthy
554
- expect(product.bonus.cents).to eq(2500)
555
- expect(product.bonus.currency.to_s).to eq("GBP")
556
-
557
- service.charge = "2"
558
- expect(service.save).to be_truthy
559
- expect(service.charge.cents).to eq(200)
560
- expect(service.charge.currency.to_s).to eq("USD")
561
-
562
- product.lambda_price = "32"
563
- expect(product.save).to be_truthy
564
- expect(product.lambda_price.cents).to eq(3200)
565
- expect(product.lambda_price.currency.to_s).to eq("CAD")
566
- end
567
-
568
- it "overrides default currency with model currency, in fixnum assignments" do
569
- product.discount_value = 5
570
- expect(product.save).to be_truthy
571
- expect(product.discount_value.cents).to eq(500)
572
- expect(product.discount_value.currency.to_s).to eq("USD")
573
- end
574
-
575
- it "overrides default currency with model currency, in string assignments" do
576
- product.discount_value = "5"
577
- expect(product.save).to be_truthy
578
- expect(product.discount_value.cents).to eq(500)
579
- expect(product.discount_value.currency.to_s).to eq("USD")
580
- end
581
-
582
- it "falls back to default currency, in fixnum assignments" do
583
- service.discount = 5
584
- expect(service.save).to be_truthy
585
- expect(service.discount.cents).to eq(500)
586
- expect(service.discount.currency.to_s).to eq("EUR")
587
- end
588
-
589
- it "falls back to default currency, in string assignments" do
590
- service.discount = "5"
591
- expect(service.save).to be_truthy
592
- expect(service.discount.cents).to eq(500)
593
- expect(service.discount.currency.to_s).to eq("EUR")
594
- end
595
-
596
- it "sets field to nil, in nil assignments if allow_nil is set" do
597
- product.optional_price = nil
598
- expect(product.save).to be_truthy
599
- expect(product.optional_price).to be_nil
600
- end
601
-
602
- it "sets field to nil, in instantiation if allow_nil is set" do
603
- pr = Product.new(optional_price: nil, price_cents: 5320,
604
- discount: 350, bonus_cents: 320)
605
- expect(pr.optional_price).to be_nil
606
- expect(pr.save).to be_truthy
607
- expect(pr.optional_price).to be_nil
608
- end
609
-
610
- it "sets field to nil, in blank assignments if allow_nil is set" do
611
- product.optional_price = ""
612
- expect(product.save).to be_truthy
613
- expect(product.optional_price).to be_nil
614
- end
615
-
616
- context "when the monetized field is an aliased attribute" do
617
- it "writes the subunits to the original (unaliased) column" do
618
- pending if Rails::VERSION::MAJOR < 4
619
- product.renamed = "$10.00"
620
- expect(product.aliased_cents).to eq 10_00
621
- end
622
- end
623
-
624
- context "for column with model currency:" do
625
- it "has default currency if not specified" do
626
- product = Product.create(sale_price_amount: 1234)
627
- product.sale_price.currency.to_s == 'USD'
628
- end
629
-
630
- it "is overridden by instance currency column" do
631
- product = Product.create(sale_price_amount: 1234,
632
- sale_price_currency_code: 'CAD')
633
- expect(product.sale_price.currency.to_s).to eq('CAD')
634
- end
635
-
636
- it 'can change currency of custom column' do
637
- product = Product.create!(
638
- price: Money.new(10,'USD'),
639
- bonus: Money.new(10,'GBP'),
640
- discount: 10,
641
- sale_price_amount: 1234,
642
- sale_price_currency_code: 'USD'
643
- )
644
-
645
- expect(product.sale_price.currency.to_s).to eq('USD')
646
-
647
- product.sale_price = Money.new 456, 'CAD'
648
- product.save
649
- product.reload
650
-
651
- expect(product.sale_price.currency.to_s).to eq('CAD')
652
- expect(product.discount_value.currency.to_s).to eq('USD')
653
- end
654
- end
655
-
656
- context "for model with currency column:" do
657
- let(:transaction) do
658
- Transaction.create(amount_cents: 2400, tax_cents: 600,
659
- currency: :usd)
660
- end
661
-
662
- let(:dummy_product) do
663
- DummyProduct.create(price_cents: 2400, currency: :usd)
664
- end
665
-
666
- let(:dummy_product_with_nil_currency) do
667
- DummyProduct.create(price_cents: 2600) # nil currency
668
- end
669
-
670
- let(:dummy_product_with_invalid_currency) do
671
- # invalid currency
672
- DummyProduct.create(price_cents: 2600, currency: :foo)
673
- end
674
-
675
- it "correctly serializes the currency to a new instance of model" do
676
- d = DummyProduct.new
677
- d.price = Money.new(10, "EUR")
678
- d.save!
679
- d.reload
680
- expect(d.currency).to eq("EUR")
681
- end
682
-
683
- it "overrides default currency with the value of row currency" do
684
- expect(transaction.amount.currency).to eq(Money::Currency.find(:usd))
685
- end
686
-
687
- it "overrides default currency with the currency registered for the model" do
688
- expect(dummy_product_with_nil_currency.price.currency).to eq(
689
- Money::Currency.find(:gbp)
690
- )
691
- end
692
-
693
- it "overrides default currency with the currency registered for the model if currency is invalid" do
694
- expect(dummy_product_with_invalid_currency.price.currency).to eq(
695
- Money::Currency.find(:gbp)
696
- )
697
- end
698
-
699
- it "overrides default and model currency with the row currency" do
700
- expect(dummy_product.price.currency).to eq(Money::Currency.find(:usd))
701
- end
702
-
703
- it "constructs the money attribute from the stored mapped attribute values" do
704
- expect(transaction.amount).to eq(Money.new(2400, :usd))
705
- end
706
-
707
- it "correctly instantiates Money objects from the mapped attributes" do
708
- t = Transaction.new(amount_cents: 2500, currency: "CAD")
709
- expect(t.amount).to eq(Money.new(2500, "CAD"))
710
- end
711
-
712
- it "correctly assigns Money objects to the attribute" do
713
- transaction.amount = Money.new(2500, :eur)
714
- expect(transaction.save).to be_truthy
715
- expect(transaction.amount.cents).to eq(Money.new(2500, :eur).cents)
716
- expect(transaction.amount.currency.to_s).to eq("EUR")
717
- end
718
-
719
- it "uses default currency if a non Money object is assigned to the attribute" do
720
- transaction.amount = 234
721
- expect(transaction.amount.currency.to_s).to eq("USD")
722
- end
723
-
724
- it "constructs the money object from the mapped method value" do
725
- expect(transaction.total).to eq(Money.new(3000, :usd))
726
- end
727
-
728
- it "allows currency column postfix to be blank" do
729
- allow(MoneyRails::Configuration).to receive(:currency_column) { { postfix: nil, column_name: 'currency' } }
730
- expect(dummy_product_with_nil_currency.price.currency).to eq(Money::Currency.find(:gbp))
731
- end
732
-
733
- it "updates inferred currency column based on currency column postfix" do
734
- product.reduced_price = Money.new(999_00, 'CAD')
735
- product.save
736
-
737
- expect(product.reduced_price_cents).to eq(999_00)
738
- expect(product.reduced_price_currency).to eq('CAD')
739
- end
740
-
741
- context "and field with allow_nil: true" do
742
- it "doesn't set currency to nil when setting the field to nil" do
743
- t = Transaction.new(amount_cents: 2500, currency: "CAD")
744
- t.optional_amount = nil
745
- expect(t.currency).to eq("CAD")
746
- end
747
- end
748
-
749
- # TODO: these specs should mock locale_backend with expected values
750
- # instead of manipulating it directly
751
- context "and an Italian locale" do
752
- around(:each) do |example|
753
- I18n.with_locale(:it) do
754
- example.run
755
- end
756
- end
757
-
758
- context "when using :i18n locale backend" do
759
- it "validates with the locale's decimal mark" do
760
- transaction.amount = "123,45"
761
- expect(transaction.valid?).to be_truthy
762
- end
763
-
764
- it "does not validate with the currency's decimal mark" do
765
- transaction.amount = "123.45"
766
- expect(transaction.valid?).to be_falsey
767
- end
768
-
769
- it "validates with the locale's currency symbol" do
770
- transaction.amount = "€123"
771
- expect(transaction.valid?).to be_truthy
772
- end
773
-
774
- it "does not validate with the transaction's currency symbol" do
775
- transaction.amount = "$123.45"
776
- expect(transaction.valid?).to be_falsey
777
- end
778
- end
779
-
780
- context "when using :currency locale backend" do
781
- around(:each) do |example|
782
- begin
783
- Money.locale_backend = :currency
784
- example.run
785
- ensure
786
- Money.locale_backend = :i18n
787
- end
788
- end
789
-
790
- it "does not validate with the locale's decimal mark" do
791
- transaction.amount = "123,45"
792
- expect(transaction.valid?).to be_falsey
793
- end
794
-
795
- it "validates with the currency's decimal mark" do
796
- transaction.amount = "123.45"
797
- expect(transaction.valid?).to be_truthy
798
- end
799
-
800
- it "does not validate with the locale's currency symbol" do
801
- transaction.amount = "€123"
802
- expect(transaction.valid?).to be_falsey
803
- end
804
-
805
- it "validates with the transaction's currency symbol" do
806
- transaction.amount = "$123"
807
- expect(transaction.valid?).to be_truthy
808
- end
809
- end
810
- end
811
- end
812
- end
813
-
814
- describe ".register_currency" do
815
- it "attaches currency at model level" do
816
- expect(Product.currency).to eq(Money::Currency.find(:usd))
817
- expect(DummyProduct.currency).to eq(Money::Currency.find(:gbp))
818
- end
819
- end
820
-
821
- describe "#read_monetized" do
822
- it "returns monetized attribute's value" do
823
- reduced_price = product.read_monetized(:reduced_price, :reduced_price_cents)
824
-
825
- expect(reduced_price).to be_an_instance_of(Money)
826
- expect(reduced_price).to eq(Money.new(product.reduced_price_cents, product.reduced_price_currency))
827
- end
828
-
829
- context "memoize" do
830
- it "memoizes monetized attribute's value" do
831
- product.instance_variable_set '@reduced_price', nil
832
- reduced_price = product.read_monetized(:reduced_price, :reduced_price_cents)
833
-
834
- expect(product.instance_variable_get('@reduced_price')).to eq(reduced_price)
835
- end
836
-
837
- it "resets memoized attribute's value if amount has changed" do
838
- reduced_price = product.read_monetized(:reduced_price, :reduced_price_cents)
839
- product.reduced_price_cents = 100
840
-
841
- expect(product.read_monetized(:reduced_price, :reduced_price_cents)).not_to eq(reduced_price)
842
- end
843
-
844
- it "resets memoized attribute's value if currency has changed" do
845
- reduced_price = product.read_monetized(:reduced_price, :reduced_price_cents)
846
- product.reduced_price_currency = 'CAD'
847
-
848
- expect(product.read_monetized(:reduced_price, :reduced_price_cents)).not_to eq(reduced_price)
849
- end
850
- end
851
-
852
- context "with preserve_user_input set" do
853
- around(:each) do |example|
854
- MoneyRails::Configuration.preserve_user_input = true
855
- example.run
856
- MoneyRails::Configuration.preserve_user_input = false
857
- end
858
-
859
- it "has no effect if validation passes" do
860
- product.price = '14'
861
-
862
- expect(product.save).to be_truthy
863
- expect(product.read_monetized(:price, :price_cents).to_s).to eq('14.00')
864
- end
865
-
866
- it "preserves user input if validation fails" do
867
- product.price = '14,0'
868
-
869
- expect(product.save).to be_falsy
870
- expect(product.read_monetized(:price, :price_cents).to_s).to eq('14,0')
871
- end
872
- end
873
-
874
- context "with a monetized attribute that is nil" do
875
- let(:service) do
876
- Service.create(discount_cents: nil)
877
- end
878
- let(:default_currency_lambda) { double("Default Currency Fallback") }
879
- subject { service.read_monetized(:discount, :discount_cents, options) }
880
-
881
- around(:each) do |example|
882
- service # Instantiate instance which relies on Money.default_currency
883
- original_default_currency = Money.default_currency
884
- Money.default_currency = -> { default_currency_lambda.read_currency }
885
- example.run
886
- Money.default_currency = original_default_currency
887
- end
888
-
889
- context "when allow_nil options is set" do
890
- let(:options) { { allow_nil: true } }
891
- it "does not attempt to read the fallback default currency" do
892
- expect(default_currency_lambda).not_to receive(:read_currency)
893
- subject
894
- end
895
- end
896
- end
897
- end
898
-
899
- describe "#write_monetized" do
900
- let(:value) { Money.new(1_000, 'LVL') }
901
-
902
- it "sets monetized attribute's value to Money object" do
903
- product.write_monetized :price, :price_cents, value, false, nil, {}
904
-
905
- expect(product.price).to be_an_instance_of(Money)
906
- expect(product.price_cents).to eq(value.cents)
907
- # Because :price does not have a column for currency
908
- expect(product.price.currency).to eq(Product.currency)
909
- end
910
-
911
- it "sets monetized attribute's value from a given Fixnum" do
912
- product.write_monetized :price, :price_cents, 10, false, nil, {}
913
-
914
- expect(product.price).to be_an_instance_of(Money)
915
- expect(product.price_cents).to eq(1000)
916
- end
917
-
918
- it "sets monetized attribute's value from a given Float" do
919
- product.write_monetized :price, :price_cents, 10.5, false, nil, {}
920
-
921
- expect(product.price).to be_an_instance_of(Money)
922
- expect(product.price_cents).to eq(1050)
923
- end
924
-
925
- it "resets monetized attribute when given blank input" do
926
- product.write_monetized :price, :price_cents, nil, false, nil, { allow_nil: true }
927
-
928
- expect(product.price).to eq(nil)
929
- end
930
-
931
- it "sets monetized attribute to 0 when given a blank value" do
932
- currency = product.price.currency
933
- product.write_monetized :price, :price_cents, nil, false, nil, {}
934
-
935
- expect(product.price.amount).to eq(0)
936
- expect(product.price.currency).to eq(currency)
937
- end
938
-
939
- it "does not memoize monetized attribute's value if currency is read-only" do
940
- product.write_monetized :price, :price_cents, value, false, nil, {}
941
-
942
- price = product.instance_variable_get('@price')
943
-
944
- expect(price).to be_an_instance_of(Money)
945
- expect(price.amount).not_to eq(value.amount)
946
- end
947
-
948
- describe "instance_currency_name" do
949
- it "updates instance_currency_name attribute" do
950
- product.write_monetized :sale_price, :sale_price_amount, value, false, :sale_price_currency_code, {}
951
-
952
- expect(product.sale_price).to eq(value)
953
- expect(product.sale_price_currency_code).to eq('LVL')
954
- end
955
-
956
- it "memoizes monetized attribute's value with currency" do
957
- product.write_monetized :sale_price, :sale_price_amount, value, false, :sale_price_currency_code, {}
958
-
959
- expect(product.instance_variable_get('@sale_price')).to eq(value)
960
- end
961
-
962
- it "ignores empty instance_currency_name" do
963
- product.write_monetized :sale_price, :sale_price_amount, value, false, '', {}
964
-
965
- expect(product.sale_price.amount).to eq(value.amount)
966
- expect(product.sale_price.currency).to eq(Product.currency)
967
- end
968
-
969
- it "ignores instance_currency_name that model does not respond to" do
970
- product.write_monetized :sale_price, :sale_price_amount, value, false, :non_existing_currency, {}
971
-
972
- expect(product.sale_price.amount).to eq(value.amount)
973
- expect(product.sale_price.currency).to eq(Product.currency)
974
- end
975
- end
976
-
977
- describe "error handling" do
978
- let!(:old_price_value) { product.price }
979
-
980
- it "ignores values that do not implement to_money method" do
981
- product.write_monetized :price, :price_cents, [10], false, nil, {}
982
-
983
- expect(product.price).to eq(old_price_value)
984
- end
985
-
986
- context "raise_error_on_money_parsing enabled" do
987
- before { MoneyRails.raise_error_on_money_parsing = true }
988
- after { MoneyRails.raise_error_on_money_parsing = false }
989
-
990
- it "raises a MoneyRails::Error when given an invalid value" do
991
- expect {
992
- product.write_monetized :price, :price_cents, '10-50', false, nil, {}
993
- }.to raise_error(MoneyRails::Error)
994
- end
995
-
996
- it "raises a MoneyRails::Error error when trying to set invalid currency" do
997
- allow(product).to receive(:currency_for_price).and_return('INVALID_CURRENCY')
998
- expect {
999
- product.write_monetized :price, :price_cents, 10, false, nil, {}
1000
- }.to raise_error(MoneyRails::Error)
1001
- end
1002
- end
1003
-
1004
- context "raise_error_on_money_parsing disabled" do
1005
- it "ignores when given invalid value" do
1006
- product.write_monetized :price, :price_cents, '10-50', false, nil, {}
1007
-
1008
- expect(product.price).to eq(old_price_value)
1009
- end
1010
-
1011
- it "raises a MoneyRails::Error error when trying to set invalid currency" do
1012
- allow(product).to receive(:currency_for_price).and_return('INVALID_CURRENCY')
1013
- product.write_monetized :price, :price_cents, 10, false, nil, {}
1014
-
1015
- # Can not use public accessor here because currency_for_price is stubbed
1016
- expect(product.instance_variable_get('@price')).to eq(old_price_value)
1017
- end
1018
- end
1019
- end
1020
- end
1021
-
1022
- describe "#currency_for" do
1023
- it "detects currency based on instance currency name" do
1024
- product = Product.new(sale_price_currency_code: 'CAD')
1025
- currency = product.send(:currency_for, :sale_price, :sale_price_currency_code, nil)
1026
-
1027
- expect(currency).to be_an_instance_of(Money::Currency)
1028
- expect(currency.iso_code).to eq('CAD')
1029
- end
1030
-
1031
- it "detects currency based on currency passed as a block" do
1032
- product = Product.new
1033
- currency = product.send(:currency_for, :lambda_price, nil, ->(_) { 'CAD' })
1034
-
1035
- expect(currency).to be_an_instance_of(Money::Currency)
1036
- expect(currency.iso_code).to eq('CAD')
1037
- end
1038
-
1039
- it "detects currency based on currency passed explicitly" do
1040
- product = Product.new
1041
- currency = product.send(:currency_for, :bonus, nil, 'CAD')
1042
-
1043
- expect(currency).to be_an_instance_of(Money::Currency)
1044
- expect(currency.iso_code).to eq('CAD')
1045
- end
1046
-
1047
- it "falls back to a registered currency" do
1048
- product = Product.new
1049
- currency = product.send(:currency_for, :amount, nil, nil)
1050
-
1051
- expect(currency).to eq(Product.currency)
1052
- end
1053
-
1054
- it "falls back to a default currency" do
1055
- transaction = Transaction.new
1056
- currency = transaction.send(:currency_for, :amount, nil, nil)
1057
-
1058
- expect(currency).to eq(Money.default_currency)
1059
- end
1060
- end
1061
- end
1062
- end