Mishap
Mishap

Reputation: 283

Why is Rails setting my attribute to nil on create?

Ruby 2.2.0 on Rails 4.2.2

I'm attempting to write a custom date validator (that is going to be expanded to handle some other cases once I get this part working), but right now it's failing to appropriately validate (and return false) on strings - it doesn't even run. It seems that rails is completely ignoring the date_ended value when it's set to a string. When I try the same test except with an integer it correctly validates and fails that validation. If I don't allow nil values, the validator correctly prevents record creation on a string value, but only because it rejects the nil value. Any and all suggestions are appreciated.

The same exact problem exists for date_started.

Edit: I've confirmed that the validation is failing on the second expectation and not the first validation by double-checking that CommitteeMember.count is 0 before the first expectation.

CommitteeMember:

class CommitteeMember < ActiveRecord::Base
  belongs_to :committee
  belongs_to :member

  validates :committee_id, presence: true
  validates :member_id, uniqueness: { scope: :committee_id }, presence: true
  validates :date_started, date: true, allow_nil: true
  validates :date_ended, date: true, allow_nil: true
end

schema.rb relevant lines:

create_table "committee_members", force: :cascade do |t|                                          
  t.integer  "committee_id", null: false                                                          
  t.integer  "member_id",    null: false                                                          
  t.date     "date_started"                                                                       
  t.date     "date_ended"                                                                         
  t.datetime "created_at",   null: false                                                          
  t.datetime "updated_at",   null: false                                                                                                                                                                                                                          
end 

DateValidator (custom validator):

(note the printed value in the middle)

class DateValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    puts "Validating #{value}"
    unless value.kind_of?(Date)
      record.errors[attribute] << "must be of type date"
    end
  end
end

CommiteeMember relevant spec:

(note the printed value in the middle)

it 'should fail with a string for date_ended' do
  expect(CommitteeMember.count).to eq(0)
  CommitteeMember.create!(member_id: 1, committee_id: 1, date_ended: "S")
  ap CommitteeMember.first
  expect(CommitteeMember.count).to eq(0)
end

Spec Output:

$ rspec spec/models/committee_member_spec.rb
......#<CommitteeMember:0x00000007d1f888> {
              :id => 1,
    :committee_id => 1,
       :member_id => 1,
    :date_started => nil,
      :date_ended => nil,
      :created_at => Tue, 11 Aug 2015 19:22:51 UTC +00:00,
      :updated_at => Tue, 11 Aug 2015 19:22:51 UTC +00:00
}
F

Failures:

  1) CommitteeMember validations should fail with a string for date_ended
     Failure/Error: expect(CommitteeMember.count).to eq(0)

       expected: 0
            got: 1

       (compared using ==)
     # ./spec/models/committee_member_spec.rb:52:in `block (3 levels) in <top (required)>'

Finished in 0.54864 seconds (files took 2.43 seconds to load)
7 examples, 1 failure

Failed examples:

rspec ./spec/models/committee_member_spec.rb:48 # CommitteeMember validations should fail with a string for date_ended

Upvotes: 0

Views: 1401

Answers (1)

eirikir
eirikir

Reputation: 3842

Since the attributes are date attributes, Rails automatically parses any values that you attempt to assign. If it cannot parse as a date, it will leave the value as nil, e.g. in the console:

c = CommitteeMember.new
c.date_started = 'S'
=> "S"
c.date_started
=> nil

In fact, Rails will actually parse a string into a Date:

c.date_started = '2016-1-1'
=> "2016-1-1"
c.date_started
=> Fri, 01 Jan 2016
c.date_started.class
=> Date

This means that you don't need to validate that your date fields are dates at all, because Rails won't store them otherwise. Instead, just validate that they exist:

validates_presence_of :date_started, :date_ended

Upvotes: 3

Related Questions