minting-rails 0.8.2 → 0.8.3
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 +40 -0
- data/lib/generators/templates/minting.rb +2 -2
- data/lib/minting/money_attribute/money_attribute.rb +66 -40
- data/lib/minting/money_attribute/money_type.rb +2 -2
- data/lib/minting/money_attribute/version.rb +1 -1
- data/lib/minting/railties.rb +5 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f128e6d9ea67e7b1f082a06c27bc7f9a332094bdfefcdf5c63ed101eafbb59b
|
|
4
|
+
data.tar.gz: a8d8a159f47e1a9fa906777072bcb9872db56367a5b2479b786968aaa43804fe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1076b8630bc62f73a37c3b1f72121e5aee2afce3ebba8b6964dfe84f0cde55961081ea84b48e2514b2230c9ebbacd9a939aca796037401e667e7a4bb8849fde
|
|
7
|
+
data.tar.gz: a6346b842fb7c1337b13ffecf18967f72d581125ecdc9999e82dd358b4021d5ac2824ad4c1fdc8ae43085065343523b75c81796da6f6a5a03a60e536f5620e6e
|
data/README.md
CHANGED
|
@@ -269,6 +269,46 @@ end
|
|
|
269
269
|
|
|
270
270
|
The mapping keys are `:amount` and `:currency`; values are your database column names.
|
|
271
271
|
|
|
272
|
+
## Column resolution
|
|
273
|
+
|
|
274
|
+
When you declare `money_attribute :name`, the gem resolves which database columns to use by checking the table schema in this order:
|
|
275
|
+
|
|
276
|
+
| Step | Condition | Columns used | Mode |
|
|
277
|
+
|---|---|---|---|
|
|
278
|
+
| 1 | `mapping:` provided | As specified | Explicit composite |
|
|
279
|
+
| 2 | `name_currency` column exists | `name` + `name_currency` | Composite (multi-currency) |
|
|
280
|
+
| 3 | `name == 'amount'` AND `currency` column exists | `amount` + `currency` | Composite (multi-currency) |
|
|
281
|
+
| 4 | `name_amount` + `name_currency` columns exist | `name_amount` + `name_currency` | Composite (multi-currency) |
|
|
282
|
+
| 5 | `name` column exists (no currency partner) | `name` alone | Single-column (fixed-currency) |
|
|
283
|
+
|
|
284
|
+
**Example**
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
create_table :financial_transactions do |t|
|
|
288
|
+
t.integer :amount
|
|
289
|
+
t.string :currency, limit: 3
|
|
290
|
+
t.integer :discount
|
|
291
|
+
t.string :discount_currency, limit: 3
|
|
292
|
+
t.decimal :price_amount
|
|
293
|
+
t.string :price_currency, limit: 3
|
|
294
|
+
t.bigint :surplus
|
|
295
|
+
t.bigint :tax
|
|
296
|
+
t.decimal :total_amount
|
|
297
|
+
t.string :currency_code, limit: 3
|
|
298
|
+
end
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
class FinancialTransaction < ApplicationRecord
|
|
303
|
+
money_attribute :amount # step 3: amount(int) + currency
|
|
304
|
+
money_attribute :discount # step 2: discount(int) + discount_currency
|
|
305
|
+
money_attribute :price # step 4: price_amount(dec) + price_currency
|
|
306
|
+
money_attribute :surplus, currency: 'EUR' # step 5: surplus(int) (single-column, will use EUR)
|
|
307
|
+
money_attribute :tax # step 5: tax(int) (single-column, will use default currency)
|
|
308
|
+
money_attribute :total, mapping: { amount: :total_amount, currency: :currency_code } # step 1: explicit
|
|
309
|
+
end
|
|
310
|
+
```
|
|
311
|
+
|
|
272
312
|
## Querying
|
|
273
313
|
|
|
274
314
|
Fixed-currency attributes support Rails-native querying through the custom type:
|
|
@@ -10,8 +10,8 @@ Mint.configure do |config|
|
|
|
10
10
|
# {currency: 'NGN', subunit: 3, symbol: '₦'}
|
|
11
11
|
# ]
|
|
12
12
|
config.added_currencies = [
|
|
13
|
-
{ currency: '
|
|
14
|
-
{ currency: '
|
|
13
|
+
{ currency: 'XCRC', subunit: 2, symbol: '₡' },
|
|
14
|
+
{ currency: 'XNGN', subunit: 3, symbol: '₦' }
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
# To set the default currency
|
|
@@ -1,65 +1,91 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Mint
|
|
4
|
-
# MoneyAttribute
|
|
5
4
|
module MoneyAttribute
|
|
6
5
|
extend ActiveSupport::Concern
|
|
7
6
|
|
|
8
7
|
class_methods do
|
|
9
|
-
# Money attribute
|
|
10
8
|
def money_attribute(name, currency: Mint.default_currency, mapping: nil)
|
|
9
|
+
columns = attribute_names
|
|
11
10
|
currency = Currency.resolve!(currency)
|
|
11
|
+
name = name.to_s
|
|
12
12
|
parser = Parser.new(currency)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
resolved_mapping = mapping || resolve_mapping(name, columns)
|
|
14
|
+
|
|
15
|
+
if columns.include?(name) && resolved_mapping.nil?
|
|
16
|
+
define_single_column_money_attribute(name, currency, parser)
|
|
16
17
|
else
|
|
17
|
-
|
|
18
|
-
options = {
|
|
19
|
-
allow_nil: true, class_name: 'Mint::Money',
|
|
20
|
-
constructor: parser, converter: parser,
|
|
21
|
-
mapping: {
|
|
22
|
-
aggregated[:amount] => amount_extractor_for(aggregated[:amount]),
|
|
23
|
-
aggregated[:currency] => :currency_code
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
composed_of(name, options)
|
|
18
|
+
define_composite_money_attribute(name, resolved_mapping, parser)
|
|
27
19
|
end
|
|
28
20
|
end
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
col = columns.find { |c| c.name == column_name.to_s }
|
|
22
|
+
private
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
24
|
+
# --- Preparation (no side effects) ---
|
|
25
|
+
|
|
26
|
+
def resolve_mapping(name, columns)
|
|
27
|
+
return nil unless columns.include?(name)
|
|
28
|
+
|
|
29
|
+
if columns.include?("#{name}_currency")
|
|
30
|
+
{ amount: name, currency: :"#{name}_currency" }
|
|
31
|
+
elsif columns.include?('currency') && name == 'amount'
|
|
32
|
+
{ amount: name, currency: :currency }
|
|
38
33
|
end
|
|
39
34
|
end
|
|
40
35
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
missing_keys = (%i[amount currency] - mapping.keys).join(', ')
|
|
44
|
-
if missing_keys.present?
|
|
45
|
-
raise ArgumentError,
|
|
46
|
-
"Mapping for :#{name} money attribute is missing required keys: #{missing_keys}"
|
|
47
|
-
end
|
|
48
|
-
composite = { amount: mapping[:amount].to_s, currency: mapping[:currency].to_s }
|
|
49
|
-
else
|
|
50
|
-
composite = { amount: "#{name}_amount", currency: "#{name}_currency" }
|
|
51
|
-
end
|
|
36
|
+
def resolve_composite_for(name, mapping:)
|
|
37
|
+
composite = { amount: "#{name}_amount", currency: "#{name}_currency" }
|
|
52
38
|
|
|
53
|
-
|
|
54
|
-
if
|
|
55
|
-
raise ArgumentError,
|
|
56
|
-
"Could not find columns for :#{name} money attribute. " \
|
|
57
|
-
"Expected: #{composite.values.join(', ')}, " \
|
|
58
|
-
"Found: #{attribute_names.join(', ')}"
|
|
59
|
-
end
|
|
39
|
+
composite[:amount] = mapping[:amount].to_s if mapping&.key?(:amount)
|
|
40
|
+
composite[:currency] = mapping[:currency].to_s if mapping&.key?(:currency)
|
|
60
41
|
|
|
42
|
+
assert_columns_exist!(name, composite)
|
|
61
43
|
composite
|
|
62
44
|
end
|
|
45
|
+
|
|
46
|
+
def assert_columns_exist!(name, composite)
|
|
47
|
+
missing = composite.values - attribute_names
|
|
48
|
+
return if missing.empty?
|
|
49
|
+
|
|
50
|
+
raise ArgumentError,
|
|
51
|
+
"Could not find columns for :#{name} money attribute. " \
|
|
52
|
+
"Expected: #{composite.values.join(', ')}, " \
|
|
53
|
+
"Found: #{attribute_names.join(', ')}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def amount_extractor_for(column_name)
|
|
57
|
+
integer_column?(column_name) ? :fractional : :to_d
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def money_constructor_for(amount_column) = integer_column?(amount_column) ? :from_fractional : :from
|
|
61
|
+
|
|
62
|
+
def integer_column?(column_name)
|
|
63
|
+
col = columns.find { |c| c.name == column_name }
|
|
64
|
+
%i[integer bigint].include?(col&.type)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# --- Configuration (registers types, normalizers, composed_of) ---
|
|
68
|
+
|
|
69
|
+
def define_single_column_money_attribute(name, currency, parser)
|
|
70
|
+
column_type = integer_column?(name) ? ActiveRecord::Type::Integer.new : ActiveRecord::Type::Decimal.new
|
|
71
|
+
attribute(name.to_sym, :mint_money, currency:, column_type: column_type)
|
|
72
|
+
normalizes(name.to_sym, with: parser)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def define_composite_money_attribute(name, mapping, parser)
|
|
76
|
+
aggregated = resolve_composite_for(name, mapping:)
|
|
77
|
+
|
|
78
|
+
composed_of(name.to_sym, {
|
|
79
|
+
allow_nil: true,
|
|
80
|
+
class_name: 'Mint::Money',
|
|
81
|
+
constructor: money_constructor_for(aggregated[:amount]),
|
|
82
|
+
converter: parser,
|
|
83
|
+
mapping: {
|
|
84
|
+
aggregated[:amount] => amount_extractor_for(aggregated[:amount]),
|
|
85
|
+
aggregated[:currency] => :currency_code
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
end
|
|
63
89
|
end
|
|
64
90
|
end
|
|
65
91
|
end
|
|
@@ -26,9 +26,9 @@ module Mint
|
|
|
26
26
|
return nil unless value
|
|
27
27
|
|
|
28
28
|
if @column_type.is_a?(ActiveRecord::Type::Integer)
|
|
29
|
-
Mint.
|
|
29
|
+
Mint::Money.from_fractional(value, @currency)
|
|
30
30
|
else
|
|
31
|
-
Mint.
|
|
31
|
+
Mint::Money.from(value, @currency)
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
data/lib/minting/railties.rb
CHANGED
|
@@ -33,13 +33,15 @@ module Mint
|
|
|
33
33
|
def self.register_custom_currencies!
|
|
34
34
|
Array(Mint.config.added_currencies).each do |currency_data|
|
|
35
35
|
if currency_data.respond_to?(:values_at)
|
|
36
|
-
code = currency_data[:currency]
|
|
37
|
-
subunit = currency_data[:subunit]
|
|
38
|
-
symbol = currency_data[:symbol]
|
|
36
|
+
code = currency_data[:currency]
|
|
37
|
+
subunit = currency_data[:subunit]
|
|
38
|
+
symbol = currency_data[:symbol]
|
|
39
39
|
else
|
|
40
40
|
code, subunit, symbol = *currency_data
|
|
41
41
|
end
|
|
42
42
|
Currency.register(code:, subunit:, symbol:)
|
|
43
|
+
rescue KeyError
|
|
44
|
+
nil
|
|
43
45
|
end
|
|
44
46
|
end
|
|
45
47
|
end
|