Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #3692 (closed defect: fixed)

Opened 2 years ago

Last modified 2 years ago

has_and_belongs_to_many not populating related items on new record creation

Reported by: sam.barnum@gmail.com Assigned to: David
Priority: high Milestone:
Component: ActiveRecord Version: 1.1.1
Severity: major Keywords: has_and_belongs_to_many, habtm
Cc: sclaret@bccrc.ca, sam.barnum@gmail.com, jeffcalow@hotmail.com, david@loudthinking.com, josh@hasmanythrough.com

Description

from http://wiki.rubyonrails.com/rails/pages/CheckboxHABTM

When I create a new item and try setting the foo_ids where foo is a many-to-many, it doesn’t get saved to the database. When I update a record that already exists in the database, it works (the join records are created). To get it to work, I need to re-fetch the newly create row by its id, then call update_attributes on it. {{{ def create

@department = Department.new(params[:department]) # FIX! can't set many-to-many relationships on creation if @department.save

Department.find(@department.id).update_attributes(params[:department]) # hack to get around many-to-many not saving until object has an id flash[:notice] = 'Department was successfully created.' redirect_to :action => 'list'

else

render :action => 'new'

end

end

}}}

Judging from the discussion on the wiki, this is a bug which appeared in 1.0.0, as this used to work in older versions of rails.

Attachments

habtm_create.patch.diff (2.7 kB) - added by josh@hasmanythrough.com on 05/29/06 02:18:26.
Patch corrects habtm.create, with more extensive test cases
habtm_create_patch.diff (4.3 kB) - added by josh@hasmanythrough.com on 05/29/06 02:43:16.
Let's try it WITH docs this time. (And correct typo in file name.)

Change History

02/20/06 20:05:45 changed by jeffcalow@hotmail.com

I have an interesting variation that may indicate what is going wrong. With the Book class, only the first lexical HABTM saves to the join table properly, the second one does not (e.g. only genres is saved in below classes). This is stable in that when I reversed the order of :genres and :authors, only authors got saved. Maybe there is a problem with the first (or last) callback?

class Book < ActiveRecord::Base
  has_and_belongs_to_many :genres
  has_and_belongs_to_many :authors
end

class Genre < ActiveRecord::Base
  has_and_belongs_to_many :books
end

class Author < ActiveRecord::Base
  has_and_belongs_to_many :books
end

  def create
    @book = Book.new(params[:book])
    if @book.save
      Book.find(@book.id).update_attributes(params[:book]) # work around for bug 3692
      flash[:notice] = 'Book was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

Looking in the log, the select's for the HABTM items were done, but the insert was not.

When I applied the work around described in this ticket, both got saved.

03/02/06 10:56:03 changed by tmacedo@webreakstuff.com

I've also encountered this issue and used a similar aproach in order to solve. By looking at the log I see that the INSERT for the join table isn't made upon the creation, only when updating.

03/13/06 19:51:18 changed by charles.dupont@vanderbilt.edu

  • keywords changed from has_and_belongs_to_many to has_and_belongs_to_many, hatbm, needs_review.

03/20/06 06:48:25 changed by david

  • keywords changed from has_and_belongs_to_many, hatbm, needs_review to has_and_belongs_to_many, hatbm.

03/20/06 23:56:26 changed by anonymous

  • keywords changed from has_and_belongs_to_many, hatbm to has_and_belongs_to_many, habtm.

05/26/06 23:56:41 changed by sclaret@bccrc.ca

  • version changed from 1.0.0 to 1.1.1.
  • severity changed from normal to major.

Jeff I am having the exact same problem as you with these classes:

class Group < ActiveRecord::Base
  has_and_belongs_to_many :users
end

class User < ActiveRecord::Base
  has_and_belongs_to_many :groups
end

Only :groups saves to the join table properly. I am using rails 1.1.2 so this bug is obviously still not fixed.

05/27/06 00:07:39 changed by sclaret@bccrc.ca

This bug is also present in edge rails as of now.

05/27/06 01:06:00 changed by sclaret@bccrc.ca

  • cc set to sclaret@bccrc.ca, sam.barnum@gmail.com, jeffcalow@hotmail.com, david@loudthinking.com.

Here is my workaround. It seems that this is not a problem with IDs at all. The way I worked around it would indicate that it is a bug in the validation code.

  #workaround for bug #3692
  @@user_ids = nil
  def before_validation_on_create
    unless self.users.empty?
      @@user_ids = self.users.collect { |user| user.id }
      self.users.clear
    end 
  end
  def after_validation_on_create
    unless @@user_ids == nil
      self.user_ids = @@user_ids
      @@user_ids = nil
    end 
  end

05/29/06 02:18:26 changed by josh@hasmanythrough.com

  • attachment habtm_create.patch.diff added.

Patch corrects habtm.create, with more extensive test cases

05/29/06 02:19:02 changed by josh@hasmanythrough.com

  • cc changed from sclaret@bccrc.ca, sam.barnum@gmail.com, jeffcalow@hotmail.com, david@loudthinking.com to sclaret@bccrc.ca, sam.barnum@gmail.com, jeffcalow@hotmail.com, david@loudthinking.com, josh@hasmanythrough.com.

Here's a patch that corrects the behavior, and some more extensive tests that validate that habtm.build and habtm.create populate the join table, both when the base object is new and when it has already been saved. Note that this habtm.create method follows the example of has_many.create, in that when the base object is new (not saved), the created objects aren't saved either; in this case create() is equivalent to build().

05/29/06 02:43:16 changed by josh@hasmanythrough.com

  • attachment habtm_create_patch.diff added.

Let's try it WITH docs this time. (And correct typo in file name.)

05/29/06 03:48:19 changed by rick

  • status changed from new to closed.
  • resolution set to fixed.

(In [4379]) Fix the has_and_belongs_to_many #create doesn't populate the join for new records. Closes #3692 [josh@hasmanythrough.com]

07/24/06 03:12:36 changed by anonymous

blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2 blog2