money-rails 0.7.1 → 0.8.1

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.
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## master
4
+
5
+ ## 0.8.0
6
+ - Added defaults for amount and currency columns in database schema, based on the default currency.
7
+ - Use a better default subunit_unit name (choose the value of column.postfix set in the config).
8
+ - Began support of Rails 4.
9
+ - Added global settings for money object formatted output (:no_cents_if_whole, :symbol options).
10
+ - Enhanced money validator.
11
+ - Added ability to use numericality validations on monetize (GH-70).
12
+ - Fixed error caused by ActiveSupport::HashWithIndifferentAccess
13
+ (GH-62).
14
+ - Added money-rails test helper (rspec matcher).
15
+
3
16
  ## 0.7.1
4
17
  - Fix error when instantiating new model in mongoid extension (GH-60)
5
18
 
data/README.md CHANGED
@@ -44,29 +44,6 @@ configuration parameters for the rails app.
44
44
 
45
45
  ### ActiveRecord
46
46
 
47
- #### Migration helpers
48
-
49
- If you want to add money field to product model you may use ```add_money``` helper. That
50
- helper might be customized inside ```MoneyRails.configure``` block. You should customize
51
- ```add_money``` helper to match the most common use case and utilize it across all migrations.
52
-
53
- ```ruby
54
- class MonetizeProduct < ActiveRecord::Migration
55
- def change
56
- add_money :products, :price
57
-
58
- # OR
59
-
60
- change_table :products do |t|
61
- t.money :price
62
- end
63
- end
64
- end
65
- ```
66
-
67
- ```add_money``` helper is revertable, so you may use it inside ```change``` migrations.
68
- If you writing separate ```up``` and ```down``` methods, you may use ```remove_money``` helper.
69
-
70
47
  #### Usage example
71
48
 
72
49
  For example, we create a Product model which has an integer price_cents column
@@ -98,6 +75,39 @@ Now the model objects will have a ```discount``` attribute which
98
75
  is a Money object, wrapping the value of ```discount_subunit``` column to a
99
76
  Money instance.
100
77
 
78
+ #### Migration helpers
79
+
80
+ If you want to add money field to product model you may use ```add_money``` helper. That
81
+ helper might be customized inside ```MoneyRails.configure``` block. You should customize
82
+ ```add_money``` helper to match the most common use case and utilize it across all migrations.
83
+
84
+ ```ruby
85
+ class MonetizeProduct < ActiveRecord::Migration
86
+ def change
87
+ add_money :products, :price
88
+
89
+ # OR
90
+
91
+ change_table :products do |t|
92
+ t.money :price
93
+ end
94
+ end
95
+ end
96
+ ```
97
+
98
+ Another example where the currency column is not including:
99
+
100
+ ```ruby
101
+ class MonetizeItem < ActiveRecord::Migration
102
+ def change
103
+ add_money :items, :price, currency: { present: false }
104
+ end
105
+ end
106
+ ```
107
+
108
+ ```add_money``` helper is revertable, so you may use it inside ```change``` migrations.
109
+ If you writing separate ```up``` and ```down``` methods, you may use ```remove_money``` helper.
110
+
101
111
  #### Allow nil values
102
112
 
103
113
  If you want to allow the assignment of nil and/or blank values to a specific
@@ -114,6 +124,20 @@ product.optional_price # => nil
114
124
  product.optional_price_cents # => nil
115
125
  ```
116
126
 
127
+ #### Numericality validation options
128
+
129
+ You can also pass along
130
+ [numericality validation options](http://guides.rubyonrails.org/active_record_validations_callbacks.html#numericality)
131
+ such as this:
132
+
133
+ ```ruby
134
+ monetize :price_in_a_range_cents, :allow_nil => true,
135
+ :numericality => {
136
+ :greater_than_or_equal_to => 0,
137
+ :less_than_or_equal_to => 10000
138
+ }
139
+ ```
140
+
117
141
  ### Mongoid 2.x and 3.x
118
142
 
119
143
  `Money` is available as a field type to supply during a field definition:
@@ -290,6 +314,26 @@ MoneyRails.configure do |config|
290
314
  #
291
315
  config.include_validations = true
292
316
 
317
+ # Default ActiveRecord migration configuration values for columns:
318
+ #
319
+ # config.amount_column = { prefix: '', # column name prefix
320
+ # postfix: '_cents', # column name postfix
321
+ # column_name: nil, # full column name (overrides prefix, postfix and accessor name)
322
+ # type: :integer, # column type
323
+ # present: true, # column will be created
324
+ # null: false, # other options will be treated as column options
325
+ # default: 0
326
+ # }
327
+ #
328
+ # config.currency_column = { prefix: '',
329
+ # postfix: '_currency',
330
+ # column_name: nil,
331
+ # type: :string,
332
+ # present: true,
333
+ # null: false,
334
+ # default: 'USD'
335
+ # }
336
+
293
337
  # Register a custom currency
294
338
  #
295
339
  # config.register_currency = {
@@ -303,10 +347,17 @@ MoneyRails.configure do |config|
303
347
  # :thousands_separator => ".",
304
348
  # :decimal_mark => ","
305
349
  # }
350
+
351
+ # Set money formatted output globally.
352
+ # Default value is nil meaning "ignore this option".
353
+ # Options are nil, true, false.
354
+ #
355
+ # config.no_cents_if_whole = nil
356
+ # config.symbol = nil
306
357
  end
307
358
  ```
