money-rails 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ - Refactored instance currency implementation. Now, instance currency
6
+ is permitted to override the field currency, if they are both non-nil
7
+ values. (GH-23)
8
+ - Replaced deprecated composed_of with a custom implementation for
9
+ activerecord. (GH-20)
10
+ - Refactored testing structure to support multiple ORMs/ODMS.
11
+ - Added Mongoid 2.x basic support. It uses serialization
12
+ (a differrent approach than activerecord for now). (GH-19)
13
+
3
14
  ## 0.4.0
15
+
4
16
  - Provide ActionView helpers integration.
5
17
  - Map blank value assignments (for monetized fields) to nil.
6
18
  - Allow nil values for monetized fields. (GH-15)
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # RubyMoney - Money-Rails
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
+ [![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/Robymoney/money-rails)
4
6
 
5
7
  ## Introduction
6
8
 
@@ -40,14 +42,16 @@ configuration parameters for the rails app.
40
42
 
41
43
  ## Usage
42
44
 
45
+ ### ActiveRecord
46
+
43
47
  For example, we create a Product model which has an integer price_cents column
44
48
  and we want to handle it by using a Money object instead:
45
49
 
46
50
  ```ruby
47
51
  class Product < ActiveRecord::Base
48
-
52
+
49
53
  monetize :price_cents
50
-
54
+
51
55
  end
52
56
  ```
53
57
 
@@ -55,7 +59,7 @@ Now each Product object will also have an attribute called ```price``` which
55
59
  is a Money object and can be used for money comparisons, conversions etc.
56
60
 
57
61
  In this case the name of the money attribute is created automagically by removing the
58
- ```_cents``` suffix of the column name.
62
+ ```_cents``` suffix of the column name.
59
63
 
60
64
  If you are using another db column name or you prefer another name for the
61
65
  money attribute, then you can provide ```as``` argument with a string
@@ -69,7 +73,7 @@ Now the model objects will have a ```discount``` attribute which
69
73
  is a Money object, wrapping the value of ```discount_subunit``` column to a
70
74
  Money instance.
71
75
 
72
- ### Allow nil values
76
+ #### Allow nil values
73
77
 
74
78
  If you want to allow the assignment of nil and/or blank values to a specific
75
79
  monetized field, you can use the `:allow_nil` parameter like this:
@@ -85,6 +89,45 @@ product.optional_price # => nil
85
89
  product.optional_price_cents # => nil
86
90
  ```
87
91
 
92
+ ### Mongoid 2.X
93
+
94
+ `Money` is available as a field type to supply during a field definition:
95
+
96
+ ```ruby
97
+ class Product
98
+ include Mongoid::Document
99
+
100
+ field :price, type: Money
101
+ end
102
+
103
+ obj = Product.new
104
+ # => #<Product _id: 4fe865699671383656000001, _type: nil, price: nil>
105
+
106
+ obj.price
107
+ # => nil
108
+
109
+ obj.price = Money.new(100, 'EUR')
110
+ # => #<Money cents:100 currency:EUR>
111
+
112
+ obj.price
113
+ #=> #<Money cents:100 currency:EUR>
114
+
115
+ obj.save
116
+ # => true
117
+
118
+ obj
119
+ # => #<Product _id: 4fe865699671383656000001, _type: nil, price: {:cents=>100, :currency_iso=>"EUR"}>
120
+
121
+ obj.price
122
+ #=> #<Money cents:100 currency:EUR>
123
+
124
+ ## You can access the money hash too :
125
+ obj[:price]
126
+ # => {:cents=>100, :currency_iso=>"EUR"}
127
+ ```
128
+
129
+ The usual options on `field` as `index`, `default`, ..., are available.
130
+
88
131
  ### Currencies
89
132
 
90
133
  Money-rails supports a set of options to handle currencies for your
@@ -107,11 +150,11 @@ satisfy your needs.
107
150
 
108
151
  #### Model Currency
109
152
 
110
- You can define a specific currency for an activerecord model. This currency is
111
- used for the creation and conversions of the Money objects referring to
112
- every monetized attributes of the specific model. This means it overrides
113
- the global default currency of Money library. To attach a currency to a
114
- model use the ```register_currency``` macro:
153
+ You can define a specific currency for an activerecord model (not for mongoid).
154
+ This currency is used for the creation and conversions of the Money objects
155
+ referring to every monetized attributes of the specific model.
156
+ This means it overrides the global default currency of Money library.
157
+ To attach a currency to a model use the ```register_currency``` macro:
115
158
 
116
159
  ```ruby
117
160
  # app/models/product.rb
@@ -160,8 +203,8 @@ currency values. If you need to provide differrent currency per model
160
203
  instance, then you need to add a column with the name ```currency```
161
204
  in your db table. Money-rails will discover this automatically,
162
205
  and will use this knowledge to override the model level and global
163
- default values. Attribute currency cannot be combined with instance
164
- currency!
206
+ default values. Non-nil instance currency values also override attribute
207
+ currency values, so they have the highest precedence.
165
208
 
166
209
  ```ruby
167
210
  class Transaction < ActiveRecord::Base
@@ -177,17 +220,12 @@ class Transaction < ActiveRecord::Base
177
220
 
178
221
  end
179
222
 
180
- # Now instantiating with a specific currency overrides the
223
+ # Now instantiating with a specific currency overrides
181
224
  # the model and global currencies
182
225
  t = Transaction.new(:amount_cents => 2500, :currency => "CAD")
183
226
  t.amount == Money.new(2500, "CAD") # true
184
227
  ```
185
228
 
186
- WARNING: In this case :with_currency is not permitted and the usage
187
- of this parameter will cause an ArgumentError exception.
188
-
189
- In general, the use of this strategy is discouraged unless there is a reason.
190
-
191
229
  ### Configuration parameters
192
230
 
193
231
  You can handle a bunch of configuration params through ```money.rb``` initializer:
data/Rakefile CHANGED
@@ -16,9 +16,34 @@ require 'rspec/core/rake_task'
16
16
 
17
17
  RSpec::Core::RakeTask.new
18
18
 
19
- task :default => :spec
19
+ task :default => "spec:all"
20
20
  task :test => :spec
21
21
 
22
+ namespace :spec do
23
+ desc "Run Tests against mongoid"
24
+ task :mongoid2 do
25
+ sh "BUNDLE_GEMFILE='gemfiles/mongoid2.gemfile' bundle --quiet"
26
+ sh "BUNDLE_GEMFILE='gemfiles/mongoid2.gemfile' bundle exec rake -t spec"
27
+ end
28
+
29
+ desc "Run Tests against activerecord"
30
+ task :activerecord do
31
+ sh "bundle --quiet"
32
+ sh "bundle exec rake -t spec"
33
+ end
34
+
35
+ desc "Run Tests against all ORMs"
36
+ task :all do
37
+ # Mongoid 2
38
+ sh "BUNDLE_GEMFILE='gemfiles/mongoid2.gemfile' bundle --quiet"
39
+ sh "BUNDLE_GEMFILE='gemfiles/mongoid2.gemfile' bundle exec rake -t spec"
40
+
41
+ # ActiveRecord
42
+ sh "bundle --quiet"
43
+ sh "bundle exec rake -t spec"
44
+ end
45
+ end
46
+
22
47
  desc "Update AUTHORS file"
23
48
  task :authors do
24
49
  sh "git shortlog -s | awk '{ print $2 \" \" $3 }' > AUTHORS"
@@ -21,11 +21,10 @@ module MoneyRails
21
21
  ":with_currency or :with_model_currency")
