What is the default code for bulk has_many :through join assignment in rails?
-
16-09-2019 - |
Question
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 :)
Solution
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:
- some other rails methods might rely on the default implementation
- you rely on a specific API that might going to change in future ActiveRecord versions
OTHER TIPS
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.
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