measured 2.8.1 → 3.0.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +11 -5
  3. data/.github/workflows/cla.yml +23 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +13 -0
  6. data/Gemfile +2 -0
  7. data/README.md +114 -4
  8. data/cache/volume.json +532 -0
  9. data/dev.yml +1 -2
  10. data/gemfiles/{activesupport-6.0.gemfile → rails-6.0.gemfile} +1 -0
  11. data/gemfiles/{activesupport-6.1.gemfile → rails-6.1.gemfile} +1 -0
  12. data/gemfiles/rails-7.0.gemfile +6 -0
  13. data/gemfiles/rails-edge.gemfile +6 -0
  14. data/lib/measured/measurable.rb +3 -1
  15. data/lib/measured/rails/active_record.rb +130 -0
  16. data/lib/measured/rails/validations.rb +68 -0
  17. data/lib/measured/railtie.rb +12 -0
  18. data/lib/measured/units/volume.rb +2 -0
  19. data/lib/measured/units/weight.rb +1 -1
  20. data/lib/measured/version.rb +1 -1
  21. data/lib/measured.rb +2 -0
  22. data/lib/tapioca/dsl/compilers/measured_rails.rb +110 -0
  23. data/measured.gemspec +5 -0
  24. data/test/internal/app/models/thing.rb +14 -0
  25. data/test/internal/app/models/thing_with_custom_unit_accessor.rb +18 -0
  26. data/test/internal/app/models/thing_with_custom_value_accessor.rb +19 -0
  27. data/test/internal/app/models/validated_thing.rb +45 -0
  28. data/test/internal/config/database.yml +3 -0
  29. data/test/internal/config.ru +9 -0
  30. data/test/internal/db/.gitignore +1 -0
  31. data/test/internal/db/schema.rb +99 -0
  32. data/test/internal/log/.gitignore +1 -0
  33. data/test/measurable_test.rb +4 -0
  34. data/test/rails/active_record_test.rb +433 -0
  35. data/test/rails/validation_test.rb +252 -0
  36. data/test/tapioca/dsl/compilers/measured_rails_test.rb +220 -0
  37. data/test/test_helper.rb +15 -0
  38. data/test/units/volume_test.rb +107 -1
  39. data/test/units/weight_test.rb +3 -1
  40. metadata +80 -7
  41. data/gemfiles/activesupport-5.2.gemfile +0 -5