22
22
  end
23
23
 
24
- # Optional table column which holds currency iso_codes
25
- # It allows per row currency values
26
- # Overrides default currency
27
- model_currency_name = options[:with_model_currency] ||
24
+ # Optional accessor to be run on an instance to detect currency
25
+ instance_currency_name = options[:with_model_currency] ||
28
26
  options[:model_currency] || "currency"
27
+ instance_currency_name = instance_currency_name.to_s
29
28
 
30
29
  # This attribute allows per column currency values
31
30
  # Overrides row and default currency
@@ -47,63 +46,45 @@ module MoneyRails
47
46
  name = subunit_name << "_money"
48
47
  end
49
48
 
50
- has_currency_table_column = self.attribute_names.include? model_currency_name
49
+ # Include numericality validation if needed
50
+ validates_numericality_of subunit_name, :allow_nil => options[:allow_nil] if MoneyRails.include_validations
51
51
 
52
- if has_currency_table_column
53
- raise(ArgumentError, ":with_currency should not be used with tables" \
54
- " which contain a column for currency values") if field_currency_name
52
+ define_method name do
53
+ amount = send(subunit_name)
54
+ attr_currency = send("currency_for_#{name}")
55
55
 
56
- mappings = [[subunit_name, "cents"], [model_currency_name, "currency_as_string"]]
57
- constructor = Proc.new { |cents, currency|
58
- Money.new(cents, currency || self.respond_to?(:currency) &&
59
- self.currency || Money.default_currency)
60
- }
61
- converter = Proc.new { |value|
62
- raise(ArgumentError, "Only Money objects are allowed for assignment")
63
- }
64
- else
65
- mappings = [[subunit_name, "cents"]]
66
- constructor = Proc.new { |cents|
67
- Money.new(cents, field_currency_name || self.respond_to?(:currency) &&
68
- self.currency || Money.default_currency)
69
- }
70
- converter = Proc.new { |value|
71
- if options[:allow_nil] && value.blank?
72
- nil
73
- elsif value.respond_to?(:to_money)
74
- value.to_money(field_currency_name || self.respond_to?(:currency) &&
75
- self.currency || Money.default_currency)
76
- else
77
- raise(ArgumentError, "Can't convert #{value.class} to Money")
78
- end
79
- }
80
- end
56
+ # Dont create a new Money instance if the values haven't changed
57
+ memoized = instance_variable_get("@#{name}")
58
+ return memoized if memoized && memoized.cents == amount &&
59
+ memoized.currency == attr_currency
81
60
 
82
- class_eval do
83
- composed_of name.to_sym,
84
- :class_name => "Money",
85
- :mapping => mappings,
86
- :constructor => constructor,
87
- :converter => converter,
88
- :allow_nil => options[:allow_nil]
61
+ amount = Money.new(amount, attr_currency) unless amount.blank?
62
+
63
+ instance_variable_set "@#{name}", amount
89
64
  end
90
65
 
91
- if options[:allow_nil]
92
- class_eval do
93
- # Fixes issue with composed_of that breaks on blank params
94
- # TODO: This should be removed when we will only support rails >=4.0
95
- define_method "#{name}_with_blank_support=" do |value|
96
- value = nil if value.blank?
97
- send "#{name}_without_blank_support=", value
98
- end
99
- alias_method_chain "#{name}=", :blank_support
66
+ define_method "#{name}=" do |value|
67
+ if options[:allow_nil] && value.blank?
68
+ money = nil
69
+ else
70
+ money = value.is_a?(Money) ? value : value.to_money(send("currency_for_#{name}"))
100
71
  end
72
+
73
+ send("#{subunit_name}=", money.try(:cents))
74
+ send("#{instance_currency_name}=", money.try(:currency).try(:iso_code)) if self.respond_to?("#{instance_currency_name}=")
75
+
76
+ instance_variable_set "@#{name}", money
101
77
  end
102
78
 
