money-rails 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,8 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.1
4
+ - Fix error when instantiating new model in mongoid extension (GH-60)
5
+
3
6
  ## 0.7.0
4
7
  - Added custom validator for Money fields (GH-36)
5
8
  - Added mongodb service test support for travis CI
@@ -13,6 +16,7 @@
13
16
  - Allow immediate subclasses to inherit monetized_attributes
14
17
  - Stopped support for MRI < 1.9.2
15
18
  - Fixed issue related to symbolized keys in Mongoid (GH-40)
19
+ - Added add_money/remove_money & t.money/t.remove_money methods for ActiveRecord migrations
16
20
 
17
21
  TODOs (for upcoming releases):
18
22
  - decouple validator from active_record
@@ -31,7 +35,7 @@ TODOs (for upcoming releases):
31
35
  - Replaced deprecated composed_of with a custom implementation for
32
36
  activerecord. (GH-20)
33
37
  - Refactored testing structure to support multiple ORMs/ODMS.
34
- - Added Mongoid 2.x basic support. It uses serialization
38
+ - Added Mongoid 2.x basic support. It uses serialization
35
39
  (a differrent approach than activerecord for now). (GH-19)
36
40
 
37
41
  ## 0.4.0
data/README.md CHANGED
@@ -44,6 +44,31 @@ 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
+ #### Usage example
71
+
47
72
  For example, we create a Product model which has an integer price_cents column
48
73
  and we want to handle it by using a Money object instead:
49
74
 
@@ -1,5 +1,5 @@
1
1
  en:
2
2
  errors:
3
3
  messages:
4
- invalid_currencym: Must be a valid currency (eg. '100', '5%{decimal}24', or '123%{thousands}456%{decimal}78')
4
+ invalid_currency: Must be a valid currency (eg. '100', '5%{decimal}24', or '123%{thousands}456%{decimal}78')
5
5
 
@@ -23,6 +23,26 @@ MoneyRails.configure do |config|
23
23
  #
24
24
  #config.include_validations = true
25
25
 
26
+ # Default ActiveRecord migration configuration values for columns:
27
+ #
28
+ # config.amount_column = { prefix: '', # column name prefix
29
+ # postfix: '_cents', # column name postfix
30
+ # column_name: nil, # full column name (overrides prefix, postfix and accessor name)
31
+ # type: :integer, # column type
32
+ # present: true, # column will be created
33
+ # null: false, # other options will be treated as column options
34
+ # default: 0
35
+ # }
36
+ #
37
+ # config.currency_column = { prefix: '',
38
+ # postfix: '_currency',
39
+ # column_name: nil,
40
+ # type: :string,
41
+ # present: true,
42
+ # null: false,
43
+ # default: 'USD'
44
+ # }
45
+
26
46
  # Register a custom currency
27
47
  #
28
48
  # Example:
@@ -19,36 +19,40 @@ module MoneyRails
19
19
  if !raw_value.blank?
20
20
  # remove currency symbol, and negative sign
21
21
  currency = record.send("currency_for_#{attr}")
22
- raw_value = raw_value.to_s.gsub(currency.symbol, "").gsub(/^-/, "")
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)
24
+ symbol = I18n.t('number.currency.format.unit', default: currency.symbol)
23
25
 
24
- decimal_pieces = raw_value.split(currency.decimal_mark)
26
+ raw_value = raw_value.to_s.gsub(symbol, "").gsub(/^-/, "")
27
+
28
+ decimal_pieces = raw_value.split(decimal_mark)
25
29
 
26
30
  # check for numbers like 12.23.45
27
31
  if decimal_pieces.length > 2
28
- record.errors.add(attr, I18n.t('errors.messages.invalid_currencym',
29
- { :thousands => currency.thousands_separator,
30
- :decimal => currency.decimal_mark }))
32
+ record.errors.add(attr, I18n.t('errors.messages.invalid_currency',
33
+ { :thousands => thousands_separator,
34
+ :decimal => decimal_mark }))
31
35
  end
32
36
 
33
- pieces = decimal_pieces[0].split(currency.thousands_separator)
37
+ pieces = decimal_pieces[0].split(thousands_separator)
34
38
 
35
39
  # check for valid thousands separation
36
40
  if pieces.length > 1
37
- record.errors.add(attr, I18n.t('errors.messages.invalid_currencym',
38
- { :thousands => currency.thousands_separator,
39
- :decimal => currency.decimal_mark })) if pieces[0].length > 3
41
+ record.errors.add(attr, I18n.t('errors.messages.invalid_currency',
42
+ { :thousands => thousands_separator,
43
+ :decimal => decimal_mark })) if pieces[0].length > 3
40
44
  (1..pieces.length-1).each do |index|
41
- record.errors.add(attr, I18n.t('errors.messages.invalid_currencym',
42
- { :thousands => currency.thousands_separator,
43
- :decimal => currency.decimal_mark })) if pieces[index].length != 3
45
+ record.errors.add(attr, I18n.t('errors.messages.invalid_currency',
46
+ { :thousands => thousands_separator,
47
+ :decimal => decimal_mark })) if pieces[index].length != 3
44
48
  end
45
49
  end
46
50
 
47
51
  # remove thousands separators
48
- raw_value = raw_value.to_s.gsub(currency.thousands_separator, '')
52
+ raw_value = raw_value.to_s.gsub(thousands_separator, '')
49
53
 
50
54
  # normalize decimal mark
51
- raw_value = raw_value.to_s.gsub(currency.decimal_mark, '.')
55
+ raw_value = raw_value.to_s.gsub(decimal_mark, '.')
52
56
  end
53
57
  super(record, attr, raw_value)
54
58
  end
@@ -0,0 +1,19 @@
1
+ module MoneyRails
2
+ module ActiveRecord
3
+ module MigrationExtensions
4
+ class OptionsExtractor
5
+ def self.extract(attribute, table_name, accessor, options = {})
6
+ default = MoneyRails::Configuration.send("#{attribute}_column").merge(options[attribute] || {})
7
+
8
+ default[:column_name] ||= [default[:prefix], accessor, default[:postfix]].join
9
+ default[:table_name] = table_name
10
+
11
+ excluded_keys = [:amount, :currency, :type, :prefix, :postfix, :present, :column_name, :table_name]
12
+ default[:options] = default.except *excluded_keys
13
+
14
+ default.slice(:present, :table_name, :column_name, :type, :options).values
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module MoneyRails
2
+ module ActiveRecord
3
+ module MigrationExtensions
4
+ module SchemaStatements
5
+ def add_money(table_name, accessor, options={})
6
+ [:amount, :currency].each do |attribute|
7
+ column_present, *opts = OptionsExtractor.extract attribute, table_name, accessor, options
8
+ add_column *opts if column_present
9
+ end
10
+ end
11
+
12
+ def remove_money(table_name, accessor, options={})
13
+ [:amount, :currency].each do |attribute|
14
+ column_present, table_name, column_name, _, _ = OptionsExtractor.extract attribute, table_name, accessor, options
15
+ remove_column table_name, column_name if column_present
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module MoneyRails
2
+ module ActiveRecord
3
+ module MigrationExtensions
4
+ module Table
5
+ def money(accessor, options={})
6
+ [:amount, :currency].each do |attribute|
7
+ column_present, _, *opts = OptionsExtractor.extract attribute, :no_table, accessor, options
8
+ column *opts if column_present
9
+ end
10
+ end
11
+
12
+ def remove_money(accessor, options={})
13
+ [:amount, :currency].each do |attribute|
14
+ column_present, _, column_name, _, _ = OptionsExtractor.extract attribute, :no_table, accessor, options
15
+ remove column_name if column_present
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -43,5 +43,12 @@ module MoneyRails
43
43
  # Use (by default) validation of numericality for each monetized field.
44
44
  mattr_accessor :include_validations
45
45
  @@include_validations = true
46
+
47
+ # Default ActiveRecord migration configuration values for columns
48
+ mattr_accessor :amount_column
49
+ @@amount_column = { postfix: '_cents', type: :integer, null: false, default: 0, present: true }
50
+
51
+ mattr_accessor :currency_column
52
+ @@currency_column = { postfix: '_currency', type: :string, null: false, default: 'USD', present: true }
46
53
  end
47
54
  end
@@ -6,6 +6,11 @@ module MoneyRails
6
6
  require 'money-rails/active_model/validator'
