【问题标题】:validates_uniqueness_of in destroyed nested model railsvalidates_uniqueness_of 在被破坏的嵌套模型导轨中
【发布时间】:2010-05-05 10:12:36
【问题描述】:

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

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

Task 模型中的唯一性验证在更新 Project 时会出现问题。

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

参数哈希看起来像

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

在销毁旧任务之前对任务进行验证。因此验证失败。知道如何验证它不认为任务被破坏吗?

【问题讨论】:

  • 只是好奇为什么不更新旧任务而不是删除旧任务并创建具有相同名称的新任务。
  • 你的意思是我需要遍历旧任务并检查是否有任何与新任务同名但标记为已销毁的旧任务,然后只更新旧任务?
  • Arun ... 这只是一个测试用例(添加与您要删除的另一个任务同名的任务)还是您在每次编辑时都这样做,即删除任务并重新创建它们。
  • trustfundbaby - 实际上我没有想到那个测试用例。我错误地删除了一个任务。所以我不得不添加一个与已删除任务同名的新任务。我最终遇到了上述情况。
  • 我很困惑,如果你已经删除了任务,为什么你去提交编辑时要传递删除任务的参数?你是通过js删除任务,然后发送参数进行实际删除吗?

标签: ruby-on-rails nested-forms validates-uniqueness-of


【解决方案1】:

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

【讨论】:

  • add_to_base 已被弃用,在 3.1 中不可用。使用 self.errors.add(:base, message)
  • 嘿,我收到了一封来自某人的电子邮件,说他们通过 StackOverflow 找到了我的解决方法。很高兴知道它帮助了人们。也许我应该为 Rails 3 更新它,因为现在这种实现会被认为非常糟糕!
  • 感谢 Andrew,为我节省了大量时间。我没有找到任何其他方法来实现这一点。
  • 布莱恩,试试这个:techbrownbags.wordpress.com/2014/02/05/…
【解决方案2】:

据我了解,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

结束

【讨论】:

  • 优秀的方法!这个答案提供了最好的解决方案,imo。坚固、快速、故障安全和可靠。数据库索引不会说谎。 ;)
【解决方案3】:

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

如果您有一个具有唯一字段索引的表,并且您标记了一条记录 用于销毁,然后您建立一个与 唯一字段,然后当您调用保存时,数据库级别的唯一索引 会抛出错误。

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

【讨论】:

    【解决方案4】:

    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
    

    【讨论】:

      【解决方案5】:

      参考this

      你为什么不使用 :scope

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

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

      【讨论】:

      • scope => 'project_id' 将适用于不同的项目 ID,但在上述情况下 project_id 是相同的。我需要销毁一个任务,然后为一个项目添加一个同名的新任务。
      • 请您将您的代码粘贴到这里,以便有人可以帮助您。
      • In controller def update @project = Project.find(params[:id]) @project.update_attributes(params[:project]) end 模型代码和我上面提到的一样。我已经完成了与瑞恩·贝茨在 railscasts 第 197 集中描​​述的完全相似
      猜你喜欢
      • 2013-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-26
      • 2015-05-22
      • 1970-01-01
      • 1970-01-01
      • 2015-02-13
      相关资源
      最近更新 更多