I had the same problem. I solved it by adding an “update_attributes”
method
to HABTM. I added this code to application_helper.rb:
module ActiveRecord
module Associations
class HasAndBelongsToManyAssociation
def update_attributes(record, join_attributes = {})
Did they pass in an ID or an object?
if record.is_a? ActiveRecord::Base
Check the type of the passed in record
raise_on_type_mismatch(record)
Find the actual record in @target, if @target is loaded
if loaded?
record_in_arr = @target.find { | item | item == record }
raise ActiveRecord::RecordNotFound, “#{record.class}
#{record.idhttp://record.id}
not found in collection” unless !record_in_arr.nil?
record = record_in_arr
record_id = record.id http://record.id
else
record_id = record.id http://record.id
record = nil
end
else
The record isn’t an ActiveRecord, assume it’s an ID
record_id = record.to_i
If the target is loaded, find the record in the target
if loaded?
Find the actual record in @target
record_in_arr = @target.find { | item | item.id http://item.id ==
record_id }
raise ActiveRecord::RecordNotFound, “Item with ID #{record_id} not found
in
collection” if record_in_arr.nil?
record = record_in_arr
else
Not loaded- for performance, don’t load it, just do an update based on
the
ID
record = nil
end
end
Break the join_attributes into columns and values for those columns
cols_and_vals = join_attributes.to_a.transpose
Join the columns together with ’ = ?, ', so the result for [a, b]
would be ‘a = ?, b’ NOTE: We will have to add a trailing ’ = ?’
in the SQL
col_string = cols_and_vals[0].join(’ = ?, ')
#NOTE: :before_update doesn’t do anything right now- this is “future
proofing”
callback(:before_update, record)
Do the SQL, passing in the args
ret = @owner.connection().update(sanitize_sql([“UPDATE #{@join_table}
SET
#{col_string} = ? WHERE #{@association_class_primary_key_name} = ? AND
#{@association_foreign_key} = ?”, cols_and_vals[1],
@owner.idhttp://owner.id,
record_id].flatten), “Update Attributes”)
Fix up @target, the array of items IF it’s loaded
join_attributes.each { | att, att_val | record[att] = att_val } if
!record.nil?
#NOTE: :after_update doesn’t do anything right now- this is “future
proofing”
callback(:after_update, record)
return ret
end
end
end
class ActiveRecord::Base
#MES- CUSTOMIZATION: Making it so that HABTM can support before_update
and
after_update callbacks
FROM active_record\associations.rb
def self.has_and_belongs_to_many(association_id, options = {},
&extension)
#MES- Note that the ONLY difference between this function and
ActiveRecord::Associations::ClassMethods is that the validation
supports
the :before_update, and :after_update option
options.assert_valid_keys(
:class_name, :table_name, :foreign_key, :association_foreign_key,
:conditions, :include,
:join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq,
:before_add, :after_add,
:before_remove, :after_remove, :extend, :before_update, :after_update
)
options[:extend] = create_extension_module(association_id, extension) if
block_given?
association_name, association_class_name,
association_class_primary_key_name
associate_identification(association_id, options[:class_name],
options[:foreign_key])
require_association_class(association_class_name)
options[:join_table] ||=
join_table_name(undecorated_table_name(self.to_s),
undecorated_table_name(association_class_name))
add_multiple_associated_save_callbacks(association_name)
collection_accessor_methods(association_name, association_class_name,
association_class_primary_key_name, options,
HasAndBelongsToManyAssociation)
Don’t use a before_destroy callback since users’ before_destroy
callbacks will be executed after the association is wiped out.
old_method = “destroy_without_habtm_shim_for_#{association_name}”
class_eval <<-end_eval
alias_method :#{old_method}, :destroy_without_callbacks
def destroy_without_callbacks
#{association_name}.clear
#{old_method}
end
end_eval
add_association_callbacks(association_name, options)
deprecated api
deprecated_collection_count_method(association_name)
deprecated_add_association_relation(association_name)
deprecated_remove_association_relation(association_name)
deprecated_has_collection_method(association_name)
end
def self.add_association_callbacks(association_name, options)
#MES- Note that the ONLY difference between this function and the
original
is the
addition of the before_update and after_update callbacks
#callbacks = %w(before_add after_add before_remove after_remove)
callbacks = %w(before_add after_add before_remove after_remove
before_update
after_update)
callbacks.each do |callback_name|
full_callback_name =
“#{callback_name.to_s}for#{association_name.to_s}”
defined_callbacks = options[callback_name.to_sym]
if options.has_key?(callback_name.to_sym)
class_inheritable_reader full_callback_name.to_sym
write_inheritable_array(full_callback_name.to_sym,
[defined_callbacks].flatten)
end
end
end
end