103
- # Include numericality validation if needed
104
- if MoneyRails.include_validations
105
- class_eval do
106
- validates_numericality_of subunit_name, :allow_nil => options[:allow_nil]
79
+ define_method "currency_for_#{name}" do
80
+ if self.respond_to?(instance_currency_name) && send(instance_currency_name).present?
81
+ Money::Currency.find(send(instance_currency_name))
82
+ elsif field_currency_name
83
+ Money::Currency.find(field_currency_name)
84
+ elsif self.class.respond_to?(:currency)
85
+ self.class.currency
86
+ else
87
+ Money.default_currency
107
88
  end
108
89
  end
109
90
  end
@@ -1,3 +1,6 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ require 'active_support/core_ext/module/attribute_accessors'
3
+
1
4
  module MoneyRails
2
5
 
3
6
  # MoneyRails configuration module.
@@ -11,7 +11,7 @@ module MoneyRails
11
11
  begin; require 'mongoid'; rescue LoadError; end
12
12
  if defined? ::Mongoid
13
13
  if ::Mongoid::VERSION =~ /^2(.*)/
14
- # Mongoid 2.x stuff here
14
+ require 'money-rails/mongoid/two' # Loading the file is enough
15
15
  end
16
16
 
17
17
  if ::Mongoid::VERSION =~ /^3(.*)/
@@ -0,0 +1,25 @@
1
+ # Class name does not really matches the folder hierarchy, because
2
+ # in order for (de)serialization to work, the class must be re-opened.
3
+ # But this file brings mongoid 2.X compat., so...
4
+
5
+ class Money
6
+ include ::Mongoid::Fields::Serializable
7
+
8
+ # Mongo friendly -> Money
9
+ def deserialize(object)
10
+ return nil if object.nil?
11
+
12
+ object = object.with_indifferent_access
13
+ ::Money.new object[:cents], object[:currency_iso]
14
+ end
15
+
16
+ # Money -> Mongo friendly
17
+ def serialize(object)
18
+ return nil unless object.is_a? Money
19
+
20
+ {
21
+ :cents => object.cents,
22
+ :currency_iso => object.currency.iso_code
23
+ }
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module MoneyRails
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -28,4 +28,5 @@ Gem::Specification.new do |s|
28
28
  s.add_development_dependency "rspec", "~> 2.10"
29
29
  s.add_development_dependency "rspec-rails", "~> 2.10"
30
30
  s.add_development_dependency "guard-rspec", "~> 1.1"
31
+ s.add_development_dependency 'database_cleaner', ['>= 0']
31
32
  end
@@ -1,217 +1,247 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe MoneyRails::ActiveRecord::Monetizable do
3
+ if defined? ActiveRecord
4
+ describe MoneyRails::ActiveRecord::Monetizable do
4
5
 
5
- describe "monetize" do
6
- before :each do
7
- @product = Product.create(:price_cents => 3000, :discount => 150,
8
- :bonus_cents => 200, :optional_price => 100)
9
- @service = Service.create(:charge_cents => 2000, :discount_cents => 120)
10
- end
11
-
12
- it "attaches a Money object to model field" do
13
- @product.price.should be_an_instance_of(Money)
14
- @product.discount_value.should be_an_instance_of(Money)
15
- @product.bonus.should be_an_instance_of(Money)
16
- end
17
-
18
- it "returns the expected money amount as a Money object" do
19
- @product.price.should == Money.new(3000, "USD")
20
- end
21
-
22
- it "assigns the correct value from a Money object" do
23
- @product.price = Money.new(3210, "USD")
24
- @product.save.should be_true
25
- @product.price_cents.should == 3210
26
- end
27
-
28
- it "respects :as argument" do
29
- @product.discount_value.should == Money.new(150, "USD")
30
- end
31
-
32
- it "uses numericality validation" do
33
- @product.price_cents = "foo"
34
- @product.save.should be_false
6
+ describe "monetize" do
7
+ before :each do
8
+ @product = Product.create(:price_cents => 3000, :discount => 150,
9
+ :bonus_cents => 200, :optional_price => 100)
10
+ @service = Service.create(:charge_cents => 2000, :discount_cents => 120)
11
+ end
35
12
 
