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
data/dev.yml CHANGED
@@ -1,8 +1,7 @@
1
1
  name: measured
2
2
 
3
3
  up:
4
- - ruby:
5
- version: 3.0.2
4
+ - ruby
6
5
  - bundler
7
6
 
8
7
  commands:
@@ -3,3 +3,4 @@ source 'https://rubygems.org'
3
3
  gemspec path: '..'
4
4
 
5
5
  gem 'activesupport', '~> 6.0'
6
+ gem "activerecord", '~> 6.0'
@@ -3,3 +3,4 @@ source 'https://rubygems.org'
3
3
  gemspec path: '..'
4
4
 
5
5
  gem 'activesupport', '~> 6.1'
6
+ gem "activerecord", '~> 6.1'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '..'
4
+
5
+ gem 'activesupport', '~> 7.0'
6
+ gem 'activerecord', '~> 7.0'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '..'
4
+
5
+ gem 'activesupport', github: 'rails/rails', branch: 'main'
6
+ gem 'activerecord', github: 'rails/rails', branch: 'main'
@@ -17,6 +17,8 @@ class Measured::Measurable < Numeric
17
17
  value
18
18
  when Integer
19
19
  Rational(value)
20
+ when String
21
+ /\d+\/\d+/.match?(value) ? Rational(value) : BigDecimal(value)
20
22
  else
21
23
  BigDecimal(value)
22
24
  end
@@ -30,7 +32,7 @@ class Measured::Measurable < Numeric
30
32
  else
31
33
  value.to_f.to_s
32
34
  end
33
- str.gsub(/\.0*\Z/, "")
35
+ /\.0*\Z/.match?(str) ? str.gsub(/\.0*\Z/, "") : str
34
36
  end.freeze
35
37
  end
