Enables ActiveModel-equivalent validation in ActiveRecord (before_type_cast)

Current behavior

There is a form class created using ActiveModel

hoge_form.rb


class HogeForm
  include ActiveModel::Model
  include ActiveModel::Attributes
  include ActiveModel::AttributeMethods

  attribute :number, :integer
  validates :number, numericality: { only_integer: true }
end

I want to make a validation error when a value other than an integer value comes for number

Verification


form = HogeForm.new
form.number = 'hoge'
form.valid?
# => true
form.number
# => 0

Substituting hoge will internally cast to integer and will not result in a validation error

Expected behavior

Doing the same with ActiveRecord will result in an error as it validates the value before casting. Expect an error in ActiveModel as well

Research

Click here for the source code of the implementation of the validation of numericality of ActiveModel. (https://github.com/rails/rails/blob/master/activemodel/lib/active_model/validations/numericality.rb)

Cite a method to specify the value to use for validation validation

activemodel/lib/active_model/validations/numericality.rb



      def prepare_value_for_validation(value, record, attr_name)
        return value if record_attribute_changed_in_place?(record, attr_name)

        came_from_user = :"#{attr_name}_came_from_user?"

        if record.respond_to?(came_from_user)
          if record.public_send(came_from_user)
            raw_value = record.public_send(:"#{attr_name}_before_type_cast")
          elsif record.respond_to?(:read_attribute)
            raw_value = record.read_attribute(attr_name)
          end
        else
          before_type_cast = :"#{attr_name}_before_type_cast"
          if record.respond_to?(before_type_cast)
            raw_value = record.public_send(before_type_cast)
          end
        end

        raw_value || value
      end

After reading it roughly, it seems that if there is an implementation of # {attribute} _before_type_cast, that value will be used.

Since ActiveModel does not include the functions around before_type_cast, it is validated with the value after casting.

Solutions

If ActiveModel has the function of before_type_cast, it will be the same validation as ActiveRecord.

I searched for a source that implements the functionality of before_type_cast in ActiveRecord https://github.com/rails/rails/blob/2a0d4950398d1c5b5cd73186ac5269a9afa933e8/activerecord/lib/active_record/attribute_methods/before_type_cast.rb

Include this module in the form class

hoge_form.rb


class HogeForm
  include ActiveModel::Model
  include ActiveModel::Attributes
  include ActiveModel::AttributeMethods
  #add to
  include ActiveRecord::AttributeMethods::BeforeTypeCast
  

  attribute :number, :integer
  validates :number, numericality: { only_integer: true }
end

Operation check

Operation check]


form = HogeForm.new
form.number = 'hoge'
form.number #Value after cast
=> 0
form.number_before_type_cast # before_type_cast is available
=> "hoge"
form.valid?
=> false #invalid state
form.errors.messages
=> {:number=>["Please enter a numerical value"]} #Not a number(hoge)So error

form.number = 1.5 #Substitute decimal
=> 1.5
form.number #cast 1
=> 1
form.valid? # invalid
=> false
form.errors.messages
=> {:number=>["Please enter as an integer"]} #1 before the cast properly.Validated against 5

Afterword

I think it is better to incorporate BeforeTypeCast into ActiveModel as it should be. I didn't seem to have any side effects when I include`` ActiveRecord :: AttributeMethods :: BeforeTypeCast. It seems that it is not recommended (I do not take responsibility for the defect)

Our Collabbit is looking for a Rails engineer https://github.com/collabit-inc/job-offer-engineer

Recommended Posts

Enables ActiveModel-equivalent validation in ActiveRecord (before_type_cast)
About validation methods in JUnit
ActiveRecord :: NotNullViolation in Devise error
Validation function in Play Framework