money_attribute 0.12.0 → 0.14.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: 306761efbc2f4f645f3e975ea830ff26901bc4ba5fac664346f5d7b5720050b8
4
- data.tar.gz: 13dfc1b02f256f22ac1027700164ae57cee60bc77e1b233c28549b20988adaf7
3
+ metadata.gz: 6648e54fd55ca614b6b64674e0fe4135b6de228226056f1d39b30c9e577128c5
4
+ data.tar.gz: 3416fe702dac29b141eed67555a4d94d1dd104df755966c131ef9f00f735dcc4
5
5
  SHA512:
6
- metadata.gz: 3d39e999c298070baf49359ed226f9582defac099e4da3b342f3b367df81c658575e973095a0c8de4a19d8c7116af778d38160de6dbb263fd050a6a36bb3f0a9
7
- data.tar.gz: 02c399705be0e9ac78c3f9dd1bbe9603055d6702df60a43e12e57fb0fa16ecfd4212d129a91294bdb4323f487030def2bc3fe52a7484552b93c4f2e81b6a71ed
6
+ metadata.gz: 6051b71da3b44a030d7af3cc44f0c9c9958bd85bf4072f95a9c103e2fa6f6eed1ed773cec56ea9c8d6e4261893c7a1a41df1f886ccd4a2b1f9af228b99cbac85
7
+ data.tar.gz: e1157dd7957fd6b54649182a665aa464985bfce04a901d7e6570bf0f9e1d453fedb3407de3251a3dec79e72d2193f6e1bbcb412d3d5219a72f38244a62fb86c6
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,19 +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
95
 
96
- By default `t.money :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.
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.
97
97
 
98
98
  ```ruby
99
99
  class CreateProducts < ActiveRecord::Migration[8.1]
100
100
  def change
101
101
  create_table :products do |t|
102
102
  t.string :name
103
- t.money :price # price (decimal) + price_currency (string)
104
- t.money :price_amount # price_amount + price_currency (strips _amount suffix)
105
- t.money :fee, currency: false # single column, no currency
106
- t.money :tax, amount: { 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
107
107
  t.timestamps
108
108
  end
109
109
  end
@@ -111,9 +111,9 @@ end
111
111
 
112
112
  class AddPriceToProducts < ActiveRecord::Migration[8.1]
113
113
  def change
114
- add_money :products, :price # add price + price_currency
115
- add_money :products, :discount, amount: { type: :integer }
116
- 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
117
117
  end
118
118
  end
119
119
  ```
@@ -122,20 +122,20 @@ end
122
122
 
123
123
  | Migration call | Columns created | Model declaration |
124
124
  |---|---|---|
125
- | `t.money :price` | `price` decimal + `price_currency` string | `money_attribute :price` |
126
- | `t.money :price_amount` | `price_amount` decimal + `price_currency` string | `money_attribute :price` |
127
- | `t.money :price, currency: false` | `price` decimal | `money_attribute :price` |
128
- | `t.money :price, amount: { type: :integer }` | `price` integer + `price_currency` string | `money_attribute :price` |
129
- | `t.money :price, amount: { column: :a }, currency: { column: :c }` | `a` + `c` | `money_attribute :price, mapping: { amount: :a, currency: :c }` |
130
- | `t.money :price, currency: { limit: 3 }` | `price` decimal + `price_currency` string(3) | `money_attribute :price` |
131
- | `t.money :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 :price` | Removes `price` + `price_currency` | `money_attribute :price` |
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
133
 
134
134
  Inside `change_table`:
135
135
 
136
136
  ```ruby
137
137
  change_table :products do |t|
138
- t.remove_money :obsolete_fee # removes obsolete_fee + obsolete_fee_currency
138
+ t.remove_money_attribute :obsolete_fee # removes obsolete_fee + obsolete_fee_currency
139
139
  end
140
140
  ```
141
141
 
@@ -201,7 +201,7 @@ If none of those keys are set, `format` is used as a plain string (simple format
201
201
 
202
202
  | | Fixed currency (single column) | Multi-currency (amount + currency) |
203
203
  |---|---|---|
204
- | **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`) |
205
205
  | **Model** | `money_attribute :price, currency: 'USD'` | `money_attribute :price` |
206
206
  | **When to use** | Column always holds the same currency | Each row can hold a different currency |
