Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Failing validations in join model when using has_many :through

My full code can be seen at https://github.com/andyw8/simpleform_examples

I have a join model ProductCategory with the following validations:

validates :product, presence: true
validates :category, presence: true

My Product model has the following associations:

has_many :product_categories
has_many :categories, through: :product_categories

When I try to create a new product with a category, the call to @product.save! in the controller fails with:

Validation failed: Product categories is invalid

When I remove the validations, everything works and the join models are saved correctly.

I'm using strong_parameters but I don't think that should be related to this issue.

like image 393
Andy Waite Avatar asked Jan 03 '13 14:01

Andy Waite


2 Answers

This is a "racing condition" in the callback chain.

When you create a product it doesn't have any id before it is saved, therefore there is no product in the scope of ProductCategory.

Product.new(name: "modern times", category_ids:[1, 2]) #=> #<Product id: nil >

At that stage of validation (before saving), ProductCatgory cannot assign any id to it's foreign key product_id.

That's the reason you have association validations : so that the validation happens in the scope of the whole transaction

UPDATE: As said in the comment you still can't ensure presence of a product/category. There's many ways around depending on why you want do this (e.g direct access to ProductCategory through some form)

  • You can create a flag to have validates :product, presence: true, if: :direct_access?
  • or if you can only update them: validates :product, presence: true, on: "update"
  • create your product first (in the products_controller) and add the categories after

... But indeed these are all compromises or workarounds from the simple @product.create(params)

like image 81
charlysisto Avatar answered Nov 01 '22 04:11

charlysisto


Specifying inverse_of on your joining models has been documented to fix this issue:

https://github.com/rails/rails/issues/6161#issuecomment-6330795 https://github.com/rails/rails/pull/7661#issuecomment-8614206

Simplified Example:

class Product < ActiveRecord::Base
  has_many :product_categories, :inverse_of => :product
  has_many :categories, through: :product_categories
end

class Category < ActiveRecord::Base
  has_many :product_categories, inverse_of: :category
  has_many :products, through: :product_categories
end

class ProductCategory < ActiveRecord::Base
  belongs_to :product
  belongs_to :category

  validates :product, presence: true
  validates :category, presence: true
end

Product.new(:categories => [Category.new]).valid? # complains that the ProductCategory is invalid without inverse_of specified

Adapted from: https://github.com/rails/rails/issues/8269#issuecomment-12032536

like image 10
cweston Avatar answered Nov 01 '22 04:11

cweston