Problem with has_many :through
Posted 10/21/2006 by dnaffis 1 CommentsAdd Your Comment
I recently ran into a problem using has_many :through relationships. The edge code works fine when using standard id’s but for those using legacy databases or non-standard id’s in your join table the code fails when trying to add or delete an association.
Something like this would fail:
create_table :books, :force => true do |t|
t.column :name, :string
end
create_table :citations, :id => false, :force => true do |t|
t.column :book1_id, :integer
t.column :book2_id, :integer
end
class Book < ActiveRecord::Base
has_many :citations, :foreign_key => 'book1_id'
has_many :references, :through => :citations, :source => :reference_of, :uniq => true
end
class Citation < ActiveRecord::Base
belongs_to :reference_of, :class_name => "Book", :foreign_key => :book2_id
belongs_to :book1, :class_name => "Book", :foreign_key => :book1_id
belongs_to :book2, :class_name => "Book", :foreign_key => :book2_id
end
awdr = Book.create!(:name => "Agile Web Development with Rails")
rfr = Book.create!(:name => "Ruby for Rails")
awdr.references << rfr
awdr.delete(rfr)
There’s further information at http://dev.rubyonrails.org/ticket/6466
If you’re running into this problem you can patch your local version of rails. First freeze edge in your tree. Then create the file has_many_through_patch.rb in your lib directory with the following code:
module ActiveRecord
class HasManyThroughCantDisassociateNewRecords < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot disassociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
end
end
module Associations
class HasManyThroughAssociation
# Construct attributes for :through pointing to owner and associate.
def construct_join_attributes(associate)
construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
end
# Remove +records+ from this association. Does not destroy +records+.
def delete(*records)
return if records.empty?
records.each { |associate| raise_on_type_mismatch(associate) }
through = @reflection.through_reflection
raise ActiveRecord::HasManyThroughCantDisassociateNewRecords.new(@owner, through) if @owner.new_record?
load_target
klass = through.klass
klass.transaction do
flatten_deeper(records).each do |associate|
raise_on_type_mismatch(associate)
raise ActiveRecord::HasManyThroughCantDisassociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
@owner.send(@reflection.through_reflection.name).proxy_target.delete(klass.delete_all(construct_join_attributes(associate)))
@target.delete(associate)
end
end
self
end
end
end
end
Then in your environment.rb add the following:
require 'has_many_through_patch'
You should be able to add and delete now until the patch is committed.
November 12th, 2008 at 07:48 PM
4x9d8ipzfglcw7kk