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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 874e817fa1e157ee49e460c8848dabff3fe525d9849571d21045de2694dd0770
4
- data.tar.gz: 933dcdd5adc296d9aa635da79fc181e15652ed884c2e3b1231655a5652a2beca
3
+ metadata.gz: 47ae4d496ececb42ec6ab60e278b3b523518c057074ead9610d90395ae4c5cb7
4
+ data.tar.gz: 3f26f8a306a4d64cf982ca0d079cdf7738dc10b3acaed2ccb2036b67755b83e9
5
5
  SHA512:
6
- metadata.gz: b5547742974245c1cffd5b690fa4c763e79224e553c90c2e19e88cdccb0d2c7ccdf669f9000e1917abe01fb880c2a50b0b2b475ffebf3fdfe01b787b0f4630ad
7
- data.tar.gz: d0b760447e051fb0a1dc15492ba737676af935443ad5dcec4fb18988c898db37bf5980781d52cc64a4c60c651d03646ad77bb63ffdb6cb046c6d19f67cc7d092
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.money :price
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 `add_money` / `remove_money` for existing tables and `t.money` / `t.remove_money` for `create_table` / `change_table` blocks:
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.money :price # price (decimal) + price_currency (string)
102
- t.money :price_amount # price_amount + price_currency (strips _amount suffix)
103
- t.money :fee, currency: false # single column, no currency
104
- t.money :tax, type: :bigint # bigint amount + currency
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
- add_money :products, :price # add price + price_currency
113
- add_money :products, :discount, type: :integer
114
- remove_money :products, :obsolete_fee # reversible in change
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.money :price` | `price` decimal + `price_currency` string | `money_attribute :price` |
124
- | `t.money :price_amount` | `price_amount` decimal + `price_currency` string | `money_attribute :price` |
125
- | `t.money :price, currency: false` | `price` decimal | `money_attribute :price` |
126
- | `t.money :price, type: :integer` | `price` integer + `price_currency` string | `money_attribute :price` |
127
- | `t.money :price, amount: :a, currency: :c` | `a` + `c` | `money_attribute :price, mapping: { amount: :a, currency: :c }` |
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.money :price` (or `t.decimal :price`) | `t.money :price` (or `t.decimal :price_amount` + `t.string :price_currency`) |
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 | `name_amount` + `name_currency` columns exist | `name_amount` + `name_currency` | Composite (multi-currency) |
283
- | 5 | `name` column exists (no currency partner) | `name` alone | Single-column (fixed-currency) |
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
- amount_col = options.key?(:amount) ? options[:amount].to_s : name
12
-
13
- if options.key?(:currency)
14
- currency_col = if options[:currency] == false
15
- nil
16
- else
17
- options[:currency].to_s
18
- end
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
- stripped = name.end_with?('_amount') ? name.sub(/_amount$/, '') : name
21
- currency_col = "#{stripped}_currency"
22
+ amount_col = name
23
+ amount_opts = {}
22
24
  end
23
25
 
24
- col_type = options[:type] || :decimal
25
-
26
- amount_opts = {}
27
- amount_opts[:type] = col_type
26
+ amount_opts[:type] ||= :decimal
28
27
 
29
- if options.key?(:amount) && options[:amount].is_a?(Hash)
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
- currency_opts = {}
42
- currency_opts[:limit] = options[:currency_limit] if options[:currency_limit]
43
-
44
- if options[:currency].is_a?(Hash)
45
- currency_opts[:null] = options[:currency][:null] if options[:currency].key?(:null)
46
- currency_opts[:default] = options[:currency][:default] if options[:currency].key?(:default)
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 add_money(table_name, accessor, options = {})
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 remove_money(table_name, accessor, options = {})
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 money(accessor, options = {})
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 remove_money(accessor, options = {})
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MoneyAttribute
4
- VERSION = '0.11.0'
4
+ VERSION = '0.13.0'
5
5
  end
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: 0.11.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilson Ferraz