habtm - stack too deep error

Hi all,

I am trying to create many-to-many relationship between models "Topic"
and "Category"

I am trying following in rails console which gives me exception.

t1 = Topic.new(:title => "t1")
c1 = Category.new(:name => "c1")
t1.categories << c1
t1.save

Gives Exception........

Am I doing something really wrong?

Thanks,
Siddharth

Here is my Topic model
class Topic < ActiveRecord::Base
  has_and_belongs_to_many :categories,
    :class_name => "Category",
    :join_table => "topics_categories",
    :association_foreign_key => "category_id",
    :foreign_key => "topic_id",
    :after_add => :add_topic_to_category,
    :after_remove => :remove_topic_from_category

    def add_topic_to_category(category)
      category.topics << self unless category.topics.include?(self)
    end
    def remove_topic_from_category(category)
      category.topics.delete(self) rescue nil
    end
end

and Category
class Category < ActiveRecord::Base
  has_and_belongs_to_many :topics,
    :class_name => "Topic",
    :join_table => "topics_categories",
    :association_foreign_key => "topic_id",
    :foreign_key => "category_id"
end

and topics_categories table
create table topics_categories (
  topic_id bigint,
  category_id bigint
);

Full stack trace
Loading development environment.

t1 = Topic.new(:title => "t1")

=> #<Topic:0xb766da88 @new_record=true,
@attributes={"created_on"=>nil, "num_comments"=>nil, "title"=>"t1",
"updated_on"=>nil, "url"=>"", "blocked"=>0, "description"=>"",
"user_id"=>nil, "num_views"=>nil}>

c1 = Category.new(:name => "c1")

=> #<Category:0xb7614cd0 @new_record=true, @attributes={"name"=>"c1",
"blocked"=>0, "description"=>"", "usage_count"=>nil}>

t1.categories << c1

=> [#<Category:0xb7614cd0 @topics=[#<Topic:0xb766da88
@categories=[...], @new_record=true, @attributes={"created_on"=>nil,
"num_comments"=>nil, "title"=>"t1", "updated_on"=>nil, "url"=>"",
"blocked"=>0, "description"=>"", "user_id"=>nil, "num_views"=>nil}>],
@new_record=true, @attributes={"name"=>"c1", "blocked"=>0,
"description"=>"", "usage_count"=>nil}>]

t1.save

SystemStackError: stack level too deep
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:912:in
`validate_associated_records_for_categories'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:802:in
`run_validations'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:800:in
`run_validations'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:764:in
`valid_without_callbacks'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb:310:in
`valid?'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:918:in
`validate_associated_records_for_topics'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in
`method_missing'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/has_and_belongs_to_many_association.rb:81:in
`method_missing'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:913:in
`validate_associated_records_for_topics'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:802:in
`run_validations'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:800:in
`run_validations'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:764:in
`valid_without_callbacks'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb:310:in
`valid?'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:918:in
`validate_associated_records_for_categories'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in
`method_missing'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/has_and_belongs_to_many_association.rb:81:in
`method_missing'
... 2796 levels...
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb:310:in
`valid?'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:918:in
`validate_associated_records_for_categories'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/association_proxy.rb:110:in
`method_missing'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations/has_and_belongs_to_many_association.rb:81:in
`method_missing'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:913:in
`validate_associated_records_for_categories'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:802:in
`run_validations'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:800:in
`run_validations'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:764:in
`valid_without_callbacks'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb:310:in
`valid?'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/validations.rb:735:in
`save_without_transactions'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/transactions.rb:126:in
`save'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/connection_adapters/abstract/database_statements.rb:51:in
`transaction'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/transactions.rb:91:in
`transaction'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/transactions.rb:118:in
`transaction'
        from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/transactions.rb:126:in
`save'
        from (irb):4>>

Siddharth Karandikar wrote:

I am trying to create many-to-many relationship between models "Topic"
and "Category"

Check to ensure that your model file is not being required twice. I have had this before when the habtm macro is defined twice by accident because the model was being required twice.

Eric

Siddharth Karandikar wrote:
> I am trying to create many-to-many relationship between models "Topic"
> and "Category"

Check to ensure that your model file is not being required twice. I have
had this before when the habtm macro is defined twice by accident
because the model was being required twice.

Thanks Eric for reply.

I far as I know, Rails handles all the "requires" part, so how should
we add a check there?
Is this rails console specific issue?

- Siddharth

here is your problem:

from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:913:in
`validate_associated_records_for_topics'

from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:913:in
`validate_associated_records_for_categories'

from this i assume you left out the validations from your models in
your post as this suggests you have validates_associated :topics in
your Category model and validates_associated :categories in your Topic
model.

this is a big no-no as is causes exactly what you are experiencing, an
infinite validation loop. when you save your topic it tries to
validate the associated category which then tries to validate the
associated topic which then tries to validate the associated category
and so on.

here is your problem:

from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:913:in
`validate_associated_records_for_topics'

from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/associations.rb:913:in
`validate_associated_records_for_categories'

from this i assume you left out the validations from your models in
your post as this suggests you have validates_associated :topics in
your Category model and validates_associated :categories in your Topic
model.

this is a big no-no as is causes exactly what you are experiencing, an
infinite validation loop. when you save your topic it tries to
validate the associated category which then tries to validate the
associated topic which then tries to validate the associated category
and so on.

Thanks Chris for pointing out the real cause. It has to be because of
infinite loop.
But if you see the model classes that I have posted in my first mail,
those doesn't contain any of these "validates_associated" methods. So
rails is trying to do this validations on its own.

Now the question is, how to tell rails not to validate for these things?

- सिद्धार्थ [Siddharth]
[written using www.paahijen.com]

Shit,

You really over complicated this.

rename your table to categories_topics and

> class Topic < ActiveRecord::Base
> has_and_belongs_to_many :categories,
> :class_name => "Category",
> :join_table => "topics_categories",
> :association_foreign_key => "category_id",
> :foreign_key => "topic_id",
> :after_add => :add_topic_to_category,
> :after_remove => :remove_topic_from_category

becomes

class Topic < ActiveRecord::Base
   has_and_belongs_to_many :categories
end

Thanks for showing me the simpler version. But I doubt this will solve
the "stack too deep" issue.

- Siddharth

I agree with Keynan on this, although I probably would have said it
differently. :slight_smile:

might I suggest that you remove the :after_add callback from the habtm
and see what happens? I don't see a need to have that callback
because the relationship will exist because you just added it.
doesn't matter if you create the relationship via Topic or Category,
it's still the same relationship. same goes for your after_remove
callback. you'd just be trying to remove the relationship you just
removed.

So my suggestion is this

Category < ActiveRecord::Base
  has_and_belongs_to_many :topics,
      # this line not necessary if you name join table "categories_topics"
      :join_table => "topics_categories",
      :uniq => true
end

Topic < ActiveRecord::Base
  has_and_belongs_to_many :categories,
      # this line not necessary if you name join table "categories_topics"
      :join_table => "topics_categories",
      :uniq => true
end

try this out and see what happens

Cool. That works!

Hmm .. then I need to see why that's been done in one of the rails
recipes chapter. :slight_smile:
Anyways, thanks guys.

- Siddharth