money-rails 0.7.1 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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