Reputation: 49
I have following models:
Product (id, name):
has_many :prices
Product_price (id, product_id, price): The thing is that each product can have different prices
belongs_to :product
Subscription (id, name):
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
How do I validate it, so that for a given subscription it's impossible to have a product with two different prices?
For example:
I have two products: Notebook (id: 1) and Pencil (id: 2) And their prices are:
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)
And a Basic subscription:
(id: 1, name: "Basic")
Let's say I have Subscription_price_set:
(id: 1, product_price_id: 1, subscription_id: 1)
Now I should be able to create another Subscription_price_set
with subscription_id: 1
, but the only allowable product_price_ids should be id: 4 and id: 5.
Any hints on how to achieve that?
Upvotes: 0
Views: 765
Reputation: 49
I've created a custom validation method in Subscription_price_set
model, and it did the trick :)
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
Upvotes: 1
Reputation: 102443
Use scope
to make a uniqueness validation on multiple columns:
validates_uniqueness_of :subscription_id, scope: :product_price_id
However this does not actually guarantee uniqueness.
To safeguard against race conditions you need to compliment the validation with a database index:
class AddIndexToSubscriptionPriceSets < ActiveRecord::Migration[6.0]
def change
add_index :subscription_price_sets, [:subscription_id, :product_price_id] , unique: true
end
end
Your also using the foreign_key
option all wrong. Rails is driven by convention over configuration and will derive the foreign key from the name of the association. You only ever need to specify foreign_key
if the name of the association does not match.
belongs_to :subscription
belongs_to :product_price
On the has_many
association it will actually cause an error:
has_many :subscription_price_sets,
foreign_key: :subscription_price_set_id,
inverse_of: :subscription
This will result in the following join
JOINS subscription_price_sets ON subscription_price_sets.subscription_price_set_id = subscriptions.id
Which of course will blow up as there is no such column. The foreign_key option on a has_many association is used to specify which column on the other table that corresponds to this table. All you really need is:
has_many :subscription_price_sets
Rails can also deduce the inverse of an association based and you only need to specify when you are "going off the rails" and the names don't match up.
Upvotes: 2