30

I am using Ruby on Rails v3.2.2. I would like to solve the issue related to the validation of a foreign key when using accepts_nested_attributes_for and validates_associated RoR methods. That is, I have following model classes:

class Article < ActiveRecord::Base
  has_many :category_associations, :foreign_key => 'category_id'

  accepts_nested_attributes_for :category_associations, :reject_if => lambda { |attributes| attributes[:category_id].blank? }
  validates_associated :category_associations
end

class CategoryAssociation < ActiveRecord::Base
  belongs_to :article, :foreign_key => 'article_id'
  belongs_to :category, :foreign_key => 'category_id'

  validates :article_id, :presence => true
  validates :category_id, :presence => true
end

... and I have following controller actions:

class ArticlesController < ApplicationController
  def new
    @article = Article.new
    5.times { @article.category_associations.build }

    # ...
  end

 def create
   @article = Article.new(params[:article])

   if @article.save
     # ...
   else
     # ...
   end
 end
end

With the above code ("inspired" by the Nested Model Form Part 1 Rails Cast) my intent is to store category associations when creating an article (note: category objects are already present in the database; in my case, I would like just storing-creating category associations). However, when I submit the related form from the related view file, I get the following error (I am logging error messages):

{:"category_associations.article_id"=>["can't be blank"], :category_associations=>["is invalid"]}

Why it happens since validates_associated seems to run the method article.category_association.valid? but only if the article.category_association.article_id is not nil? How can I solve the problem with the presence validation of the article_id foreign key?

However, if I comment out the validates :article_id, :presence => true in the CategoryAssociation model class, it works as expected but it seems to be not a right approach to do not validate foreign keys.


If I comment out the validates_associated :category_associations in the Article model class, I still get the error:

{:"category_associations.article_id"=>["can't be blank"]}
Community
  • 1
  • 1
user12882
  • 4,702
  • 9
  • 39
  • 54
  • 1
    Maybe, possible workarounds are: http://kueda.net/blog/2012/09/04/validates_presence_of-and-accepts_nested_attributes_for and http://stackoverflow.com/questions/1209200/how-to-create-nested-objects-using-accepts-nested-attributes-for. – user12882 Nov 12 '12 at 18:25
  • user12882 i have come across something similar, are you using rails 3.2.9? I think it might be a bug in the update.. EDIT: never mind, i see you're using 3.2.2 – jay Nov 13 '12 at 07:08

4 Answers4

45

Use inverse_of to link the associations and then validate the presence of the associated object, not the presence of the actual foreign key.

Example from the docs:

class Member < ActiveRecord::Base
  has_many :posts, inverse_of: :member
  accepts_nested_attributes_for :posts
end

class Post < ActiveRecord::Base
  belongs_to :member, inverse_of: :posts
  validates_presence_of :member