@@ -0,0 +1,433 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class Measured::Rails::ActiveRecordTest < ActiveSupport::TestCase
5
+ setup do
6
+ reset_db
7
+ end
8
+
9
+ test ".measured raises if called with something that isn't a Measured::Measurable" do
10
+ assert_raises Measured::Rails::Error do
11
+ Thing.measured(Object, :field)
12
+ end
13
+ end
14
+
15
+ test ".measured raises if called with something that isn't a class" do
16
+ assert_raises Measured::Rails::Error do
17
+ Thing.measured(:not_correct, :field)
18
+ end
19
+ end
20
+
21
+ test ".measured raises if you attempt to define a field twice" do
22
+ assert_raises Measured::Rails::Error do
23
+ Thing.measured Measured::Length, :height
24
+ end
25
+ end
26
+
27
+ test ".measured defines a reader for the field" do
28
+ assert_equal length, thing.length
29
+ end
30
+
31
+ test ".measured defines a writer for the field that returns" do
32
+ assert_equal new_length, thing.length=(new_length)
33
+ end
34
+
35
+ test ".measured_fields returns the configuration for all measured fields on the class" do
36
+ expected = {
37
+ length: { class: Measured::Length },
38
+ width: { class: Measured::Length },
39
+ height: { class: Measured::Length },
40
+ volume: { class: Measured::Volume },
41
+ total_weight: { class: Measured::Weight },
42
+ extra_weight: { class: Measured::Weight },
43
+ length_with_max_on_assignment: { max_on_assignment: 500, class: Measured::Length }
44
+ }
45
+
46
+ assert_equal expected, Thing.measured_fields
47
+ end
48
+
49
+ test "reader returns the exact same object if the values are equivalent" do
50
+ thing.length = new_length
51
+ assert_equal new_length.object_id, thing.length.object_id
52
+ end
53
+
54
+ test "reader creates an instance from the _value and _unit columns" do
55
+ thing = Thing.new
56
+ thing.width_value = 23
57
+ thing.width_unit = "ft"
58
+ assert_equal Measured::Length.new(23, :ft), thing.width
59
+ end
60
+
61
+ test "reader creates creating an instance from columns caches the same object" do
62
+ thing = Thing.new
63
+ thing.width_value = 23
64
+ thing.width_unit = "ft"
65
+ assert_equal thing.width.object_id, thing.width.object_id
66
+ end
67
+
68
+ test "reader deals with only the _value column set" do
69
+ thing = Thing.new
70
+ thing.width_value = 23
71
+ assert_nil thing.width
72
+ end
73
+
74
+ test "reader deals with only the _unit column set" do
75
+ thing = Thing.new
76
+ thing.width_unit = "cm"
77
+ assert_nil thing.width
78
+ end
79
+
80
+ test "reader deals with nil-ing out the _value column" do
81
+ thing.width_value = nil
82
+ assert_nil thing.width
83
+ end
84
+
85
+ test "reader deals with nil-ing out the _unit column" do
86
+ thing.width_unit = nil
87
+ assert_nil thing.width
88
+ end
89
+
90
+ test "writer sets the value to nil if it is an incompatible object" do
91
+ thing.length = Object.new
92
+ assert_nil thing.length
93
+ end
94
+
95
+ test "writer assigning nil blanks out the unit and value columns" do
96
+ thing.width = nil
97
+ assert_nil thing.width
98
+ assert_nil thing.width_unit
99
+ assert_nil thing.width_value
100
+ end
101
+
102
+ test "assigning an invalid _unit sets the column but the measurable object is nil" do
103
+ thing.width_unit = "invalid"
104
+ assert_nil thing.width
105
+ assert_equal "invalid", thing.width_unit
106
+ end
107
+
108
+ test "assigning an invalid _unit sets the column but the measurable object is nil and there is validation on the column" do
109
+ validated_thing.length_unit = "invalid"
110
+ validated_thing.valid?
111
+ assert_nil validated_thing.length
112
+ assert_equal "invalid", validated_thing.length_unit
113
+ end
114
+
115
+ test "assigning a valid _unit sets it" do
116
+ thing.width_unit = :mm
117
+ assert_equal thing.width, Measured::Length.new(6, "mm")
118
+ assert_equal "mm", thing.width_unit
119
+ end
120
+
121
+ test "assigning a non-base unit to _unit converts it to its base unit" do
122
+ thing.width_unit = "millimetre"
123
+ assert_equal thing.width, Measured::Length.new(6, "mm")
124
+ assert_equal "mm", thing.width_unit
125
+ end
126
+
127
+ test "building a new object from attributes builds a measured object" do
128
+ thing = Thing.new(length_value: "30", length_unit: "m")
129
+ assert_equal Measured::Length.new(30, :m), thing.length
130
+ end
131
+
132
+ test "building a new object with a measured object assigns the properties" do
133
+ thing = Thing.new(length: new_length)
134
+ assert_equal new_length, thing.length
135
+ assert_equal 20, thing.length_value
136
+ assert_equal "in", thing.length_unit
137
+ end
138
+
139
+ test "assigning attributes updates the measured object" do
140
+ thing.attributes = {length_value: "30", length_unit: "m"}
141
+ assert_equal Measured::Length.new(30, :m), thing.length
142
+ end
143
+
144
+ test "assigning partial attributes updates the measured object" do
145
+ thing.attributes = {length_value: "30"}
146
+ assert_equal Measured::Length.new(30, :cm), thing.length
147
+ end
148
+
149
+ test "assigning the _unit leaves the _value unchanged" do
150
+ thing.total_weight_unit = :lb
151
+ assert_equal thing.total_weight, Measured::Weight.new(200, "lb")
152
+ end
153
+
154
+ test "assigning the _value leaves the _unit unchanged" do
155
+ thing.total_weight_value = "10"
156
+ assert_equal thing.total_weight, Measured::Weight.new(10, :g)
157
+ end
158
+
159
+ test "assigning the _unit to an invalid unit does not raise" do
160
+ thing.total_weight_value = 123
161
+ thing.total_weight_unit = :invalid
162
+ assert_nil thing.total_weight
163
+ end
164
+
165
+ test "save persists the attributes and retrieves an object" do
166
+ thing = Thing.new length: Measured::Length.new(3, :m)
167
+ assert thing.save
168
+ assert_equal 3, thing.length_value
169
+ assert_equal "m", thing.length_unit
170
+ thing.reload
171
+ assert_equal 3, thing.length_value
172
+ assert_equal "m", thing.length_unit
173
+ end
174
+
175
+ test "save pulls attributes from assigned object" do
176
+ thing = Thing.new total_weight_value: "100", total_weight_unit: :lb
177
+ assert thing.save
178
+ thing.reload
179
+ assert_equal 100, thing.total_weight_value
180
+ assert_equal "lb", thing.total_weight_unit
181
+ assert_equal Measured::Weight.new(100, :lb), thing.total_weight
182
+ end
183
+
184
+ test "save succeeds if you assign an invalid unit and there is no validation" do
185
+ thing = Thing.new total_weight_value: "100", total_weight_unit: :invalid
186
+ assert thing.save
187
+ thing.reload
188
+ assert_nil thing.total_weight
189
+ assert_equal 100, thing.total_weight_value
190
+ end
191
+
192
+ test "save fails if you assign an invalid unit and there is validation" do
193
+ thing = validated_thing
194
+ thing.length_unit = "invalid"
195
+ refute thing.save
196
+ assert_nil thing.length
197
+ end
198
+
199
+ test "update sets one then the other" do
200
+ thing = Thing.create!
201
+ assert thing.update(width_value: 11.1)
202
+ assert_nil thing.width
203
+ assert thing.update(width_unit: "cm")
204
+ assert_equal Measured::Length.new(11.1, :cm), thing.width
205
+ end
206
+
207
+ test "update sets only the _value column" do
208
+ thing = Thing.create!
209
+ assert thing.update(width_value: "314")
210
+ assert_equal 314, thing.width_value
211
+ thing.reload
212
+ assert_equal 314, thing.width_value
213
+ assert_nil thing.width
214
+ end
215
+
216
+ test "update sets only the _unit column" do
217
+ thing = Thing.create!
218
+ assert thing.update(width_unit: :cm)
219
+ assert_equal "cm", thing.width_unit
220
+ thing.reload
221
+ assert_equal "cm", thing.width_unit
222
+ assert_nil thing.width
223
+ end
224
+
225
+ test "update sets only the _unit column and converts it" do
226
+ thing = Thing.create!
227
+ assert thing.update(width_unit: "inch")
228
+ assert_equal "in", thing.width_unit
229
+ thing.reload
230
+ assert_equal "in", thing.width_unit
231
+ end
232
+
233
+ test "update sets the _unit column to something invalid" do
234
+ thing = Thing.create!
235
+ assert thing.update(width_unit: :invalid)
236
+ assert_equal "invalid", thing.width_unit
237
+ thing.reload
238
+ assert_equal "invalid", thing.width_unit
239
+ assert_nil thing.width
240
+ end
241
+
242
+ test "update does not set the _unit column to something invalid if there is validation" do
243
+ thing = validated_thing
244
+ thing.save!
245
+ refute thing.update(length_unit: :invalid)
246
+ end
247
+
248
+ test "update sets one column then the other" do
249
+ thing = Thing.create!
250
+ assert thing.update(width_unit: "inch")
251
+ assert_nil thing.width
252
+ assert thing.update(width_value: "314")
253
+ assert_equal Measured::Length.new(314, :in), thing.width
254
+ end
255
+
256
+ test "update sets both columns" do
257
+ thing = Thing.create!
258
+ assert thing.update(width_unit: :cm, width_value: 2)
259
+ assert_equal Measured::Length.new(2, :cm), thing.width
260
+ thing.reload
261
+ assert_equal Measured::Length.new(2, :cm), thing.width
262
+ end
263
+
264
+ test "update modifies the _value column" do
265
+ assert thing.update(height_value: 2)
266
+ assert_equal Measured::Length.new(2, :m), thing.height
267
+ thing.reload
268
+ assert_equal Measured::Length.new(2, :m), thing.height
269
+ end
270
+
271
+ test "update modifies only the _unit column" do
272
+ assert thing.update(height_unit: "foot")
273
+ assert_equal Measured::Length.new(1, :ft), thing.height
274
+ thing.reload
275
+ assert_equal Measured::Length.new(1, :ft), thing.height
276
+ end
277
+
278
+ test "update modifies the _unit column to be something invalid" do
279
+ assert thing.update(height_unit: :invalid)
280
+ assert_nil thing.height
281
+ assert_equal "invalid", thing.height_unit
282
+ thing.reload
283
+ assert_nil thing.height
284
+ assert_equal "invalid", thing.height_unit
285
+ end
286
+
287
+ test "update modifies both columns" do
288
+ assert thing.update(height_unit: "mm", height_value: 1.23)
289
+ assert_equal Measured::Length.new(1.23, :mm), thing.height
290
+ thing.reload
291
+ assert_equal Measured::Length.new(1.23, :mm), thing.height
292
+ end
293
+
294
+ test "assigning the _value with a BigDecimal rounds to the column's rounding scale" do
295
+ thing.height = Measured::Length.new(BigDecimal('23.4567891'), :mm)
296
+ assert_equal thing.height_value, BigDecimal('23.46')
297
+ end
298
+
299
+ test "assigning the _value with a float uses all the rounding scale permissible" do
300
+ thing.height = Measured::Length.new(4.45678912, :mm)
301
+ assert_equal thing.height_value, BigDecimal('4.46')
302
+ end
303
+
304
+ test "assigning a number with more significant digits than permitted by the column precision does not raise exception when it can be rounded to have lesser significant digits per column's scale" do
305
+ assert_nothing_raised do
306
+ thing.height = Measured::Length.new(4.45678912123123123, :mm)
307
+ assert_equal thing.height_value, BigDecimal('4.46')
308
+ end
309
+ end
310
+
311
+ test "assigning a number with more significant digits than permitted by the column precision raises exception" do
312
+ assert_raises Measured::Rails::Error, "The value 44567891212312312.3 being set for column: 'height' has too many significant digits. Please ensure it has no more than 10 significant digits." do
313
+ thing.height = Measured::Length.new(44567891212312312.3, :mm)
314
+ end
315
+ end
316
+
317
+ test "assigning a large number but with a small amount of significant digits than permitted by the column precision raises exception" do
318
+ assert_raises Measured::Rails::Error, "The value 2000000000000000.0 being set for column: 'height' has too many significant digits. Please ensure it has no more than 10 significant digits." do
319
+ thing.height = Measured::Length.new(2_000_000_000_000_000, :mm)
320
+ end
321
+ end
322
+
323
+ test "assigning a large number that's just smaller, equal to, and over the size of the column precision raises exception" do
324
+ assert_nothing_raised do
325
+ thing.height = Measured::Length.new(99999999.99, :mm)
326
+ end
327
+
328
+ assert_raises Measured::Rails::Error, "The value 100000000.0 being set for column: 'height' has too many significant digits. Please ensure it has no more than 10 significant digits." do
329
+ thing.height = Measured::Length.new(100000000, :mm)
330
+ end
331
+
332
+ assert_raises Measured::Rails::Error, "The value 100000000.01 being set for column: 'height' has too many significant digits. Please ensure it has no more than 10 significant digits." do
333
+ thing.height = Measured::Length.new(100000000.01, :mm)
334
+ end
335
+ end
336
+
337
+ test "assigning a large number to a field that specifies max_on_assignment" do
338
+ thing = Thing.create!(length_with_max_on_assignment: Measured::Length.new(10000000000000000, :mm))
339
+ assert_equal Measured::Length.new(500, :mm), thing.length_with_max_on_assignment
340
+ end
341
+
342
+ test "assigning a small number to a field that specifies max_on_assignment" do
343
+ thing = Thing.create!(length_with_max_on_assignment: Measured::Length.new(1, :mm))
344
+ assert_equal Measured::Length.new(1, :mm), thing.length_with_max_on_assignment
345
+ end
346
+
347
+ test "using a similar unit accessor for multiple size fields" do
348
+ assert_equal Measured::Length.new(1, :m), custom_unit_thing.length
349
+ assert_equal Measured::Length.new(2, :m), custom_unit_thing.width
350
+ assert_equal Measured::Length.new(3, :m), custom_unit_thing.height
351
+ assert_equal Measured::Volume.new(9, :l), custom_unit_thing.volume
352
+ assert_equal Measured::Weight.new(10, :g), custom_unit_thing.total_weight
353
+ assert_equal Measured::Weight.new(12, :g), custom_unit_thing.extra_weight
354
+ end
355
+
356
+ test "changing unit value when shared affects all fields" do
357
+ custom_unit_thing.length = Measured::Length.new(15, :in)
358
+ custom_unit_thing.total_weight = Measured::Weight.new(42, :kg)
359
+
360
+ assert_equal custom_unit_thing.length, Measured::Length.new(15, :in)
361
+ assert_equal custom_unit_thing.width, Measured::Length.new(2, :in)
362
+ assert_equal custom_unit_thing.height, Measured::Length.new(3, :in)
363
+ assert_equal custom_unit_thing.total_weight, Measured::Weight.new(42, :kg)
364
+ assert_equal custom_unit_thing.extra_weight, Measured::Weight.new(12, :kg)
365
+ end
366
+
367
+ test "using custom value fields works correctly" do
368
+ assert_equal custom_value_thing.length, Measured::Length.new(4, :m)
369
+ assert_equal custom_value_thing.width, Measured::Length.new(5, :m)
370
+ assert_equal custom_value_thing.height, Measured::Length.new(6, :m)
371
+ assert_equal custom_value_thing.volume, Measured::Volume.new(13, :l)
372
+ assert_equal custom_value_thing.total_weight, Measured::Weight.new(14, :g)
373
+ assert_equal custom_value_thing.extra_weight, Measured::Weight.new(15, :g)
374
+ end
375
+
376
+ private
377
+
378
+ def length
379
+ @length ||= Measured::Length.new(10, :cm)
380
+ end
381
+
382
+ def new_length
383
+ @new_length ||= Measured::Length.new(20, :in)
384
+ end
385
+
386
+ def thing
387
+ @thing ||= Thing.create!(
388
+ length: length,
389
+ width: Measured::Length.new(6, :in),
390
+ volume: Measured::Volume.new(6, :l),
391
+ height: Measured::Length.new(1, :m),
392
+ total_weight: Measured::Weight.new(200, :g),
393
+ extra_weight: Measured::Weight.new(16, :oz)
394
+ )
395
+ end
396
+
397
+ def validated_thing
398
+ @thing ||= ValidatedThing.new(
399
+ length: Measured::Length.new(1, :m),
400
+ length_true: Measured::Length.new(2, :cm),
401
+ length_message: Measured::Length.new(3, :mm),
402
+ length_message_from_block: Measured::Length.new(7, :mm),
403
+ length_units: Measured::Length.new(4, :m),
404
+ length_units_singular: Measured::Length.new(5, :ft),
405
+ length_presence: Measured::Length.new(6, :m),
406
+ length_numericality_inclusive: Measured::Length.new(15, :in),
407
+ length_numericality_exclusive: Measured::Length.new(4, :m),
408
+ length_numericality_equality: Measured::Length.new(100, :cm),
409
+ )
410
+ end
411
+
412
+ def custom_unit_thing
413
+ @custom_unit_thing ||= ThingWithCustomUnitAccessor.new(
414
+ length: Measured::Length.new(1, :m),
415
+ width: Measured::Length.new(2, :m),
416
+ height: Measured::Length.new(3, :m),
417
+ volume: Measured::Volume.new(9, :l),
418
+ total_weight: Measured::Weight.new(10, :g),
419
+ extra_weight: Measured::Weight.new(12, :g),
420
+ )
421
+ end
422
+
423
+ def custom_value_thing
424
+ @custom_value_thing ||= ThingWithCustomValueAccessor.new(
425
+ length: Measured::Length.new(4, :m),
426
+ width: Measured::Length.new(5, :m),
427
+ height: Measured::Length.new(6, :m),
428
+ volume: Measured::Volume.new(13, :l),
429
+ total_weight: Measured::Weight.new(14, :g),
430
+ extra_weight: Measured::Weight.new(15, :g),
431
+ )
432
+ end
433
+ end
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+ require "test_helper"
3
+
4
+ class Measured::Rails::ValidationTest < ActiveSupport::TestCase
5
+ setup do
6
+ reset_db
7
+ end
8
+
9
+ test "validation mock is valid" do
10
+ assert thing.valid?
11
+ end
12
+
13
+ test "validation measurable: validation leaves a model valid and deals with blank unit" do
14
+ assert ValidatedThing.new(length_presence: Measured::Length.new(4, :in)).valid?
15
+ end
16
+
17
+ test "validation fails when unit is nil" do
18
+ thing.length_unit = ''
19
+ refute thing.valid?
20
+ assert_equal({ length: ["is not a valid unit"] }, thing.errors.to_hash)
21
+ end
22
+
23
+ test "validation fails on model with custom unit with nil value" do
24
+ custom_unit_thing.size_unit = ''
25
+ refute custom_unit_thing.valid?
26
+ assert_equal(
27
+ {
28
+ length: ["is not a valid unit"],
29
+ width: ["is not a valid unit"],
30
+ height: ["is not a valid unit"],
31
+ },
32
+ custom_unit_thing.errors.to_hash
33
+ )
34
+ end
35
+
36
+ test "validation true works by default" do
37
+ thing.length_unit = "junk"
38
+ refute thing.valid?
39
+ assert_equal ["Length is not a valid unit"], thing.errors.full_messages
40
+ end
41
+
42
+ test "validation can override the message with a static string" do
43
+ thing.length_message_unit = "junk"
44
+ refute thing.valid?
45
+ assert_equal ["Length message has a custom failure message"], thing.errors.full_messages
46
+ end
47
+
48
+ test "validation can override the message with a block" do
49
+ thing.length_message_from_block_unit = "junk"
50
+ refute thing.valid?
51
+ assert_equal ["Length message from block junk is not a valid unit"], thing.errors.full_messages
52
+ end
53
+
54
+ test "validation may be any valid unit" do
55
+ length_units.each do |unit|
56
+ thing.length_unit = unit
57
+ assert thing.valid?
58
+ thing.length_unit = unit.to_s
59
+ assert thing.valid?
60
+ thing.length = Measured::Length.new(123, unit)
61
+ assert thing.valid?
62
+ end
63
+ end
64
+
65
+ test "validation accepts a list of units in any format as an option and only allows them to be valid" do
66
+ thing.length_units_unit = :m
67
+ assert thing.valid?
68
+ thing.length_units_unit = :cm
69
+ assert thing.valid?
70
+ thing.length_units_unit = "cm"
71
+ assert thing.valid?
72
+ thing.length_units_unit = "meter"
73
+ assert thing.valid?
74
+ thing.length_units = Measured::Length.new(3, :cm)
75
+ assert thing.valid?
76
+ thing.length_units_unit = :mm
77
+ refute thing.valid?
78
+ thing.length_units = Measured::Length.new(3, :ft)
79
+ refute thing.valid?
80
+ end
81
+
82
+ test "validation lets the unit be singular" do
83
+ thing.length_units_singular_unit = :ft
84
+ assert thing.valid?
85
+ thing.length_units_singular_unit = "feet"
86
+ assert thing.valid?
87
+ thing.length_units_singular_unit = :mm
88
+ refute thing.valid?
89
+ thing.length_units_singular_unit = "meter"
90
+ refute thing.valid?
91
+ end
92
+
93
+ test "validation for unit reasons uses the default message" do
94
+ thing.length_units_unit = :mm
95
+ refute thing.valid?
96
+ assert_equal ["Length units is not a valid unit"], thing.errors.full_messages
97
+ end
98
+
99
+ test "validation for unit reasons also uses the custom message" do
100
+ thing.length_units_singular_unit = :mm
101
+ refute thing.valid?
102
+ assert_equal ["Length units singular custom message too"], thing.errors.full_messages
103
+ end
104
+
105
+ test "validation for unit reasons adds one message if unit is not supported by default and is not custom supported" do
106
+ thing.length_units_singular_unit = :t
107
+ refute thing.valid?
108
+
109
+ assert_equal ["Length units singular custom message too"], thing.errors.full_messages
110
+ end
111
+
112
+ test "validation presence works on measured columns" do
113
+ thing.length_presence = nil
114
+ refute thing.valid?
115
+ assert_equal ["Length presence can't be blank"], thing.errors.full_messages
116
+ thing.length_presence_unit = "m"
117
+ refute thing.valid?
118
+ thing.length_presence_value = "3"
119
+ assert thing.valid?
120
+ end
121
+
122
+ test "validation fails if only only the value is set" do
123
+ thing.length_unit = nil
124
+ refute thing.valid?
125
+ end
126
+
127
+ test "validation checks that numericality comparisons are against a Measurable subclass" do
128
+ thing.length_invalid_comparison = Measured::Length.new(30, :in)
129
+ assert_raises ArgumentError, ":not_a_measured_subclass must be a Measurable object" do
130
+ thing.valid?
131
+ end
132
+ end
133
+
134
+ test "validation for numericality uses a default invalid message" do
135
+ thing.length_numericality_inclusive = Measured::Length.new(30, :in)
136
+ refute thing.valid?
137
+ assert_equal ["Length numericality inclusive 30 in must be <= 20 in"], thing.errors.full_messages
138
+
139
+ thing.length_numericality_inclusive = Measured::Length.new(1, :mm)
140
+ refute thing.valid?
141
+ assert_equal ["Length numericality inclusive 1 mm must be >= 10 in"], thing.errors.full_messages
142
+ end
143
+
144
+ test "validation for numericality uses the override message" do
145
+ thing.length_numericality_exclusive = Measured::Length.new(2, :m)
146
+ refute thing.valid?
147
+ assert_equal ["Length numericality exclusive is super not ok"], thing.errors.full_messages
148
+
149
+ thing.length_numericality_exclusive = Measured::Length.new(6000, :mm)
150
+ refute thing.valid?
151
+ assert_equal ["Length numericality exclusive is super not ok"], thing.errors.full_messages
152
+ end
153
+
154
+ test "validation for numericality checks :greater_than and :less_than and can use symbols as method names to look up values" do
155
+ thing.length_numericality_exclusive = Measured::Length.new(4, :m)
156
+ assert thing.valid?
157
+
158
+ thing.length_numericality_exclusive = Measured::Length.new(1, :m)
159
+ refute thing.valid?
160
+ end
161
+
162
+ test "validation for numericality checks :greater_than_or_equal_to and :less_than_or_equal_to" do
163
+ thing.length_numericality_inclusive = Measured::Length.new(10, :in)
164
+ assert thing.valid?
165
+
166
+ thing.length_numericality_exclusive = Measured::Length.new(3, :m)
167
+ refute thing.valid?
168
+ end
169
+
170
+ test "validation for numericality checks :equal_to and can use procs to look up values" do
171
+ thing.length_numericality_equality = Measured::Length.new(100, :cm)
172
+ assert thing.valid?
173
+
174
+ thing.length_numericality_equality = Measured::Length.new(1, :m)
175
+ assert thing.valid?
176
+
177
+ thing.length_numericality_equality = Measured::Length.new("99.9", :cm)
178
+ refute thing.valid?
179
+
180
+ thing.length_numericality_equality = Measured::Length.new(101, :cm)
181
+ refute thing.valid?
182
+ end
183
+
184
+ test "validation for numericality handles a nil unit but a valid value" do
185
+ thing.length_numericality_exclusive_unit = nil
186
+ thing.length_numericality_exclusive_value = 1
187
+ refute thing.valid?
188
+ end
189
+
190
+ test "allow a nil value but a valid unit" do
191
+ thing.length_numericality_exclusive_unit = :cm
192
+ thing.length_numericality_exclusive_value = nil
193
+ assert thing.valid?
194
+ end
195
+
196
+ test "validations work as expected given a measured field with custom validators" do
197
+ assert custom_unit_thing.valid?
198
+
199
+ assert custom_unit_thing.size_unit = 'invalid'
200
+
201
+ refute custom_unit_thing.valid?
202
+
203
+ assert_equal(custom_unit_thing.errors[:length], ["is not a valid unit"])
204
+ assert_equal(custom_unit_thing.errors[:height], ["is not a valid unit"])
205
+ assert_equal(custom_unit_thing.errors[:width], ["is not a valid unit"])
206
+ end
207
+
208
+ test "validations work as expected on measured field with custom value accessor" do
209
+ assert custom_value_thing.valid?
210
+ end
211
+
212
+ private
213
+
214
+ def thing
215
+ @thing ||= ValidatedThing.new(
216
+ length: Measured::Length.new(1, :m),
217
+ length_true: Measured::Length.new(2, :cm),
218
+ length_message: Measured::Length.new(3, :mm),
219
+ length_message_from_block: Measured::Length.new(7, :mm),
220
+ length_units: Measured::Length.new(4, :m),
221
+ length_units_singular: Measured::Length.new(5, :ft),
222
+ length_presence: Measured::Length.new(6, :m),
223
+ length_numericality_inclusive: Measured::Length.new(15, :in),
224
+ length_numericality_exclusive: Measured::Length.new(4, :m),
225
+ length_numericality_equality: Measured::Length.new(100, :cm),
226
+ )
227
+ end
228
+
229
+ def custom_unit_thing
230
+ @custom_unit_thing ||= ThingWithCustomUnitAccessor.new(
231
+ length: Measured::Length.new(1, :m),
232
+ width: Measured::Length.new(2, :m),
233
+ height: Measured::Length.new(3, :m),
234
+ total_weight: Measured::Weight.new(10, :g),
235
+ extra_weight: Measured::Weight.new(12, :g),
236
+ )
237
+ end
238
+
239
+ def custom_value_thing
240
+ @custom_value_thing = ThingWithCustomValueAccessor.new(
241
+ length: Measured::Length.new(1, :m),
242
+ width: Measured::Length.new(2, :m),
243
+ height: Measured::Length.new(3, :m),
244
+ total_weight: Measured::Weight.new(10, :g),
245
+ extra_weight: Measured::Weight.new(12, :g),
246
+ )
247
+ end
248
+
249
+ def length_units
250
+ @length_units ||= [:m, :meter, :cm, :mm, :millimeter, :in, :ft, :feet, :yd]
251
+ end
252
+ end