我有一个Project模型,它接受Task的嵌套属性。
class Project < ActiveRecord::Base
has_many :tasks
accepts_nested_attributes_for :tasks, :allow_destroy => :true
end
class Task < ActiveRecord::Base
validates_uniqueness_of :name end
任务模型中的唯一性验证在更新项目时会出现问题。
在编辑项目时,我删除任务T1然后添加一个同名T1的新任务,唯一性验证限制了项目的保存。
params hash看起来像
task_attributes => { {"id" =>
"1","name" => "T1", "_destroy" =>
"1"},{"name" => "T1"}}
在销毁旧任务之前完成对任务的验证。因此验证失败。任何想法如何验证它不会将任务销毁?
答案 0 :(得分:15)
Andrew France在此thread中创建了一个补丁,验证在内存中完成。
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
答案 1 :(得分:3)
据我了解,Reiner关于在内存中验证的方法在我的案例中并不实用,因为我有很多“书籍”,500K并且还在增长。如果你想将所有内容都带入记忆中,那将是一个很大的打击。
我想出的解决方案是:
通过在db / migrate /中将以下内容添加到迁移文件中,将唯一性条件放在数据库中(我发现这总是一个好主意,因为根据我的经验,Rails并不总是在这里做得很好)。
add_index :tasks [ :project_id, :name ], :unique => true
在控制器中,将save或update_attributes放在事务中,并挽救数据库异常。如,
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
端
答案 2 :(得分:1)
对于Rails 4.0.1,此问题已被此拉取请求标记为https://github.com/rails/rails/pull/10417
如果您的表具有唯一的字段索引,则标记记录 对于销毁,您构建一个具有相同值的新记录 唯一字段,然后当您调用save时,数据库级别唯一索引 错误将被抛出。
就个人而言,这仍然不适合我,所以我认为它还没有完全修复。
答案 3 :(得分:1)
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
答案 4 :(得分:-3)
参考this
为什么不使用:scope
class Task < ActiveRecord::Base
validates_uniqueness_of :name, :scope=>'project_id'
end
这将为每个项目创建唯一的任务。