308
359
 
309
- * ```default_currecy```: Set the default (application wide) currency (USD is the default)
360
+ * ```default_currency```: Set the default (application wide) currency (USD is the default)
310
361
  * ```include_validations```: Permit the inclusion of a ```validates_numericality_of```
311
362
  validation for each monetized field (the default is true)
312
363
  * ```register_currency```: Register one custom currency. This option can be
@@ -318,6 +369,11 @@ end
318
369
  only! This rate is added to the attached bank object.
319
370
  * ```default_bank```: The default bank object holding exchange rates etc.
320
371
  (https://github.com/RubyMoney/money#currency-exchange)
372
+ * ```no_cents_if_whole```: Force `Money#format` method to use its value as the default for ```no_cents_if_whole``` key.
373
+ * ```symbol```: Use its value as the default for ```symbol``` key in
374
+ `Money#format` method.
375
+ * ```amount_column```: Provide values for the amount column (holding the fractional part of a money object).
376
+ * ```currency_column```: Provide default values or even disable (`present: false`) the currency column.
321
377
 
322
378
  ### Helpers
323
379
 
@@ -335,7 +391,7 @@ This will render a `span` dom element with the default currency symbol.
335
391
  ```
336
392
  This will render a formatted money value without the currency symbol and
337
393
  without the cents part if it contains only zeros (uses
338
- `:no_cents_fi_whole flag`).
394
+ `:no_cents_if_whole flag`).
339
395
 
340
396
  * humanize with symbol helper
341
397
 
@@ -362,6 +418,36 @@ without the cents part.
362
418
  This will render a formatted money value including the currency symbol and
363
419
  without the cents part.
364
420
 
421
+ ### Testing
422
+
423
+ If you use Rspec there is an test helper implementation.
424
+ Just write `require "money-rails/test_helpers"` in spec_helper.rb and
425
+ `include MoneyRails::TestHelpers` inside a describe block you want to
426
+ use the helper.
427
+
428
+ * the `monetize` matcher
429
+
430
+ ```
431
+ monetize(:price_cents).should be_true
432
+ ```
433
+ This will ensure that a column called `price_cents` is being monetized.
434
+
435
+ ```
436
+ monetize(:price_cents).as(:discount_value).should be_true
437
+ ```
438
+ By using `as` chain you can specify the exact name to which a monetized
439
+ column is being mapped.
440
+
441
+ ```
442
+ monetize(:price_cents).with_currency(:gbp).should be_true
443
+ ```
444
+
445
+ By using the `with_currency` chain you can specify the expected currency
446
+ for the chosen money attribute. (You can also combine all the chains.)
447
+
448
+ For examples on using the test_helpers look at
449
+ [test_helpers_spec.rb](https://github.com/RubyMoney/money-rails/blob/master/spec/test_helpers_spec.rb)
450
+
365
451
  ## Supported ORMs/ODMs
366
452
 
367
453
  * ActiveRecord (>= 3.x)
data/Rakefile CHANGED
@@ -54,7 +54,7 @@ namespace :spec do
54
54
  end
55
55
  end
56
56
 
57
- desc "Update AUTHORS file"
58
- task :authors do
59
- sh "git shortlog -s | awk '{ print $2 \" \" $3 }' > AUTHORS"
57
+ desc "Update CONTRIBUTORS file"
58
+ task :contributors do
59
+ sh "git shortlog -s | awk '{ print $2 \" \" $3 }' > CONTRIBUTORS"
60
60
  end
@@ -4,7 +4,7 @@ MoneyRails.configure do |config|
4
4
 
5
5
  # To set the default currency
6
6
  #
7
- #config.default_currency = :usd
7
+ # config.default_currency = :usd
8
8
 
9
9
  # Set default bank object
10
10
  #
@@ -21,7 +21,7 @@ MoneyRails.configure do |config|
21
21
  # To handle the inclusion of validations for monetized fields
22
22
  # The default value is true
23
23
  #
24
- #config.include_validations = true
24
+ # config.include_validations = true
25
25
 
26
26
  # Default ActiveRecord migration configuration values for columns:
27
27
  #
@@ -58,4 +58,10 @@ MoneyRails.configure do |config|
58
58
  # :decimal_mark => ","
59
59
  # }
60
60
 
61
+ # Set money formatted output globally.
62
+ # Default value is nil meaning "ignore this option".
63
+ # Options are nil, true, false.
64
+ #
65
+ # config.no_cents_if_whole = nil
66
+ # config.symbol = nil
61
67
  end
@@ -1,5 +1,6 @@
1
1
  require "money"
2
2
  require "money-rails/configuration"
3
+ require "money-rails/money"
3
4
  require "money-rails/version"
4
5
  require 'money-rails/hooks'
5
6
 
@@ -23,15 +23,17 @@ module MoneyRails
23
23
  thousands_separator = I18n.t('number.currency.format.delimiter', default: currency.thousands_separator)
24
24
  symbol = I18n.t('number.currency.format.unit', default: currency.symbol)
25
25
 
26
- raw_value = raw_value.to_s.gsub(symbol, "").gsub(/^-/, "")
26
+ raw_value = raw_value.to_s.gsub(symbol, "")
27
+ abs_raw_value = raw_value.gsub(/^-/, "")
27
28
 
28
- decimal_pieces = raw_value.split(decimal_mark)
29
+ decimal_pieces = abs_raw_value.split(decimal_mark)
29
30
 
30
- # check for numbers like 12.23.45
31
- if decimal_pieces.length > 2
31
+ # check for numbers like '12.23.45' or '....'
32
+ unless [1, 2].include? decimal_pieces.length
32
33
  record.errors.add(attr, I18n.t('errors.messages.invalid_currency',
33
34
  { :thousands => thousands_separator,
34
35
  :decimal => decimal_mark }))
36
+ return
35
37
  end
36
38
 
37
39
  pieces = decimal_pieces[0].split(thousands_separator)
@@ -48,11 +50,12 @@ module MoneyRails
48
50
  end
49
51
  end
50
52
 
51
- # remove thousands separators
52
- raw_value = raw_value.to_s.gsub(thousands_separator, '')
53
-
54
- # normalize decimal mark
55
- raw_value = raw_value.to_s.gsub(decimal_mark, '.')
53
+ # Remove thousands separators, normalize decimal mark,
54
+ # remove whitespaces and _ (E.g. 99 999 999 or 12_300_200.20)
55
+ raw_value = raw_value.to_s
56
+ .gsub(thousands_separator, '')
57
+ .gsub(decimal_mark, '.')
58
+ .gsub(/[\s_]/, '')
56
59
  end
57
60
  super(record, attr, raw_value)
58
61
  end
@@ -35,12 +35,12 @@ module MoneyRails
35
35
 
36
36
  # Form target name for the money backed ActiveModel field:
37
37
  # if a target name is provided then use it
38
- # if there is a "_cents" suffix then just remove it to create the target name
38
+ # if there is a "_{column.postfix}" suffix then just remove it to create the target name
39
39
  # if none of the previous is the case then use a default suffix
40
40
  if name
41
41
  name = name.to_s
42
- elsif subunit_name =~ /_cents$/
43
- name = subunit_name.sub(/_cents$/, "")
42
+ elsif subunit_name =~ /#{MoneyRails::Configuration.amount_column[:postfix]}$/
43
+ name = subunit_name.sub(/#{MoneyRails::Configuration.amount_column[:postfix]}$/, "")
44
44
  else
45
45
  # FIXME: provide a better default
46
46
  name = [subunit_name, "money"].join("_")
@@ -57,10 +57,17 @@ module MoneyRails
57
57
 
58
58
  # Include numericality validation if needed
59
59
  if MoneyRails.include_validations
60
- validates_numericality_of subunit_name, :allow_nil => options[:allow_nil]
61
-
60
+ validation_options = {
61
+ :allow_nil => options[:allow_nil],
62
+ :numericality => true
63
+ }
64
+ validates subunit_name, validation_options
65
+
66
+ validation_options = { :allow_nil => options[:allow_nil] }
67
+ validation_options = options[:numericality].merge(validation_options) if options[:numericality]
68
+
62
69
  # Allow only Money objects or Numeric values!
63
- validates name.to_sym, 'money_rails/active_model/money' => { :allow_nil => options[:allow_nil] }
70
+ validates name.to_sym, 'money_rails/active_model/money' => validation_options
64
71
  end
65
72
 
66
73
  define_method name do |*args|
@@ -1,5 +1,6 @@
1
1
  require 'active_support/core_ext/module/delegation'
2
2
  require 'active_support/core_ext/module/attribute_accessors'
3
+ require 'active_support/core_ext/string/inflections'
3
4
 
4
5
  module MoneyRails
5
6
 
@@ -19,9 +20,15 @@ module MoneyRails
19
20
 
20
21
  # Configuration parameters
21
22
 
23
+ def default_currency
24
+ Money.default_currency
25
+ end
26
+
22
27
  # Set default currency of money library
23
28
  def default_currency=(currency_name)
24
29
  Money.default_currency = Money::Currency.new(currency_name)
30
+ set_amount_column_for_default_currency!
31
+ set_currency_column_for_default_currency!
25
32
  end
26
33
 
27
34
  # Register a custom currency
@@ -29,6 +36,15 @@ module MoneyRails
29
36
  Money::Currency.register(currency_options)
30
37
  end
31
38
 
39
+ def set_amount_column_for_default_currency!
40
+ amount_column.merge! postfix: "_#{default_currency.subunit.downcase.pluralize}" if default_currency.subunit
41
+ end
42
+
43
+ def set_currency_column_for_default_currency!
44
+ iso_code = default_currency.iso_code
45
+ currency_column.merge! default: iso_code
46
+ end
47
+
32
48
  # Set default bank object
33
49
  #
34
50
  # example (given that eu_central_bank is in Gemfile):
@@ -50,5 +66,12 @@ module MoneyRails
50
66
 
51
67
  mattr_accessor :currency_column
52
68
  @@currency_column = { postfix: '_currency', type: :string, null: false, default: 'USD', present: true }
69
+
70
+ # Use nil values to ignore defaults
71
+ mattr_accessor :no_cents_if_whole
72
+ @@no_cents_if_whole = nil
73
+
74
+ mattr_accessor :symbol
75
+ @@symbol = nil
53
76
  end
54
77
  end
@@ -5,32 +5,47 @@ module MoneyRails
5
5
  content_tag(:span, Money.default_currency.symbol, :class => "currency_symbol")
6
6
  end
7
7
 
8
- def humanized_money(value, symbol=false)
8
+ def humanized_money(value, options={})
9
+ if !options || !options.is_a?(Hash)
10
+ warn "humanized_money now takes a hash of formatting options, please specify { :symbol => true }"
11
+ options = { :symbol => options }
12
+ end
13
+
14
+ options = {
15
+ :no_cents_if_whole => true,
16
+ :symbol => false
17
+ }.merge(options)
18
+
9
19
  if value.is_a?(Money)
10
- value.format(:no_cents_if_whole => true, :symbol => symbol)
20
+ value.format(options)
11
21
  elsif value.respond_to?(:to_money)
12
- value.to_money.format(:no_cents_if_whole => true, :symbol => symbol)
22
+ value.to_money.format(options)
13
23
  else
14
24
  ""
15
25
  end
16
26
  end
17
27
 
18
28
  def humanized_money_with_symbol(value)
19
- humanized_money(value, true)
29
+ humanized_money(value, :symbol => true)
20
30
  end
21
31
 
22
- def money_without_cents(value, symbol=false)
23
- if value.is_a?(Money)
24
- value.format(:no_cents => true, :symbol => symbol)
25
- elsif value.respond_to?(:to_money)
26
- value.to_money.format(:no_cents => true, :symbol => symbol)
27
- else
28
- ""
32
+ def money_without_cents(value, options={})
33
+ if !options || !options.is_a?(Hash)
34
+ warn "money_without_cents now takes a hash of formatting options, please specify { :symbol => true }"
35
+ options = { :symbol => options }
29
36
  end
37
+
38
+ options = {
39
+ :no_cents => true,
40
+ :no_cents_if_whole => false,
41
+ :symbol => false
42
+ }.merge(options)
43
+
44
+ humanized_money(value, options)
30
45
  end
31
46
 
32
47
  def money_without_cents_and_with_symbol(value)
33
- money_without_cents(value, true)
48
+ money_without_cents(value, :symbol => true)
34
49
  end
35
50
  end
36
51
  end
@@ -18,10 +18,8 @@ module MoneyRails
18
18
  if defined? ::Mongoid
19
19
  if ::Mongoid::VERSION =~ /^2(.*)/
20
20
  require 'money-rails/mongoid/two' # Loading the file is enough
21
- end
22
-
23
- if ::Mongoid::VERSION =~ /^3(.*)/
24
- require 'money-rails/mongoid/three'
21
+ else
22
+ require 'money-rails/mongoid/money'
25
23
  end
26
24
  end
27
25