Skip to content
Andrew Geweke edited this page Dec 21, 2013 · 7 revisions

flex_columns is designed to give you "as much schema as you want, but no more". As a result, by default, it allows you to store any data you want in each field. However, you can constrain this using the exact same way you constrain it in any ActiveRecord model: using ActiveModel validations. flex_columns also provides a "types" shorthand that declares validations for you based on migration-style syntax.

You can declare validations either inside the flex column or outside; either will work, and, if both are declared, they will both apply:

class User < ActiveRecord::Base
  flex_column :preferences do
    field :max_number_of_emails_per_week
    field :background_color

    validates :max_number_of_emails_per_week, :numericality => { :greater_than_or_equal_to => 0 }
    validates :background_color, :inclusion => { :in => %w{red blue green} }
  end

  validates :max_number_of_emails_per_week, :numericality => { :less_than_or_equal_to => 20 }
end

user = User.new
user.max_number_of_emails_per_week = 40
user.background_color = "yellow"

user.save                          # => false
user.valid?                        # => false
user.errors.keys                   # => [ 'max_number_of_emails_per_week', 'user_attributes.background_color' ]
user.errors[:max_number_of_emails_per_week] # => [ 'must be less than or equal to 20' ]
user.errors[:'user_attributes.background_color'] # => [ 'must be in the list' ]

You can use the full range of ActiveModel validations, including custom validations with methods, objects, or anything else ActiveModel (and therefore ActiveRecord) supports. Knock yourself out!

ActiveRecord will run all validations on an object before it's saved, including those on columns that haven't been modified. flex_columns, on the other hand, will not run validations against a flex column that you haven't written to; this is to avoid the (possibly expensive) process of deserializing a column that hasn't been touched, just to run a validation that will almost certainly succeed. If you want to ensure that validations run against a flex column no matter what, you can simply touch it, using something like my_user.user_attributes.touch!; this will ensure that any validations on that object run. (An implication of this is that declaring validations within the flex-column class can be of higher performance, since validations declared on the enclosing model will always run, no matter what.)

Note the distinction above between error keys that are prefixed with user_attributes. and those that aren't; the former come from validations declared within the flex column, and the latter from validations declared directly within the User class. This is a function of the way ActiveModel validations work: they call the method named the same as whatever attribute you're validating on, and so such a method must be present for the validation to work. flex_columns prefixes any errors from validations within the flex-column definition with the name of the flex column, because you can have the same field defined in other flex columns, or a completely unrelated column defined on the model; we need a way to distinguish errors on that flex field from other errors on identically-named columns or other flex attributes.

This is also related to another point: in order to declare validations outside of the flex-column definition, those attributes must be exposed directly on the outer class. This is the default behavior of flex_columns, but can be disabled. If you do disable it, you'll either need to declare your validations directly within the flex column itself, or use a custom validation on the enclosing class to access your flex-column attribute.

Clone this wiki locally