money-rails 0.8.1 → 0.9.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.
@@ -1,8 +1,24 @@
1
1
  # Changelog
2
2
 
3
- ## master
4
-
5
- ## 0.8.0
3
+ ## master (next release)
4
+ - Add testing tasks for rails 4.x
5
+ - Fix issue with Numeric values in MoneyValidator (GH-83).
6
+ - Fix test helper
7
+ - Fix issue with money validator (GH-102).
8
+ - Change validation logic. Support Subunit and Money field
9
+ validation with NumericalityValidator options.
10
+ Moreover, now Money objects are normalized and pass through
11
+ all validation steps.
12
+ - Add support for the global configuration of the sign_before_setting formatting option.
13
+
14
+ ## 0.8.1
15
+ - Remove unnecessary files from gem build.
16
+ - Add options to ActionView helpers that enable the usage of any of the rules ::Money.format allows.
17
+ - Fix "setting amount_column for default_currency" to only accept
18
+ postfix for default_currency with subunit.
19
+ - Add mongoid 4 support.
20
+
21
+ ## 0.8.0 (yanked)
6
22
  - Added defaults for amount and currency columns in database schema, based on the default currency.
7
23
  - Use a better default subunit_unit name (choose the value of column.postfix set in the config).
8
24
  - Began support of Rails 4.
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Build Status](https://secure.travis-ci.org/RubyMoney/money-rails.png?branch=master)](http://travis-ci.org/RubyMoney/money-rails)
4
4
  [![Dependency Status](https://gemnasium.com/RubyMoney/money-rails.png)](https://gemnasium.com/RubyMoney/money-rails)
5
- [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/RubyMoney/money-rails)
5
+ [![Code Climate](https://codeclimate.com/github/RubyMoney/money-rails.png)](https://codeclimate.com/github/RubyMoney/money-rails)
6
6
 
7
7
  ## Introduction
8
8
 
@@ -40,6 +40,17 @@ $ rails g money_rails:initializer
40
40
  There, you can define the default currency value and set other
41
41
  configuration parameters for the rails app.
42
42
 
43
+ ### Setup for money-rails development
44
+
45
+ Our tests are executed with several ORMs - see `Rakefile` for details. To install all required gems run these:
46
+
47
+ bundle install --gemfile=gemfiles/mongoid2.gemfile
48
+ bundle install --gemfile=gemfiles/mongoid3.gemfile
49
+ bundle install --gemfile=gemfiles/rails3.gemfile
50
+ bundle install --gemfile=gemfiles/rails4.gemfile
51
+
52
+ Then you can run the test suite with `rake`,
53
+
43
54
  ## Usage
44
55
 
45
56
  ### ActiveRecord
@@ -117,6 +128,11 @@ monetized field, you can use the `:allow_nil` parameter like this:
117
128
  # in Product model
118
129
  monetize :optional_price_cents, :allow_nil => true
119
130
 
131
+ # in Migration
132
+ def change
133
+ add_money :products, :optional_price, amount: { null: true, default: nil }
134
+ end
135
+
120
136
  # then blank assignments are permitted
121
137
  product.optional_price = nil
122
138
  product.save # returns without errors
@@ -127,7 +143,7 @@ product.optional_price_cents # => nil
127
143
  #### Numericality validation options
128
144
 
129
145
  You can also pass along
130
- [numericality validation options](http://guides.rubyonrails.org/active_record_validations_callbacks.html#numericality)
146
+ [numericality validation options](http://guides.rubyonrails.org/active_record_validations.html#numericality)
131
147
  such as this:
132
148
 
133
149
  ```ruby
@@ -138,6 +154,13 @@ monetize :price_in_a_range_cents, :allow_nil => true,
138
154
  }
139
155
  ```
140
156
 
157
+ Or, if you prefer, you can skip validations entirely for the attribute. This is useful if chosen attributes
158
+ are aggregate methods and you wish to avoid executing them on every record save.
159
+
160
+ ```ruby
161
+ monetize :price_in_a_range_cents, :disable_validation => true
162
+ ```
163
+
141
164
  ### Mongoid 2.x and 3.x
142
165
 
143
166
  `Money` is available as a field type to supply during a field definition:
@@ -354,6 +377,7 @@ MoneyRails.configure do |config|
354
377
  #
355
378
  # config.no_cents_if_whole = nil
356
379
  # config.symbol = nil
380
+ # config.sign_before_symbol = nil
357
381
  end
358
382
  ```
359
383
 
@@ -372,6 +396,7 @@ end
372
396
  * ```no_cents_if_whole```: Force `Money#format` method to use its value as the default for ```no_cents_if_whole``` key.
373
397
  * ```symbol```: Use its value as the default for ```symbol``` key in
374
398
  `Money#format` method.
399
+ * ```sign_before_symbol```: Force `Money#format` to place the negative sign before the currency symbol.
375
400
  * ```amount_column```: Provide values for the amount column (holding the fractional part of a money object).
376
401
  * ```currency_column```: Provide default values or even disable (`present: false`) the currency column.
377
402
 
@@ -467,4 +492,4 @@ You can see a full list of the currently supported interpreters in [travis.yml](
467
492
 
468
493
  ## License
469
494
 
470
- MIT License. Copyright 2012 RubyMoney.
495
+ MIT License. Copyright 2012-2013 RubyMoney.
data/Rakefile CHANGED
@@ -32,6 +32,18 @@ namespace :spec do
32
32
  sh "BUNDLE_GEMFILE='gemfiles/mongoid2.gemfile' bundle exec rake -t spec"
33
33
  end
34
34
 
35
+ desc "Run Tests against rails 4"
36
+ task :rails4 do
37
+ sh "BUNDLE_GEMFILE='gemfiles/rails4.gemfile' bundle --quiet"
38
+ sh "BUNDLE_GEMFILE='gemfiles/rails4.gemfile' bundle exec rake -t spec"
39
+ end
40
+
41
+ desc "Run Tests against rails 3"
42
+ task :rails3 do
43
+ sh "BUNDLE_GEMFILE='gemfiles/rails3.gemfile' bundle --quiet"
44
+ sh "BUNDLE_GEMFILE='gemfiles/rails3.gemfile' bundle exec rake -t spec"
45
+ end
46
+
35
47
  desc "Run Tests against activerecord"
36
48
  task :activerecord do
37
49
  sh "bundle --quiet"
@@ -48,9 +60,13 @@ namespace :spec do
48
60
  sh "BUNDLE_GEMFILE='gemfiles/mongoid2.gemfile' bundle --quiet"
49
61
  sh "BUNDLE_GEMFILE='gemfiles/mongoid2.gemfile' bundle exec rake -t spec"
50
62
 
51
- # ActiveRecord
52
- sh "bundle --quiet"
53
- sh "bundle exec rake -t spec"
63
+ # rails 4
64
+ sh "BUNDLE_GEMFILE='gemfiles/rails4.gemfile' bundle --quiet"
65
+ sh "BUNDLE_GEMFILE='gemfiles/rails4.gemfile' bundle exec rake -t spec"
66
+
67
+ # rails 3
68
+ sh "BUNDLE_GEMFILE='gemfiles/rails3.gemfile' bundle --quiet"
69
+ sh "BUNDLE_GEMFILE='gemfiles/rails3.gemfile' bundle exec rake -t spec"
54
70
  end
55
71
  end
56
72
 
@@ -9,21 +9,36 @@ module MoneyRails
9
9
  subunit_attr = record.class.monetized_attributes[attr.to_sym]
10
10
  return unless record.changed_attributes.keys.include? subunit_attr
11
11
 
12
+ raw_value = nil
13
+
12
14
  # WARNING: Currently this is only defined in ActiveRecord extension!
13
15
  before_type_cast = "#{attr}_money_before_type_cast"
14
- raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast.to_sym)
16
+ raw_value = record.send(before_type_cast) if record.respond_to?(
17
+ before_type_cast.to_sym)
15
18
 
16
- # Skip it if raw_value is already a Money object
17
- return if raw_value.is_a?(Money) || raw_value.nil?
19
+ # If raw value is nil and changed subunit is nil, then
20
+ # nil is a assigned value, elsewhere we should treat the
21
+ # subunit value as the one assigned.
22
+ if raw_value.nil?
23
+ if record.send(subunit_attr)
24
+ raw_value = record.send(subunit_attr)
25
+ end
26
+ end
18
27
 
19
- if !raw_value.blank?
28
+ return if options[:allow_nil] && raw_value.nil?
29
+
30
+ # Skip normalization for Numeric values
31
+ # which can directly be handled by NumericalityValidator
32
+ if raw_value.present? && !raw_value.is_a?(Numeric)
20
33
  # remove currency symbol, and negative sign
21
34
  currency = record.send("currency_for_#{attr}")
22
- decimal_mark = I18n.t('number.currency.format.separator', default: currency.decimal_mark)
23
- thousands_separator = I18n.t('number.currency.format.delimiter', default: currency.thousands_separator)
35
+ decimal_mark = I18n.t('number.currency.format.separator',
36
+ default: currency.decimal_mark)
37
+ thousands_separator = I18n.t('number.currency.format.delimiter',
38
+ default: currency.thousands_separator)
24
39
  symbol = I18n.t('number.currency.format.unit', default: currency.symbol)
25
40
 
26
- raw_value = raw_value.to_s.gsub(symbol, "")
41
+ raw_value = raw_value.to_s.strip.gsub(symbol, "")
27
42
  abs_raw_value = raw_value.gsub(/^-/, "")
28
43
 
29
44
  decimal_pieces = abs_raw_value.split(decimal_mark)
@@ -57,6 +72,7 @@ module MoneyRails
57
72
  .gsub(decimal_mark, '.')
58
73
  .gsub(/[\s_]/, '')
59
74
  end
75
+
60
76
  super(record, attr, raw_value)
61
77
  end
62
78
  end
@@ -21,10 +21,13 @@ module MoneyRails
21
21
  ":with_currency or :with_model_currency")
22
22
  end
23
23
 
24
- # Optional accessor to be run on an instance to detect currency
24
+ # Optional accessor to be run on an instance to detect currency
25
25
  instance_currency_name = options[:with_model_currency] ||
26
- options[:model_currency] || "currency"
27
- instance_currency_name = instance_currency_name.to_s
26
+ options[:model_currency] ||
27
+ MoneyRails::Configuration.currency_column[:column_name]
28
+
29
+ instance_currency_name = instance_currency_name &&
30
+ instance_currency_name.to_s
28
31
 
29
32
  # This attribute allows per column currency values
30
33
  # Overrides row and default currency
@@ -55,32 +58,76 @@ module MoneyRails
55
58
  end
56
59
  end unless respond_to? :monetized_attributes
57
60
 
58
- # Include numericality validation if needed
59
- if MoneyRails.include_validations
60
- validation_options = {
61
+ # Include numericality validations if needed.
62
+ # There are two validation options:
63
+ #
64
+ # 1. Subunit field validation (e.g. cents should be > 100)
65
+ # 2. Money field validation (e.g. euros should be > 10)
66
+ #
67
+ # All the options which are available for Rails numericality
68
+ # validation, are also available for both types.
69
+ # E.g.
70
+ # monetize :price_in_a_range_cents, :allow_nil => true,
71
+ # :subunit_numericality => {
72
+ # :greater_than_or_equal_to => 0,
73
+ # :less_than_or_equal_to => 10000,
74
+ # },
75
+ # :numericality => {
76
+ # :greater_than_or_equal_to => 0,
77
+ # :less_than_or_equal_to => 100,
78
+ # :message => "Must be greater than zero and less than $100"
79
+ # }
80
+ #
81
+ # To disable validation entirely, use :disable_validation, E.g:
82
+ # monetize :price_in_a_range_cents, :disable_validation => true
83
+ if MoneyRails.include_validations && !options[:disable_validation]
84
+
85
+ subunit_validation_options =
86
+ unless options.has_key? :subunit_numericality
87
+ true
88
+ else
89
+ options[:subunit_numericality]
90
+ end
91
+
92
+ money_validation_options =
93
+ unless options.has_key? :numericality
94
+ true
95
+ else
96
+ options[:numericality]
97
+ end
98
+
99
+ # This is a validation for the subunit
100
+ validates subunit_name, {
61
101
  :allow_nil => options[:allow_nil],
62
- :numericality => true
102
+ :numericality => subunit_validation_options
63
103
  }
64
- validates subunit_name, validation_options
65
104
 
66
- validation_options = { :allow_nil => options[:allow_nil] }
67
- validation_options = options[:numericality].merge(validation_options) if options[:numericality]
68
-
69
105
  # Allow only Money objects or Numeric values!
70
- validates name.to_sym, 'money_rails/active_model/money' => validation_options
106
+ validates name.to_sym, {
107
+ :allow_nil => options[:allow_nil],
108
+ 'money_rails/active_model/money' => money_validation_options
109
+ }
71
110
  end
72
111
 
73
112
  define_method name do |*args|
113
+
114
+ # Get the cents
74
115
  amount = send(subunit_name, *args)
116
+
117
+ # Get the currency object
75
118
  attr_currency = send("currency_for_#{name}")
76
119
 
77
- # Dont create a new Money instance if the values haven't changed
120
+ # Get the cached value
78
121
  memoized = instance_variable_get("@#{name}")
122
+
123
+ # Dont create a new Money instance if the values haven't been changed.
79
124
  return memoized if memoized && memoized.cents == amount &&
80
125
  memoized.currency == attr_currency
81
126
 
127
+ # If amount is NOT nil (or empty string) load the amount in a Money
82
128
  amount = Money.new(amount, attr_currency) unless amount.blank?
83
129
 
130
+ # Cache and return the value (it may be nil)
84
131
  instance_variable_set "@#{name}", amount
85
132
  end
86
133
 
@@ -89,6 +136,7 @@ module MoneyRails
89
136
  # Lets keep the before_type_cast value
90
137
  instance_variable_set "@#{name}_money_before_type_cast", value
91
138
 
139
+ # Use nil or get a Money object
92
140
  if options[:allow_nil] && value.blank?
93
141
  money = nil
94
142
  else
@@ -99,14 +147,27 @@ module MoneyRails
99
147
  end
100
148
  end
101
149
 
150
+ # Update cents
102
151
  send("#{subunit_name}=", money.try(:cents))
103
- send("#{instance_currency_name}=", money.try(:currency).try(:iso_code)) if self.respond_to?("#{instance_currency_name}=")
104
152
 
153
+ # Update currency iso value if there is an instance currency attribute
154
+ if instance_currency_name.present? &&
155
+ self.respond_to?("#{instance_currency_name}=")
156
+
157
+ send("#{instance_currency_name}=",
158
+ money.try(:currency).try(:iso_code))
159
+ end
160
+
161
+ # Save and return the new Money object
105
162
  instance_variable_set "@#{name}", money
106
163
  end
107
164
 
108
165
  define_method "currency_for_#{name}" do
109
- if self.respond_to?(instance_currency_name) && send(instance_currency_name).present?
166
+ if instance_currency_name.present? &&
167
+ self.respond_to?(instance_currency_name) &&
168
+ send(instance_currency_name).present? &&
169
+ Money::Currency.find(send(instance_currency_name))
170
+
110
171
  Money::Currency.find(send(instance_currency_name))
111
172
  elsif field_currency_name
112
173
  Money::Currency.find(field_currency_name)
@@ -123,7 +184,7 @@ module MoneyRails
123
184
 
124
185
  # Hook to ensure the reset of before_type_cast attr
125
186
  # TODO: think of a better way to avoid this
126
- after_validation do
187
+ after_save do
127
188
  instance_variable_set "@#{name}_money_before_type_cast", nil
128
189
  end
129
190
  end
@@ -51,7 +51,7 @@ module MoneyRails
51
51
  # MoneyRails.configure do |config|
52
52
  # config.default_bank = EuCentralBank.new
53
53
  # end
54
- delegate :default_bank=, :to => :Money
54
+ delegate :default_bank=, :default_bank, :to => :Money
55
55
 
56
56
  # Provide exchange rates
57
57
  delegate :add_rate, :to => :Money
@@ -73,5 +73,8 @@ module MoneyRails
73
73
 
74
74
  mattr_accessor :symbol
75
75
  @@symbol = nil
76
+
77
+ mattr_accessor :sign_before_symbol
78
+ @@sign_before_symbol = nil
76
79
  end
77
80
  end
@@ -25,8 +25,8 @@ module MoneyRails
25
25
  end
26
26
  end
27
27
 
28
- def humanized_money_with_symbol(value)
29
- humanized_money(value, :symbol => true)
28
+ def humanized_money_with_symbol(value, options={})
29
+ humanized_money(value, options.merge(:symbol => true))
30
30
  end
31
31
 
32
32
  def money_without_cents(value, options={})
@@ -10,7 +10,8 @@ class Money
10
10
  # TODO: Add here more setting options
11
11
  defaults = {
12
12
  no_cents_if_whole: MoneyRails::Configuration.no_cents_if_whole,
13
- symbol: MoneyRails::Configuration.symbol
13
+ symbol: MoneyRails::Configuration.symbol,
14
+ sign_before_symbol: MoneyRails::Configuration.sign_before_symbol
14
15
  }.reject { |k,v| v.nil? }
15
16
 
16
17
  rules.reverse_merge!(defaults)
@@ -3,8 +3,8 @@ class Money
3
3
  # Converts an object of this instance into a database friendly value.
4
4
  def mongoize
5
5
  {
6
- :cents => cents,
7
- :currency_iso => currency.iso_code
6
+ :cents => cents.mongoize,
7
+ :currency_iso => currency.iso_code.mongoize
8
8
  }
9
9
  end
10
10
 
@@ -17,12 +17,12 @@ class Money
17
17
  def serialize(object)
18
18
  case
19
19
  when object.is_a?(Money)
20
- {
21
- :cents => object.cents,
22
- :currency_iso => object.currency.iso_code
23
- }
20
+ {
21
+ :cents => object.cents.is_a?(BigDecimal) ? object.cents.to_s : object.cents,
22
+ :currency_iso => object.currency.iso_code
23
+ }
24
24
  when object.respond_to?(:to_money)
25
- serialize(object.to_money)
25
+ serialize(object.to_money)
26
26
  else nil
27
27
  end
28
28
  end
@@ -17,29 +17,29 @@ module MoneyRails
17
17
  match do |target|
18
18
  matched = true
19
19
  money_attr = @as.presence || attr.to_s.sub(/_cents$/, "")
20
- matched = false unless target.send(money_attr).instance_of? Money
21
- if @currency_iso
22
- matched = false unless target.send(money_attr.to_sym).currency.id == @currency_iso
23
- end
20
+ matched = false if !target.respond_to?(money_attr) ||
21
+ !target.send(money_attr).instance_of?(Money) ||
22
+ (@currency_iso &&
23
+ target.send(money_attr.to_sym).currency.id != @currency_iso)
24
24
  matched
25
25
  end
26
26
 
27
27
  description do
28
- description = "monetize #{expected}"
28
+ description = "monetize #{attr}"
29
29
  description << " as #{@as}" if @as
30
30
  description << " with currency #{@currency_iso}" if @currency_iso
31
31
  description
32
32
  end
33
33
 
34
34
  failure_message_for_should do |actual|
35
- msg = "expected that #{actual} would be monetized"
35
+ msg = "expected that #{attr} of #{actual} would be monetized"
36
36
  msg << " as #{@as}" if @as
37
37
  msg << " with currency #{@currency_iso}" if @currency_iso
38
38
  msg
39
39
  end
40
40
 
41
41
  failure_message_for_should_not do |actual|
42
- msg = "expected that #{actual} would not be monetized"
42
+ msg = "expected that #{attr} of #{actual} would not be monetized"
43
43
  msg << " as #{@as}" if @as
44
44
  msg << " with currency #{@currency_iso}" if @currency_iso
45
45
  msg