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.
@@ -0,0 +1,23 @@
1
+ require "active_support/core_ext/module/aliasing.rb"
2
+ require "active_support/core_ext/hash/reverse_merge.rb"
3
+
4
+ class Money
5
+
6
+ def format_with_settings(*rules)
7
+ rules = normalize_formatting_rules(rules)
8
+
9
+ # Apply global defaults for money only for non-nil values
10
+ # TODO: Add here more setting options
11
+ defaults = {
12
+ no_cents_if_whole: MoneyRails::Configuration.no_cents_if_whole,
13
+ symbol: MoneyRails::Configuration.symbol
14
+ }.reject { |k,v| v.nil? }
15
+
16
+ rules.reverse_merge!(defaults)
17
+
18
+ format_without_settings(rules)
19
+ end
20
+
21
+ alias_method_chain :format, :settings
22
+
23
+ end
@@ -27,7 +27,7 @@ class Money
27
27
  case
28
28
  when object.is_a?(Money) then object.mongoize
29
29
  when object.is_a?(Hash) then
30
- object.symbolize_keys!
30
+ object.symbolize_keys! if object.respond_to?(:symbolize_keys!)
31
31
  ::Money.new(object[:cents], object[:currency_iso]).mongoize
32
32
  when object.respond_to?(:to_money) then
33
33
  object.to_money.mongoize
@@ -0,0 +1,49 @@
1
+ require 'rspec/expectations'
2
+
3
+ module MoneyRails
4
+ module TestHelpers
5
+ extend RSpec::Matchers::DSL
6
+
7
+ matcher :monetize do |attr|
8
+
9
+ chain(:with_currency) do |currency|
10
+ @currency_iso = currency
11
+ end
12
+
13
+ chain(:as) do |virt_attr|
14
+ @as = virt_attr
15
+ end
16
+
17
+ match do |target|
18
+ matched = true
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
24
+ matched
25
+ end
26
+
27
+ description do
28
+ description = "monetize #{expected}"
29
+ description << " as #{@as}" if @as
30
+ description << " with currency #{@currency_iso}" if @currency_iso
31
+ description
32
+ end
33
+
34
+ failure_message_for_should do |actual|
35
+ msg = "expected that #{actual} would be monetized"
36
+ msg << " as #{@as}" if @as
37
+ msg << " with currency #{@currency_iso}" if @currency_iso
38
+ msg
39
+ end
40
+
41
+ failure_message_for_should_not do |actual|
42
+ msg = "expected that #{actual} would not be monetized"
43
+ msg << " as #{@as}" if @as
44
+ msg << " with currency #{@currency_iso}" if @currency_iso
45
+ msg
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,3 @@
1
1
  module MoneyRails
2
- VERSION = "0.7.1"
2
+ VERSION = "0.8.1"
3
3
  end
@@ -16,15 +16,21 @@ Gem::Specification.new do |s|
16
16
  s.files += %w(CHANGELOG.md LICENSE README.md)
17
17
  s.files += %w(Rakefile money-rails.gemspec)
18
18
 