207
207
  | **Column type** | `decimal`, `integer`, or `bigint` | `decimal`, `integer`, or `bigint` for amount; `string` for currency |
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MoneyAttribute
4
+ class Converter
5
+ def initialize(currency = MoneyAttribute.default_currency)
6
+ @default_currency = currency
7
+ end
8
+
9
+ def parse(amount, currency = @default_currency)
10
+ case amount
11
+ when Mint::Money, NilClass then amount
12
+ when Numeric then Mint::Money.from(amount, currency)
13
+ else Mint.parse(amount, currency)
14
+ end
15
+ end
16
+ alias call parse
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MoneyAttribute
4
+ module FormBuilderExtension
5
+ def money_field(method, options = {})
6
+ money = object.public_send(method)
7
+ value = money ? money.to_s : nil
8
+
9
+ @template.text_field_tag(field_name(method), value,
10
+ { id: field_id(method) }.merge(options))
11
+ end
12
+
13
+ def money_amount(method, options = {})
14
+ money_from_column = object.public_send(method)
15
+ value = money_from_column ? money_from_column.to_s : nil
16
+
17
+ @template.text_field_tag(field_name(method), value,
18
+ { id: field_id(method) }.merge(options))
19
+ end
20
+ end
21
+ end
@@ -8,14 +8,14 @@ module MoneyAttribute
8
8
  def money_attribute(name, currency: MoneyAttribute.default_currency, mapping: nil)
9
9
  columns = attribute_names
10
10
  currency = ::Mint::Currency.resolve!(currency)
11
+ converter = Converter.new(currency)
11
12
  name = name.to_s
12
- parser = Parser.new(currency)
13
13
  resolved_mapping = mapping || resolve_mapping(name, columns)
14
14
 
15
15
  if columns.include?(name) && resolved_mapping.nil?
16
- define_single_column_money_attribute(name, currency, parser)
16
+ define_single_column_money_attribute(name, currency)
17
17
  else
18
- define_composite_money_attribute(name, resolved_mapping, parser)
18
+ define_composite_money_attribute(name, resolved_mapping)
19
19
  end
20
20
  end
21
21
 
@@ -55,7 +55,22 @@ module MoneyAttribute
55
55
 
56
56
  def amount_extractor_for(column_name) = integer_column?(column_name) ? :subunits : :to_d
57
57
 
58
- def money_constructor_for(amount_column) = integer_column?(amount_column) ? :from_subunits : :from
58
+ def money_constructor_for(amount_column)
59
+ default = MoneyAttribute.default_currency
60
+ if integer_column?(amount_column)
61
+ lambda { |amount, currency|
62
+ return nil if amount.nil?
63
+
64
+ Mint::Money.from_subunits(amount, currency.presence || default)
65
+ }
66
+ else
67
+ lambda { |amount, currency|
68
+ return nil if amount.nil?
69
+
70
+ Mint::Money.from(amount, currency.presence || default)
71
+ }
72
+ end
73
+ end
59
74
 
60
75
  def integer_column?(column_name)
61
76
  col = columns.find { |c| c.name == column_name }
@@ -64,20 +79,20 @@ module MoneyAttribute
64
79
 
65
80
  # --- Configuration (registers types, normalizers, composed_of) ---
66
81
 
67
- def define_single_column_money_attribute(name, currency, parser)
82
+ def define_single_column_money_attribute(name, currency)
68
83
  column_type = integer_column?(name) ? ActiveRecord::Type::Integer.new : ActiveRecord::Type::Decimal.new
69
84
  attribute(name.to_sym, :money, currency:, column_type: column_type)
70
- normalizes(name.to_sym, with: parser)
85
+ normalizes(name.to_sym, with: Converter.new(currency))
71
86
  end
72
87
 
73
- def define_composite_money_attribute(name, mapping, parser)
88
+ def define_composite_money_attribute(name, mapping)
74
89
  aggregated = resolve_composite_for(name, mapping:)
75
90
 
