Rails 3:嵌套 fields_for 的唯一性验证

发布于 2024-10-27 14:54:36 字数 509 浏览 1 评论 0原文

A 有两个模型,“shop”和“product”,通过 has_many :through 链接。

在商店表单中,有多个产品的嵌套属性,并且我在产品的唯一性验证方面遇到了一些麻烦。如果我输入一个产品,保存它,然后尝试为新产品输入相同的名称,则唯一性验证会成功触发。

但是,如果我在同一嵌套表单的两行中输入相同的产品名称,则该表单将被接受 - 不会触发唯一性验证。

我猜这是一个相当常见的问题,但我找不到任何简单的解决方案。有人对确保在相同嵌套表单中遵守唯一性验证的最简单方法有任何建议吗?

编辑:产品型号如下

class Product < ActiveRecord::Base

  has_many :shop_products
  has_many :shops, :through => :shop_products

  validates_presence_of :name
  validates_uniqueness_of :name
end

A have two models, "shop" and "product", linked via has_many :through.

In the shop form there are nested attributes for multiple products, and I'm having a little trouble with the product's uniqueness validation. If I enter a product, save it, then try to enter the same name for a new product, the uniqueness validation triggers successfully.

However, if I enter the same product name in 2 rows of the same nested form, the form is accepted - the uniqueness validation doesn't trigger.

I'm guessing this is a fairly common problem, but I can't find any simple solution. Anyone have any suggestions on the easiest way to ensure uniqueness validations are obeyed within the same nested form?

Edit: Product model included below

class Product < ActiveRecord::Base

  has_many :shop_products
  has_many :shops, :through => :shop_products

  validates_presence_of :name
  validates_uniqueness_of :name
end

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

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

发布评论

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

评论(4

夜访吸血鬼 2024-11-03 14:54:37

早期的答案确实很好,虽然它们是一个很好的起点,但已经过去了几年了!

这是一个最新的选项!

# config/initializers/nested_attributes_uniqueness_validator.rb
class NestedAttributesUniquenessValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, items)
    field = options[:field]
    return if items.map(&field).uniq.size == items.size

    values = items.map { |item| item.send(field) }
    duplicate_values = values.select{ |value| values.count(value)>1 }.uniq.join(', ')
    record.errors.add(attribute, "are not unique. Duplicate #{field}s detected. Duplicate values: #{duplicate_values}" )
    duplicates = items.find_all { |item| values.count(item.send(field)) > 1 && item.id.nil? }
    duplicates.each { |obj| obj.errors.add(field, :taken) }
  end
end
# app/models/shop.rb
class Shop < ActiveRecord::Base
  validates :products, nested_attributes_uniqueness: { field: :name }
end
# app/controllers/shops_controller.rb
class ShopsController < ApplicationController
  def create
    @shop = Shop.new(shop_params)
    return success_response if @shop.save
    
    failure_response(@shop.errors.full_messages)
  end
  
  private
  
  def shop_params
    params.require(:shop).permit(:name, :address, products_attributes: %i[name price])
  end
end

success_response 和 failure_response 可以是您需要的任何内容!

The earlier answers are really good, and while they were a great starting point, it has been a few years!

Here's an up-to-date option!

# config/initializers/nested_attributes_uniqueness_validator.rb
class NestedAttributesUniquenessValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, items)
    field = options[:field]
    return if items.map(&field).uniq.size == items.size

    values = items.map { |item| item.send(field) }
    duplicate_values = values.select{ |value| values.count(value)>1 }.uniq.join(', ')
    record.errors.add(attribute, "are not unique. Duplicate #{field}s detected. Duplicate values: #{duplicate_values}" )
    duplicates = items.find_all { |item| values.count(item.send(field)) > 1 && item.id.nil? }
    duplicates.each { |obj| obj.errors.add(field, :taken) }
  end
end
# app/models/shop.rb
class Shop < ActiveRecord::Base
  validates :products, nested_attributes_uniqueness: { field: :name }
end
# app/controllers/shops_controller.rb
class ShopsController < ApplicationController
  def create
    @shop = Shop.new(shop_params)
    return success_response if @shop.save
    
    failure_response(@shop.errors.full_messages)
  end
  
  private
  
  def shop_params
    params.require(:shop).permit(:name, :address, products_attributes: %i[name price])
  end
end

With success_response and failure_response being whatever you need them to be!

明明#如月 2024-11-03 14:54:36

为了扩展 Alberto 的解决方案,以下自定义验证器接受要验证的字段(属性),并向嵌套资源添加错误。

# config/initializers/nested_attributes_uniqueness_validator.rb
class NestedAttributesUniquenessValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value.map(&options[:field]).uniq.size == value.size
      record.errors[attribute] << "must be unique"
      duplicates = value - Hash[value.map{|obj| [obj[options[:field]], obj]}].values
      duplicates.each { |obj| obj.errors[options[:field]] << "has already been taken" }
    end
  end
end

# app/models/shop.rb
class Shop < ActiveRecord::Base
  validates :products, :nested_attributes_uniqueness => {:field => :name}
end

To expand on Alberto's solution, the following custom validator accepts a field (attribute) to validate, and adds errors to the nested resources.

# config/initializers/nested_attributes_uniqueness_validator.rb
class NestedAttributesUniquenessValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value.map(&options[:field]).uniq.size == value.size
      record.errors[attribute] << "must be unique"
      duplicates = value - Hash[value.map{|obj| [obj[options[:field]], obj]}].values
      duplicates.each { |obj| obj.errors[options[:field]] << "has already been taken" }
    end
  end
end

# app/models/shop.rb
class Shop < ActiveRecord::Base
  validates :products, :nested_attributes_uniqueness => {:field => :name}
end
懒的傷心 2024-11-03 14:54:36

您可以编写一个自定义验证器,例如

# app/validators/products_name_uniqueness_validator.rb
class ProductsNameUniquenessValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors[attribute] << "Products names must be unique" unless value.map(&:name).uniq.size == value.size
  end
end

# app/models/shop.rb
class Shop < ActiveRecord::Base
  validates :products, :products_name_uniqueness => true
end

You could write a custom validator like

# app/validators/products_name_uniqueness_validator.rb
class ProductsNameUniquenessValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors[attribute] << "Products names must be unique" unless value.map(&:name).uniq.size == value.size
  end
end

# app/models/shop.rb
class Shop < ActiveRecord::Base
  validates :products, :products_name_uniqueness => true
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文