如何验证关联的每个实例的限制,而不是 Rails 中的关联本身

How to validate a limit on each instance of the association, but not the association itself in Rails

我有一个 Product 模型,每个产品可以有很多选项,例如尺寸和颜色。每个Option也可以有很多Choices。所以 "Size" Option 可能有 "Small," "Medium," 和 "Large" 作为 Choices 而 "Color" 选项可能有 "Red" 和 "Blue" 作为选择。

使用 Simple Form 我实际上是在尝试在产品表单上做这样的事情:

问题是,如果用户有多个产品选项(例如尺寸和颜色),它只会在每组 Options 中为他们提供一个单选按钮。所以他们可以 select "Blue" 但不能 "Blue" 和 "XL," 例如。

我可以做的另一件事是使用 as: :check_boxes 而不是 as: :radio_buttons 但是用户可以 select 不止一种颜色(例如红色和蓝色),而只有一个选择应该允许每个选项。

那么什么是最好的 "Rails" 方法来验证关联的每个实例的限制,而不是关联本身?如果必须的话,我可以在客户端 javascript 中执行此操作,但这似乎不如在服务器端进行验证安全。

加上Product应该可以有很多Choices。因此,这并不是对 ProductsChoices 之间关联的真正验证,而是对通过 Options 可用的每组选择限制为 1 Choice 的验证型号。

例如,一件 T 恤可能是红色和 XL,但它不应该是红色和蓝色 + 小号和 XL?

这是我的模型:

class Product < ApplicationRecord
  has_many :choices,  through: :options
end

class Option < ApplicationRecord
  belongs_to :product

  has_many :choices
end

class Choice < ApplicationRecord
  belongs_to :option
end

如果客户假设 order/select 具有规格的产品,您实际上可能需要一个连接模型 (Selection/Order),而不是对产品模型应用验证。 Product 模型似乎只是供您设置用户可以 select 该产品的选项和选择。

如果这是这里的实际情况,您只需创建连接模型并使用多态设置它 "feature." 像这样:

class Order
  belongs_to :product
  belongs_to :featurable, polymorphic: true
  belongs_to :user

  validates_inclusion_of :featurable_type, in: %w[Option Choice]
end

较新的 Rails 版本将验证 belongs_to 字段是否存在。

我说多态是因为我假设选项可能没有选择,有时您可以 select 选项本身。如果所有选项都有选择,那么只需将 belongs_to :featurable 更改为 belongs_to :choice 并删除包含验证。 belongs_to :user 在那里,因为我假设特定用户会输入这个 order/selection.

如果您的产品可能有多种选择 select,那么您的结构可能更像这样:

class ProductOrder
  belongs_to :product
  belongs_to :user
  has_many :choice_selections
end

class ChoiceSelection
  belongs_to :product_order
  belongs_to :featurable, polymorphic: true

  validates_inclusion_of :featurable_type, in: %w[Option Choice]
  validate :unique_option

  def option
    featurable.is_a?(Option) ? featurable : featurable.option
  end

  def unique_option
    return unless product_order.choice_selections.find_by(option: option).present?
    errors.add(:base, 'choice option must be unique')
  end
end

如果所有选项都会有选择:

您不需要多态关联。

class ProductOrder
  belongs_to :product
  belongs_to :user
  has_many :choice_selections
end

class ChoiceSelection
  belongs_to :product_order
  belongs_to :choice
  belongs_to :option

  validates_uniqueness_of :option, scope: :product_order
end

但是,要回答您在上面发布的问题:

我要做的第一件事是在产品模型中创建自定义验证。

务必在产品模型中添加 has_many :options 行,使其看起来更像这样:

class Product < ApplicationRecord
  has_many :options
  has_many :choices, through: :options
end

否则,through可能行不通。

然后,像这样添加验证:

class Product < ApplicationRecord
  # ...

   validate :one_choice_per_option

   private

   def one_choice_per_option
     if options.any? { |option| option.choices.count > 1 }
       errors.add(:options, 'can only have one choice')
     end
   end

  # ...
end

请注意,此验证将阻止您为产品选项创建多个选择。我确实希望它能让您更好地了解如何创建自定义验证。我强烈建议重新评估您的数据库结构以将 product/option/choice 设置和用户 select 分开。

如果您可能会在其他模型中使用此验证,您可能需要考虑制作一个 validator