Created
February 20, 2014 15:05
-
-
Save diamondap/9115715 to your computer and use it in GitHub Desktop.
In Rails 3.2.13 using accepts_nested_attributes_for, a scoped validates_uniqueness_of validator will incorrectly reject unique values.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# In Rails 3.2.13 using accepts_nested_attributes_for, a scoped | |
# validates_uniqueness_of validator will incorrectly reject unique values. | |
# Below are two models, Question and AnswerChoice. The validates_uniqueness_of | |
# validator in AnswerChoice says that AnswerChoice label must be unique within a | |
# Question. This validator incorrectly rejects unique labels when they are | |
# reordered. Follow these steps to see the bug. | |
# | |
# This occurs when using nested resources and allowing users to edit the | |
# Question and AnswerChoices in a single form. The form looks like this: | |
# | |
# Question: _______________________________ | |
# Answer 1: ___________________________ | |
# Answer 2: ___________________________ | |
# Answer 3: ___________________________ | |
# | |
# 1. Create a new question. | |
# 2. Add three answer choices setting the label of the first choice to | |
# "One", the label of the second choice to "Two" and the label of the | |
# third choice to "Three". | |
# 3. Save the Question and all the answer choices. | |
# 4. Edit the question again, change the labels to "Two", "Three", "One", | |
# in that order. | |
# 5. Save the Question. | |
# | |
# Expected: No validation error. | |
# Actual: You get an error saying that the answer choices must be unique. | |
# Schema info for Question | |
# Table name: questions | |
# | |
# id :integer not null, primary key | |
# prompt :string(500) not null | |
# sort_order :integer not null | |
# created_by :integer default(0), not null | |
# updated_by :integer | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
class Question < ActiveRecord::Base | |
has_many :answer_choices | |
accepts_nested_attributes_for :choices, :allow_destroy => true | |
validates_presence_of :prompt | |
validates :choices, length: { minimum: 2, message: "Question must have at least 2 choices." } | |
attr_accessible :prompt, :answer_choices_attributes | |
end | |
# Schema for AnswerChoice | |
# id :integer not null primary key | |
# question_id :integer not null | |
# value :integer | |
# label :string | |
# sort_order :integer not null | |
# is_correct :boolean not null default false | |
# created_by :integer not null default 0 | |
# updated_by :integer | |
# created_at :datetime not null | |
# updated_at :datetime not null | |
class AnswerChoice < ActiveRecord::Base | |
belongs_to :question | |
attr_accessible :value, :label, :sort_order, :is_correct | |
validates :sort_order, presence: true | |
validates_length_of :label, :maximum => 400 | |
validates_uniqueness_of :label, :scope => [:question_id], :allow_blank => false, :case_sensitive => false, message: "Answer choice must be unique." | |
end | |
# The following validator works without error for this case: | |
class UniqueChoiceValidator < ActiveModel::Validator | |
def validate(record) | |
exists = {} | |
record.choices.each_with_index do |choice, index| | |
choice.label ||= '' # conver nils so downcase doesn't raise | |
if exists[choice.label.downcase] | |
record.errors[:choices] << "Choices must be unique" | |
record.choices[index].errors[:label] << "Choices must be unique!" | |
end | |
exists[choice.label.downcase] = true | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment