money_attribute 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/README.md +143 -41
- data/Rakefile +43 -2
- data/lib/generators/money_attribute/initializer_generator.rb +10 -0
- data/lib/generators/templates/money_attribute.rb +20 -12
- data/lib/money_attribute/attribute_spec.rb +84 -0
- data/lib/money_attribute/attribute_spec_registry.rb +78 -0
- data/lib/money_attribute/column_type_validations.rb +47 -0
- data/lib/money_attribute/configuration.rb +43 -15
- data/lib/money_attribute/converter.rb +30 -5
- data/lib/money_attribute/core_ext/numeric.rb +17 -0
- data/lib/money_attribute/core_ext/string.rb +11 -1
- data/lib/money_attribute/current.rb +13 -0
- data/lib/money_attribute/form_builder_extension.rb +48 -1
- data/lib/money_attribute/macro.rb +106 -72
- data/lib/money_attribute/migration_extensions/helper.rb +62 -3
- data/lib/money_attribute/migration_extensions/schema_statements.rb +89 -9
- data/lib/money_attribute/migration_extensions/table_definition.rb +68 -11
- data/lib/money_attribute/money_amount.rb +49 -25
- data/lib/money_attribute/query/amount_condition.rb +207 -0
- data/lib/money_attribute/query/amount_order.rb +28 -0
- data/lib/money_attribute/query/currency_condition.rb +29 -0
- data/lib/money_attribute/query/helpers.rb +53 -0
- data/lib/money_attribute/query/pick.rb +41 -0
- data/lib/money_attribute/query/pluck.rb +39 -0
- data/lib/money_attribute/query/sum.rb +52 -0
- data/lib/money_attribute/query.rb +125 -0
- data/lib/money_attribute/railtie.rb +47 -1
- data/lib/money_attribute/type.rb +41 -33
- data/lib/money_attribute/version.rb +2 -1
- data/lib/money_attribute.rb +16 -1
- metadata +17 -5
- data/lib/money_attribute/core_ext.rb +0 -8
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'query/helpers'
|
|
4
|
+
require_relative 'query/currency_condition'
|
|
5
|
+
require_relative 'query/amount_condition'
|
|
6
|
+
require_relative 'query/amount_order'
|
|
7
|
+
require_relative 'query/pluck'
|
|
8
|
+
require_relative 'query/pick'
|
|
9
|
+
require_relative 'query/sum'
|
|
10
|
+
|
|
11
|
+
module MoneyAttribute
|
|
12
|
+
# Money-aware query helpers for ActiveRecord.
|
|
13
|
+
#
|
|
14
|
+
# Offer.where_currency(price: 'EUR')
|
|
15
|
+
# Offer.where_amount(price: 10..100)
|
|
16
|
+
# Offer.order_by_amount(price: :desc)
|
|
17
|
+
# Offer.pluck_amount(:price)
|
|
18
|
+
# Offer.pick_amount(:price)
|
|
19
|
+
# Offer.sum_amount(:price)
|
|
20
|
+
#
|
|
21
|
+
module Query
|
|
22
|
+
extend ActiveSupport::Concern
|
|
23
|
+
|
|
24
|
+
class_methods do
|
|
25
|
+
# Filters by currency for one or more money attributes.
|
|
26
|
+
#
|
|
27
|
+
# @param conditions [Hash{Symbol => String}] attribute name to currency code
|
|
28
|
+
# @return [ActiveRecord::Relation]
|
|
29
|
+
# @raise [ArgumentError] if the attribute is not a composite money attribute
|
|
30
|
+
def where_currency(conditions)
|
|
31
|
+
scope = all
|
|
32
|
+
conditions.each { |attr, value| scope = scope.resolve_currency_condition(attr, value) }
|
|
33
|
+
scope
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Filters by amount for one or more money attributes.
|
|
37
|
+
#
|
|
38
|
+
# Accepts a hash of conditions or a SQL string with +?+ placeholders.
|
|
39
|
+
# Hash: +{ attr: value }+, supports +Mint::Money+, +Range+, +Array+.
|
|
40
|
+
# SQL: only money attribute names, +and+, +or+, +not+, +is+, +null+.
|
|
41
|
+
#
|
|
42
|
+
# @param args [Array] a condition hash, or a SQL string with bind values
|
|
43
|
+
# @return [ActiveRecord::Relation]
|
|
44
|
+
# @raise [ArgumentError] if an identifier is not a registered money attribute
|
|
45
|
+
def where_amount(*args)
|
|
46
|
+
if args.first.is_a?(Hash)
|
|
47
|
+
scope = all
|
|
48
|
+
args.first.each { |attr, value| scope = scope.resolve_amount_condition(attr, value) }
|
|
49
|
+
scope
|
|
50
|
+
else
|
|
51
|
+
all.resolve_amount_condition_from_sql(*args)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Orders by amount for one or more money attributes.
|
|
56
|
+
#
|
|
57
|
+
# Composite attributes sort by currency ASC first, then amount.
|
|
58
|
+
#
|
|
59
|
+
# @param conditions [Hash{Symbol => Symbol}] attribute name to +:asc+ or +:desc+
|
|
60
|
+
# @return [ActiveRecord::Relation]
|
|
61
|
+
# @raise [ArgumentError] if the attribute is not a registered money attribute
|
|
62
|
+
def order_by_amount(conditions)
|
|
63
|
+
scope = all
|
|
64
|
+
conditions.each { |attr, dir| scope = scope.resolve_amount_order(attr, dir || :asc) }
|
|
65
|
+
scope
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Plucks money-aware amounts from the current relation.
|
|
69
|
+
#
|
|
70
|
+
# Offer.pluck_amount(:price)
|
|
71
|
+
# # => [Mint::Money(10.0, 'EUR'), Mint::Money(20.0, 'USD')]
|
|
72
|
+
#
|
|
73
|
+
# @param attrs [Array<Symbol>] one or more registered money attribute names
|
|
74
|
+
# @return [Array<Mint::Money>] for a single attribute
|
|
75
|
+
# @return [Array<Array>] for multiple attributes, one row array per attribute
|
|
76
|
+
# @raise [ArgumentError] if any attribute is not a registered money attribute
|
|
77
|
+
def pluck_amount(*attrs)
|
|
78
|
+
all.pluck_amount(*attrs)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Picks a single money-aware value from the current relation.
|
|
82
|
+
#
|
|
83
|
+
# Offer.pick_amount(:price)
|
|
84
|
+
# # => Mint::Money(10.0, 'EUR')
|
|
85
|
+
#
|
|
86
|
+
# @param attrs [Array<Symbol>] one or more registered money attribute names
|
|
87
|
+
# @return [Mint::Money, Array, nil] Money for a single attribute, row array for multiple, nil if empty
|
|
88
|
+
# @raise [ArgumentError] if any attribute is not a registered money attribute
|
|
89
|
+
def pick_amount(*attrs)
|
|
90
|
+
all.pick_amount(*attrs)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Sums money-aware amounts, grouping by currency for composite attributes.
|
|
94
|
+
#
|
|
95
|
+
# Offer.sum_amount(:price)
|
|
96
|
+
# # => [Mint::Money(30.0, 'EUR'), Mint::Money(50.0, 'USD')]
|
|
97
|
+
#
|
|
98
|
+
# @param attr [Symbol] a registered money attribute name
|
|
99
|
+
# @return [Array<Mint::Money>] one Money per currency (or one for single-column attributes)
|
|
100
|
+
# @raise [ArgumentError] if the attribute is not a registered money attribute
|
|
101
|
+
def sum_amount(attr)
|
|
102
|
+
all.sum_amount(attr)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Internal mixin combining all query sub-modules onto relations.
|
|
108
|
+
#
|
|
109
|
+
# @api private
|
|
110
|
+
module QueryMethods
|
|
111
|
+
include QueryHelpers
|
|
112
|
+
include CurrencyCondition
|
|
113
|
+
include AmountCondition
|
|
114
|
+
include AmountOrder
|
|
115
|
+
include PluckAmount
|
|
116
|
+
include PickAmount
|
|
117
|
+
include SumAmount
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
ActiveSupport.on_load(:active_record) do
|
|
122
|
+
include MoneyAttribute::AttributeSpecRegistry
|
|
123
|
+
include MoneyAttribute::Query
|
|
124
|
+
ActiveRecord::Relation.include(MoneyAttribute::QueryMethods)
|
|
125
|
+
end
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MoneyAttribute
|
|
4
|
+
# Rails engine integration.
|
|
5
|
+
#
|
|
6
|
+
# On boot, includes the migration and form-builder extensions into the
|
|
7
|
+
# corresponding Rails classes, wires Mint's locale backend to Rails i18n,
|
|
8
|
+
# and registers custom currencies declared in the initializer.
|
|
9
|
+
#
|
|
10
|
+
# @example Generated initializer
|
|
11
|
+
# MoneyAttribute.configure do |config|
|
|
12
|
+
# config.default_currency = 'BRL'
|
|
13
|
+
# config.added_currencies = [
|
|
14
|
+
# { currency: 'BTB', subunit: 8, symbol: '₿' }
|
|
15
|
+
# ]
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# @api private
|
|
4
19
|
class Railtie < ::Rails::Railtie
|
|
5
20
|
generators do
|
|
6
21
|
require 'generators/money_attribute/initializer_generator'
|
|
@@ -20,15 +35,28 @@ module MoneyAttribute
|
|
|
20
35
|
register_custom_currencies!
|
|
21
36
|
end
|
|
22
37
|
|
|
38
|
+
# Configures Mint to use the Rails locale currency format.
|
|
39
|
+
#
|
|
40
|
+
# @return [void]
|
|
41
|
+
# @api private
|
|
23
42
|
def self.setup_locale_backend!
|
|
24
43
|
::Mint.locale_backend = method(:build_locale_format).to_proc
|
|
25
44
|
end
|
|
26
45
|
|
|
46
|
+
# Builds the locale-aware currency formatting hash.
|
|
47
|
+
#
|
|
48
|
+
# @return [Hash] the +:decimal+, +:thousand+, and +:format+ keys for Mint
|
|
49
|
+
# @api private
|
|
27
50
|
def self.build_locale_format
|
|
28
51
|
fmt = I18n.t('number.currency.format', default: {})
|
|
29
52
|
{ decimal: fmt[:separator], thousand: fmt[:delimiter], format: build_format(fmt) }
|
|
30
53
|
end
|
|
31
54
|
|
|
55
|
+
# Builds the final currency format string or hash for Mint.
|
|
56
|
+
#
|
|
57
|
+
# @param fmt [Hash] the Rails currency format settings
|
|
58
|
+
# @return [String, Hash] a single format string or a per-sign format hash
|
|
59
|
+
# @api private
|
|
32
60
|
def self.build_format(fmt)
|
|
33
61
|
if %i[positive negative zero].any? { |k| fmt.key?(k) }
|
|
34
62
|
build_hash_format(fmt)
|
|
@@ -37,6 +65,11 @@ module MoneyAttribute
|
|
|
37
65
|
end
|
|
38
66
|
end
|
|
39
67
|
|
|
68
|
+
# Builds a per-sign currency format hash.
|
|
69
|
+
#
|
|
70
|
+
# @param fmt [Hash] the Rails currency format settings
|
|
71
|
+
# @return [Hash] the +:positive+, +:negative+, and +:zero+ format strings
|
|
72
|
+
# @api private
|
|
40
73
|
def self.build_hash_format(fmt)
|
|
41
74
|
{
|
|
42
75
|
positive: translate_format(fmt[:positive] || fmt[:format]),
|
|
@@ -45,10 +78,23 @@ module MoneyAttribute
|
|
|
45
78
|
}
|
|
46
79
|
end
|
|
47
80
|
|
|
81
|
+
# Translates Rails currency placeholders into Mint placeholders.
|
|
82
|
+
#
|
|
83
|
+
# @param str [String, nil] the Rails format string
|
|
84
|
+
# @return [String] the translated format string
|
|
85
|
+
# @api private
|
|
48
86
|
def self.translate_format(str)
|
|
49
87
|
str.to_s.gsub('%n', '%<amount>f').gsub('%u', '%<symbol>s')
|
|
50
88
|
end
|
|
51
89
|
|
|
90
|
+
# Registers custom currencies configured by the application.
|
|
91
|
+
#
|
|
92
|
+
# Accepts hashes with +:currency+, +:subunit+, +:symbol+ keys or the
|
|
93
|
+
# matching positional array form. Already-registered currencies are skipped.
|
|
94
|
+
#
|
|
95
|
+
# @return [void]
|
|
96
|
+
# @raise [ArgumentError] if a currency hash is missing a required key
|
|
97
|
+
# @api private
|
|
52
98
|
def self.register_custom_currencies!
|
|
53
99
|
Array(MoneyAttribute.config.added_currencies).each do |currency_data|
|
|
54
100
|
if currency_data.respond_to?(:values_at)
|
|
@@ -58,7 +104,7 @@ module MoneyAttribute
|
|
|
58
104
|
else
|
|
59
105
|
code, subunit, symbol = *currency_data
|
|
60
106
|
end
|
|
61
|
-
::
|
|
107
|
+
Money::Currency.register(code:, subunit:, symbol:)
|
|
62
108
|
rescue KeyError => e
|
|
63
109
|
unless e.message.include?('already registered')
|
|
64
110
|
raise ArgumentError,
|
data/lib/money_attribute/type.rb
CHANGED
|
@@ -1,54 +1,62 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MoneyAttribute
|
|
4
|
-
#
|
|
5
|
-
class
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def cast(value)
|
|
13
|
-
if value.is_a?(String)
|
|
14
|
-
Mint::Money.parse(value, @currency)
|
|
15
|
-
else
|
|
16
|
-
super
|
|
17
|
-
end
|
|
18
|
-
end
|
|
4
|
+
# Base type for money amount attributes. Handles casting and validation.
|
|
5
|
+
class AmountType < ActiveRecord::Type::Value
|
|
6
|
+
# Casts string input into a +Mint::Money+ value.
|
|
7
|
+
#
|
|
8
|
+
# @param value [String, Numeric, Mint::Money, nil] the input value
|
|
9
|
+
# @return [Mint::Money, Numeric, nil] a Money value for strings, otherwise delegates to super
|
|
10
|
+
def cast(value) = value.is_a?(String) ? Money.parse(value, MoneyAttribute.default_currency) : super
|
|
19
11
|
|
|
12
|
+
# Validates that the value is compatible with the fixed currency type.
|
|
13
|
+
#
|
|
14
|
+
# @param value [Object] the value to validate
|
|
15
|
+
# @return [void]
|
|
16
|
+
# @raise [ArgumentError] when the value has a mismatched currency or is an unsupported type
|
|
20
17
|
def assert_valid_value(value)
|
|
21
18
|
case value
|
|
22
19
|
when NilClass, Numeric, String then return
|
|
23
20
|
when Mint::Money
|
|
24
|
-
|
|
21
|
+
currency = MoneyAttribute.default_currency
|
|
22
|
+
return if value.currency == currency
|
|
25
23
|
|
|
26
|
-
message = "'#{value.inspect}' has different currency. Only #{
|
|
24
|
+
message = "'#{value.inspect}' has different currency. Only #{currency.code} allowed."
|
|
27
25
|
else
|
|
28
26
|
message = "'#{value.inspect}' is not a valid type for the attribute."
|
|
29
27
|
end
|
|
30
28
|
raise ArgumentError, message
|
|
31
29
|
end
|
|
30
|
+
end
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
# Type for integer columns storing subunits (e.g. cents).
|
|
33
|
+
class IntegerAmountType < AmountType
|
|
34
|
+
# Deserializes a subunit integer into a +Mint::Money+ value.
|
|
35
|
+
#
|
|
36
|
+
# @param value [Integer, nil] the raw database value
|
|
37
|
+
# @return [Mint::Money, nil]
|
|
38
|
+
def deserialize(value) = value && Money.from_subunits(value.to_i, MoneyAttribute.default_currency)
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
# Serializes a +Mint::Money+ value into subunits.
|
|
41
|
+
#
|
|
42
|
+
# @param value [Mint::Money, nil]
|
|
43
|
+
# @return [Integer, nil]
|
|
44
|
+
def serialize(value) = value&.subunits
|
|
45
|
+
end
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
# Type for decimal columns storing unit values (e.g. 12.34).
|
|
48
|
+
class DecimalAmountType < AmountType
|
|
49
|
+
# Deserializes a decimal value into a +Mint::Money+ value.
|
|
50
|
+
#
|
|
51
|
+
# @param value [BigDecimal, nil] the raw database value
|
|
52
|
+
# @return [Mint::Money, nil]
|
|
53
|
+
def deserialize(value) = value&.to_money(MoneyAttribute.default_currency)
|
|
45
54
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
55
|
+
# Serializes a +Mint::Money+ value into a decimal.
|
|
56
|
+
#
|
|
57
|
+
# @param value [Mint::Money, nil]
|
|
58
|
+
# @return [BigDecimal, nil]
|
|
59
|
+
def serialize(value) = value&.to_d
|
|
52
60
|
end
|
|
53
61
|
end
|
|
54
62
|
|
data/lib/money_attribute.rb
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# MoneyAttribute provides ActiveRecord integrations for the Minting money gem.
|
|
4
|
+
#
|
|
5
|
+
# Two storage modes:
|
|
6
|
+
#
|
|
7
|
+
# - +money_attribute :price+ — composite (amount + currency columns)
|
|
8
|
+
# - +money_amount :price+ — single column (fixed currency)
|
|
9
|
+
#
|
|
10
|
+
# @see MoneyAttribute::Macro
|
|
11
|
+
# @see MoneyAttribute::MoneyAmount
|
|
12
|
+
|
|
3
13
|
require 'minting'
|
|
4
|
-
require 'money_attribute/core_ext'
|
|
14
|
+
require 'money_attribute/core_ext/numeric'
|
|
5
15
|
require 'money_attribute/core_ext/string'
|
|
6
16
|
require 'money_attribute/configuration'
|
|
17
|
+
require 'money_attribute/current'
|
|
18
|
+
require 'money_attribute/column_type_validations'
|
|
19
|
+
require 'money_attribute/attribute_spec'
|
|
20
|
+
require 'money_attribute/attribute_spec_registry'
|
|
7
21
|
require 'money_attribute/macro'
|
|
8
22
|
require 'money_attribute/money_amount'
|
|
9
23
|
require 'money_attribute/converter'
|
|
10
24
|
require 'money_attribute/type'
|
|
11
25
|
require 'money_attribute/form_builder_extension'
|
|
26
|
+
require 'money_attribute/query'
|
|
12
27
|
require 'money_attribute/railtie'
|
|
13
28
|
require 'money_attribute/version'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: money_attribute
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1
|
|
4
|
+
version: 1.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gilson Ferraz
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '2.
|
|
18
|
+
version: '2.1'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '2.
|
|
25
|
+
version: '2.1'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: rails
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -50,16 +50,28 @@ files:
|
|
|
50
50
|
- lib/generators/money_attribute/initializer_generator.rb
|
|
51
51
|
- lib/generators/templates/money_attribute.rb
|
|
52
52
|
- lib/money_attribute.rb
|
|
53
|
+
- lib/money_attribute/attribute_spec.rb
|
|
54
|
+
- lib/money_attribute/attribute_spec_registry.rb
|
|
55
|
+
- lib/money_attribute/column_type_validations.rb
|
|
53
56
|
- lib/money_attribute/configuration.rb
|
|
54
57
|
- lib/money_attribute/converter.rb
|
|
55
|
-
- lib/money_attribute/core_ext.rb
|
|
58
|
+
- lib/money_attribute/core_ext/numeric.rb
|
|
56
59
|
- lib/money_attribute/core_ext/string.rb
|
|
60
|
+
- lib/money_attribute/current.rb
|
|
57
61
|
- lib/money_attribute/form_builder_extension.rb
|
|
58
62
|
- lib/money_attribute/macro.rb
|
|
59
63
|
- lib/money_attribute/migration_extensions/helper.rb
|
|
60
64
|
- lib/money_attribute/migration_extensions/schema_statements.rb
|
|
61
65
|
- lib/money_attribute/migration_extensions/table_definition.rb
|
|
62
66
|
- lib/money_attribute/money_amount.rb
|
|
67
|
+
- lib/money_attribute/query.rb
|
|
68
|
+
- lib/money_attribute/query/amount_condition.rb
|
|
69
|
+
- lib/money_attribute/query/amount_order.rb
|
|
70
|
+
- lib/money_attribute/query/currency_condition.rb
|
|
71
|
+
- lib/money_attribute/query/helpers.rb
|
|
72
|
+
- lib/money_attribute/query/pick.rb
|
|
73
|
+
- lib/money_attribute/query/pluck.rb
|
|
74
|
+
- lib/money_attribute/query/sum.rb
|
|
63
75
|
- lib/money_attribute/railtie.rb
|
|
64
76
|
- lib/money_attribute/type.rb
|
|
65
77
|
- lib/money_attribute/version.rb
|
|
@@ -88,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
88
100
|
- !ruby/object:Gem::Version
|
|
89
101
|
version: '0'
|
|
90
102
|
requirements: []
|
|
91
|
-
rubygems_version: 4.0.
|
|
103
|
+
rubygems_version: 4.0.16
|
|
92
104
|
specification_version: 4
|
|
93
105
|
summary: Money attributes for ActiveRecord
|
|
94
106
|
test_files: []
|