19
+ s.files.delete("spec/dummy/log")
20
+ s.files.delete("spec/dummy/log/development.log")
21
+ s.files.delete("spec/dummy/log/test.log")
22
+ s.files.delete("spec/dummy/db/development.sqlite3")
23
+ s.files.delete("spec/dummy/db/test.sqlite3")
24
+
19
25
  s.test_files = s.files.grep(/^spec\//)
20
26
 
21
27
  s.require_path = "lib"
22
28
 
23
29
  s.add_dependency "money", "~> 5.1.0"
24
- s.add_dependency "activesupport", "~> 3.0"
25
- s.add_dependency "railties", "~> 3.0"
30
+ s.add_dependency "activesupport", ">= 3.0"
31
+ s.add_dependency "railties", ">= 3.0"
26
32
 
27
- s.add_development_dependency "rails", "~> 3.0"
33
+ s.add_development_dependency "rails", ">= 3.0"
28
34
  s.add_development_dependency "rspec-rails", "~> 2.10"
29
35
  s.add_development_dependency 'database_cleaner', ['>= 0.8.0']
30
36
  end
@@ -44,7 +44,9 @@ if defined? ActiveRecord
44
44
  describe 'currency' do
45
45
  subject { Item.columns_hash['price_currency'] }
46
46
 
47
- its (:default) { should eq 'USD' }
47
+ # set in spec/dummy/config/initializers/money.rb
48
+ its (:default) { should eq 'EUR' }
49
+
48
50
  its (:null) { should be_false }
49
51
  its (:type) { should eq :string }
50
52
  end
@@ -45,7 +45,9 @@ if defined? ActiveRecord
45
45
  describe 'currency' do
46
46
  subject { Item.columns_hash['price_currency'] }
47
47
 
48
- its (:default) { should eq 'USD' }
48
+ # set in spec/dummy/config/initializers/money.rb
49
+ its (:default) { should eq 'EUR' }
50
+
49
51
  its (:null) { should be_false }
50
52
  its (:type) { should eq :string }
51
53
  end
@@ -67,7 +67,7 @@ if defined? ActiveRecord
67
67
 
68
68
  @product.price = Money.new(320, "USD")
69
69
  @product.save.should be_true
70
-
70
+
71
71
  @product.sale_price = "12.34"
72
72
  @product.sale_price_currency_code = 'EUR'
73
73
  @product.valid?.should be_true
@@ -79,12 +79,44 @@ if defined? ActiveRecord
79
79
  @product.errors[:price].first.should match(/Must be a valid/)
80
80
  end
81
81
 
82
+ it "fails validation with the proper error message if money value is nothing but periods" do
83
+ @product.price = "..."
84
+ @product.save.should be_false
85
+ @product.errors[:price].first.should match(/Must be a valid/)
86
+ end
87
+
82
88
  it "fails validation with the proper error message if money value has invalid thousands part" do
83
89
  @product.price = "12,23.24"
84
90
  @product.save.should be_false
85
91
  @product.errors[:price].first.should match(/Must be a valid/)
86
92
  end
87
93
 
94
+ it "fails validation with the proper error message using numericality validations" do
95
+ @product.price_in_a_range = "-123"
96
+ @product.valid?.should be_false
97
+ @product.errors[:price_in_a_range].first.should match(/Must be greater than zero and less than \$10k/)
98
+
99
+ @product.price_in_a_range = "123"
100
+
101
+ @product.valid?.should be_true
102
+
103
+ @product.price_in_a_range = "10001"
104
+ @product.valid?.should be_false
105
+ @product.errors[:price_in_a_range].first.should match(/Must be greater than zero and less than \$10k/)
106
+ end
107
+
108
+ it "passes validation when amount contains spaces (99 999 999.99)" do
109
+ @product.price = "99 999 999.99"
110
+ @product.should be_valid
111
+ @product.price_cents.should == 9999999999
112
+ end
113
+
114
+ it "passes validation when amount contains underscores (99_999_999.99)" do
115
+ @product.price = "99_999_999.99"
116
+ @product.should be_valid
117
+ @product.price_cents.should == 9999999999
118
+ end
119
+
88
120
  it "passes validation if money value has correct format" do
89
121
  @product.price = "12,230.24"
90
122
  @product.save.should be_true
@@ -96,21 +128,31 @@ if defined? ActiveRecord
96
128
  end
97
129
 
98
130
  it "uses i18n currency format when validating" do
131
+ old_locale = I18n.locale
132
+
99
133
  I18n.locale = "en-GB"
100
134
  Money.default_currency = Money::Currency.find('EUR')
101
135
  "12.00".to_money.should == Money.new(1200, :eur)
102
136
  transaction = Transaction.new(amount: "12.00", tax: "13.00")
103
- transaction.amount_cents.should == 1200
137
+ transaction.amount_cents.should == 1200
104
138
  transaction.valid?.should be_true
139
+
140
+ # reset locale setting
141
+ I18n.locale = old_locale
105
142
  end
106
143
 
107
144
  it "defaults to Money::Currency format when no I18n information is present" do
145
+ old_locale = I18n.locale
146
+
108
147
  I18n.locale = "zxsw"
109
148
  Money.default_currency = Money::Currency.find('EUR')
110
149
  "12,00".to_money.should == Money.new(1200, :eur)
111
150
  transaction = Transaction.new(amount: "12,00", tax: "13,00")
112
- transaction.amount_cents.should == 1200
151
+ transaction.amount_cents.should == 1200
113
152
  transaction.valid?.should be_true
153
+
154
+ # reset locale setting
155
+ I18n.locale = old_locale
114
156
  end
115
157
 
116
158
  it "doesn't allow nil by default" do
@@ -247,7 +289,7 @@ if defined? ActiveRecord
247
289
  end
248
290
 
249
291
  it "sets field to nil, in nil assignments if allow_nil is set" do
250
- @product.optional_price = nil
292
+ @product.optional_price = nil
251
293
  @product.save.should be_true
252
294
  @product.optional_price.should be_nil
253
295
  end
@@ -16,5 +16,68 @@ describe "configuration" do
16
16
  Money.us_dollar(100).exchange_to("CAD").should == Money.new(124, "CAD")
17
17
  Money.ca_dollar(100).exchange_to("USD").should == Money.new(80, "USD")
18
18
  end
19
+
20
+ it "sets no_cents_if_whole value for formatted output globally" do
21
+ # Disable the usage of I18n (to avoid default symbols for :en)
22
+ Money.use_i18n = false
23
+
24
+ value = Money.new(12345600, "EUR")
25
+ mark = Money::Currency.find(:eur).decimal_mark
26
+ value.format.should =~ /#{mark}/
27
+
28
+ MoneyRails.no_cents_if_whole = true
29
+ value.format.should_not =~ /#{mark}/
30
+ value.format(no_cents_if_whole: false).should =~ /#{mark}/
31
+
32
+ MoneyRails.no_cents_if_whole = false
33
+ value.format.should =~ /#{mark}/
34
+ value.format(no_cents_if_whole: true).should_not =~ /#{mark}/
35
+
36
+ # Reset global settings
37
+ MoneyRails.no_cents_if_whole = nil
38
+ Money.use_i18n = true
39
+ end
40
+
41
+ it "sets symbol for formatted output globally" do
42
+ value = Money.new(12345600, "EUR")
43
+ symbol = Money::Currency.find(:eur).symbol
44
+ value.format.should =~ /#{symbol}/
45
+
46
+ MoneyRails.symbol = false
47
+ value.format.should_not =~ /#{symbol}/
48
+ value.format(symbol: true).should =~ /#{symbol}/
49
+
50
+ MoneyRails.symbol = true
51
+ value.format.should =~ /#{symbol}/
52
+ value.format(symbol: false).should_not =~ /#{symbol}/
53
+
54
+ # Reset global setting
55
+ MoneyRails.symbol = nil
56
+ end
57
+
58
+ it "changes the amount and currency column settings based on the default currency" do
59
+ old_currency = MoneyRails.default_currency
60
+ MoneyRails.default_currency = :inr
61
+
62
+ MoneyRails.amount_column[:postfix].should == "_#{MoneyRails.default_currency.subunit.downcase.pluralize}"
63
+ MoneyRails.currency_column[:default].should == MoneyRails.default_currency.iso_code
64
+
65
+ # Reset global setting
66
+ MoneyRails.default_currency = old_currency
67
+ end
68
+
69
+ it "accepts default currency which doesn't have minor unit" do
70
+ old_currency = MoneyRails.default_currency
71
+
72
+ expect {
73
+ MoneyRails.default_currency = :jpy
74
+ }.to_not raise_error
75
+
76
+ MoneyRails.amount_column[:postfix].should == "_cents"
77
+
78
+ # Reset global setting
79
+ MoneyRails.default_currency = old_currency
80
+ end
81
+
19
82
  end
20
83
  end
@@ -23,4 +23,11 @@ class Product < ActiveRecord::Base
23
23
  monetize :sale_price_amount, :as => :sale_price,
24
24
  :with_model_currency => :sale_price_currency_code
25
25
 
26
+ monetize :price_in_a_range_cents, :allow_nil => true,
27
+ :numericality => {
28
+ :greater_than_or_equal_to => 0,
29
+ :less_than_or_equal_to => 10000,
30
+ :message => "Must be greater than zero and less than $10k"
31
+ }
32
+
26
33
  end
@@ -0,0 +1,5 @@
1
+ class AddPriceInARangeCentsToProducts < ActiveRecord::Migration
2
+ def change
3
+ add_column :products, :price_in_a_range_cents, :integer
4
+ end
5
+ end
@@ -11,7 +11,7 @@
11
11
  #
12
12
  # It's strongly recommended to check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(:version => 20120712202655) do
14
+ ActiveRecord::Schema.define(:version => 20130124023419) do
15
15
 
16
16
  create_table "dummy_products", :force => true do |t|
17
17
  t.string "currency"
@@ -20,6 +20,9 @@ ActiveRecord::Schema.define(:version => 20120712202655) do
20
20
  t.datetime "updated_at", :null => false
21
21
  end
22
22
 
23
+ create_table "items", :force => true do |t|
24
+ end
25
+
23
26
  create_table "products", :force => true do |t|
24
27
  t.integer "price_cents"
25
28
  t.integer "discount"
@@ -29,6 +32,7 @@ ActiveRecord::Schema.define(:version => 20120712202655) do
29
32
  t.integer "optional_price_cents"
30
33
  t.integer "sale_price_amount", :default => 0, :null => false
31
34
  t.string "sale_price_currency_code"
35
+ t.integer "price_in_a_range_cents"
32
36
  end
33
37
 
34
38
  create_table "services", :force => true do |t|
@@ -1,5 +1,5 @@
1
1
  CREATE TABLE "dummy_products" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "currency" varchar(255), "price_cents" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
2
- CREATE TABLE "products" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "price_cents" integer, "discount" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL, "bonus_cents" integer, "optional_price_cents" integer);
2
+ CREATE TABLE "products" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "price_cents" integer, "discount" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL, "bonus_cents" integer, "optional_price_cents" integer, "price_in_a_range_cents" integer);
3
3
  CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL);
