views:

236

answers:

3

Hi there, I have a basic has_many :through relationship that is bi-directional:

calendars have many calendar_calendar_events
calendars have many events through calendar_calendar_events


events have many calendar_calendar_events
events have many calendars through calendar_calendar_events

I'm wanting to assign calendars to an event with the basic calendar_ids= function that has_many :through sets up, however, I want to override this function to add some extra magic. I've had a look through the rails source and can't find the code for this function. I'm wondering if someone could point me to it. I'll then override it for this class to add the stuff that I want :)

+2  A: 

After a bit of a hunt I found it:

http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/collection_accessor_methods

It didn't look like what I thought it would look like, so that's why I probably missed it. I ended up overriding the calendars= method instead of the calendar_ids= method and everything works well.

Brendon Muir
+2  A: 

You can find the source code in the file lib/active_record/associations.rb at line 1295

    def collection_accessor_methods(reflection, association_proxy_class, writer = true)
      collection_reader_method(reflection, association_proxy_class)

      if writer
        define_method("#{reflection.name}=") do |new_value|
          # Loads proxy class instance (defined in collection_reader_method) if not already loaded
          association = send(reflection.name)
          association.replace(new_value)
          association
        end

        define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
          ids = (new_value || []).reject { |nid| nid.blank? }
          send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
        end
      end
    end

You should definitely avoid to overwrite such this method to add magic stuff. Rails is already "too much magic" sometimes. I would suggest to create a virtual attribute with all your custom logic for several reasons:

  1. some other rails methods might rely on the default implementation
  2. you rely on a specific API that might going to change in future ActiveRecord versions
Simone Carletti
That's a very good point. In my example I just overwrote the calendars= method in my events model. Is that still bad? Is it possible to chain these methods? I've never done that before but I've seen it around a bit. All I want to do is add an extra event to the new_value array of events on the calendars= parameters if necessary.
Brendon Muir
A: 

In response to the answer above, I used alias_method_chain to override the default setter and add my feature. Works quite well, though I'm not sure why I have to send the method setter instead of just using it normally. It didn't seem to work though so this will do :)

  def calendars_with_primary_calendar=(new_calendars)
    new_calendars << calendar unless new_record?
    send('calendars_without_primary_calendar=', new_calendars) # Not sure why we have to call it this way
  end

  alias_method_chain :calendars=, :primary_calendar
Brendon Muir