36
- @product.price_cents = 2000
37
- @product.save.should be_true
38
- end
13
+ it "attaches a Money object to model field" do
14
+ @product.price.should be_an_instance_of(Money)
15
+ @product.discount_value.should be_an_instance_of(Money)
16
+ @product.bonus.should be_an_instance_of(Money)
17
+ end
39
18
 
40
- it "doesn't allow nil by default" do
41
- @product.price_cents = nil
42
- @product.save.should be_false
43
- end
19
+ it "returns the expected money amount as a Money object" do
20
+ @product.price.should == Money.new(3000, "USD")
21
+ end
44
22
 
45
- it "allows nil if optioned" do
46
- @product.optional_price = nil
47
- @product.save.should be_true
48
- @product.optional_price.should be_nil
49
- end
23
+ it "assigns the correct value from a Money object" do
24
+ @product.price = Money.new(3210, "USD")
25
+ @product.save.should be_true
26
+ @product.price_cents.should == 3210
27
+ end
50
28
 
51
- it "uses Money default currency if :with_currency has not been used" do
52
- @service.discount.currency.should == Money::Currency.find(:eur)
53
- end
29
+ it "respects :as argument" do
30
+ @product.discount_value.should == Money.new(150, "USD")
31
+ end
54
32
 
55
- it "overrides default currency with the currency registered for the model" do
56
- @product.price.currency.should == Money::Currency.find(:usd)
57
- end
33
+ it "uses numericality validation" do
34
+ @product.price_cents = "foo"
35
+ @product.save.should be_false
58
36
 
59
- it "overrides default currency with the value of :with_currency argument" do
60
- @service.charge.currency.should == Money::Currency.find(:usd)
61
- @product.bonus.currency.should == Money::Currency.find(:gbp)
62
- end
37
+ @product.price_cents = 2000
38
+ @product.save.should be_true
39
+ end
63
40
 
64
- it "assigns correctly Money objects to the attribute" do
65
- @product.price = Money.new(2500, :USD)
66
- @product.save.should be_true
67
- @product.price.cents.should == 2500
68
- @product.price.currency_as_string.should == "USD"
69
- end
41
+ it "doesn't allow nil by default" do
42
+ @product.price_cents = nil
43
+ @product.save.should be_false
44
+ end
70
45
 
71
- it "assigns correctly Fixnum objects to the attribute" do
72
- @product.price = 25
73
- @product.save.should be_true
74
- @product.price.cents.should == 2500
75
- @product.price.currency_as_string.should == "USD"
46
+ it "allows nil if optioned" do
47
+ @product.optional_price = nil
48
+ @product.save.should be_true
49
+ @product.optional_price.should be_nil
50
+ end
76
51
 
77
- @service.discount = 2
78
- @service.save.should be_true
79
- @service.discount.cents.should == 200
80
- @service.discount.currency_as_string.should == "EUR"
81
- end
52
+ it "uses Money default currency if :with_currency has not been used" do
53
+ @service.discount.currency.should == Money::Currency.find(:eur)
54
+ end
82
55
 
83
- it "assigns correctly String objects to the attribute" do
84
- @product.price = "25"
85
- @product.save.should be_true
86
- @product.price.cents.should == 2500
87
- @product.price.currency_as_string.should == "USD"
56
+ it "overrides default currency with the currency registered for the model" do
57
+ @product.price.currency.should == Money::Currency.find(:usd)
58
+ end
88
59
 
89
- @service.discount = "2"
90
- @service.save.should be_true
91
- @service.discount.cents.should == 200
92
- @service.discount.currency_as_string.should == "EUR"
93
- end
60
+ it "overrides default currency with the value of :with_currency argument" do
61
+ @service.charge.currency.should == Money::Currency.find(:usd)
62
+ @product.bonus.currency.should == Money::Currency.find(:gbp)
63
+ end
94
64
 