4
4
  CREATE TABLE "services" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "charge_cents" integer, "discount_cents" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
5
5
  CREATE TABLE "transactions" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "amount_cents" integer, "tax_cents" integer, "currency" varchar(255), "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
@@ -14,4 +14,6 @@ INSERT INTO schema_migrations (version) VALUES ('20120528181002');
14
14
 
15
15
  INSERT INTO schema_migrations (version) VALUES ('20120528210103');
16
16
 
17
- INSERT INTO schema_migrations (version) VALUES ('20120607210247');
17
+ INSERT INTO schema_migrations (version) VALUES ('20120607210247');
18
+
19
+ INSERT INTO schema_migrations (version) VALUES ('20130124023419');
@@ -8,10 +8,24 @@ describe 'MoneyRails::ActionViewExtension' do
8
8
  end
9
9
 
10
10
  describe '#humanized_money' do
11
- subject { helper.humanized_money Money.new(12500) }
11
+ let(:options) { {} }
12
+ subject { helper.humanized_money Money.new(12500), options }
12
13
  it { should be_a String }
13
14
  it { should_not include Money.default_currency.symbol }
14
15
  it { should_not include Money.default_currency.decimal_mark }
16
+
17
+ context 'with symbol options' do
18
+ let(:options) { { :symbol => true } }
19
+ it { should include Money.default_currency.symbol }
20
+ end
21
+
22
+ context 'with deprecated symbol' do
23
+ let(:options) { true }
24
+ before(:each) do
25
+ helper.should_receive(:warn)
26
+ end
27
+ it { should include Money.default_currency.symbol }
28
+ end
15
29
  end
