money_attribute 1.1.0 → 1.2.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: bcfa5f42f59d3a7f372ddbff422a4e2aa45faccf4f5bd912fc2509e974bda609
4
- data.tar.gz: 173509ac929dc32881a8bae54be2c3a7c0f92632ab8fae0192d84ae9b42433eb
3
+ metadata.gz: cb71d942a1dd57ae30fa3d1d8941efacf402d9346192b99cca4d0eabf69246fb
4
+ data.tar.gz: 70dbb86d5d328e5d42fd09593358445d931261b59212d49f3a8e6a450d275c09
5
5
  SHA512:
6
- metadata.gz: 18598feb924b703ae70e0fe68a76dd93d3ec9f22753d8556616d8c42ebae8460512e9451ed2e80b96feb44d380e8c03e1d2c0a23f22cf956773d001f5e27d83b
7
- data.tar.gz: 1178b4b944cbd54b93d8d96f03a55403141086573d5f15f6225a820e65fb9a3e4fae7f0277518563274fa91a7dc7fd6a4ae889e1391dfb69efe72c07de0bb53a
6
+ metadata.gz: 93e1d50ccda57e7a535fba457e4551525923358b45ef629b381bb04c900eb1fc7b76c1aa11d18410a0669fb96f610a53df1e76e10dcc58860e1d0cd8e3dd19f6
7
+ data.tar.gz: 55d8b84306bf8988e066d426e525af1d6bfb2b0f202727a9a3a38aa5a52f9461d57edac0d5c003f5996141a5e662439d661109ba6aab975fd420b882ea754a29
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Store and read Active Record attributes as `Mint::Money` objects with no manual serialization.
7
7
 
