minting-rails 0.4.5 → 0.5.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: dddd6623b7e46d60d0e8e29da21afa581d28c6818ae4d37d5a5c1268da04425f
4
- data.tar.gz: 4e6c1fa638f4f22c365d0cc5bc6540c9dccbdc770e8550f10c4ce94c4150088d
3
+ metadata.gz: bebeb146f003c49cd0f1c667648090a35f96d5d22edfec3c6616f5f45d812988
4
+ data.tar.gz: f6084d3f41dce76e7b437505566a1a606f54a92419cac641e017d33a9a2d0400
5
5
  SHA512:
6
- metadata.gz: 0e0a4ff1d7dd9a0ccb543545b75740567d0c985e174bd6e1460e44f08a74bd9f0b68a016e42374bcfb83d859bdb253afb2941fcb1d0ac3d4a6507574802b8dc0
7
- data.tar.gz: '09b31cae39bb1d85c3f34fe8c077406874fb8ebd429f37bb2347555fa8fef486685601ec45d06791ac8c7ebce1d8d2a87e7efcd8d46e2aad3c519226210799f5'
6
+ metadata.gz: 46539fbf075c057d2fb2727320975b13dd20f5efbe5a0ad2955d51f78f1720ecb4e9a3e7f52c9428f798dabf13a04a258403df6fe0735fff901a0c8e6e2ccf08
7
+ data.tar.gz: 66528813c392c867719ea9dcecf24fbc78a8bdd9fe9b234d6ca82c201b6d6da533ebd3058b0610223def05344e10843505a3c30fe13d65acd5593717b2d8fce2
data/README.md CHANGED
@@ -166,6 +166,68 @@ offer.price_currency
166
166
  # => "EUR"
167
167
  ```
168
168
 
169
+ ### Integer storage
170
+
171
+ By default, money attributes are stored as `decimal` columns. If you prefer to store amounts as integer subunits (cents, pence, etc.), use a `bigint` or `integer` column instead. Minting::Rails detects the column type automatically and adapts serialization accordingly.
172
+
173
+ Migration:
174
+
175
+ ```ruby
176
+ class CreateOrders < ActiveRecord::Migration[7.1]
177
+ def change
178
+ create_table :orders do |t|
179
+ t.bigint :total_amount
180
+ t.string :total_currency
181
+
182
+ t.timestamps
183
+ end
184
+ end
185
+ end
186
+ ```
187
+
188
+ Model:
189
+
190
+ ```ruby
191
+ class Order < ApplicationRecord
192
+ money_attribute :total
193
+ end
194
+ ```
195
+
196
+ The amount is stored and retrieved in subunits:
197
+
198
+ ```ruby
199
+ order = Order.new(total: 19.99.to_money(:USD))
200
+
201
+ order.total
202
+ # => #<Mint::Money ... USD 19.99>
203
+
204
+ order.total_amount
205
+ # => 1999
206
+
207
+ order.total_currency
208
+ # => "USD"
209
+ ```
210
+
211
+ The same applies to single-column fixed-currency attributes:
212
+
213
+ ```ruby
214
+ class Ticket < ApplicationRecord
215
+ money_attribute :price, currency: 'USD'
216
+ end
217
+ ```
218
+
219
+ Migration:
220
+
221
+ ```ruby
222
+ t.bigint :price
223
+ ```
224
+
225
+ No model change is needed. The column type drives the behavior.
226
+
227
+ > **Note:** Integer storage avoids decimal rounding issues and is more efficient for large tables.
228
+
229
+ ### Default Currency
230
+
169
231
  When you assign a plain number or string, Minting::Rails uses `Mint.default_currency`:
170
232
 
171
233
  ```ruby
@@ -27,11 +27,10 @@ module Mint
27
27
  end
28
28
 
29
29
  def self.assert_valid_currency!(currency)
30
- code = currency.is_a?(Mint::Currency) ? currency.code : currency.to_s
31
- currency = Mint.currency(code)
30
+ currency = Mint.currency(currency)
32
31
  return currency if Mint.valid_currency?(currency)
33
32
 
34
- raise ArgumentError, "Invalid currency '#{code}'. Please select a registered currency"
33
+ raise ArgumentError, "Invalid currency '#{currency}'. Please select a registered currency"
35
34
  end
36
35
 
37
36
  def self.default_currency
@@ -39,11 +38,7 @@ module Mint
39
38
  end
40
39
 
41
40
  def self.valid_currency?(currency)
42
- return false if currency.nil?
43
-
44
- code = currency.is_a?(Mint::Currency) ? currency.code : currency.to_s
45
- currencies = config.enabled_currencies == :all ? Mint.currencies.keys : config.enabled_currencies
46
-
47
- currencies.map(&:to_s).include?(code)
41
+ enabled = config.enabled_currencies
42
+ currency && (enabled == :all || enabled.include?(currency.code))
48
43
  end
49
44
  end
@@ -9,7 +9,7 @@ module Mint
9
9
  # Money attribute
10
10
  def money_attribute(name, currency: Mint.default_currency, mapping: nil)
11
11
  currency = Mint.assert_valid_currency!(currency)
12
- parser = proc { |amount, code = currency| MoneyAttribute.parse(amount, code) }
12
+ parser = Parser.new(currency)
13
13
  if attribute_names.include? name.to_s
14
14
  attribute(name, :mint_money, currency:)
15
15
  normalizes(name, with: parser)