16
30
 
17
31
  describe '#humanized_money_with_symbol' do
@@ -22,10 +36,19 @@ describe 'MoneyRails::ActionViewExtension' do
22
36
  end
23
37
 
24
38
  describe '#money_without_cents' do
25
- subject { helper.money_without_cents Money.new(12500) }
39
+ let(:options) { {} }
40
+ subject { helper.money_without_cents Money.new(12500), options }
26
41
  it { should be_a String }
27
42
  it { should_not include Money.default_currency.symbol }
28
43
  it { should_not include Money.default_currency.decimal_mark }
44
+
45
+ context 'with deprecated symbol' do
46
+ let(:options) { true }
47
+ before(:each) do
48
+ helper.should_receive(:warn)
49
+ end
50
+ it { should include Money.default_currency.symbol }
51
+ end
29
52
  end
30
53
 
31
54
  describe '#money_without_cents_and_with_symbol' do
@@ -6,6 +6,10 @@ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^3(.*)/
6
6
  let!(:priceable) { Priceable.create(:price => Money.new(100, 'EUR')) }
7
7
  let!(:priceable_from_num) { Priceable.create(:price => 1) }
8
8
  let!(:priceable_from_string) { Priceable.create(:price => '1 EUR' )}
9
+ let!(:priceable_from_hash) { Priceable.create(:price => {:cents=>100, :currency_iso=>"EUR"} )}
10
+ let!(:priceable_from_hash_with_indifferent_access) {
11
+ Priceable.create(:price => {:cents=>100, :currency_iso=>"EUR"}.with_indifferent_access)
12
+ }
9
13
 