95
- it "overrides default, model currency with the value of :with_currency in fixnum assignments" do
96
- @product.bonus = 25
97
- @product.save.should be_true
98
- @product.bonus.cents.should == 2500
99
- @product.bonus.currency_as_string.should == "GBP"
65
+ it "assigns correctly Money objects to the attribute" do
66
+ @product.price = Money.new(2500, :USD)
67
+ @product.save.should be_true
68
+ @product.price.cents.should == 2500
69
+ @product.price.currency_as_string.should == "USD"
70
+ end
100
71
 
101
- @service.charge = 2
102
- @service.save.should be_true
103
- @service.charge.cents.should == 200
104
- @service.charge.currency_as_string.should == "USD"
105
- end
72
+ it "assigns correctly Fixnum objects to the attribute" do
73
+ @product.price = 25
74
+ @product.save.should be_true
75
+ @product.price.cents.should == 2500
76
+ @product.price.currency_as_string.should == "USD"
106
77
 
107
- it "overrides default, model currency with the value of :with_currency in string assignments" do
108
- @product.bonus = "25"
109
- @product.save.should be_true
110
- @product.bonus.cents.should == 2500
111
- @product.bonus.currency_as_string.should == "GBP"
78
+ @service.discount = 2
79
+ @service.save.should be_true
80
+ @service.discount.cents.should == 200
81
+ @service.discount.currency_as_string.should == "EUR"
82
+ end
112
83
 
113
- @service.charge = "2"
114
- @service.save.should be_true
115
- @service.charge.cents.should == 200
116
- @service.charge.currency_as_string.should == "USD"
117
- end
84
+ it "assigns correctly String objects to the attribute" do
85
+ @product.price = "25"
86
+ @product.save.should be_true
87
+ @product.price.cents.should == 2500
88
+ @product.price.currency_as_string.should == "USD"
118
89
 
119
- it "overrides default currency with model currency, in fixnum assignments" do
120
- @product.discount_value = 5
121
- @product.save.should be_true
122
- @product.discount_value.cents.should == 500
123
- @product.discount_value.currency_as_string.should == "USD"
124
- end
90
+ @service.discount = "2"
91
+ @service.save.should be_true
92
+ @service.discount.cents.should == 200
93
+ @service.discount.currency_as_string.should == "EUR"
94
+ end
125
95
 
126
- it "overrides default currency with model currency, in string assignments" do
127
- @product.discount_value = "5"
128
- @product.save.should be_true
129
- @product.discount_value.cents.should == 500
130
- @product.discount_value.currency_as_string.should == "USD"
131
- end
96
+ it "overrides default, model currency with the value of :with_currency in fixnum assignments" do
97
+ @product.bonus = 25
98
+ @product.save.should be_true
99
+ @product.bonus.cents.should == 2500
100
+ @product.bonus.currency_as_string.should == "GBP"
132
101
 
133
- it "falls back to default currency, in fixnum assignments" do
134
- @service.discount = 5
135
- @service.save.should be_true
136
- @service.discount.cents.should == 500
137
- @service.discount.currency_as_string.should == "EUR"
138
- end
102
+ @service.charge = 2
103
+ @service.save.should be_true
104
+ @service.charge.cents.should == 200
105
+ @service.charge.currency_as_string.should == "USD"
106
+ end
139
107
 
140
- it "falls back to default currency, in string assignments" do
141
- @service.discount = "5"
142
- @service.save.should be_true
143
- @service.discount.cents.should == 500
144
- @service.discount.currency_as_string.should == "EUR"
145
- end
108
+ it "overrides default, model currency with the value of :with_currency in string assignments" do
109
+ @product.bonus = "25"
110
+ @product.save.should be_true
111
+ @product.bonus.cents.should == 2500
112
+ @product.bonus.currency_as_string.should == "GBP"
146
113
 
147
- it "sets field to nil, in nil assignments if allow_nil is set" do
148
- @product.optional_price = nil
149
- @product.save.should be_true
150
- @product.optional_price.should be_nil
151
- end
114
+ @service.charge = "2"
115
+ @service.save.should be_true
116
+ @service.charge.cents.should == 200
117
+ @service.charge.currency_as_string.should == "USD"
118
+ end
152
119
 
