防止Rails 5中

时间:2017-10-31 11:32:29

标签: ruby-on-rails ruby postgresql activerecord ruby-on-rails-5

鉴于以下模型:

class Client < ApplicationRecord
  has_many :preferences
  validates_associated :preferences
  accepts_nested_attributes_for :preferences
end

class Preference < ApplicationRecord
  belongs_to :client
  validates_uniqueness_of :block, scope: [:day, :client_id]
end

在客户端创建期间创建一批首选项时,我仍然可以创建重复日期*的首选项。这是(貌似),因为在运行validates_uniqueness_of验证时,client_id外键不可用。 (*我有一个索引,可以防止复制被保存,但我想捕获错误,并在它到达数据库之前返回用户友好的错误消息。)

有没有办法通过ActiveRecord验证来防止这种情况发生?

编辑:This appears to be a known issue

1 个答案:

答案 0 :(得分:1)

批量插入时,使用AR验证没有超级干净的方法,但您可以通过以下步骤手动完成此操作。

  1. 使用Postgresql VALUES列表对数据库进行单个查询,以加载任何可能重复的记录。
  2. 比较您要批量创建和提取任何重复项的记录
  3. 手动生成并返回错误消息
  4. 第1步看起来有点像这样

    # Build array of uniq attribute pairs we want to check for
    uniq_attrs = new_collection.map do |record|
      [
        record.day,
        record.client_id,
      ]
    end
    
    # santize the values and create a tuple like ('Monday', 5)
    values = uniq_attrs.map do |attrs|
      safe = attrs.map {|v| ActiveRecord::Base.connection.quote(v)}
      "( #{safe.join(",")} )"
    end
    
    existing = Preference.where(%{
        (day, client_id) in
        (#{values.join(",")})
     })
    
    # SQL Looks like 
    # select * from preferences where (day, client_id) in (('Monday',5), ('Tuesday', 3) ....)
    

    然后您可以使用集合existing并在第2步和第3步中使用它来提取重复项并生成错误消息。

    当我需要这个功能时,我通常会把它作为我课堂上的自我方法,所以像

    class Preference < ApplicationRecord
    
      def self.filter_duplicates(collection)
        # blah blah blah from above
    
        non_duplicates = collection.reject do |record|
          existing.find do |exist|
            exist.duplicate?(record)
          end
        end
    
        [non_duplicates, existing]
    
      end
    
      def duplicate?(record)
        record.day == self.day && 
        record.client_id = self.client_id
      end
    end