10
14
  context "mongoize" do
11
15
  it "mongoizes correctly a Money object to a hash of cents and currency" do
@@ -22,6 +26,16 @@ if defined?(Mongoid) && ::Mongoid::VERSION =~ /^3(.*)/
22
26
  priceable_from_string.price.cents.should == 100
23
27
  priceable_from_string.price.currency.should == Money::Currency.find('EUR')
24
28
  end
29
+
30
+ it "mongoizes correctly a hash of cents and currency" do
31
+ priceable_from_hash.price.cents.should == 100
32
+ priceable_from_hash.price.currency.should == Money::Currency.find('EUR')
33
+ end
34
+
35
+ it "mongoizes correctly a HashWithIndifferentAccess of cents and currency" do
36
+ priceable_from_hash_with_indifferent_access.price.cents.should == 100
37
+ priceable_from_hash_with_indifferent_access.price.currency.should == Money::Currency.find('EUR')
38
+ end
25
39
  end
26
40
 
27
41
  context "demongoize" do
@@ -0,0 +1,31 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ if defined? ActiveRecord
5
+ describe 'TestHelpers' do
6
+
7
+ require "money-rails/test_helpers"
8
+ include MoneyRails::TestHelpers
9
+
10
+ let(:product) do
11
+ Product.create(:price_cents => 3000, :discount => 150,
12
+ :bonus_cents => 200, :optional_price => 100,
13
+ :sale_price_amount => 1200)
14
+ end
15
+
16
+ describe "monetize matcher" do
17
+
18
+ it "matches model attribute without a '_cents' suffix by default" do
19
+ monetize(:price_cents).should be_true
20
+ end
21
+
22
+ it "matches model attribute specified by :as chain" do
23
+ monetize(:price_cents).as(:discount_value).should be_true
24
+ end
25
+
26
+ it "matches model attribute with currency specified by :with_currency chain" do
27
+ monetize(:price_cents).with_currency(:gbp).should be_true
28
+ end
29
+ end
30
+ end
31
+ end