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.id<http://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.id<http://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
On 11/14/05, Peter Donald
<peter.j.donald-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
wrote:>
> On 11/15/05, Chris Hall
<christopher.k.hall-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> > but what if i need to update the time? ie, i want to track not only
that
> > the article was read, but WHEN it was last read? would i still use
> > push_with_attributes or is there another mechanism i am not aware of
> that I
> > can use to update the last_read column in the join table?
>
> The path of least resistence is to change the relationship into a
> model object. You would replace the HABTM with a has_many and create a
> new "Reading" model object.
>
> Hope that helps,
>
> --
> Cheers,
>
> Peter Donald
>
> RealityForge.org: http://www.realityforge.org/
> _______________________________________________
> Rails mailing list
> Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org
> http://lists.rubyonrails.org/mailman/listinfo/rails
>
_______________________________________________
Rails mailing list
Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org
http://lists.rubyonrails.org/mailman/listinfo/rails