Search code examples
ruby-on-railsserializationcallback

How to execute code "before_serialize"? or How can I sanitize attributes before they are serialized?


I am following this pattern to ensure all my attributes are normalized before they are saved:

class MyModel < ApplicationRecord
  before_validation :normalize_attributes

  def normalize_attributes
    self.name&.strip!
    self.email&.strip!
  end
end

I have also some others more complicated:

def normalize_attributes
  # Transform to array if it is a string with things
  # separated by comas
  if self.favorite_things.is_a? String
    self.favorite_things = self.favorite_things.split(",").map(&:strip)
  end
end

It works, but the problem is that when I add this attribute to the serialized ones it fails:

serialize :favorite_things, Array

Like in here:

my_model.update(favorite_things: "ONE, TWO, THREE")

The error:

Error:
ActiveRecord::SerializationTypeMismatch: can't serialize `favorite_things`: was supposed to be a Array, but was a String. -- "ONE, TWO, THREE"

It looks like the serialize command is executed before the before_validation.

How can I sanitize attributes before they are serialized?


Solution

  • In order to ensure all your attributes are normalized before saving - overwrite setters:

    class MyModel < ApplicationRecord
      # @param value [String]
      # @return [String]
      def name=(value)
        super(value.strip)
      end
      
      # @param value [String]
      # @return [String]
      def email=(value)
        super(value.strip)
      end
      
      # @param value [String, Array]
      # @return [Array]
      def favorite_things=(value)
        return super(value) unless value.is_a? String
        
        super(favorite_things.split(",").map(&:strip))
      end
    end
    

    https://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Overwriting+default+accessors