minting-rails 0.4.5 → 0.7.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: 205d9eb9b1d27048756b4f65f2e65781ae71f131d1d838a1834ea733349d3bf4
4
+ data.tar.gz: ea2dfd05351ae59717737a0770480c1eeb6cf484264c9daea064c87ac80dd4a0
5
5
  SHA512:
6
- metadata.gz: 0e0a4ff1d7dd9a0ccb543545b75740567d0c985e174bd6e1460e44f08a74bd9f0b68a016e42374bcfb83d859bdb253afb2941fcb1d0ac3d4a6507574802b8dc0
7
- data.tar.gz: '09b31cae39bb1d85c3f34fe8c077406874fb8ebd429f37bb2347555fa8fef486685601ec45d06791ac8c7ebce1d8d2a87e7efcd8d46e2aad3c519226210799f5'
6
+ metadata.gz: a3e434d199296b8d9a22db937b2b40af35eec147a7fdc1d4a1c1a387a3a0300b806381f2e98cb3a3dfc40371b1087f1529a3a81b399b91dff4d4f454034f39c4
7
+ data.tar.gz: 8552a6d4d6c8f6567a4300c6540b04c00ca220b52b3deb0f0ba00af1fb40d0c974110eb97f04d3d0acb8d037f32852d61cd4a55a6b8d2dc17cbc398c9e58c6d4
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Minting::Rails brings [Minting](https://github.com/gferraz/minting) money objects to Active Record models.
4
4
 
5
- It adds a `money_attribute` model helper, registers a `:mint_money` Active Record type, and includes small convenience methods such as `12.to_money(:USD)`, `12.dollars`, and `'12.00'.mint(:BRL)`.
5
+ It adds a `money_attribute` model helper, registers a `:mint_money` Active Record type, and includes small convenience methods such as `12.to_money('USD')`, `12.dollars`, and `'12.00'.mint('BRL')`.
6
6
 
7
7
  ## What it does
8
8
 
@@ -15,7 +15,7 @@ It adds a `money_attribute` model helper, registers a `:mint_money` Active Recor
15
15
 
16
16
  - Ruby 3.3 or newer.
17
17
  - Rails 7.1.3.2 or newer.
18
- - Minting 1.0.0 or newer.
18
+ - Minting 1.6.0 or newer.
19
19
 
20
20
  ## Installation
21
21
 
@@ -45,6 +45,7 @@ Configure Minting in `config/initializers/minting.rb`:
45
45
  Mint.configure do |config|
46
46
  config.enabled_currencies = :all
47
47
  config.default_currency = 'USD'
48
+ config.default_format = '%<symbol>s%<amount>f'
48
49
  end
49
50
  ```
50
51
 
@@ -63,7 +64,7 @@ You can also register custom currencies before enabling or using them:
63
64
  Mint.configure do |config|
64
65
  config.added_currencies = [
65
66
  { currency: 'CRC', subunit: 2, symbol: 'CRC' },
66
- { currency: 'NGN', subunit: 2, symbol: 'NGN' }
67
+ { currency: 'NGN', subunit: 2, symbol: 'NGN' } # subunit is the number of digits after the decimal; USD has 2, JPY has 0, BHD has 3
67
68
  ]
68
69
 
69
70
  config.enabled_currencies = :all
@@ -120,7 +121,7 @@ product.discount
120
121
  Assigning a `Mint::Money` with a different currency raises `ArgumentError`:
121
122
 
122
123
  ```ruby
123
- Product.new(price: 12.to_money(:EUR))
124
+ Product.new(price: 12.to_money('EUR'))
124
125
  # raises ArgumentError because the attribute only accepts USD
125
126
  ```
126
127
 
@@ -154,7 +155,7 @@ end
154
155
  The attribute is composed from `price_amount` and `price_currency`:
155
156
 
156
157
  ```ruby
157
- offer = Offer.new(price: 15.to_money(:EUR))
158
+ offer = Offer.new(price: 15.to_money('EUR'))
158
159
 
159
160
  offer.price
160
161
  # => #<Mint::Money ... EUR 15.00>
@@ -166,6 +167,68 @@ offer.price_currency
166
167
  # => "EUR"
167
168
  ```
168
169
 
170
+ ### Integer storage
171
+
172
+ 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.
173
+
174
+ Migration:
175
+
176
+ ```ruby
177
+ class CreateOrders < ActiveRecord::Migration[7.1]
178
+ def change
179
+ create_table :orders do |t|
180
+ t.bigint :total_amount
181
+ t.string :total_currency
182
+
183
+ t.timestamps
184
+ end
185
+ end
186
+ end
187
+ ```
188
+
189
+ Model:
190
+
191
+ ```ruby
192
+ class Order < ApplicationRecord
193
+ money_attribute :total
194
+ end
195
+ ```
196
+
197
+ The amount is stored and retrieved in subunits:
198
+
199
+ ```ruby
200
+ order = Order.new(total: 19.99.to_money(:USD))
201
+
202
+ order.total
203
+ # => #<Mint::Money ... USD 19.99>
204
+
205
+ order.total_amount
206
+ # => 1999
207
+
208
+ order.total_currency
209
+ # => "USD"
210
+ ```
211
+
212
+ The same applies to single-column fixed-currency attributes:
213
+
214
+ ```ruby
215
+ class Ticket < ApplicationRecord
216
+ money_attribute :price, currency: 'USD'
217
+ end
218
+ ```
219
+
220
+ Migration:
221
+
222
+ ```ruby
223
+ t.bigint :price
224
+ ```
225
+
226
+ No model change is needed. The column type drives the behavior.
227
+
228
+ > **Note:** Integer storage is more efficient for large tables. Use Decimal when you need to stay close to SQL standards for interoperability with other systems
229
+
230
+ ### Default Currency
231
+
169
232
  When you assign a plain number or string, Minting::Rails uses `Mint.default_currency`:
170
233
 
171
234
  ```ruby
@@ -182,26 +245,26 @@ If your amount and currency columns do not follow the `<name>_amount` and `<name
182
245
  ```ruby
183
246
  class Invoice < ApplicationRecord
184
247
  money_attribute :total, mapping: {
185
- total_amount: :amount,
186
- currency_code: :currency
248
+ amount: :total_amount,
249
+ currency: :currency_code
187
250
  }
188
251
  end
189
252
  ```
190
253
 
191
- The mapping keys are your database columns. The values must identify which column stores the `:amount` and which stores the `:currency`.
254
+ The mapping keys are `:amount` and `:currency`. The values are your database columns.
192
255
 
193
256
  ## Querying
194
257
 
195
258
  Fixed-currency attributes can be queried with `Mint::Money` values:
196
259
 
197
260
  ```ruby
198
- Product.where(price: 15.to_money(:USD))
261
+ Product.where(price: 15.to_money('USD'))
199
262
  ```
200
263
 
201
264
  Composed attributes can also be queried with a money object:
202
265
 
203
266
  ```ruby
204
- Offer.where(price: 15.to_money(:EUR))
267
+ Offer.where(price: 15.to_money('EUR'))
205
268
  ```
206
269
 
207
270
  You can still query the backing columns directly when that is clearer:
@@ -215,12 +278,14 @@ Offer.where(price_amount: 15, price_currency: 'EUR')
215
278
  Minting::Rails adds a few small helpers:
216
279
 
217
280
  ```ruby
218
- 12.to_money(:USD)
219
- 12.mint(:BRL)
281
+ 12.to_money('USD')
282
+ 12.mint('BRL')
220
283
  12.dollars
284
+ 1.dollar
285
+ 1.euro
221
286
  12.euros
222
- '12.50'.to_money(:USD)
223
- '12.50'.mint(:BRL)
287
+ '12.50'.to_money('USD')
288
+ '12.50'.mint('BRL')
224
289
  ```
225
290
 
226
291
  These return `Mint::Money` instances.
@@ -241,16 +306,6 @@ bundle exec rake test
241
306
 
242
307
  The repository includes a dummy Rails application under `test/dummy` for exercising the engine in a Rails environment.
243
308
 
244
- ## Releasing
245
-
246
- Update the version in `lib/minting/money_attribute/version.rb`, update release notes, and build the gem:
247
-
248
- ```sh
249
- gem build minting-rails.gemspec
250
- ```
251
-
252
- Publishing is configured for RubyGems.org.
253
-
254
309
  ## Contributing
255
310
 
256
311
  Bug reports and pull requests are welcome on GitHub at [gferraz/minting-rails](https://github.com/gferraz/minting-rails).
@@ -27,22 +27,6 @@ Mint.configure do |config|
27
27
  #
28
28
  config.default_currency = 'BRL'
29
29
 
30
- # Specify a rounding mode (not yet implemented)
31
- # Any one of:
32
- #
33
- # BigDecimal::ROUND_UP,
34
- # BigDecimal::ROUND_DOWN,
35
- # BigDecimal::ROUND_HALF_UP,
36
- # BigDecimal::ROUND_HALF_DOWN,
37
- # BigDecimal::ROUND_HALF_EVEN,
38
- # BigDecimal::ROUND_CEILING,
39
- # BigDecimal::ROUND_FLOOR
40
- #
41
- # set to BigDecimal::ROUND_HALF_EVEN by default
42
- #
43
- # config.rounding_mode = BigDecimal::ROUND_HALF_UP
44
-
45
- # Set default money format globally.
46
30
  # Default value is nil meaning "ignore this option".
47
31
  # Example:
48
32
  #
@@ -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,15 +18,31 @@ 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
- { amount: mapping.key(:amount).to_s, currency: mapping.key(:currency).to_s }
45
+ { amount: mapping[:amount].to_s, currency: mapping[:currency].to_s }
30
46
  else
31
47
  { amount: "#{name}_amount", currency: "#{name}_currency" }
32
48
  end
@@ -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,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mint
4
+ # MoneyAttribute
5
+ module MoneyAttribute
6
+ class Parser
7
+ def initialize(currency = Mint.default_currency)
8
+ @default_currency = currency
9
+ end
10
+
11
+ def parse(amount, currency = @default_currency)
12
+ currency = Mint.assert_valid_currency!(currency)
13
+ case amount
14
+ when NilClass then nil
15
+ when Numeric then Mint::Money.create(amount, currency)
16
+ when String then Mint::Money.create(amount.to_r, currency)
17
+ when Mint::Money
18
+ return amount if amount.currency == currency
19
+ raise TypeError, "Cannot automatically convert #{amount} to #{currency.code}"
20
+ else
21
+ Mint.parse(amount, currency)
22
+ end
23
+ end
24
+ alias_method :call, :parse
25
+ end
26
+ end
27
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Mint
4
4
  module MoneyAttribute
5
- VERSION = '0.4.5'
5
+ VERSION = '0.7.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'
@@ -15,7 +15,7 @@ module Mint
15
15
  else
16
16
  code, subunit, symbol = *currency_data
17
17
  end
18
- Mint.register_currency(code:, subunit:, symbol:)
18
+ Mint.register_currency(code:, subunit:, symbol:)
19
19
  end
20
20
  end
21
21
  end
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.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilson Ferraz
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 1.0.0
18
+ version: 1.6.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 1.0.0
25
+ version: 1.6.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rails
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -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