8
- `money_attribute` uses two DB columns (amount + currency) for per-row multi-currency data. A simpler `money_amount` variant is also available for fixed-currency models (see [note](#single-column-mode--money_amount-fixed-currency)).
8
+ `money_attribute` uses two DB columns (amount + currency) for per-row multi-currency data. A simpler `money_amount` variant is also available for fixed-currency models (see [note](#single-column-mode-money_amount-fixed-currency)).
9
9
 
10
10
  ```ruby
11
11
  class Product < ApplicationRecord
@@ -188,13 +188,13 @@ MoneyAttribute integrates with Rails I18n to automatically format money amounts
188
188
 
189
189
  With `I18n.locale` set to `:en`:
190
190
  ```ruby
191
- Mint.money(1234.56, 'USD').to_s # => "$1,234.56"
191
+ Money.from(1234.56, 'USD').to_s # => "$1,234.56"
192
192
  ```
193
193
 
194
194
  Switch to `:'pt-BR'` and the separators change automatically (requires [`rails-i18n`](https://github.com/svenfuchs/rails-i18n) or your own locale file):
195
195
  ```ruby
196
196
  I18n.locale = :'pt-BR'
197
- Mint.money(1234.56, 'USD').to_s # => "$1.234,56"
197
+ Money.from(1234.56, 'USD').to_s # => "$1.234,56"
198
198
  ```
199
199
 
200
200
  The locale backend reads `number.currency.format` from your I18n translations and maps Rails format syntax (`%n` for amount, `%u` for unit) to `Mint::Money#to_s`. If the translation key is missing (no locale file for that language), it falls back to hardcoded defaults (`.` decimal, `,` thousand, `%<symbol>s%<amount>f` format).
@@ -218,9 +218,9 @@ en:
218
218
  When any of `positive`, `negative`, or `zero` is present, a Hash format is built. Missing keys fall back to `format`:
219
219
 
220
220
  ```ruby
221
- Mint.money(1234.56, 'USD').to_s # => "$1,234.56"
222
- Mint.money(-1234.56, 'USD').to_s # => "($1,234.56)"
223
- Mint.money(0, 'USD').to_s # => "--"
221
+ Money.from(1234.56, 'USD').to_s # => "$1,234.56"
222
+ Money.from(-1234.56, 'USD').to_s # => "($1,234.56)"
223
+ Money.from(0, 'USD').to_s # => "--"
224
224
  ```
225
225
 
226
226
  If none of those keys are set, `format` is used as a plain string (simple formatting).
@@ -313,7 +313,7 @@ end
313
313
  | 3 | `name == 'amount'` AND `currency` column exists | `amount` + `currency` |
314
314
  | 4 | None of the above | `<name>_amount` + `<name>_currency` (convention) |
315
315
 
316
- Step 4 raises `ArgumentError` if the convention columns don't exist. For single-column fixed-currency attributes, see [`money_amount`](#single-column-mode--money_amount).
316
+ Step 4 raises `ArgumentError` if the convention columns don't exist. For single-column fixed-currency attributes, see [`money_amount`](#single-column-mode-money_amount-fixed-currency).
317
317
 
318
318
  **Example**
319
319
 
@@ -343,6 +343,8 @@ end
343
343
 
344
344
  ## Querying
345
345
 
346
+ ### Rails-native queries
347
+
346
348
  Multi-currency (`money_attribute`) attributes support equality queries via `composed_of`:
347
349
 
348
350
  ```ruby
@@ -356,7 +358,110 @@ Offer.where(price_amount: 10..20, price_currency: 'EUR')
356
358
  Offer.where('price_amount > ? AND price_currency = ?', 10, 'EUR')
357
359
  ```
358
360
 
359
- For fixed-currency (`money_amount`) attributes, see the [single-column section](#single-column-mode--money_amount).
361
+ Fixed-currency (`money_amount`) attributes support full Rails-native querying through the custom type — equality, IN, BETWEEN, ordering, and aggregation all work:
362
+
363
+ ```ruby
364
+ Product.where(price: 10.to_money('USD')) # equality
365
+ Product.where(price: [10.to_money('USD'), 20.to_money('USD')]) # IN
366
+ Product.where(price: 10.to_money('USD')..20.to_money('USD')) # BETWEEN
367
+ Product.order(price: :desc) # ordering
368
+ Product.where(price: 10.to_money('USD')).sum(:price) # aggregation
369
+ ```
370
+
371
+ ### Money-aware query helpers
372
+
373
+ For multi-currency attributes, manually decomposing columns is tedious. The query helpers handle this automatically — just pass Money objects:
374
+
375
+ #### `where_amount`
376
+
377
+ Filters by amount value. Accepts a scalar, Range, or Array.
378
+
379
+ ```ruby
380
+ Offer.where_amount(price: 10) # equality (any currency)
381
+ Offer.where_amount(price: [10, 30]) # IN — matches EUR 10, USD 30
382
+ Offer.where_amount(price: 10..100) # BETWEEN (inclusive)
383
+ Offer.where_amount(price: 10...100) # BETWEEN (exclusive upper bound)
384
+ ```
385
+
386
+ Ranges work across currencies — `10..50` matches EUR 10 and USD 50:
387
+
388
+ ```ruby
389
+ Offer.create!(price: 10.euros)
390
+ Offer.create!(price: 50.dollars)
391
+
392
+ Offer.where_amount(price: 10..50) # => both records
393
+ ```
394
+
395
+ For integer (subunit) columns, pass `Mint::Money` objects directly — subunit conversion is handled automatically:
396
+
397
+ ```ruby
398
+ FinancialTransaction.where_amount(amount: [10.dollars, 10.yens])
399
+ FinancialTransaction.where_amount(amount: 10.dollars..100.dollars)
400
+ ```
401
+
402
+ For decimal columns, raw numbers work:
403
+
404
+ ```ruby
405
+ SimpleOffer.where_amount(price: 50)
406
+ SimpleOffer.where_amount(price: 10..100)
407
+ ```
408
+
409
+ #### `where_currency`
410
+
411
+ Filters by currency code. Composite attributes only — raises `ArgumentError` for single-column attributes.
412
+
413
+ ```ruby
414
+ Offer.where_currency(price: 'EUR')
415
+ Offer.where_currency(price: 10.euros.currency) # also accepts Currency object
416
+ ```
417
+
418
+ #### `order_by_amount`
419
+
420
+ Orders by amount. Composite attributes sort by currency ASC first, then amount. Single-column attributes sort by amount only.
421
+
422
+ ```ruby
423
+ Offer.order_by_amount(price: :asc) # EUR 10, EUR 100, USD 50
424
+ Offer.order_by_amount(price: :desc) # EUR 100, EUR 10, USD 50
425
+ Offer.order_by_amount(price: nil) # defaults to :asc
426
+ ```
427
+
428
+ #### `pluck_amount`
429
+
430
+ Returns money-aware amounts. Follows Rails' `pluck` arity — one attribute returns flat values, multiple attributes return row arrays.
431
+
432
+ ```ruby
433
+ Offer.pluck_amount(:price) # => [EUR 10.00, USD 20.00]
434
+ Offer.pluck_amount(:amount, :discount) # => [[USD 100.00, EUR 20.00], ...]
435
+ ```
436
+
437
+ #### `pick_amount`
438
+
439
+ Returns a single money-aware value. Follows Rails' `pick` arity.
440
+
441
+ ```ruby
442
+ Offer.pick_amount(:price) # => EUR 10.00
443
+ Offer.pick_amount(:amount, :discount) # => [USD 100.00, EUR 20.00]
444
+ Offer.none.pick_amount(:price) # => nil
445
+ ```
446
+
447
+ #### `sum_amount`
448
+
449
+ Sums amounts grouped by currency for composite attributes. Accepts a single attribute name only.
450
+
451
+ ```ruby
452
+ Offer.sum_amount(:price)
453
+ # => [EUR 30.00, USD 70.00] (one Money per currency, sorted by code)
454
+
455
+ SimpleOffer.sum_amount(:price)
456
+ # => [BRL 60.00] (single-column always returns one Money)
457
+
458
+ Offer.none.sum_amount(:price)
459
+ # => [Mint::Money(0, 'BRL')] (empty result returns zero Money)
460
+ ```
461
+
462
+ ### Notes
463
+
464
+ All query helpers raise `ArgumentError` for non-money attributes. Internally, money attribute metadata is registered per model class. The same attribute name can be used safely in different models, but subclasses do not automatically inherit a parent model's registered money attributes.
360
465
 
361
466
  ## Convenience methods
362
467
 
@@ -368,7 +473,7 @@ MoneyAttribute adds small helpers on `Numeric` and `String`:
368
473
  12.euros # => [EUR 12.00]
369
474
  ```
370
475
 
371
- > If you prefer not to extend core classes, use `Mint.money(12, 'USD')` instead.
476
+ > If you prefer not to extend core classes, use `Money.from(12, 'USD')` instead.
372
477
 
373
478
  ## Form helpers
374
479
 
@@ -388,6 +493,8 @@ MoneyAttribute adds `money_field` and `money_amount_field` to Rails form builder
388
493
 
389
494
  `money_amount` wraps a numeric column as `Mint::Money` using the application's default currency. No per-row currency. A lighter alternative when you don't need multi-currency support.
390
495
 
496
+ The accessor name must match the column name. `money_amount` does not support custom column mapping.
497
+
391
498
  #### Migration helpers
392
499
 
393
500
  | Method | Action |
@@ -438,15 +545,7 @@ money_amount :price
438
545
 
439
546
  #### Querying
440
547
 
441
- Fixed-currency attributes support Rails-native querying through the custom type:
442
-
443
- ```ruby
444
- Product.where(price: 10.to_money('USD')) # equality
445
- Product.where(price: [10.to_money('USD'), 20.to_money('USD')]) # IN
446
- Product.where(price: 10.to_money('USD')..20.to_money('USD')) # BETWEEN
447
- Product.order(price: :desc) # ordering
448
- Product.where(price: 10.to_money('USD')).sum(:price) # aggregation
449
- ```
548
+ Fixed-currency attributes support full Rails-native querying see [Querying](#querying) for examples.
450
549
 
451
550
  ## Roadmap
452
551
 
data/Rakefile CHANGED
@@ -15,13 +15,41 @@ Rake::TestTask.new(:test_run) do |t|
15
15
  end
16
16
 
17
17
  desc 'Migrate test database'
18
- task :test_db_migrate do
19
- sh({ 'RAILS_ENV' => 'test' }, 'bin/rails', 'db:migrate', chdir: 'test/dummy')
18
+ task :test_db_migrate do # rubocop:disable Rails/RakeEnvironment
19
+ sh({ 'RAILS_ENV' => 'test' }, 'bin/rails', 'db:migrate', chdir: 'test/dummy', %i[out err] => File::NULL)
20
20
  end
21
21
 
22
22
  desc 'Run tests (migrates test DB first)'
23
23
  task test: %i[test_db_migrate test_run]
24
24
 
25
+ # --- Adapter-specific tasks ---
26
+
27
+ %w[postgresql mysql2].each do |adapter|
28
+ namespace :test do
29
+ desc "Migrate test database (#{adapter})"
30
+ task "db_migrate:#{adapter}" do # rubocop:disable Rails/RakeEnvironment
31
+ sh({ 'RAILS_ENV' => 'test', 'DATABASE_ADAPTER' => adapter },
32
+ 'bin/rails', 'db:migrate', chdir: 'test/dummy', %i[out err] => File::NULL)
33
+ end
34
+
35
+ desc "Run tests against #{adapter}"
36
+ task adapter do # rubocop:disable Rails/RakeEnvironment
37
+ Rake::Task[:"test:db_migrate:#{adapter}"].invoke
38
+ sh({ 'DATABASE_ADAPTER' => adapter }, 'bundle', 'exec', 'rake', 'test_run')
39
+ end
40
+ end
41
+ end
42
+
43
+ desc 'Run tests against all adapters (sqlite3, postgresql, mysql2)'
44
+ task 'test:all' do # rubocop:disable Rails/RakeEnvironment
45
+ Rake::Task[:test].invoke
46
+ %w[postgresql mysql2].each do |adapter|
47
+ Rake::Task[:"test:#{adapter}"].invoke
48
+ end
49
+ end
50
+
51
+ # --- Benchmark ---
52
+
25
53
  desc 'Run money_attribute vs money-rails benchmark'
26
54
  task bench: :test_db_migrate do
27
55
  puts
@@ -41,6 +69,6 @@ task bench: :test_db_migrate do
41
69
  end
42
70
 
43
71
  desc 'Generate consolidated benchmark report (markdown)'
44
- task 'bench:report' do
72
+ task 'bench:report' do # rubocop:disable Rails/RakeEnvironment
45
73
  ruby 'benchmark/report.rb'
46
74
  end
@@ -2,21 +2,29 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  MoneyAttribute.configure do |config|
5
+ # To set the default currency
6
+ #
7
+ # It must be a registered currency
8
+ #
9
+ config.default_currency = 'USD'
10
+
11
+ # Register built-in crypto currencies
12
+ #
13
+ # 1. Register specific crypto currencies:
14
+ # Mint::Currency.register_crypto('BTC', 'ETH')
15
+ #
16
+ # 2. Register all built-in crypto currencies at once:
17
+ # Mint::Currency.register_all_crypto
18
+ #
19
+ # See available crypto currencies:
20
+ # Mint::Currency.crypto_currencies
21
+
5
22
  # Register a custom currency
6
23
  #
7
24
  # Example:
8
25
  # config.added_currencies = [
9
- # {currency: 'CRC', subunit: 2, symbol: '₡'},
10
- # {currency: 'NGN', subunit: 3, symbol: '₦'}
26
+ # {currency: 'ZCRC', subunit: 2, symbol: '₡'},
27
+ # {currency: 'ZNGN', subunit: 3, symbol: '₦'}
11
28
  # ]
12
- config.added_currencies = [
13
- { currency: 'XCRC', subunit: 2, symbol: '₡' },
14
- { currency: 'XNGN', subunit: 3, symbol: '₦' }
15
- ]
16
-
17
- # To set the default currency
18
- #
19
- # It must be a registered currency
20
- #
21
- config.default_currency = 'BRL'
29
+ config.added_currencies = []
22
30
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MoneyAttribute
4
+ # @private Constructor for integer (subunit) columns used by +composed_of+.
5
+ INTEGER_CONSTRUCTOR = lambda do |amount, currency|
6
+ next nil if amount.nil?
7
+
8
+ resolved = Money::Currency.resolve(currency.presence || MoneyAttribute.default_currency) || 'XXX'
9
+ Mint::Money.from_subunits(amount, resolved)
10
+ end.freeze
11
+
12
+ # @private Constructor for decimal (unit value) columns used by +composed_of+.
13
+ DECIMAL_CONSTRUCTOR = lambda do |amount, currency|
14
+ next nil if amount.nil?
15
+
16
+ resolved = Money::Currency.resolve(currency.presence || MoneyAttribute.default_currency) || 'XXX'
17
+ Mint::Money.from(amount, resolved)
18
+ end.freeze
19
+
20
+ # Value object holding metadata for a registered money attribute.
21
+ #
22
+ # Created by +money_attribute+ or +money_amount+ and stored in the class-level registry.
23
+ # Used by query helpers to resolve column names, build Money values, and generate SQL.
24
+ AttributeSpec = Struct.new(:name, :kind, :amount_col, :currency_col, :amount_type, keyword_init: true) do
25
+ # @return [Boolean] +true+ when the spec describes a two-column (amount + currency) attribute.
26
+ def composite? = kind == :composite
27
+
28
+ # @return [Boolean] +true+ when the spec describes a single-column (fixed currency) attribute.
29
+ def single? = kind == :single
30
+
31
+ # Returns the backing database columns for the attribute.
32
+ #
33
+ # @return [Array<String>] two-element array for composite, one-element for single.
34
+ def columns
35
+ @columns ||= (composite? ? [amount_col, currency_col] : [amount_col]).freeze
36
+ end
37
+
38
+ # @return [Boolean] +true+ when the amount column stores subunits (bigint).
39
+ def integer_amount? = amount_type == :integer
40
+
41
+ # @return [Symbol] +:subunits+ for integer columns, +:to_d+ for decimal columns.
42
+ def amount_extractor = integer_amount? ? :subunits : :to_d
43
+
44
+ # @return [Hash{String => Symbol}] mapping suitable for +composed_of+.
45
+ def composed_of_mapping = { amount_col => amount_extractor, currency_col => :currency_code }
46
+
47
+ # @return [Proc] the constructor lambda used by +composed_of+ to instantiate Money values.
48
+ def constructor
49
+ integer_amount? ? INTEGER_CONSTRUCTOR : DECIMAL_CONSTRUCTOR
50
+ end
51
+
52
+ # Converts a raw amount and currency into a +Mint::Money+ value.
53
+ #
54
+ # @param amount [Integer, BigDecimal, nil] the raw column value
55
+ # @param currency [String, Mint::Currency, nil] the currency code or object
56
+ # @return [Mint::Money, nil] the resolved money value, or nil when amount is nil
57
+ def build_money(amount, currency)
58
+ return unless amount
59
+ return amount if amount.is_a?(Mint::Money)
60
+
61
+ constructor.call(amount, currency)
62
+ end
63
+
64
+ # Normalizes a query value to the column's storage format.
65
+ #
66
+ # Composite attributes: decomposes +Mint::Money+ to subunits via +.subunits+.
67
+ # Single-column attributes: passes through (the registered Type handles serialization).
68
+ #
69
+ # @param value [Mint::Money, Numeric] the query value
70
+ # @return [Integer, BigDecimal, Mint::Money] the normalized value
71
+ def normalize_query_value(value)
72
+ return value unless integer_amount?
73
+ return value.subunits if value.is_a?(Mint::Money)
74
+
75
+ value
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/map'
4
+
5
+ module MoneyAttribute
6
+ # Stores money attribute metadata on the model class.
7
+ module AttributeSpecRegistry
8
+ extend ActiveSupport::Concern
9
+
10
+ REGISTRY = Concurrent::Map.new
11
+
12
+ class_methods do
13
+ # Registers a money attribute spec for the current model class.
14
+ #
15
+ # @param name [Symbol, String] the attribute name
16
+ # @param kind [Symbol] +:composite+ or +:single+
17
+ # @param amount_col [Symbol, String] the amount column name
18
+ # @param currency_col [Symbol, String, nil] the currency column name (composite only)
19
+ # @param amount_type [Symbol, nil] +:integer+ or +:decimal+
20
+ # @return [AttributeSpec]
21
+ def register_money_attribute_spec(name, kind:, amount_col:, currency_col: nil, amount_type: nil)
22
+ spec = MoneyAttribute::AttributeSpec.new(
23
+ name: name.to_s,
24
+ kind: kind,
25
+ amount_col: amount_col.to_s,
26
+ currency_col: currency_col&.to_s,
27
+ amount_type: amount_type
28
+ )
29
+
30
+ money_attribute_specs[spec.name] = spec
31
+ spec
32
+ end
33
+
34
+ # Returns the registered money attribute spec for the given name.
35
+ #
36
+ # @param name [Symbol, String] the attribute name
37
+ # @return [AttributeSpec, nil]
38
+ def money_attribute_spec(name)
39
+ REGISTRY[self]&.fetch(name.to_s, nil)
40
+ end
41
+
42
+ # Returns the registry hash for the current model class.
43
+ #
44
+ # @return [Hash{String => AttributeSpec}]
45
+ def money_attribute_specs
46
+ REGISTRY.fetch_or_store(self) { {} }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,27 +1,55 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MoneyAttribute
4
- class Configuration
5
- attr_accessor :added_currencies, :default_currency
4
+ # @private
5
+ CONFIG_MUTEX = Mutex.new
6
6
 
7
- def initialize
8
- @added_currencies = []
9
- @default_currency = 'USD'
7
+ class << self
8
+ # Returns the lazily initialized gem configuration.
9
+ #
10
+ # @return [Config]
11
+ def config
12
+ CONFIG_MUTEX.synchronize { @config ||= Config.new }
10
13
  end
11
- end
12
14
 
13
- cfg = Configuration.new
14
- cached_default = nil
15
+ # Yields the current configuration object for mutation.
16
+ #
17
+ # @yield [config] the current configuration
18
+ # @return [void]
19
+ def configure = yield config
20
+
21
+ # Returns the current request or default currency as a resolved currency.
22
+ # Memoized per-thread — within a request, Current.currency is stable.
23
+ #
24
+ # @return [Mint::Currency]
25
+ def default_currency
26
+ code = MoneyAttribute::Current.currency.presence || config.default_currency
15
27
 
16
- define_singleton_method(:config) { cfg }
28
+ last_code, last_currency = Thread.current[:money_attribute_default_currency]
29
+ return last_currency if last_code == code
17
30
 
18
- define_singleton_method(:configure) do |&block|
19
- block&.call(cfg)
20
- cached_default = nil
21
- cfg
31
+ currency = Money::Currency.resolve!(code)
32
+ Thread.current[:money_attribute_default_currency] = [code, currency]
33
+ currency
34
+ end
22
35
  end
23
36
 
24
- define_singleton_method(:default_currency) do
25
- cached_default ||= ::Mint::Currency.resolve!(cfg.default_currency)
37
+ # Gem configuration holding the default currency and registered custom currencies.
38
+ #
39
+ # MoneyAttribute.configure do |config|
40
+ # config.default_currency = 'BRL'
41
+ # end
42
+ class Config
43
+ # @return [String] ISO 4217 currency code used when no per-request or per-row currency is set.
44
+ attr_accessor :default_currency
45
+
46
+ # @return [Array<Hash>] custom currencies registered via +register_custom_currencies!+.
47
+ attr_accessor :added_currencies
48
+
49
+ # Initializes the default gem configuration values.
50
+ def initialize
51
+ @default_currency = 'USD'
52
+ @added_currencies = []
53
+ end
26
54
  end
27
55
  end
@@ -3,15 +3,25 @@
3
3
  module MoneyAttribute
4
4
  # :nodoc:
5
5
  class Converter
6
- def initialize(currency = MoneyAttribute.default_currency)
7
- @default_currency = currency
6
+ DEFAULT = new.freeze
7
+
8
+ # Initializes a converter with an optional fixed currency.
9
+ def initialize(currency = nil)
10
+ @static_currency = currency
11
+ end
12
+
13
+ # Returns the shared default converter instance.
14
+ def self.default
15
+ DEFAULT
8
16
  end
9
17
 
18
+ # Converts raw input into a `Money` value.
10
19
  def parse(amount)
20
+ currency = @static_currency || MoneyAttribute.default_currency
11
21
  case amount
12
22
  when Money, NilClass then amount
13
- when Numeric then Money.from(amount, @default_currency)
14
- when String then Money.parse(amount, @default_currency)
23
+ when Numeric then Money.from(amount, currency)
24
+ when String then Money.parse(amount, currency)
15
25
  else raise ArgumentError, "Cannot convert #{amount.inspect} (#{amount.class}) to Money"
16
26
  end
17
27
  end
@@ -4,5 +4,5 @@
4
4
  class Numeric
5
5
  remove_method :to_money if method_defined?(:to_money)
6
6
 
7
- def to_money(currency = MoneyAttribute.default_currency) = Mint.money(self, currency)
7
+ def to_money(currency = MoneyAttribute.default_currency) = Money.from(self, currency)
8
8
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/current_attributes'
4
+
5
+ module MoneyAttribute
6
+ # Per-request currency container. Set MoneyAttribute::Current.currency in your
7
+ # controller (or a before_action) to override the configured default for that request.
8
+ # Automatically reset after each request by MoneyAttribute::Middleware.
9
+ class Current < ::ActiveSupport::CurrentAttributes
10
+ # @return [String, nil] per-request ISO 4217 currency code override.
11
+ attribute :currency
12
+ end
13
+ end
@@ -3,6 +3,7 @@
3
3
  module MoneyAttribute
4
4
  # :nodoc:
5
5
  module FormBuilderExtension
6
+ # Renders a text input for a composed money attribute.
6
7
  def money_field(method, options = {})
7
8
  money = object.public_send(method)
8
9
  value = money&.to_fs
@@ -11,6 +12,7 @@ module MoneyAttribute
11
12
  { id: field_id(method) }.merge(options))
12
13
  end
13
14
 
15
+ # Renders a number input for a single-column money attribute.
14
16
  def money_amount_field(method, options = {})
15
17
  money_from_column = object.public_send(method)
16
18
  value = money_from_column&.to_d