36
38
 
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Measured::Rails::ActiveRecord
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def measured(measured_class, *fields)
8
+ options = fields.extract_options!
9
+ options = {}.merge(options)
10
+
11
+ measured_class = measured_class.constantize if measured_class.is_a?(String)
12
+ unless measured_class.is_a?(Class) && measured_class.ancestors.include?(Measured::Measurable)
13
+ raise Measured::Rails::Error, "Expecting #{ measured_class } to be a subclass of Measured::Measurable"
14
+ end
15
+
16
+ options[:class] = measured_class
17
+
18
+ fields.map(&:to_sym).each do |field|
19
+ raise Measured::Rails::Error, "The field #{ field } has already been measured" if measured_fields.key?(field)
20
+
21
+ measured_fields[field] = options
22
+
23
+ unit_field_name = if options[:unit_field_name]
24
+ measured_fields[field][:unit_field_name] = options[:unit_field_name].to_s
25
+ else
26
+ "#{ field }_unit"
27
+ end
28
+
29
+ value_field_name = if options[:value_field_name]
30
+ measured_fields[field][:value_field_name] = options[:value_field_name].to_s
31
+ else
32
+ "#{ field }_value"
33
+ end
34
+
35
+ # Reader to retrieve measured object
36
+ define_method(field) do
37
+ value = public_send(value_field_name)
38
+ unit = public_send(unit_field_name)
39
+
40
+ return nil unless value && unit
41
+
42
+ instance = instance_variable_get("@measured_#{ field }") if instance_variable_defined?("@measured_#{ field }")
43
+ new_instance = begin
44
+ measured_class.new(value, unit)
45
+ rescue Measured::UnitError
46
+ nil
47
+ end
48
+
49
+ if instance == new_instance
50
+ instance
51
+ else
52
+ instance_variable_set("@measured_#{ field }", new_instance)
53
+ end
54
+ end
55
+
56
+ # Writer to assign measured object
57
+ define_method("#{ field }=") do |incoming|
58
+ if incoming.is_a?(measured_class)
59
+ instance_variable_set("@measured_#{ field }", incoming)
60
+ precision = self.column_for_attribute(value_field_name).precision
61
+ scale = self.column_for_attribute(value_field_name).scale
62
+ rounded_to_scale_value = incoming.value.round(scale)
63
+
64
+ max = self.class.measured_fields[field][:max_on_assignment]
65
+ if max && rounded_to_scale_value > max
66
+ rounded_to_scale_value = max
67
+ elsif rounded_to_scale_value.to_i.to_s.length > (precision - scale)
68
+ raise Measured::Rails::Error, "The value #{rounded_to_scale_value} being set for column '#{value_field_name}' has too many significant digits. Please ensure it has no more than #{precision - scale} significant digits."
69
+ end
70
+
71
+ public_send("#{ value_field_name }=", rounded_to_scale_value)
72
+ public_send("#{ unit_field_name }=", incoming.unit.name)
73
+ else
74
+ instance_variable_set("@measured_#{ field }", nil)
75
+ public_send("#{ value_field_name}=", nil)
76
+ public_send("#{ unit_field_name }=", nil)
77
+ end
78
+ end
79
+
80
+ # Writer to override unit assignment
81
+ redefine_method("#{ unit_field_name }=") do |incoming|
82
+ unit_name = measured_class.unit_system.unit_for(incoming).try!(:name)
83
+ write_attribute(unit_field_name, unit_name || incoming)
84
+ end
85
+ end
86
+ end
87
+
88
+ def measured_fields
89
+ @measured_fields ||= {}
90
+ end
91
+
92
+ end
93
+
94
+ module Length
95
+ extend ActiveSupport::Concern
96
+
97
+ module ClassMethods
98
+ def measured_length(*fields)
99
+ measured(Measured::Length, *fields)
100
+ end
101
+ end
102
+ end
103
+
104
+ module Volume
105
+ extend ActiveSupport::Concern
106
+
107
+ module ClassMethods
108
+ def measured_volume(*fields)
109
+ measured(Measured::Volume, *fields)
110
+ end
111
+ end
112
+ end
113
+
114
+ module Weight
115
+ extend ActiveSupport::Concern
116
+
117
+ module ClassMethods
118
+ def measured_weight(*fields)
119
+ measured(Measured::Weight, *fields)
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ ::ActiveRecord::Base.include(
126
+ Measured::Rails::ActiveRecord,
127
+ Measured::Rails::ActiveRecord::Length,
128
+ Measured::Rails::ActiveRecord::Volume,
129
+ Measured::Rails::ActiveRecord::Weight,
130
+ )
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/validations"
4
+
5
+ class MeasuredValidator < ActiveModel::EachValidator
6
+ CHECKS = {
7
+ greater_than: :>,
8
+ greater_than_or_equal_to: :>=,
9
+ equal_to: :==,
10
+ less_than: :<,
11
+ less_than_or_equal_to: :<=,
12
+ }.freeze
13
+
14
+ def validate_each(record, attribute, measurable)
15
+ measured_config = record.class.measured_fields[attribute]
16
+ unit_field_name = measured_config[:unit_field_name] || "#{ attribute }_unit"
17
+ value_field_name = measured_config[:value_field_name] || "#{ attribute }_value"
18
+
19
+ measured_class = measured_config[:class]
20
+
21
+ measurable_unit_name = record.public_send(unit_field_name)
22
+ measurable_value = record.public_send(value_field_name)
23
+
24
+ return unless measurable_unit_name.present? || measurable_value.present?
25
+
26
+ measurable_unit = measured_class.unit_system.unit_for(measurable_unit_name)
27
+ record.errors.add(attribute, message(record, "is not a valid unit")) unless measurable_unit
28
+
29
+ if options[:units] && measurable_unit.present?
30
+ valid_units = Array(options[:units]).map { |unit| measured_class.unit_system.unit_for(unit) }
31
+ record.errors.add(attribute, message(record, "is not a valid unit")) unless valid_units.include?(measurable_unit)
32
+ end
33
+
34
+ if measurable_unit && measurable_value.present?
35
+ options.slice(*CHECKS.keys).each do |option, value|
36
+ comparable_value = value_for(value, record)
37
+ comparable_value = measured_class.new(comparable_value, measurable_unit) unless comparable_value.is_a?(Measured::Measurable)
38
+ unless measurable.public_send(CHECKS[option], comparable_value)
39
+ record.errors.add(attribute, message(record, "#{measurable.to_s} must be #{CHECKS[option]} #{comparable_value}"))
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def message(record, default_message)
48
+ if options[:message].respond_to?(:call)
49
+ options[:message].call(record)
50
+ else
51
+ options[:message] || default_message
52
+ end
53
+ end
54
+
55
+ def value_for(key, record)
56
+ value = case key
57
+ when Proc
58
+ key.call(record)
59
+ when Symbol
60
+ record.send(key)
61
+ else
62
+ key
63
+ end
64
+
65
+ raise ArgumentError, ":#{ value } must be a number or a Measurable object" unless (value.is_a?(Numeric) || value.is_a?(Measured::Measurable))
66
+ value
67
+ end
68
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Measured
4
+ class Rails < ::Rails::Railtie
5
+ class Error < StandardError ; end
6
+
7
+ ActiveSupport.on_load(:active_record) do
8
+ require "measured/rails/active_record"
9
+ require "measured/rails/validations"
10
+ end
11
+ end
12
+ end
@@ -3,6 +3,8 @@ Measured::Volume = Measured.build do
3
3
  si_unit :l, aliases: [:liter, :litre, :liters, :litres]
