0

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?

andre.orvalho
  • 1,034
  • 2
  • 11
  • 17
  • I suspect that if you actually tried to save (rather than calling valid) that the save would fail – Frederick Cheung Apr 09 '14 at 13:39
  • But my problem is if I have to save a poll with many poll_answers it will allow to have poll_answers with the same answers... and that's my real problem – andre.orvalho Apr 09 '14 at 15:02
  • is this actually happening? the save of the parent object and the associated ones is wrapped in a transaction, so if any of the saves fail, all will be rolled back. – Frederick Cheung Apr 09 '14 at 15:16
  • Frederick you try by yourself and copy my code and run it! I agree with your last sentence! – andre.orvalho Apr 10 '14 at 07:42

2 Answers2

1

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.

Marek Lipka
  • 50,622
  • 7
  • 87
  • 91
0

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: