31

In a Rails model I have an attribute is_subscriber, when I constructed a db migration to add this column to the database I specified the default value to be false:

t.boolean  "is_subscriber",   :default => false

I also specified in the model that this attribute needs to be present:

validates :is_subscriber, presence: true

So why do I get this error when I create a model instance without specifying this attribute?

2012-05-08T21:05:54+00:00 app[web.1]: ActiveRecord::RecordInvalid (Validation failed: Is subscriber can't be blank):
Fabio
  • 18,856
  • 9
  • 82
  • 114
Andrew Lauer Barinov
  • 5,694
  • 10
  • 59
  • 83

4 Answers4

56

From here

If you want to validate the presence of a boolean field (where the real values are true and false), you will want to use validates_inclusion_of :field_name, :in => [true, false] This is due to the way Object#blank? handles boolean values. false.blank? # => true

Or in Rails3 way

validates :field, :inclusion => {:in => [true, false]}
Fabio
  • 18,856
  • 9
  • 82
  • 114
  • 1
    Good point (voted up), but I don't think the database rule would lead to this being validated succesfully in that case either, right? – Steve Rowley May 08 '12 at 21:21
  • @SteveRowley why not? Rails handle data conversion between types, so it works like a charm. Keep in mind that migrations syntax is also translated to db syntax. – Fabio May 08 '12 at 21:25
  • I don't think it's a type conversion problem. Wouldn't it be a timing problem? You create a new object, don't specify a value for something, call `.save`, and then the validations run. If they don't raise errors, a record is inserted in the db, and if the value you left out was blank, the db creates the record with the default value specified in the migration. The problem I see is that because the validation is running before the record is inserted, so the validation should fail, because the db never had a chance to set the value for that field to anything. Sounds like I'm wrong though. Hmm. – Steve Rowley May 08 '12 at 23:13
  • Finally able to test this - looks like the attribute gets set to the migration default when the object is created. Handy! – Steve Rowley May 09 '12 at 00:33
  • @SteveRowley you're missing two things. First `save` enforce validations so you won't be able to save invalid records unless you give the `:validate => false` option to the save, so if you specify :inclusion validator you won't be able to save blank records (nil != false). Second when you specify default values in your db schema any instance of active record is initialized with those values if they are not provided on creation, so in this example `Model.new.is_subscriber` is false and not nil. – Fabio May 09 '12 at 00:35
  • But wouldn't this allow space for false negatives? anything other than booleans will be converted to false, right? – rpbaltazar Sep 27 '16 at 04:00
  • Note that if you have `validates :field, presence: true` then it will still fail. You need to remove that and use the `inclusion` validation only. DB `not null` constraints are alright. – jaredsmith Nov 04 '19 at 02:11
1

I've solved this with:

validates_presence_of :is_subscriber, :if => 'is_subscriber.nil?'
Lazarus Lazaridis
  • 5,803
  • 2
  • 21
  • 35
1

I think it is neater to wrap this in a custom validator.

in /app/validators/is_boolean_validator.rb

class IsBooleanValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, parameters)
    if !parameters.in? [true,false]
      record.errors[attribute] << 'This must be true or false.'
    end
  end
end

then you have to make sure this is loaded by adding the following to /config/application.rb

config.autoload_paths += %W["#{config.root}/app/validators/"]

(don't forget to restart your server to load this)

You can then validate more neatly with

validates: :field1, field2, is_boolean: true
Confused Vorlon
  • 9,659
  • 3
  • 46
  • 49
0
validates :column_name, :inclusion => {:in => [true, false]}
Prateek Arora
  • 616
  • 8
  • 7