76
91
  composed_of(name.to_sym, {
77
92
  allow_nil: true,
78
93
  class_name: 'Mint::Money',
79
94
  constructor: money_constructor_for(aggregated[:amount]),
80
- converter: parser,
95
+ converter: Converter.new(currency),
81
96
  mapping: {
82
97
  aggregated[:amount] => amount_extractor_for(aggregated[:amount]),
83
98
  aggregated[:currency] => :currency_code
@@ -34,7 +34,11 @@ module MoneyAttribute
34
34
  end
35
35
 
36
36
  stripped = name.end_with?('_amount') ? name.sub(/_amount$/, '') : name
37
- default_currency_col = "#{stripped}_currency"
37
+ default_currency_col = if name == 'amount' && !(options[:currency].is_a?(Hash) && options[:currency][:column])
38
+ 'currency'
39
+ else
40
+ "#{stripped}_currency"
41
+ end
38
42
 
39
43
  if options.key?(:currency) && options[:currency].is_a?(Hash)
40
44
  currency_col = options[:currency][:column]&.to_s || default_currency_col
@@ -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)
@@ -14,6 +14,8 @@ module MoneyAttribute
14
14
  ActiveRecord::ConnectionAdapters::TableDefinition.include(MoneyAttribute::MigrationExtensions::TableDefinition)
15
15
  ActiveRecord::ConnectionAdapters::Table.include(MoneyAttribute::MigrationExtensions::TableDefinition)
16
16
 
17
+ ActionView::Helpers::FormBuilder.include(MoneyAttribute::FormBuilderExtension)
18
+
17
19
  setup_locale_backend!
18
20
  register_custom_currencies!
19
21
  end
@@ -9,10 +9,17 @@ module MoneyAttribute
9
9
  super()
10
10
  end
11
11
 
12
+ def cast(value)
13
+ case value
14
+ when String then Mint::Money.parse(value, @currency)
15
+ else super
16
+ end
17
+ end
18
+
12
19
  def assert_valid_value(value)
13
20
  case value
14
21
  when NilClass, Numeric, String then return
15
- when ::Mint::Money
22
+ when Mint::Money
16
23
  return if value.currency == @currency
17
24
 
18
25
  message = "'#{value.inspect}' has different currency. Only #{@currency.code} allowed."
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MoneyAttribute
4
- VERSION = '0.12.0'
4
+ VERSION = '0.14.0'
5
5
  end
@@ -4,7 +4,8 @@ require 'minting'
4
4
  require 'money_attribute/core_ext'
5
5
  require 'money_attribute/configuration'
6
6
  require 'money_attribute/macro'
7
- require 'money_attribute/parser'
7
+ require 'money_attribute/converter'
8
8
  require 'money_attribute/type'
9
+ require 'money_attribute/form_builder_extension'
9
10
  require 'money_attribute/railtie'
10
11
  require 'money_attribute/version'
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.12.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilson Ferraz
@@ -51,12 +51,13 @@ files:
51
51
  - lib/generators/templates/money_attribute.rb
52
52
  - lib/money_attribute.rb
53
53
  - lib/money_attribute/configuration.rb
54
+ - lib/money_attribute/converter.rb
54
55
  - lib/money_attribute/core_ext.rb
56
+ - lib/money_attribute/form_builder_extension.rb
55
57
  - lib/money_attribute/macro.rb
56
58
  - lib/money_attribute/migration_extensions/helper.rb
57
59
  - lib/money_attribute/migration_extensions/schema_statements.rb
58
60
  - lib/money_attribute/migration_extensions/table_definition.rb
59
- - lib/money_attribute/parser.rb
60
61
  - lib/money_attribute/railtie.rb
61
62
  - lib/money_attribute/type.rb
62
63
  - lib/money_attribute/version.rb
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module MoneyAttribute
4
- class Parser
5
- def initialize(currency = MoneyAttribute.default_currency)
6
- @default_currency = currency
7
- end
8
-
9
- def parse(amount, currency = @default_currency)
10
- currency = ::Mint::Currency.resolve!(currency)
11
- case amount
12
- when NilClass then nil
13
- when Numeric then ::Mint::Money.from(amount, currency)
14
- when String then ::Mint::Money.from(amount.to_r, currency)
15
- when ::Mint::Money
16
- return amount if amount.currency == currency
17
-
18
- raise TypeError, "Cannot automatically convert #{amount} to #{currency.code}"
19
- else
20
- ::Mint.parse(amount, currency)
21
- end
22
- end
23
- alias call parse
24
- end
25
- end