已销毁的嵌套模型轨道中的validates_uniqueness_of

我有一个Project模型,它接受Task的嵌套属性。

class Project  :true end class Task < ActiveRecord::Base validates_uniqueness_of :name end 

任务模型中的唯一性validation在更新Project时会出现问题。

在编辑项目时,我删除任务T1,然后添加一个同名T1的新任务,唯一性validation限制了项目的保存。

params hash看起来像

 task_attributes => { {"id" => "1","name" => "T1", "_destroy" => "1"},{"name" => "T1"}} 

在销毁旧任务之前完成对任务的validation。 因此validation失败。任何想法如何validation它不会认为任务被销毁?

Andrew France在这个线程中创建了一个补丁,validation在内存中完成。

 class Author has_many :books # Could easily be made a validation-style class method of course validate :validate_unique_books def validate_unique_books validate_uniqueness_of_in_memory( books, [:title, :isbn], 'Duplicate book.') end end module ActiveRecord class Base # Validate that the the objects in +collection+ are unique # when compared against all their non-blank +attrs+. If not # add +message+ to the base errors. def validate_uniqueness_of_in_memory(collection, attrs, message) hashes = collection.inject({}) do |hash, record| key = attrs.map {|a| record.send(a).to_s }.join if key.blank? || record.marked_for_destruction? key = record.object_id end hash[key] = record unless hash[key] hash end if collection.length > hashes.length self.errors.add_to_base(message) end end end end 

据我所知,Reiner关于在内存中validation的方法在我的案例中并不实用,因为我有很多“书籍”,500K并且在不断增长。 如果你想将所有内容都带入内存,这将是一个巨大的打击。

我想出的解决方案是:

通过在db / migrate /中将以下内容添加到迁移文件中,将唯一性条件放在数据库中(我发现这总是一个好主意,因为根据我的经验,Rails并不总是在这里做得很好)。

  add_index :tasks [ :project_id, :name ], :unique => true 

在控制器中,将save或update_attributes放在事务中,并挽救数据库exception。 例如,

  def update @project = Project.find(params[:id]) begin transaction do if @project.update_attributes(params[:project]) redirect_to(project_path(@project)) else render(:action => :edit) end end rescue ... we have an exception; make sure is a DB uniqueness violation ... go down params[:project] to see which item is the problem ... and add error to base render( :action => :edit ) end end 

结束

对于Rails 4.0.1,此问题被此拉取请求标记为已修复, https://github.com/rails/rails/pull/10417

如果您有一个具有唯一字段索引的表,并且您标记了要销毁的记录,并且您构建了一个与唯一字段具有相同值的新记录,那么当您调用save时,将抛出数据库级别唯一索引错误。

就个人而言,这仍然不适合我,所以我认为它还没有完全修复。

Rainer Blessing的答案很好。 但是,当我们标记哪些任务重复时,它会更好。

 class Project < ActiveRecord::Base has_many :tasks, inverse_of: :project accepts_nested_attributes_for :tasks, :allow_destroy => :true end class Task < ActiveRecord::Base belongs_to :project validates_each :name do |record, attr, value| record.errors.add attr, :taken if record.project.tasks.map(&:name).count(value) > 1 end end 

参考这个

你为什么不使用:范围

 class Task < ActiveRecord::Base validates_uniqueness_of :name, :scope=>'project_id' end 

这将为每个项目创建唯一的任务。