money-rails 0.7.0 → 0.7.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.
@@ -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