4
4
 
5
5
  unit :m3, value: "1000 l", aliases: [:cubic_meter, :cubic_metre, :cubic_meters, :cubic_metres]
6
+ unit :cm3, value: "0.001 l", aliases: [:cubic_centimeter, :cubic_centimetre, :cubic_centimeters, :cubic_centimetres]
7
+ unit :mm3, value: "0.000001 l", aliases: [:cubic_millimeter, :cubic_millimetre, :cubic_millimeters, :cubic_millimetres]
6
8
  unit :in3, value: "0.016387064 l", aliases: [:cubic_inch, :cubic_inches]
7
9
  unit :ft3, value: "1728 in3", aliases: [:cubic_foot, :cubic_feet]
8
10
  unit :gal, value: "4.54609 l", aliases: [:imp_gal, :imperial_gallon, :imp_gals, :imperial_gallons]
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  Measured::Weight = Measured.build do
3
- si_unit :g, aliases: [:gram, :grams]
3
+ si_unit :g, aliases: [:gram, :grams, :gm, :gms]
4
4
 
5
5
  unit :t, value: "1000 kg", aliases: [:metric_ton, :metric_tons]
6
6
  unit :slug, value: "14.593903 kg", aliases: [:slugs]
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Measured
3
- VERSION = "2.8.1"
3
+ VERSION = "3.0.0"
4
4
  end
data/lib/measured.rb CHANGED
@@ -4,3 +4,5 @@ require "measured/base"
4
4
  require "measured/units/length"
5
5
  require "measured/units/weight"
6
6
  require "measured/units/volume"
7
+
8
+ require "measured/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,110 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ return unless defined?(::Measured::Rails)
5
+
6
+ module Tapioca
7
+ module Dsl
8
+ module Compilers
9
+ # `Tapioca::Dsl::Compilers::MeasuredRails` refines RBI files for subclasses of
10
+ # [`ActiveRecord::Base`](https://api.rubyonrails.org/classes/ActiveRecord/Base.html)
11
+ # that utilize the [`measured-rails`](https://github.com/shopify/measured-rails) DSL.
12
+ # This compiler is only responsible for defining the methods that would be created
13
+ # for measured fields that are defined in the Active Record model.
14
+ #
15
+ # For example, with the following model class:
16
+ #
17
+ # ~~~rb
18
+ # class Package < ActiveRecord::Base
19
+ # measured Measured::Weight, :minimum_weight
20
+ # measured Measured::Length, :total_length
21
+ # measured Measured::Volume, :total_volume
22
+ # end
23
+ # ~~~
24
+ #
25
+ # this compiler will produce the following methods in the RBI file
26
+ # `package.rbi`:
27
+ #
28
+ # ~~~rbi
29
+ # # package.rbi
30
+ # # typed: true
31
+ #
32
+ # class Package
33
+ # include GeneratedMeasuredRailsMethods
34
+ #
35
+ # module GeneratedMeasuredRailsMethods
36
+ # sig { returns(T.nilable(Measured::Weight)) }
37
+ # def minimum_weight; end
38
+ #
39
+ # sig { params(value: T.nilable(Measured::Weight)).void }
40
+ # def minimum_weight=(value); end
41
+ #
42
+ # sig { returns(T.nilable(Measured::Length)) }
43
+ # def total_length; end
44
+ #
45
+ # sig { params(value: T.nilable(Measured::Length)).void }
46
+ # def total_length=(value); end
47
+ #
48
+ # sig { returns(T.nilable(Measured::Volume)) }
49
+ # def total_volume; end
50
+ #
51
+ # sig { params(value: T.nilable(Measured::Volume)).void }
52
+ # def total_volume=(value); end
53
+ # end
54
+ # end
55
+ # ~~~
56
+ class MeasuredRails < ::Tapioca::Dsl::Compiler
57
+ extend T::Sig
58
+
59
+ ConstantType = type_member { {
60
+ fixed: T.all(
61
+ T.class_of(::ActiveRecord::Base),
62
+ ::Measured::Rails::ActiveRecord::ClassMethods
63
+ )
64
+ } }
65
+
66
+ MeasuredMethodsModuleName = T.let("GeneratedMeasuredRailsMethods", String)
67
+
68
+ sig { override.void }
69
+ def decorate
70
+ return if constant.measured_fields.empty?
71
+
72
+ root.create_path(constant) do |model|
73
+ model.create_module(MeasuredMethodsModuleName) do |mod|
74
+ populate_measured_methods(mod)
75
+ end
76
+
77
+ model.create_include(MeasuredMethodsModuleName)
78
+ end
79
+ end
80
+
81
+ sig { override.returns(T::Enumerable[Module]) }
82
+ def self.gather_constants
83
+ descendants_of(::ActiveRecord::Base)
84
+ end
85
+
86
+ private
87
+
88
+ sig { params(model: RBI::Scope).void }
89
+ def populate_measured_methods(model)
90
+ constant.measured_fields.each do |field, attrs|
91
+ class_name = qualified_name_of(attrs[:class])
92
+
93
+ next unless class_name
94
+
95
+ model.create_method(
96
+ field.to_s,
97
+ return_type: as_nilable_type(class_name)
98
+ )
99
+
100
+ model.create_method(
101
+ "#{field}=",
102
+ parameters: [create_param("value", type: as_nilable_type(class_name))],
103
+ return_type: "void"
104
+ )
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
data/measured.gemspec CHANGED
@@ -13,6 +13,8 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://github.com/Shopify/measured"
14
14
  spec.license = "MIT"
15
15
 
16
+ spec.required_ruby_version = ">= 3.0.0"
17
+
16
18
  # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
17
19
  # delete this section to allow pushing this gem to any host.
18
20
  if spec.respond_to?(:metadata)
@@ -33,4 +35,7 @@ Gem::Specification.new do |spec|
33
35
  spec.add_development_dependency "minitest-reporters"
34
36
  spec.add_development_dependency "mocha", ">= 1.4.0"
35
37
  spec.add_development_dependency "pry"
38
+ spec.add_development_dependency "combustion"
39
+ spec.add_development_dependency "sqlite3", "~> 1.4"
40
+ spec.add_development_dependency "tapioca"
36
41
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ class Thing < ActiveRecord::Base
3
+
4
+ measured_length :length, :width
5
+
6
+ measured Measured::Length, :height
7
+ measured Measured::Volume, :volume
8
+
9
+ measured_weight :total_weight
10
+
11
+ measured "Measured::Weight", :extra_weight
12
+
13
+ measured_length :length_with_max_on_assignment, {max_on_assignment: 500}
14
+ end
@@ -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