7
7
  require 'money-rails/active_record/monetizable'
8
8
  ::ActiveRecord::Base.send :include, MoneyRails::ActiveRecord::Monetizable
9
+
10
+ %w{options_extractor schema_statements table}.each { |file| require "money-rails/active_record/migration_extensions/#{file}" }
11
+ ::ActiveRecord::Migration.send :include, MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements
12
+ ::ActiveRecord::ConnectionAdapters::TableDefinition.send :include, MoneyRails::ActiveRecord::MigrationExtensions::Table
13
+ ::ActiveRecord::ConnectionAdapters::Table.send :include, MoneyRails::ActiveRecord::MigrationExtensions::Table
9
14
  end
10
15
 
11
16
  # For Mongoid
@@ -28,7 +28,7 @@ class Money
28
28
  when object.is_a?(Money) then object.mongoize
29
29
  when object.is_a?(Hash) then
30
30
  object.symbolize_keys!
31
- ::Money.new(object[:cents], object[:currency]).mongoize
31
+ ::Money.new(object[:cents], object[:currency_iso]).mongoize
32
32
  when object.respond_to?(:to_money) then
33
33
  object.to_money.mongoize
34
34
  else object
@@ -1,3 +1,3 @@
1
1
  module MoneyRails
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.1"
3
3
  end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ class Item < ActiveRecord::Base; end