153
- it "sets field to nil, in instantiation if allow_nil is set" do
154
- pr = Product.new(:optional_price => nil, :price_cents => 5320,
155
- :discount => 350, :bonus_cents => 320)
156
- pr.optional_price.should be_nil
157
- pr.save.should be_true
158
- pr.optional_price.should be_nil
159
- end
120
+ it "overrides default currency with model currency, in fixnum assignments" do
121
+ @product.discount_value = 5
122
+ @product.save.should be_true
123
+ @product.discount_value.cents.should == 500
124
+ @product.discount_value.currency_as_string.should == "USD"
125
+ end
160
126
 
161
- it "sets field to nil, in blank assignments if allow_nil is set" do
162
- @product.optional_price = ""
163
- @product.save.should be_true
164
- @product.optional_price.should be_nil
165
- end
166
-
167
- context "for model with currency column:" do
168
- before :each do
169
- @transaction = Transaction.create(:amount_cents => 2400, :tax_cents => 600,
170
- :currency => :usd)
171
- @dummy_product1 = DummyProduct.create(:price_cents => 2400, :currency => :usd)
172
- @dummy_product2 = DummyProduct.create(:price_cents => 2600) # nil currency
127
+ it "overrides default currency with model currency, in string assignments" do
128
+ @product.discount_value = "5"
129
+ @product.save.should be_true
130
+ @product.discount_value.cents.should == 500
131
+ @product.discount_value.currency_as_string.should == "USD"
173
132
  end
174
133
 
175
- it "overrides default currency with the value of row currency" do
176
- @transaction.amount.currency.should == Money::Currency.find(:usd)
134
+ it "falls back to default currency, in fixnum assignments" do
135
+ @service.discount = 5
136
+ @service.save.should be_true
137
+ @service.discount.cents.should == 500
138
+ @service.discount.currency_as_string.should == "EUR"
177
139
  end
178
140
 
179
- it "overrides default currency with the currency registered for the model" do
180
- @dummy_product2.price.currency.should == Money::Currency.find(:gbp)
141
+ it "falls back to default currency, in string assignments" do
142
+ @service.discount = "5"
143
+ @service.save.should be_true
144
+ @service.discount.cents.should == 500
145
+ @service.discount.currency_as_string.should == "EUR"
181
146
  end
182
147
 
183
- it "overrides default and model currency with the row currency" do
184
- @dummy_product1.price.currency.should == Money::Currency.find(:usd)
148
+ it "sets field to nil, in nil assignments if allow_nil is set" do
149
+ @product.optional_price = nil
150
+ @product.save.should be_true
151
+ @product.optional_price.should be_nil
185
152
  end
186
153
 
187
- it "constructs the money attribute from the stored mapped attribute values" do
188
- @transaction.amount.should == Money.new(2400, :usd)
154
+ it "sets field to nil, in instantiation if allow_nil is set" do
155
+ pr = Product.new(:optional_price => nil, :price_cents => 5320,
156
+ :discount => 350, :bonus_cents => 320)
157
+ pr.optional_price.should be_nil
158
+ pr.save.should be_true
159
+ pr.optional_price.should be_nil
189
160
  end
190
161
 
191
- it "instantiates correctly Money objects from the mapped attributes" do
192
- t = Transaction.new(:amount_cents => 2500, :currency => "CAD")
193
- t.amount.should == Money.new(2500, "CAD")
162
+ it "sets field to nil, in blank assignments if allow_nil is set" do
163
+ @product.optional_price = ""
164
+ @product.save.should be_true
165
+ @product.optional_price.should be_nil
194
166
  end
195
167
 