@@ -18,12 +18,28 @@ module Mint
18
18
  options = {
19
19
  allow_nil: true, class_name: 'Mint::Money',
20
20
  constructor: parser, converter: parser,
21
- mapping: { aggregated[:amount] => :to_i, aggregated[:currency] => :currency_code }
21
+ mapping: {
22
+ aggregated[:amount] => amount_extractor_for(aggregated[:amount]),
23
+ aggregated[:currency] => :currency_code
24
+ }
22
25
  }
23
26
  composed_of(name, options)
24
27
  end
25
28
  end
26
29
 
30
+ def amount_extractor_for(column_name)
31
+ col = columns.find { |c| c.name == column_name.to_s }
32
+
33
+ case col&.type
34
+ when :bigint, :integer
35
+ :fractional
36
+ when :decimal, :numeric
37
+ :to_d
38
+ else
39
+ :to_d # :decimal, :numeric, unknown
40
+ end
41
+ end
42
+
27
43
  def find_money_attributes(name, mapping:)
28
44
  composite = if mapping.present?
29
45
  { amount: mapping.key(:amount).to_s, currency: mapping.key(:currency).to_s }
@@ -37,25 +53,5 @@ module Mint
37
53
  composite
38
54
  end
39
55
  end
40
-
41
- def self.parse(amount, currency)
42
- money = case amount
43
- when NilClass
44
- nil
45
- when Mint::Money
46
- amount
47
- when Numeric
48
- Mint.money(amount, currency)
49
- else
50
- if amount.respond_to? :to_money
51
- amount.to_money(currency)
52
- else
53
- Mint.money(amount.to_s.split[0].to_r, currency)
54
- end
55
- end
56
- # puts "parse(#{amount}, #{currency.inspect}) => #{money.inspect}"
57
- Mint.assert_valid_currency!(currency)
58
- money
59
- end
60
56
  end
61
57
  end
@@ -3,31 +3,43 @@
3
3
  module Mint
4
4
  # MintMoneyType
5
5
  class MintMoneyType < ActiveRecord::Type::Value
6
- def initialize(currency:)
7
- @currency = Mint.currency currency
6
+ def initialize(currency:, column_type: ActiveRecord::Type::Decimal.new)
7
+ @currency = currency
8
+ @column_type = column_type
8
9
  super()
9
10
  end
10
11
 
11
12
  def assert_valid_value(value)
12
13
  case value
13
- when NilClass, Numeric, String
14
- return
14
+ when NilClass, Numeric, String then return
15
15
  when Mint::Money
16
16
  return if value.currency == @currency
17
17
 
18
18
  message = "'#{value.inspect}' has different currency. Only #{@currency.code} allowed."
19
19
  else
20
- message = "'#{value}' is not a valid type for the attribute."
20
+ message = "'#{value.inspect}' is not a valid type for the attribute."
21
21
  end
22
22
  raise ArgumentError, message
23
23
  end
24
24
 
25
25
  def deserialize(value)
26
- value && Mint.money(value, @currency)
26
+ return nil unless value
27
+
28
+ if @column_type.is_a?(ActiveRecord::Type::Integer)
29
+ Mint.money(value * @currency.multiplier, @currency)
30
+ else
31
+ Mint.money(value, @currency)
32
+ end
27
33
  end
28
34
 
29
35
  def serialize(value)
30
- value&.to_d
36
+ return nil unless value
37
+
38
+ if @column_type.is_a?(ActiveRecord::Type::Integer)
39
+ value.fractional
40
+ else
41
+ value.to_d
42
+ end
31
43
  end
32
44
 
33
45
  def self.type
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mint
4
+ # MoneyAttribute
5
+ module MoneyAttribute
6
+ class Parser
7
+ def initialize(currency)
8
+ @default_currency = currency
9
+ end
10
+
11
+ def call(amount, currency = @default_currency)
12
+ currency = Mint.assert_valid_currency!(currency)
13
+ case amount
14
+ when NilClass then nil
15
+ when Mint::Money then amount
16
+ when Numeric then Mint::Money.create(amount, currency)
17
+ when String then Mint::Money.create(amount.to_r, currency)
18
+ else
19
+ if amount.respond_to? :to_money
20
+ amount.to_money(currency)
21
+ else
22
+ Mint.parse(amount, currency)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Mint
4
4
  module MoneyAttribute
5
- VERSION = '0.4.5'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
data/lib/minting/rails.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'minting'
4
- require 'minting/money_attribute/core_ext'
4
+ require 'minting/core_ext'
5
5
  require 'minting/money_attribute/configuration'
6
6
  require 'minting/money_attribute/money_attribute'
7
7
  require 'minting/money_attribute/money_type'
8
+ require 'minting/money_attribute/parser'
8
9
  require 'minting/money_attribute/version'
9
10
  require 'minting/railties'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minting-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.5
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilson Ferraz
@@ -49,10 +49,11 @@ files:
49
49
  - Rakefile
50
50
  - lib/generators/minting/initializer_generator.rb
51
51
  - lib/generators/templates/minting.rb
52
+ - lib/minting/core_ext.rb
52
53
  - lib/minting/money_attribute/configuration.rb
53
- - lib/minting/money_attribute/core_ext.rb
54
54
  - lib/minting/money_attribute/money_attribute.rb
55
55
  - lib/minting/money_attribute/money_type.rb
56
+ - lib/minting/money_attribute/parser.rb
56
57
  - lib/minting/money_attribute/version.rb
57
58
  - lib/minting/rails.rb
58
59
  - lib/minting/railties.rb