4
+
5
+ if defined? ActiveRecord
6
+ describe MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements do
7
+ before :all do
8
+ @connection = ActiveRecord::Base.connection
9
+ @connection.drop_table :items if @connection.table_exists? :items
10
+ @connection.create_table :items
11
+ @connection.send :extend, MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements
12
+ end
13
+
14
+ describe 'add_money' do
15
+ before do
16
+ @connection.add_money :items, :price
17
+ @connection.add_money :items, :price_without_currency, currency: { present: false }
18
+ @connection.add_money :items, :price_with_full_options, amount: {
19
+ prefix: :prefix_,
20
+ postfix: :_postfix,
21
+ type: :decimal,
22
+ precision: 4,
23
+ scale: 2,
24
+ default: 1,
25
+ null: true
26
+ }, currency: {
27
+ prefix: :currency_prefix,
28
+ postfix: :currency_postfix,
29
+ column_name: :currency
30
+ }
31
+
32
+ Item.reset_column_information
33
+ end
34
+
35
+ context 'default options' do
36
+ describe 'amount' do
37
+ subject { Item.columns_hash['price_cents'] }
38
+
39
+ its (:default) { should eq 0 }
40
+ its (:null) { should be_false }
41
+ its (:type) { should eq :integer }
42
+ end
43
+
44
+ describe 'currency' do
45
+ subject { Item.columns_hash['price_currency'] }
46
+
47
+ its (:default) { should eq 'USD' }
48
+ its (:null) { should be_false }
49
+ its (:type) { should eq :string }
50
+ end
51
+ end
52
+
53
+ context 'without currency column' do
54
+ it { Item.columns_hash['price_without_currency_cents'].should_not be nil }
55
+ it { Item.columns_hash['price_without_currency_currency'].should be nil }
56
+ end
57
+
58
+ context 'full options' do
59
+ describe 'amount' do
60
+ subject { Item.columns_hash['prefix_price_with_full_options_postfix'] }
61
+
62
+ its (:default) { should eq 1 }
63
+ its (:null) { should be_true }
64
+ its (:type) { should eq :decimal }
65
+ its (:precision) { should eq 4 }
66
+ its (:scale) { should eq 2 }
67
+ end
68
+
69
+ describe 'currency' do
70
+ it { Item.columns_hash['currency'].should_not be nil }
71
+ end
72
+ end
73
+ end
74
+
75
+ describe 'remove_money' do
76
+ before do
77
+ @connection.add_money :items, :price
78
+ @connection.add_money :items, :price_without_currency, currency: { present: false }
79
+ @connection.add_money :items, :price_with_full_options, amount: { prefix: :prefix_, postfix: :_postfix }, currency: { column_name: :currency }
80
+
81
+ @connection.remove_money :items, :price
82
+ @connection.remove_money :items, :price_without_currency, currency: { present: false }
83
+ @connection.remove_money :items, :price_with_full_options, amount: { prefix: :prefix_, postfix: :_postfix }, currency: { column_name: :currency }
84
+
85
+ Item.reset_column_information
86
+ end
87
+
88
+ it { Item.columns_hash['price_cents'].should be nil }
89
+ it { Item.columns_hash['price_currency'].should be nil }
90
+
91
+ it { Item.columns_hash['price_without_currency_cents'].should be nil }
92
+
93
+ it { Item.columns_hash['prefix_price_with_full_options_postfix'].should be nil }
94
+ it { Item.columns_hash['currency'].should be nil }
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ class Item < ActiveRecord::Base; end
4
+
5
+ if defined? ActiveRecord
6
+ describe MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements do
7
+ before :all do
8
+ @connection = ActiveRecord::Base.connection
9
+ @connection.send :extend, MoneyRails::ActiveRecord::MigrationExtensions::SchemaStatements
10
+ end
11
+
12
+ describe 'money' do
13
+ before do
14
+ @connection.drop_table :items if @connection.table_exists? :items
15
+ @connection.create_table :items do |t|
16
+ t.money :price
17
+ t.money :price_without_currency, currency: { present: false }
18
+ t.money :price_with_full_options, amount: {
19
+ prefix: :prefix_,
20
+ postfix: :_postfix,
21
+ type: :decimal,
22
+ precision: 4,
23
+ scale: 2,
24
+ default: 1,
25
+ null: true
26
+ }, currency: {
27
+ prefix: :currency_prefix,
28
+ postfix: :currency_postfix,
29
+ column_name: :currency
30
+ }
31
+ end
32
+
33
+ Item.reset_column_information
34
+ end
35
+
36
+ context 'default options' do
37
+ describe 'amount' do
38
+ subject { Item.columns_hash['price_cents'] }
39
+
40
+ its (:default) { should eq 0 }
41
+ its (:null) { should be_false }
42
+ its (:type) { should eq :integer }
43
+ end
44
+
45
+ describe 'currency' do
46
+ subject { Item.columns_hash['price_currency'] }
47
+
48
+ its (:default) { should eq 'USD' }
49
+ its (:null) { should be_false }
50
+ its (:type) { should eq :string }
51
+ end
52
+ end
53
+
54
+ context 'without currency column' do
55
+ it { Item.columns_hash['price_without_currency_cents'].should_not be nil }
56
+ it { Item.columns_hash['price_without_currency_currency'].should be nil }
57
+ end
58
+
59
+ context 'full options' do
60
+ describe 'amount' do
61
+ subject { Item.columns_hash['prefix_price_with_full_options_postfix'] }
62
+
63
+ its (:default) { should eq 1 }
64
+ its (:null) { should be_true }
65
+ its (:type) { should eq :decimal }
66
+ its (:precision) { should eq 4 }
67
+ its (:scale) { should eq 2 }
68
+ end
69
+
70
+ describe 'currency' do
71
+ it { Item.columns_hash['currency'].should_not be nil }
72
+ end
73
+ end
74
+ end
75
+
76
+ describe 'remove_money' do
77
+ before do
78
+ @connection.change_table :items do |t|
79
+ t.money :price
80
+ t.money :price_without_currency, currency: { present: false }
81
+ t.money :price_with_full_options, amount: { prefix: :prefix_, postfix: :_postfix }, currency: { column_name: :currency }
82
+
83
+ t.remove_money :price
84
+ t.remove_money :price_without_currency, currency: { present: false }
85
+ t.remove_money :price_with_full_options, amount: { prefix: :prefix_, postfix: :_postfix }, currency: { column_name: :currency }
86
+ end
87
+
88
+ Item.reset_column_information
89
+ end
90
+
91
+ it { Item.columns_hash['price_cents'].should be nil }
92
+ it { Item.columns_hash['price_currency'].should be nil }
93
+
94
+ it { Item.columns_hash['price_without_currency_cents'].should be nil }
95
+
96
+ it { Item.columns_hash['prefix_price_with_full_options_postfix'].should be nil }
97
+ it { Item.columns_hash['currency'].should be nil }
98
+ end
99
+ end
100
+ end