money_attribute 0.11.0 → 0.13.0
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 +42 -18
- data/lib/money_attribute/migration_extensions/helper.rb +38 -28
- data/lib/money_attribute/migration_extensions/schema_statements.rb +2 -2
- data/lib/money_attribute/migration_extensions/table_definition.rb +2 -2
- data/lib/money_attribute/version.rb +1 -1
- 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: 47ae4d496ececb42ec6ab60e278b3b523518c057074ead9610d90395ae4c5cb7
|
|
4
|
+
data.tar.gz: 3f26f8a306a4d64cf982ca0d079cdf7738dc10b3acaed2ccb2036b67755b83e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2ef7b83fee1736f8944ebfcec5348b7c69a9b15f018b8080689c30dd4387aa2cfa10b4a459bf3ff36a117f2c22a7ce1e98909e2e6a5d3e56eada694c46b0080
|
|
7
|
+
data.tar.gz: 8e1da01132bb2309f0c5fc10ec00648a035c761e6ca3f3fa7ac43f3ed7e19d324831f8142d939225aab7c273308507c428a115509b911e2c1944cbfc12ae704e
|
data/README.md
CHANGED
|
@@ -27,7 +27,7 @@ class CreateProducts < ActiveRecord::Migration[8.1]
|
|
|
27
27
|
def change
|
|
28
28
|
create_table :products do |t|
|
|
29
29
|
t.string :name
|
|
30
|
-
t.
|
|
30
|
+
t.money_attribute :price
|
|
31
31
|
t.timestamps
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -91,17 +91,19 @@ The generator creates `config/initializers/money_attribute.rb`.
|
|
|
91
91
|
|
|
92
92
|
## Migration helpers
|
|
93
93
|
|
|
94
|
-
MoneyAttribute adds `
|
|
94
|
+
MoneyAttribute adds `add_money_attribute` / `remove_money_attribute` for existing tables and `t.money_attribute` / `t.remove_money_attribute` for `create_table` / `change_table` blocks.
|
|
95
|
+
|
|
96
|
+
By default `t.money_attribute :price` creates a `decimal(16,4)` amount column and a `string` currency column — both nullable, no default. Pass `amount: { type: :integer }` to store subunits instead, or `currency: false` to skip the currency column entirely.
|
|
95
97
|
|
|
96
98
|
```ruby
|
|
97
99
|
class CreateProducts < ActiveRecord::Migration[8.1]
|
|
98
100
|
def change
|
|
99
101
|
create_table :products do |t|
|
|
100
102
|
t.string :name
|
|
101
|
-
t.
|
|
102
|
-
t.
|
|
103
|
-
t.
|
|
104
|
-
t.
|
|
103
|
+
t.money_attribute :price # price (decimal) + price_currency (string)
|
|
104
|
+
t.money_attribute :price_amount # price_amount + price_currency (strips _amount suffix)
|
|
105
|
+
t.money_attribute :fee, currency: false # single column, no currency
|
|
106
|
+
t.money_attribute :tax, amount: { type: :bigint } # bigint amount + currency
|
|
105
107
|
t.timestamps
|
|
106
108
|
end
|
|
107
109
|
end
|
|
@@ -109,9 +111,9 @@ end
|
|
|
109
111
|
|
|
110
112
|
class AddPriceToProducts < ActiveRecord::Migration[8.1]
|
|
111
113
|
def change
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
add_money_attribute :products, :price # add price + price_currency
|
|
115
|
+
add_money_attribute :products, :discount, amount: { type: :integer }
|
|
116
|
+
remove_money_attribute :products, :obsolete_fee # reversible in change
|
|
115
117
|
end
|
|
116
118
|
end
|
|
117
119
|
```
|
|
@@ -120,11 +122,22 @@ end
|
|
|
120
122
|
|
|
121
123
|
| Migration call | Columns created | Model declaration |
|
|
122
124
|
|---|---|---|
|
|
123
|
-
| `t.
|
|
124
|
-
| `t.
|
|
125
|
-
| `t.
|
|
126
|
-
| `t.
|
|
127
|
-
| `t.
|
|
125
|
+
| `t.money_attribute :price` | `price` decimal + `price_currency` string | `money_attribute :price` |
|
|
126
|
+
| `t.money_attribute :price_amount` | `price_amount` decimal + `price_currency` string | `money_attribute :price` |
|
|
127
|
+
| `t.money_attribute :price, currency: false` | `price` decimal | `money_attribute :price` |
|
|
128
|
+
| `t.money_attribute :price, amount: { type: :integer }` | `price` integer + `price_currency` string | `money_attribute :price` |
|
|
129
|
+
| `t.money_attribute :price, amount: { column: :a }, currency: { column: :c }` | `a` + `c` | `money_attribute :price, mapping: { amount: :a, currency: :c }` |
|
|
130
|
+
| `t.money_attribute :price, currency: { limit: 3 }` | `price` decimal + `price_currency` string(3) | `money_attribute :price` |
|
|
131
|
+
| `t.money_attribute :price, amount: { precision: 14, scale: 2, null: false }, currency: { limit: 3, default: 'USD' }` | `price` decimal(14,2) NOT NULL + `price_currency` string(3) DEFAULT 'USD' | `money_attribute :price` |
|
|
132
|
+
| `t.remove_money_attribute :price` | Removes `price` + `price_currency` | `money_attribute :price` |
|
|
133
|
+
|
|
134
|
+
Inside `change_table`:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
change_table :products do |t|
|
|
138
|
+
t.remove_money_attribute :obsolete_fee # removes obsolete_fee + obsolete_fee_currency
|
|
139
|
+
end
|
|
140
|
+
```
|
|
128
141
|
|
|
129
142
|
## Configuration
|
|
130
143
|
|
|
@@ -188,7 +201,7 @@ If none of those keys are set, `format` is used as a plain string (simple format
|
|
|
188
201
|
|
|
189
202
|
| | Fixed currency (single column) | Multi-currency (amount + currency) |
|
|
190
203
|
|---|---|---|
|
|
191
|
-
| **Migration** | `t.
|
|
204
|
+
| **Migration** | `t.money_attribute :price` (or `t.decimal :price`) | `t.money_attribute :price` (or `t.decimal :price_amount` + `t.string :price_currency`) |
|
|
192
205
|
| **Model** | `money_attribute :price, currency: 'USD'` | `money_attribute :price` |
|
|
193
206
|
| **When to use** | Column always holds the same currency | Each row can hold a different currency |
|
|
194
207
|
| **Column type** | `decimal`, `integer`, or `bigint` | `decimal`, `integer`, or `bigint` for amount; `string` for currency |
|
|
@@ -224,6 +237,8 @@ offer = Offer.new(price: '12')
|
|
|
224
237
|
offer.price.currency.code # => "USD"
|
|
225
238
|
```
|
|
226
239
|
|
|
240
|
+
Unlike fixed-currency attributes, composite mode does not enforce a specific currency — any registered currency is accepted at assignment.
|
|
241
|
+
|
|
227
242
|
## Column type detection
|
|
228
243
|
|
|
229
244
|
Declare the column as `decimal`, `integer`, or `bigint` — the gem adapts:
|
|
@@ -268,7 +283,14 @@ class Invoice < ApplicationRecord
|
|
|
268
283
|
end
|
|
269
284
|
```
|
|
270
285
|
|
|
271
|
-
The mapping keys are `:amount` and `:currency`; values are your database column names.
|
|
286
|
+
The mapping keys are `:amount` and `:currency`; values are your database column names. You can provide only one key — the other falls back to the `<name>_amount` / `<name>_currency` convention:
|
|
287
|
+
|
|
288
|
+
```ruby
|
|
289
|
+
class Invoice < ApplicationRecord
|
|
290
|
+
money_attribute :total, mapping: { amount: :total_amount }
|
|
291
|
+
# currency column inferred as `total_currency`
|
|
292
|
+
end
|
|
293
|
+
```
|
|
272
294
|
|
|
273
295
|
## Column resolution
|
|
274
296
|
|
|
@@ -279,8 +301,10 @@ When you declare `money_attribute :name`, the gem resolves which database column
|
|
|
279
301
|
| 1 | `mapping:` provided | As specified | Explicit composite |
|
|
280
302
|
| 2 | `name_currency` column exists | `name` + `name_currency` | Composite (multi-currency) |
|
|
281
303
|
| 3 | `name == 'amount'` AND `currency` column exists | `amount` + `currency` | Composite (multi-currency) |
|
|
282
|
-
| 4 | `
|
|
283
|
-
| 5 |
|
|
304
|
+
| 4 | `name` column exists (no currency partner) | `name` alone | Single-column (fixed-currency) |
|
|
305
|
+
| 5 | None of the above (name column missing) | `name_amount` + `name_currency` (convention) | Composite (multi-currency) |
|
|
306
|
+
|
|
307
|
+
Step 5 raises `ArgumentError` if the convention columns don't exist in the table.
|
|
284
308
|
|
|
285
309
|
**Example**
|
|
286
310
|
|
|
@@ -8,42 +8,52 @@ module MoneyAttribute
|
|
|
8
8
|
def parse_money_args(accessor, options = {})
|
|
9
9
|
name = accessor.to_s
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
if options.key?(:amount) && options[:amount].is_a?(Hash)
|
|
12
|
+
amount_col = options[:amount][:column]&.to_s || name
|
|
13
|
+
opts = options[:amount]
|
|
14
|
+
amount_opts = {
|
|
15
|
+
type: opts[:type],
|
|
16
|
+
null: opts[:null],
|
|
17
|
+
default: opts[:default],
|
|
18
|
+
precision: opts[:precision],
|
|
19
|
+
scale: opts[:scale]
|
|
20
|
+
}.compact
|
|
19
21
|
else
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
amount_col = name
|
|
23
|
+
amount_opts = {}
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
amount_opts = {}
|
|
27
|
-
amount_opts[:type] = col_type
|
|
26
|
+
amount_opts[:type] ||= :decimal
|
|
28
27
|
|
|
29
|
-
if
|
|
30
|
-
amount_opts[:null] = options[:amount][:null] if options[:amount].key?(:null)
|
|
31
|
-
amount_opts[:default] = options[:amount][:default] if options[:amount].key?(:default)
|
|
32
|
-
amount_opts[:precision] = options[:amount][:precision] if options[:amount].key?(:precision)
|
|
33
|
-
amount_opts[:scale] = options[:amount][:scale] if options[:amount].key?(:scale)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
if col_type == :decimal && !amount_opts.key?(:precision) && !amount_opts.key?(:scale)
|
|
28
|
+
if amount_opts[:type] == :decimal && !amount_opts.key?(:precision) && !amount_opts.key?(:scale)
|
|
37
29
|
amount_opts[:precision] = 16
|
|
38
30
|
amount_opts[:scale] = 4
|
|
31
|
+
elsif amount_opts[:type] != :decimal
|
|
32
|
+
amount_opts.delete(:precision)
|
|
33
|
+
amount_opts.delete(:scale)
|
|
39
34
|
end
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
stripped = name.end_with?('_amount') ? name.sub(/_amount$/, '') : name
|
|
37
|
+
default_currency_col = if name == 'amount' && !(options[:currency].is_a?(Hash) && options[:currency][:column])
|
|
38
|
+
'currency'
|
|
39
|
+
else
|
|
40
|
+
"#{stripped}_currency"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if options.key?(:currency) && options[:currency].is_a?(Hash)
|
|
44
|
+
currency_col = options[:currency][:column]&.to_s || default_currency_col
|
|
45
|
+
opts = options[:currency]
|
|
46
|
+
currency_opts = {
|
|
47
|
+
limit: opts[:limit],
|
|
48
|
+
null: opts[:null],
|
|
49
|
+
default: opts[:default]
|
|
50
|
+
}.compact
|
|
51
|
+
elsif options[:currency] == false
|
|
52
|
+
currency_col = nil
|
|
53
|
+
currency_opts = {}
|
|
54
|
+
else
|
|
55
|
+
currency_col = default_currency_col
|
|
56
|
+
currency_opts = {}
|
|
47
57
|
end
|
|
48
58
|
|
|
49
59
|
[amount_col, currency_col, amount_opts, currency_opts]
|
|
@@ -7,14 +7,14 @@ module MoneyAttribute
|
|
|
7
7
|
module SchemaStatements
|
|
8
8
|
include Helper
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def add_money_attribute(table_name, accessor, options = {})
|
|
11
11
|
amount_col, currency_col, amount_opts, currency_opts = parse_money_args(accessor, options)
|
|
12
12
|
|
|
13
13
|
add_column(table_name, amount_col, amount_opts[:type], **amount_opts.except(:type))
|
|
14
14
|
add_column(table_name, currency_col, :string, **currency_opts) if currency_col
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def
|
|
17
|
+
def remove_money_attribute(table_name, accessor, options = {})
|
|
18
18
|
amount_col, currency_col, = parse_money_args(accessor, options)
|
|
19
19
|
|
|
20
20
|
remove_column(table_name, amount_col)
|
|
@@ -7,14 +7,14 @@ module MoneyAttribute
|
|
|
7
7
|
module TableDefinition
|
|
8
8
|
include Helper
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def money_attribute(accessor, options = {})
|
|
11
11
|
amount_col, currency_col, amount_opts, currency_opts = parse_money_args(accessor, options)
|
|
12
12
|
|
|
13
13
|
column(amount_col, amount_opts[:type], **amount_opts.except(:type))
|
|
14
14
|
column(currency_col, :string, **currency_opts) if currency_col
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def
|
|
17
|
+
def remove_money_attribute(accessor, options = {})
|
|
18
18
|
amount_col, currency_col, = parse_money_args(accessor, options)
|
|
19
19
|
|
|
20
20
|
remove_column(amount_col)
|