被破坏的嵌套模型轨道中的 validates_uniqueness_of

发布于 2024-08-31 17:53:01 字数 542 浏览 2 评论 0原文

我有一个项目模型,它接受任务的嵌套属性。

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"}}

任务验证在销毁旧任务之前完成。因此验证失败。知道如何验证以使其不认为任务被破坏吗?

I have a Project model which accepts nested attributes for 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

Uniqueness validation in Task model gives problem while updating Project.

In edit of project i delete a task T1 and then add a new task with same name T1, uniqueness validation restricts the saving of Project.

params hash look something like

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

Validation on task is done before destroying the old task. Hence validation fails.Any idea how to validate such that it doesn't consider task to be destroyed?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

空心空情空意 2024-09-07 17:53:02

Andrew France 在此线程中创建了一个补丁,其中验证是在内存中完成的。

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

Andrew France created a patch in this thread, where the validation is done in memory.

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
故事和酒 2024-09-07 17:53:02

据我了解,Reiner 关于在内存中进行验证的方法对于我来说并不实用,因为我有很多“书”,500K 并且还在不断增长。如果你想把一切都记在记忆里,那将会是一个很大的打击。

我想出的解决方案是:

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

  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

结束

As I understand it, Reiner's approach about validating in memory would not be practical in my case, as I have a lot of "books", 500K and growing. That would be a big hit if you want to bring all into memory.

The solution I came up with is to:

Place the uniqueness condition in the database (which I've found is always a good idea, as in my experience Rails does not always do a good job here) by adding the following to your migration file in db/migrate/:

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

In the controller, place the save or update_attributes inside a transaction, and rescue the Database exception. E.g.,

 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

end

萌梦深 2024-09-07 17:53:02

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

Rainer Blessing's answer is good.
But it's better when we can mark which tasks are duplicated.

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
三人与歌 2024-09-07 17:53:02

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

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

就我个人而言,这对我来说仍然不起作用,所以我认为它还没有完全解决。

For Rails 4.0.1, this issue is marked as being fixed by this pull request, https://github.com/rails/rails/pull/10417

If you have a table with a unique field index, and you mark a record
for destruction, and you build a new record with the same value as the
unique field, then when you call save, a database level unique index
error will be thrown.

Personally this still doesn't work for me, so I don't think it's completely fixed yet.

香橙ぽ 2024-09-07 17:53:02

参考 这个

你为什么不使用 :scope

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

这将为每个项目创建独特的任务。

Ref this

Why don't you use :scope

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

this will create unique Task for each project.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文