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.
- data/CHANGELOG.md +5 -1
- data/README.md +25 -0
- data/config/locales/money.en.yml +1 -1
- data/lib/generators/templates/money.rb +20 -0
- data/lib/money-rails/active_model/validator.rb +18 -14
- data/lib/money-rails/active_record/migration_extensions/options_extractor.rb +19 -0
- data/lib/money-rails/active_record/migration_extensions/schema_statements.rb +21 -0
- data/lib/money-rails/active_record/migration_extensions/table.rb +21 -0
- data/lib/money-rails/configuration.rb +7 -0
- data/lib/money-rails/hooks.rb +5 -0
- data/lib/money-rails/mongoid/three.rb +1 -1
- data/lib/money-rails/version.rb +1 -1
- data/spec/active_record/migration_extensions/schema_statements_spec.rb +97 -0
- data/spec/active_record/migration_extensions/table_spec.rb +100 -0
- data/spec/active_record/monetizable_spec.rb +19 -1
- data/spec/dummy/config/locales/en-GB.yml +11 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/test.log +4446 -0
- metadata +11 -3
- data/config/locales/money.es.yml +0 -5
data/CHANGELOG.md
CHANGED
@@ -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
|
|
data/config/locales/money.en.yml
CHANGED
@@ -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
|
-
|
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
|
-
|
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.
|
29
|
-
{ :thousands =>
|
30
|
-
:decimal =>
|
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(
|
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.
|
38
|
-
{ :thousands =>
|
39
|
-
:decimal =>
|
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.
|
42
|
-
{ :thousands =>
|
43
|
-
:decimal =>
|
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(
|
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(
|
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
|
data/lib/money-rails/hooks.rb
CHANGED
@@ -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[:
|
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
|
data/lib/money-rails/version.rb
CHANGED
@@ -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
|