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.
- 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
|