196
- it "assigns correctly Money objects to the attribute" do
197
- @transaction.amount = Money.new(2500, :eur)
198
- @transaction.save.should be_true
199
- @transaction.amount.cents.should == Money.new(2500, :eur).cents
200
- @transaction.amount.currency_as_string.should == "EUR"
168
+ context "for column with currency:" do
169
+ it "is overridden by instance currency" do
170
+ product = Product.create(:price_cents => 5320, :discount => 350, :bonus_cents => 320)
171
+ product.stub(:currency) { "EUR" }
172
+ product.bonus.currency_as_string.should == "EUR"
173
+ end
201
174
  end
202
175
 
203
- it "raises exception if a non Money object is assigned to the attribute" do
204
- expect { @transaction.amount = "not a Money object" }.to raise_error(ArgumentError)
205
- expect { @transaction.amount = 234 }.to raise_error(ArgumentError)
176
+ context "for column with model currency:" do
177
+ it "has default currency if not specified" do
178
+ product = Product.create(:sale_price_amount => 1234)
179
+ product.sale_price.currency_as_string == 'USD'
180
+ end
181
+ it "is overridden by instance currency column" do
182
+ product = Product.create(:sale_price_amount => 1234,
183
+ :sale_price_currency_code => 'CAD')
184
+ product.sale_price.currency_as_string.should == 'CAD'
185
+ end
206
186
  end
207
187
 
188
+ context "for model with currency column:" do
189
+ before :each do
190
+ @transaction = Transaction.create(:amount_cents => 2400, :tax_cents => 600,
191
+ :currency => :usd)
192
+ @dummy_product1 = DummyProduct.create(:price_cents => 2400, :currency => :usd)
193
+ @dummy_product2 = DummyProduct.create(:price_cents => 2600) # nil currency
194
+ end
195
+
196
+ it "serializes correctly the currency to a new instance of model" do
197
+ d = DummyProduct.new
198
+ d.price = Money.new(10, "EUR")
199
+ d.save!
200
+ d.reload
201
+ d.currency.should == "EUR"
202
+ end
203
+
204
+ it "overrides default currency with the value of row currency" do
205
+ @transaction.amount.currency.should == Money::Currency.find(:usd)
206
+ end
207
+
208
+ it "overrides default currency with the currency registered for the model" do
209
+ @dummy_product2.price.currency.should == Money::Currency.find(:gbp)
210
+ end
211
+
212
+ it "overrides default and model currency with the row currency" do
213
+ @dummy_product1.price.currency.should == Money::Currency.find(:usd)
214
+ end
215
+
216
+ it "constructs the money attribute from the stored mapped attribute values" do
217
+ @transaction.amount.should == Money.new(2400, :usd)
218
+ end
219
+
220
+ it "instantiates correctly Money objects from the mapped attributes" do
221
+ t = Transaction.new(:amount_cents => 2500, :currency => "CAD")
222
+ t.amount.should == Money.new(2500, "CAD")
223
+ end
224
+
225
+ it "assigns correctly Money objects to the attribute" do
226
+ @transaction.amount = Money.new(2500, :eur)
227
+ @transaction.save.should be_true
228
+ @transaction.amount.cents.should == Money.new(2500, :eur).cents
229
+ @transaction.amount.currency_as_string.should == "EUR"
230
+ end
231
+
232
+ it "uses default currency if a non Money object is assigned to the attribute" do
233
+ @transaction.amount = 234
234
+ @transaction.amount.currency_as_string.should == "USD"
235
+ end
236
+
237
+ end
208
238
  end
209
- end
210
239
 
211
- describe "register_currency" do
212
- it "attaches currency at model level" do
213
- Product.currency.should == Money::Currency.find(:usd)
214
- DummyProduct.currency.should == Money::Currency.find(:gbp)
240
+ describe "register_currency" do
241
+ it "attaches currency at model level" do
242
+ Product.currency.should == Money::Currency.find(:usd)
243
+ DummyProduct.currency.should == Money::Currency.find(:gbp)
244
+ end
215
245
  end
216
246
  end
217
247
  end