measured 2.8.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) 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 +8 -0
  6. data/Gemfile +2 -0
  7. data/README.md +114 -4
  8. data/dev.yml +1 -2
  9. data/gemfiles/{activesupport-6.0.gemfile → rails-6.0.gemfile} +1 -0
  10. data/gemfiles/{activesupport-6.1.gemfile → rails-6.1.gemfile} +1 -0
  11. data/gemfiles/rails-7.0.gemfile +6 -0
  12. data/gemfiles/rails-edge.gemfile +6 -0
  13. data/lib/measured/measurable.rb +3 -1
  14. data/lib/measured/rails/active_record.rb +130 -0
  15. data/lib/measured/rails/validations.rb +68 -0
  16. data/lib/measured/railtie.rb +12 -0
  17. data/lib/measured/units/weight.rb +1 -1
  18. data/lib/measured/version.rb +1 -1
  19. data/lib/measured.rb +2 -0
  20. data/lib/tapioca/dsl/compilers/measured_rails.rb +110 -0
  21. data/measured.gemspec +5 -0
  22. data/test/internal/app/models/thing.rb +14 -0
  23. data/test/internal/app/models/thing_with_custom_unit_accessor.rb +18 -0
  24. data/test/internal/app/models/thing_with_custom_value_accessor.rb +19 -0
  25. data/test/internal/app/models/validated_thing.rb +45 -0
  26. data/test/internal/config/database.yml +3 -0
  27. data/test/internal/config.ru +9 -0
  28. data/test/internal/db/.gitignore +1 -0
  29. data/test/internal/db/schema.rb +99 -0
  30. data/test/internal/log/.gitignore +1 -0
  31. data/test/measurable_test.rb +4 -0
  32. data/test/rails/active_record_test.rb +433 -0
  33. data/test/rails/validation_test.rb +252 -0
  34. data/test/tapioca/dsl/compilers/measured_rails_test.rb +220 -0
  35. data/test/test_helper.rb +15 -0
  36. data/test/units/weight_test.rb +3 -1
  37. metadata +80 -7
  38. data/gemfiles/activesupport-5.2.gemfile +0 -5
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ class ThingWithCustomUnitAccessor < ActiveRecord::Base
3
+ measured_length :length, :width, unit_field_name: :size_unit
4
+ validates :length, measured: true
5
+ validates :width, measured: true
6
+
7
+ measured_volume :volume
8
+ validates :volume, measured: true
9
+
10
+ measured Measured::Length, :height, unit_field_name: :size_unit
11
+ validates :height, measured: true
12
+
13
+ measured_weight :total_weight, unit_field_name: :weight_unit
14
+ validates :total_weight, measured: true
15
+
16
+ measured "Measured::Weight", :extra_weight, unit_field_name: :weight_unit
17
+ validates :extra_weight, measured: true
18
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ class ThingWithCustomValueAccessor < ActiveRecord::Base
3
+ measured_length :length, value_field_name: :custom_length
4
+ validates :length, measured: true
5
+ measured_length :width, value_field_name: :custom_width
6
+ validates :width, measured: true
7
+
8
+ measured_volume :volume, value_field_name: :custom_volume
9
+ validates :volume, measured: true
10
+
11
+ measured_length :height, value_field_name: :custom_height
12
+ validates :height, measured: true
13
+
14
+ measured_weight :total_weight, value_field_name: :custom_weight
15
+ validates :total_weight, measured: true
16
+
17
+ measured_weight :extra_weight, value_field_name: :custom_extra_weight
18
+ validates :extra_weight, measured: true
19
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ class ValidatedThing < ActiveRecord::Base
3
+ measured_length :length
4
+ validates :length, measured: true
5
+
6
+ measured_length :length_true
7
+ validates :length_true, measured: true
8
+
9
+ measured_length :length_message
10
+ validates :length_message, measured: {message: "has a custom failure message"}
11
+
12
+ measured_length :length_message_from_block
13
+ validates :length_message_from_block, measured: { message: Proc.new { |record| "#{record.length_message_from_block_unit} is not a valid unit" } }
14
+
15
+ measured_length :length_units
16
+ validates :length_units, measured: {units: [:meter, "cm"]}
17
+
18
+ measured_length :length_units_singular
19
+ validates :length_units_singular, measured: {units: :ft, message: "custom message too"}
20
+
21
+ measured_length :length_presence
22
+ validates :length_presence, measured: true, presence: true
23
+
24
+ measured_length :length_numericality_inclusive
25
+ validates :length_numericality_inclusive, measured: {greater_than_or_equal_to: :low_bound, less_than_or_equal_to: :high_bound }
26
+
27
+ measured_length :length_numericality_exclusive
28
+ validates :length_numericality_exclusive, measured: {greater_than: Measured::Length.new(3, :m), less_than: Measured::Length.new(500, :cm), message: "is super not ok"}
29
+
30
+ measured_length :length_numericality_equality
31
+ validates :length_numericality_equality, measured: {equal_to: Proc.new { Measured::Length.new(100, :cm) }, message: "must be exactly 100cm"}
32
+
33
+ measured_length :length_invalid_comparison
34
+ validates :length_invalid_comparison, measured: {equal_to: "not_a_measured_subclass"}
35
+
36
+ private
37
+
38
+ def low_bound
39
+ Measured::Length.new(10, :in)
40
+ end
41
+
42
+ def high_bound
43
+ Measured::Length.new(20, :in)
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: ":memory:"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "bundler"
5
+
6
+ Bundler.require :default, :development
7
+
8
+ Combustion.initialize! :all
9
+ run Combustion::Application
@@ -0,0 +1 @@
1
+ measured.sqlite*
@@ -0,0 +1,99 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # Note that this schema.rb definition is the authoritative source for your
6
+ # database schema. If you need to create the application database on another
7
+ # system, you should be using db:schema:load, not running all the migrations
8
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define do
14
+ create_table "thing_with_custom_unit_accessors", force: :cascade do |t|
15
+ t.decimal "length_value", precision: 10, scale: 2
16
+ t.decimal "width_value", precision: 10, scale: 2
17
+ t.decimal "height_value", precision: 10, scale: 2
18
+ t.decimal "volume_value", precision: 10, scale: 2
19
+ t.string "volume_unit", limit: 12
20
+ t.string "size_unit", limit: 12
21
+ t.decimal "total_weight_value", precision: 10, scale: 2, default: "10.0"
22
+ t.decimal "extra_weight_value", precision: 10, scale: 2
23
+ t.string "weight_unit", limit: 12
24
+ t.datetime "created_at", null: false
25
+ t.datetime "updated_at", null: false
26
+ end
27
+
28
+ create_table "things", force: :cascade do |t|
29
+ t.decimal "length_value", precision: 10, scale: 2
30
+ t.string "length_unit", limit: 12
31
+ t.decimal "width_value", precision: 10, scale: 2
32
+ t.string "width_unit", limit: 12
33
+ t.decimal "height_value", precision: 10, scale: 2
34
+ t.string "height_unit", limit: 12
35
+ t.decimal "volume_value", precision: 10, scale: 2
36
+ t.string "volume_unit", limit: 12
37
+ t.decimal "total_weight_value", precision: 10, scale: 2, default: "10.0"
38
+ t.string "total_weight_unit", limit: 12, default: "g"
39
+ t.decimal "extra_weight_value", precision: 10, scale: 2
40
+ t.string "extra_weight_unit", limit: 12
41
+ t.decimal "length_with_max_on_assignment_value", precision: 10, scale: 2
42
+ t.string "length_with_max_on_assignment_unit", limit: 12
43
+ t.datetime "created_at", null: false
44
+ t.datetime "updated_at", null: false
45
+ end
46
+
47
+ create_table "validated_things", force: :cascade do |t|
48
+ t.decimal "length_value", precision: 10, scale: 2
49
+ t.string "length_unit", limit: 12
50
+ t.decimal "length_true_value", precision: 10, scale: 2
51
+ t.string "length_true_unit", limit: 12
52
+ t.decimal "length_message_value", precision: 10, scale: 2
53
+ t.string "length_message_unit", limit: 12
54
+ t.decimal "length_message_from_block_value", precision: 10, scale: 2
55
+ t.string "length_message_from_block_unit", limit: 12
56
+ t.decimal "length_units_value", precision: 10, scale: 2
57
+ t.string "length_units_unit", limit: 12
58
+ t.decimal "length_units_singular_value", precision: 10, scale: 2
59
+ t.string "length_units_singular_unit", limit: 12
60
+ t.decimal "length_presence_value", precision: 10, scale: 2
61
+ t.string "length_presence_unit", limit: 12
62
+ t.decimal "length_invalid_value", precision: 10, scale: 2
63
+ t.string "length_invalid_unit", limit: 12
64
+ t.datetime "created_at", null: false
65
+ t.datetime "updated_at", null: false
66
+ t.decimal "length_numericality_inclusive_value", precision: 10, scale: 2
67
+ t.string "length_numericality_inclusive_unit", limit: 12
68
+ t.decimal "length_numericality_exclusive_value", precision: 10, scale: 2
69
+ t.string "length_numericality_exclusive_unit", limit: 12
70
+ t.decimal "length_numericality_equality_value", precision: 10, scale: 2
71
+ t.string "length_numericality_equality_unit", limit: 12
72
+ t.decimal "length_invalid_comparison_value", precision: 10, scale: 2
73
+ t.string "length_invalid_comparison_unit", limit: 12
74
+ t.decimal "length_non_zero_scalar_value", precision: 10, scale: 2
75
+ t.string "length_non_zero_scalar_unit", limit: 12
76
+ t.decimal "length_zero_scalar_value", precision: 10, scale: 2
77
+ t.string "length_zero_scalar_unit", limit: 12
78
+ t.decimal "length_numericality_less_than_than_scalar_value", precision: 10, scale: 2
79
+ t.string "length_numericality_less_than_than_scalar_unit", limit: 12
80
+ end
81
+
82
+ create_table "thing_with_custom_value_accessors", force: :cascade do |t|
83
+ t.decimal "custom_length", precision: 10, scale: 2
84
+ t.string "length_unit", limit: 12
85
+ t.decimal "custom_width", precision: 10, scale: 2
86
+ t.string "width_unit", limit: 12
87
+ t.decimal "custom_height", precision: 10, scale: 2
88
+ t.string "height_unit", limit: 12
89
+ t.decimal "custom_volume", precision: 10, scale: 2
90
+ t.string "volume_unit", limit: 12
91
+ t.decimal "custom_weight", precision: 10, scale: 2, default: "10.0"
92
+ t.string "total_weight_unit", limit: 12
93
+ t.decimal "custom_extra_weight", precision: 10, scale: 2
94
+ t.string "extra_weight_unit", limit: 12
95
+ t.datetime "created_at", null: false
96
+ t.datetime "updated_at", null: false
97
+ end
98
+
99
+ end
@@ -0,0 +1 @@
1
+ *.log
@@ -41,6 +41,10 @@ class Measured::MeasurableTest < ActiveSupport::TestCase
41
41
  assert_equal BigDecimal("9.1234572342342"), Magic.new("9.1234572342342", :fire).value
42
42
  end
43
43
 
44
+ test "#initialize converts strings to Rational if they follow Rational pattern" do
45
+ assert_equal Rational(1, 3), Magic.new("1/3", :fire).value
46
+ end
47
+
44
48
  test "#initialize converts to the base unit" do
45
49
  assert_equal @fireball, Magic.new(1, :fire).unit
46
50
  end
@@ -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