end
Arctodus
  • 5,743
  • 3
  • 32
  • 44
  • 2
    I don't know why that worked, but it definitely did. Thanks! – jerhinesmith Jul 23 '14 at 21:14
  • just had a similar problem and `inverse_of` solved it too, although I'm not sure why :/ – GMA Dec 15 '14 at 17:50
  • 8
    I've found (in rails 4.2) that if you do ```validates :member_id, presence: true``` it won't work, but if you do ```validates :member, presence: true``` it will work ... – Doug May 15 '15 at 17:53
  • 1
    @MarnenLaibow-Koser: I assume the reason is that the member's id isn't assigned yet since the _new_ is used for initialization and thus it isn't stored in the database yet. The association on the other hand is valid even though the associated object doesn't yet have the id (isn't persisted). – Roope Hakulinen Jul 10 '15 at 13:24
  • @Doug I am using rails 4.2.5 but I get the same issue. Specifying `inverse_of` fixes it. – Vedant Agarwala Aug 01 '16 at 13:34
  • I think this is also an issue with Rails 5 because it validates the presence of FKs automatically. – Arel Feb 16 '17 at 05:25
0

Since you have a possible nested form with accepts_nested_attributes_for, therefore in CategoryAssociation you need to make the validation conditional, requiring presence for only for only updates:

validates :article_id, presence: true, on: :update

Aside from Active Record associations, you should have a foreign key constraints at the db level.

user1322092
  • 4,020
  • 7
  • 35
  • 52
0

If you're stucked with this kind of errors too, try to replace:

validates :article_id, :presence => true
validates :category_id, :presence => true

with:

validates :article, :presence => true
validates :category, :presence => true

worked for me.

-2

Validations will run on create or save (as you'd expect), so ask yourself, "at each one of those is there a saved instance being referred to?", because without a save an instance won't have an id as it's the database that assigns the id.


Edit: Like I've said in the comments, if you're going to downvote then leave a comment as to why.

ian
  • 12,003
  • 9
  • 51
  • 107
  • **You**: [...] ask yourself, "at each one of those is there a saved instance being referred to? **I**: Of course that at each one of those a saved instance being referred is **not** there (that is, it does **not** exist yet) since I am trying to create new ones in "one go". – user12882 Nov 12 '12 at 14:36
  • Firstly, if you're downvoting an answer either give the reason or put in the real answer yourself. @user12882 If you know what the problem is, what's the real question? Just save the associated objects first and _then_ save the article. Then it validates. You're not actually validating foreign keys at this level anyway, the database does that, you're validating associations. – ian Nov 12 '12 at 16:37
  • **You**: If you know what the problem is, what's the real question? **I**: I know the behavior if I change somethings, not the problem itself; since that, and since your last comment, my sub-question is: why I should first save *associated objects* and then the *article*? should it be the inverse? more, why should I write my own "handler" code in order to save those objects since RoR "already provides" / "can make" that by using `accepts_nested_attributes_for` (even if in my case it seems do not work as expected because the `validates :article_id, :presence => true`)? – user12882 Nov 12 '12 at 16:56
  • What do you exactly mean with "You're not actually validating foreign keys at this level anyway, the database does that, you're validating associations"? – user12882 Nov 12 '12 at 16:56
  • there's no need to keep quoting, I can see the comments ;) The database engine validates foreign keys, when you add foreign key constraints. You're validating objects and their associations, it's the business logic layer. As to why you'd save the associated objects first, simply because you've asked another object to validate it's associations to them. They need to be existant for you to validate against them. – ian Nov 12 '12 at 17:08
  • ;-) If you refer to an `has_many :through` association (*BTW*: the `:category_associations` will be used in a further `has_many :through` association that is not mentioned in the question), maybe it could be right to save the associated objects first, otherwise why should I save associated objects first? I really don't understand that point. – user12882 Nov 12 '12 at 17:20
  • For any association that is validated, whether it be an object assocation or a foreign key relationship, the associated thing must exist prior to the thing that references it. There's no right or wrong there, it's just how it must work. If you believe that the article should exist prior to the category then change the order of association or defer validation. – ian Nov 12 '12 at 17:23
  • OK, maybe I understood. Assuming that, and focusing on my case (see the code in the question), *before* to save `category_associations` the `article` must first be created and the `article_id` foreign key value must be set for all those `category_associations`. *This doesn't happen* (that is, I get the error `{:"category_associations.article_id"=>["can't be blank"]}`), even if the code seems to be correct and category objects exist (that is, at each one of those association instance there is a saved instance being referred to). At this time, that's the point related to my trouble. – user12882 Nov 12 '12 at 17:32
  • Because you've validated in both directions for an id, which means it'll never work, as on creation each looks for the other's id but one is created first. Change the validations from validating the id (which would be better done by a foreign key constraint in the database) and instead validate for an _instance_. Then you can have an Article instance that has unsaved instances of Category - it will validate and on save everything tallies up. – ian Nov 14 '12 at 14:42