andre.orvalho
andre.orvalho

Reputation: 1044

rails validates uniqueness is failing oddly

I have 2 models:

class PollAnswer < ActiveRecord::Base

  belongs_to :poll, inverse_of: :poll_answers

  # Validations
  validates :answer, presence: true, uniqueness: { scope: :poll_id, case_sensitive: false }

  validates :votes_number, presence: true
  validates :poll, presence: true
end

class Poll < ActiveRecord::Base
  # Associations
  has_many :poll_answers, inverse_of: :poll, dependent: :destroy

  # Attributes
  accepts_nested_attributes_for :poll_answers, reject_if: :all_blank, allow_destroy: true

  # Validations
  validates :question, presence: true
  validate :has_minimum_2_poll_answers

  private

  def has_minimum_2_poll_answers
    remaining_poll_answers = poll_answers.reject(&:marked_for_destruction?)
    if remaining_poll_answers.size < 2
      errors.add :poll_answers, I18n.t(:minimum_2_poll_answers, scope: "activerecord.errors.models.polls")
    end
  end
end

and a simple test:

let(:new_poll_answer) { build(:poll_answer) }
let(:new_poll_answer1) { build(:poll_answer) }
let(:new_poll) { create(:poll) }

it "validates the uniqueness of an answer scoped to poll_id" do
  new_poll_answer.answer = "andre"
  new_poll_answer.poll = new_poll
  new_poll_answer1.answer = "andre"
  new_poll_answer1.poll = new_poll
  expect(new_poll_answer.valid?).to eq(false)
end

and it fails:

1) PollAnswer validations validates the uniqueness of an answer scoped to poll_id
     Failure/Error: expect(new_poll_answer.valid?).to eq(false)

   expected: false
        got: true

   (compared using ==)

 # ./spec/models/poll_answer_spec.rb:22:in `block (3 levels) in <top (required)>'
 # -e:1:in `<main>'

Any ideas why?

UPDATE:

After Marek Lipka comment I can see that's exactly my problem because this is how accepts_nested_attributes_for works. so it does not validate the uniqueness.

I tried with this test:

it "validates the uniqueness of an answer scoped to poll_id" do
  new_poll_answer.answer = "andre"
  new_poll_answer.poll = new_poll
  new_poll_answer1.answer = "andre"
  new_poll_answer1.poll = new_poll
  new_poll.save
  puts "#{new_poll.inspect}"
  puts "#{new_poll_answer.inspect}"
  puts "#{new_poll_answer1.inspect}"
  expect(new_poll_answer1.valid?).to eq(false)
end

and I get this:

#<Poll id: 62, question: "Question", created_at: "2014-04-08 12:31:06", updated_at: "2014-04-08 12:31:06", active: true, published_at: "2014-04-08 11:31:06">
#<PollAnswer id: nil, answer: "andre", votes_number: 0, poll_id: 62, created_at: nil, updated_at: nil>
#<PollAnswer id: nil, answer: "andre", votes_number: 0, poll_id: 62, created_at: nil, updated_at: nil>

Failures:

  1) PollAnswer validations validates the uniqueness of an answer scoped to poll_id
     Failure/Error: expect(new_poll_answer1.valid?).to eq(false)

       expected: false
            got: true

       (compared using ==)

Which for me is still weird if you look at my custom validation for the poll class called has_minimum_2_poll_answers.

How could I validate correctly that a poll should only be create if there is no poll_answers with the same answer?

Upvotes: 0

Views: 969

Answers (2)

user3518192
user3518192

Reputation: 1

Also note that beyond the requirement for one of the records to have been previously saved, it is not possible to guarantee uniqueness validation in the application layer. Uniqueness validation can only be guaranteed using database layer constraints (i.e unique indexes).

Excerpt from the Rails docs:

Concurrency and integrity

Using this validation method in conjunction with ActiveRecord::Validations#save does not guarantee the absence of duplicate record insertions, because uniqueness checks on the application level are inherently prone to race conditions. For example, suppose that two users try to post a Comment at the same time, and a Comment's title must be unique. At the database-level, the actions performed by these users could be interleaved in the following manner:

Upvotes: 0

Marek Lipka
Marek Lipka

Reputation: 51151

You didn't save your first new_poll_answer, uniqueness validation doesn't work against unsaved records. You need to do:

new_poll_answer.save

before testing new_poll_answer1 for validity.

Upvotes: 1

Related Questions