Rails 联合验证 table

Rails validation on a joint table

我有以下型号:

产品(id,名称):

    has_many :prices

Product_price (id, product_id, price): 每个产品可以有不同的价格

    belongs_to :product

订阅(id,名称):

    has_many :subscription_price_sets,
             foreign_key: :subscription_price_set_id,
             inverse_of: :subscription
    has_many :product_prices, through: :subscription_price_sets

Subscription_price_set (id, product_price_id, subscription_id):

    belongs_to :subscription,
               foreign_key: :subscription_id
    belongs_to :product_price,
               foreign_key: :product_price_id

如何验证它,以便对于给定的订阅,不可能有两种不同价格的产品?

例如:

我有两个产品:Notebook (id: 1) 和 Pencil (id: 2) 他们的价格是:

Product_prices:

(id: 1, product_id: 1, price: 4)
(id: 2, product_id: 1, price: 12)
(id: 3, product_id: 1, price: 10)
(id: 4, product_id: 2, price: 3)
(id: 5, product_id: 2, price: 2)

以及基本订阅:

(id: 1, name: "Basic")

假设我有 Subscription_price_set:

(id: 1, product_price_id: 1, subscription_id: 1)

现在我应该可以用 subscription_id: 1 创建另一个 Subscription_price_set,但是唯一允许的 product_price_id 应该是 id: 4 和 id: 5.

关于如何实现的任何提示?

使用scope对多列进行唯一性验证:

validates_uniqueness_of :subscription_id, scope: :product_price_id

然而,这实际上并不能保证唯一性。

为了防止竞争条件,您需要使用数据库索引补充验证:

class AddIndexToSubscriptionPriceSets < ActiveRecord::Migration[6.0]
  def change
    add_index :subscription_price_sets, [:subscription_id, :product_price_id] , unique: true
  end
end

你还使用了 foreign_key 选项全错了。 Rails 由约定优于配置驱动,将从关联名称派生外键。如果关联名称不匹配,您只需要指定 foreign_key

belongs_to :subscription
belongs_to :product_price

has_many关联上它实际上会导致错误:

has_many :subscription_price_sets,
         foreign_key: :subscription_price_set_id,
         inverse_of: :subscription

这将导致以下加入

JOINS subscription_price_sets ON subscription_price_sets.subscription_price_set_id = subscriptions.id

当然会爆炸,因为没有这样的专栏。 has_many 关联上的 foreign_key 选项用于指定另一个 table 上的哪一列对应于此 table。您真正需要的是:

has_many :subscription_price_sets

Rails 也可以推导出基于关联的倒数,你只需要指定当你是 "going off the rails" 并且名称不匹配时。

我在 Subscription_price_set 模型中创建了一个自定义验证方法,它成功了:)

validate :product_uniqness

private

def product_uniqness
  return unless subscription.product_prices.pluck(:product_id)
                         .include?(product_price.product_id)

  errors.add(:product_price_id, 'You can\'t add the same product twice')
end