money-rails 0.8.1 → 0.9.0

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