amounts 0.0.8 → 0.1.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 +4 -4
- data/.rubocop.yml +37 -0
- data/CHANGELOG.md +113 -0
- data/Gemfile +4 -0
- data/README.md +215 -1
- data/Rakefile +47 -10
- data/lib/amount/active_record/amount_validator.rb +9 -18
- data/lib/amount/active_record/attribute_definition.rb +3 -1
- data/lib/amount/active_record/locale/en.yml +7 -0
- data/lib/amount/active_record/model.rb +9 -0
- data/lib/amount/active_record.rb +12 -0
- data/lib/amount/compact.rb +137 -0
- data/lib/amount/{allocation.rb → concerns/allocatable.rb} +1 -1
- data/lib/amount/{arithmetic.rb → concerns/calculable.rb} +1 -1
- data/lib/amount/concerns/coercible.rb +65 -0
- data/lib/amount/concerns/constructible.rb +92 -0
- data/lib/amount/{conversion.rb → concerns/convertible.rb} +1 -1
- data/lib/amount/concerns/formattable.rb +112 -0
- data/lib/amount/concerns/localizable.rb +39 -0
- data/lib/amount/{comparison.rb → concerns/orderable.rb} +2 -2
- data/lib/amount/concerns/parsable.rb +58 -0
- data/lib/amount/concerns/registrable.rb +80 -0
- data/lib/amount/{serialization.rb → concerns/serializable.rb} +3 -3
- data/lib/amount/display.rb +81 -46
- data/lib/amount/locale.rb +102 -0
- data/lib/amount/registry.rb +69 -7
- data/lib/amount/ui_parser.rb +109 -0
- data/lib/amount/version.rb +1 -1
- data/lib/amount.rb +55 -199
- data/test/amount/active_record/amount_validator_test.rb +201 -0
- data/test/amount/active_record/attribute_definition_test.rb +234 -0
- data/test/amount/active_record/migration_methods_test.rb +60 -0
- data/test/amount/active_record/model_test.rb +40 -0
- data/test/amount/active_record/type_test.rb +80 -0
- data/test/amount/active_record_test.rb +12 -0
- data/test/amount/compact_test.rb +371 -0
- data/test/amount/concerns/allocatable_test.rb +166 -0
- data/test/amount/concerns/calculable_test.rb +152 -0
- data/test/amount/concerns/coercible_test.rb +49 -0
- data/test/amount/concerns/constructible_test.rb +113 -0
- data/test/amount/concerns/convertible_test.rb +54 -0
- data/test/amount/concerns/formattable_test.rb +336 -0
- data/test/amount/concerns/localizable_test.rb +59 -0
- data/test/amount/concerns/orderable_test.rb +83 -0
- data/test/amount/concerns/parsable_test.rb +98 -0
- data/test/amount/concerns/registrable_test.rb +67 -0
- data/test/amount/concerns/serializable_test.rb +98 -0
- data/test/amount/display_test.rb +181 -0
- data/test/amount/locale_test.rb +590 -0
- data/test/amount/parser_test.rb +29 -0
- data/test/amount/registry/generated_constructors_test.rb +94 -0
- data/test/amount/registry_test.rb +149 -0
- data/test/amount/ui_parser_test.rb +284 -0
- data/test/amount_test.rb +55 -0
- data/test/dummy/config/application.rb +3 -0
- data/test/generators/amount/active_record/registry_generator_test.rb +116 -0
- data/test/integration/postgresql_test.rb +94 -0
- data/test/support/active_record_test_support.rb +65 -0
- data/test/support/error_helpers.rb +22 -0
- data/test/support/locale_test_setup.rb +36 -0
- data/test/test_helper.rb +1 -0
- metadata +47 -11
- data/test/postgresql_integration_test.rb +0 -71
- data/test/test_active_record.rb +0 -375
- data/test/test_amount.rb +0 -675
- data/test/test_registry_generator.rb +0 -104
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../test_helper"
|
|
4
|
+
require_relative "../../support/active_record_test_support"
|
|
5
|
+
|
|
6
|
+
class AmountActiveRecordAttributeDefinitionTest < Minitest::Test
|
|
7
|
+
include ActiveRecordTestSetup
|
|
8
|
+
|
|
9
|
+
def test_reader_returns_amount
|
|
10
|
+
holding = Holding.create!(amount_atomic: 1_500_000, amount_symbol: "USDC")
|
|
11
|
+
|
|
12
|
+
assert_equal Amount.new("1.5", :USDC), holding.amount
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_multi_symbol_reader_returns_nil_when_atomic_is_nil
|
|
16
|
+
assert_nil Holding.create!.amount
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_fixed_symbol_reader_returns_nil_when_atomic_is_nil
|
|
20
|
+
assert_nil Holding.create!.fee
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_multi_symbol_writer_stores_amount_atomic
|
|
24
|
+
assert_equal 1_500_000, amount_written_holding.amount_atomic
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_multi_symbol_writer_stores_amount_symbol
|
|
28
|
+
assert_equal "USDC", amount_written_holding.amount_symbol
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Rails 8.1 cuts a String cast to an integer column to 16 bytes, so the
|
|
32
|
+
# writer must hand Rails the Integer, never its string form.
|
|
33
|
+
def test_writer_keeps_atomic_values_longer_than_sixteen_digits
|
|
34
|
+
holding = Holding.new(amount: Amount.new(10**30, :SOL, from: :atomic))
|
|
35
|
+
|
|
36
|
+
assert_equal 10**30, holding.amount_atomic
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_fixed_symbol_writer_keeps_atomic_values_longer_than_sixteen_digits
|
|
40
|
+
holding = Holding.new(fee: Amount.new(10**30, :SOL, from: :atomic))
|
|
41
|
+
|
|
42
|
+
assert_equal 10**30, holding.fee_atomic
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_fixed_symbol_writer_stores_amount_atomic
|
|
46
|
+
holding = Holding.new
|
|
47
|
+
holding.fee = Amount.new("2.5", :SOL)
|
|
48
|
+
|
|
49
|
+
assert_equal 2_500_000_000, holding.fee_atomic
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_nil_assignment_clears_atomic_column
|
|
53
|
+
assert_nil nil_assigned_holding.amount_atomic
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_nil_assignment_clears_symbol_column
|
|
57
|
+
assert_nil nil_assigned_holding.amount_symbol
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_nil_assignment_reads_back_nil
|
|
61
|
+
assert_nil nil_assigned_holding.amount
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_where_scope_matches_atomic_and_symbol
|
|
65
|
+
usdc = create_same_value_in_two_symbols
|
|
66
|
+
|
|
67
|
+
assert_equal [usdc.id], Holding.where_amount("USDC|1.5").pluck(:id)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_in_scope_matches_symbol
|
|
71
|
+
usdc = create_same_value_in_two_symbols
|
|
72
|
+
|
|
73
|
+
assert_equal [usdc.id], Holding.amount_in(:USDC).pluck(:id)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def test_gt_scope_filters_multi_symbol_amounts_by_symbol_and_atomic_value
|
|
77
|
+
_low, mid, high = create_usdc_ladder
|
|
78
|
+
|
|
79
|
+
assert_equal [mid.id, high.id], Holding.where_amount_gt("USDC|1.50").pluck(:id)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def test_gte_scope_filters_multi_symbol_amounts_by_symbol_and_atomic_value
|
|
83
|
+
_low, mid, high = create_usdc_ladder
|
|
84
|
+
|
|
85
|
+
assert_equal [mid.id, high.id], Holding.where_amount_gte("USDC|2.00").pluck(:id)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_lt_scope_filters_multi_symbol_amounts_by_symbol_and_atomic_value
|
|
89
|
+
low, mid, _high = create_usdc_ladder
|
|
90
|
+
|
|
91
|
+
assert_equal [low.id, mid.id], Holding.where_amount_lt("USDC|2.50").pluck(:id)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_lte_scope_filters_multi_symbol_amounts_by_symbol_and_atomic_value
|
|
95
|
+
low, mid, _high = create_usdc_ladder
|
|
96
|
+
|
|
97
|
+
assert_equal [low.id, mid.id], Holding.where_amount_lte("USDC|2.00").pluck(:id)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_between_scope_includes_both_bounds_for_multi_symbol_amounts
|
|
101
|
+
low, mid, high = create_usdc_ladder(other: "USD|2.00")
|
|
102
|
+
|
|
103
|
+
assert_equal [low.id, mid.id, high.id], Holding.where_amount_between("USDC|1.00", "USDC|3.00").pluck(:id)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_between_scope_filters_multi_symbol_amounts_inside_bounds
|
|
107
|
+
_low, mid, _high = create_usdc_ladder(other: "USD|2.00")
|
|
108
|
+
|
|
109
|
+
assert_equal [mid.id], Holding.where_amount_between("USDC|1.50", "USDC|2.50").pluck(:id)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_between_scope_requires_matching_symbols_for_multi_symbol_amounts
|
|
113
|
+
assert_raises(Amount::TypeMismatch) do
|
|
114
|
+
Holding.where_amount_between("USDC|1.00", "USD|2.00").load
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_gt_scope_supports_fixed_symbol_numeric_values
|
|
119
|
+
_low, mid, high = create_fee_ladder
|
|
120
|
+
|
|
121
|
+
assert_equal [mid.id, high.id], Holding.where_fee_gt(0.25).pluck(:id)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_gte_scope_supports_fixed_symbol_numeric_values
|
|
125
|
+
_low, mid, high = create_fee_ladder
|
|
126
|
+
|
|
127
|
+
assert_equal [mid.id, high.id], Holding.where_fee_gte(0.50).pluck(:id)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def test_lt_scope_supports_fixed_symbol_numeric_values
|
|
131
|
+
low, mid, _high = create_fee_ladder
|
|
132
|
+
|
|
133
|
+
assert_equal [low.id, mid.id], Holding.where_fee_lt(0.75).pluck(:id)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_lte_scope_supports_fixed_symbol_numeric_values
|
|
137
|
+
low, mid, _high = create_fee_ladder
|
|
138
|
+
|
|
139
|
+
assert_equal [low.id, mid.id], Holding.where_fee_lte(0.50).pluck(:id)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def test_between_scope_supports_fixed_symbol_numeric_values
|
|
143
|
+
_low, mid, _high = create_fee_ladder
|
|
144
|
+
|
|
145
|
+
assert_equal [mid.id], Holding.where_fee_between(0.30, 0.60).pluck(:id)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def test_changed_predicate_uses_virtual_amount_attribute
|
|
149
|
+
assert changed_holding.amount_changed?
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_change_uses_virtual_amount_attribute
|
|
153
|
+
assert_equal [Amount.new("1.0", :USDC), Amount.new("2.5", :USDC)], changed_holding.amount_change
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def test_saved_change_predicate_uses_virtual_amount_attribute
|
|
157
|
+
assert saved_holding.saved_change_to_amount?
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def test_before_last_save_uses_virtual_amount_attribute
|
|
161
|
+
assert_equal Amount.new("1.0", :USDC), saved_holding.amount_before_last_save
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_saved_change_uses_virtual_amount_attribute
|
|
165
|
+
assert_equal [Amount.new("1.0", :USDC), Amount.new("2.5", :USDC)], saved_holding.saved_change_to_amount
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_validation_rejects_atomic_without_symbol
|
|
169
|
+
refute Holding.new(amount_atomic: 100).valid?
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def test_validation_requires_both_amount_columns_or_neither
|
|
173
|
+
holding = Holding.new(amount_atomic: 100)
|
|
174
|
+
holding.valid?
|
|
175
|
+
|
|
176
|
+
assert_includes holding.errors[:amount], "must set both atomic and symbol or neither"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def test_group_sum_totals_first_symbol_atomic_column
|
|
180
|
+
assert_equal 3_500_000, grouped_sums["USDC"]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def test_group_sum_totals_second_symbol_atomic_column
|
|
184
|
+
assert_equal 300, grouped_sums["USD"]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def amount_written_holding
|
|
190
|
+
Holding.new.tap { |holding| holding.amount = Amount.new("1.5", :USDC) }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def nil_assigned_holding
|
|
194
|
+
holding = Holding.create!(amount: Amount.new("1", :USDC))
|
|
195
|
+
holding.amount = nil
|
|
196
|
+
holding
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# The USDC record; a USD record with the same value is created alongside.
|
|
200
|
+
def create_same_value_in_two_symbols
|
|
201
|
+
usdc = Holding.create!(amount: Amount.new("1.5", :USDC))
|
|
202
|
+
Holding.create!(amount: Amount.new("1.5", :USD))
|
|
203
|
+
usdc
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# USDC 1.00, 2.00 and 3.00 (returned), plus one record in another symbol.
|
|
207
|
+
def create_usdc_ladder(other: "USD|2.50")
|
|
208
|
+
ladder = ["USDC|1.00", "USDC|2.00", "USDC|3.00"].map { |amount| Holding.create!(amount:) }
|
|
209
|
+
Holding.create!(amount: other)
|
|
210
|
+
ladder
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def create_fee_ladder
|
|
214
|
+
[0.25, 0.50, 0.75].map { |fee| Holding.create!(fee:) }
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def changed_holding
|
|
218
|
+
holding = Holding.create!(amount: Amount.new("1.0", :USDC))
|
|
219
|
+
holding.amount = "USDC|2.5"
|
|
220
|
+
holding
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def saved_holding
|
|
224
|
+
changed_holding.tap(&:save!)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def grouped_sums
|
|
228
|
+
Holding.create!(amount: Amount.new("1.5", :USDC))
|
|
229
|
+
Holding.create!(amount: Amount.new("2.0", :USDC))
|
|
230
|
+
Holding.create!(amount: Amount.new("3.0", :USD))
|
|
231
|
+
|
|
232
|
+
Holding.group(:amount_symbol).sum(:amount_atomic)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../test_helper"
|
|
4
|
+
require_relative "../../support/active_record_test_support"
|
|
5
|
+
|
|
6
|
+
class AmountActiveRecordMigrationMethodsTest < Minitest::Test
|
|
7
|
+
include ActiveRecordTestSetup
|
|
8
|
+
|
|
9
|
+
def test_creates_atomic_column_for_multi_symbol_amount
|
|
10
|
+
assert columns.key?("amount_atomic")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_creates_symbol_column_for_multi_symbol_amount
|
|
14
|
+
assert columns.key?("amount_symbol")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_creates_atomic_column_for_fixed_symbol_amount
|
|
18
|
+
assert columns.key?("fee_atomic")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_creates_no_symbol_column_for_fixed_symbol_amount
|
|
22
|
+
refute columns.key?("fee_symbol")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_atomic_column_precision_defaults_to_seventy_eight
|
|
26
|
+
assert_equal 78, columns.fetch("amount_atomic").precision
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_symbol_column_limit_is_ten_characters
|
|
30
|
+
assert_equal 10, columns.fetch("amount_symbol").limit
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_atomic_column_uses_given_precision
|
|
34
|
+
assert_equal 40, columns.fetch("reserve_atomic").precision
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_default_sets_symbol_column_default
|
|
38
|
+
assert_equal "USDC", columns.fetch("reserve_symbol").default
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_schema_declares_default_atomic_column_as_scale_zero_decimal
|
|
42
|
+
assert_includes schema_sql, "\"amount_atomic\" decimal(78,0)"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_schema_declares_atomic_column_with_given_precision_as_scale_zero_decimal
|
|
46
|
+
assert_includes schema_sql, "\"reserve_atomic\" decimal(40,0)"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def columns
|
|
52
|
+
Holding.columns_hash
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def schema_sql
|
|
56
|
+
::ActiveRecord::Base.connection.select_value(<<~SQL)
|
|
57
|
+
SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'holdings'
|
|
58
|
+
SQL
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../test_helper"
|
|
4
|
+
require_relative "../../support/active_record_test_support"
|
|
5
|
+
|
|
6
|
+
class AmountActiveRecordModelTest < Minitest::Test
|
|
7
|
+
include ActiveRecordTestSetup
|
|
8
|
+
|
|
9
|
+
def test_fixed_symbol_writer_with_wrong_symbol_makes_record_invalid
|
|
10
|
+
refute wrong_symbol_fee_holding.valid?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_fixed_symbol_writer_with_wrong_symbol_reports_error_on_validation
|
|
14
|
+
holding = wrong_symbol_fee_holding
|
|
15
|
+
holding.valid?
|
|
16
|
+
|
|
17
|
+
assert_includes holding.errors[:fee], "expected SOL, got USDC"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_multi_symbol_writer_with_raw_numeric_makes_record_invalid
|
|
21
|
+
refute raw_numeric_amount_holding.valid?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_multi_symbol_writer_with_raw_numeric_reports_error_on_validation
|
|
25
|
+
holding = raw_numeric_amount_holding
|
|
26
|
+
holding.valid?
|
|
27
|
+
|
|
28
|
+
assert_includes holding.errors[:amount], "raw numeric assignment requires a fixed symbol"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def wrong_symbol_fee_holding
|
|
34
|
+
Holding.new.tap { |holding| holding.fee = Amount.new("1", :USDC) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def raw_numeric_amount_holding
|
|
38
|
+
Holding.new.tap { |holding| holding.amount = 5 }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../test_helper"
|
|
4
|
+
require_relative "../../support/active_record_test_support"
|
|
5
|
+
|
|
6
|
+
class AmountActiveRecordTypeTest < Minitest::Test
|
|
7
|
+
include ActiveRecordTestSetup
|
|
8
|
+
|
|
9
|
+
def test_multi_symbol_writer_parses_string_into_atomic_column
|
|
10
|
+
assert_equal 250, holding_with(amount: "USD|2.50").amount_atomic
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_multi_symbol_writer_parses_string_into_symbol_column
|
|
14
|
+
assert_equal "USD", holding_with(amount: "USD|2.50").amount_symbol
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_multi_symbol_writer_loads_atomic_hash
|
|
18
|
+
holding = holding_with(amount: { "atomic" => 42, "symbol" => "USDC" })
|
|
19
|
+
|
|
20
|
+
assert_equal Amount.new(42, :USDC, from: :atomic), holding.amount
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_fixed_symbol_writer_converts_raw_float_into_atomic_column
|
|
24
|
+
assert_equal 1_250_000_000, holding_with(fee: 1.25).fee_atomic
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_fixed_symbol_writer_reads_back_raw_float_as_amount
|
|
28
|
+
assert_equal Amount.new("1.25", :SOL), holding_with(fee: 1.25).fee
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_large_atomic_value_round_trips_through_sqlite
|
|
32
|
+
skip "SQLite stores DECIMAL(78,0) values above 64-bit range imprecisely under ActiveRecord"
|
|
33
|
+
|
|
34
|
+
huge_atomic = 10**30
|
|
35
|
+
holding = Holding.create!(amount: Amount.new(huge_atomic, :SOL, from: :atomic))
|
|
36
|
+
|
|
37
|
+
assert_equal huge_atomic, holding.reload.amount.atomic
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_custom_class_symbol_reads_back_as_registered_class
|
|
41
|
+
assert_instance_of metal_class, metal_record.weight
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_custom_class_symbol_reads_back_atomic_value
|
|
45
|
+
assert_equal 25_000, metal_record.weight.atomic
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def holding_with(**attributes)
|
|
51
|
+
holding = Holding.new
|
|
52
|
+
attributes.each { |name, value| holding.public_send("#{name}=", value) }
|
|
53
|
+
holding
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def metal_class
|
|
57
|
+
@metal_class ||= Class.new(Amount) do
|
|
58
|
+
def self.name
|
|
59
|
+
"MetalAmount"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def metal_record
|
|
65
|
+
Amount.register :METAL, decimals: 4, class: metal_class
|
|
66
|
+
|
|
67
|
+
::ActiveRecord::Schema.define do
|
|
68
|
+
create_table :metal_holdings, force: true do |t|
|
|
69
|
+
t.amount :weight, symbol: :METAL, null: false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
klass = Class.new(::ActiveRecord::Base) do
|
|
74
|
+
self.table_name = "metal_holdings"
|
|
75
|
+
has_amount :weight, symbol: :METAL
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
klass.create!(weight: metal_class.new("2.5", :METAL)).reload
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../test_helper"
|
|
4
|
+
require_relative "../../lib/amount/active_record"
|
|
5
|
+
|
|
6
|
+
class AmountActiveRecordTest < Minitest::Test
|
|
7
|
+
def test_locale_file_is_loaded_once
|
|
8
|
+
Amount::ActiveRecord.install_locale!
|
|
9
|
+
|
|
10
|
+
assert_equal 1, I18n.load_path.count(Amount::ActiveRecord::LOCALE_PATH)
|
|
11
|
+
end
|
|
12
|
+
end
|