0

I need to perform operations on all of a Rails 5 ActiveRecord object's associated objects, but I don't want to have to write a separate method call explicitly for each object.
For example, let's say "vacation" is the object I'm dealing with, and it could have many associated objects (let's assume all has_one for simplicity): agent, traveler, plane, ship, hotel. I could do:

do_stuff_to_assoc_object(vacation.agent)

do_stuff_to_assoc_object(vacation.traveler)

do_stuff_to_assoc_object(vacation.plane) ...etc.

but that's very inelegant, especially if there are many associations. Thanks to How to get all the associated models from ActiveRecord object? , I know I can get the associated objects' model class names as strings, or the AssociationReflection object, but how how do I get the actual object they represent?

   parent_object.reflect_on_all_associations(:has_one).map(&:class_name).each do |model_name|
      ### how to convert model_name into the object? 
      do_stuff_to_assoc_object(obj)
    end

  def do_stuff_to_assoc_object(obj)
     # I do things to the associated object here
  end
Superduper
  • 1,433
  • 2
  • 9
  • 14

1 Answers1

1

Model name can be converted to object using public_send in case you fetched the association models, check below:

parent_object.reflect_on_all_associations(:has_one).map(&:class_name).each do |model_name|
      # assuming that parent_object is the object that has all associations.
      obj = parent_object.public_send(model_name)
      do_stuff_to_assoc_object(obj)
    end

  def do_stuff_to_assoc_object(obj)
     # I do things to the associated object here
  end

As per @Clemens Kofler comment, to avoid double iteration, we can remove the .map as following:

parent_object.reflect_on_all_associations(:has_one).each do |association|
          # assuming that parent_object is the object that has all associations.
          obj = parent_object.public_send(association.class_name)
          do_stuff_to_assoc_object(obj)
        end

      def do_stuff_to_assoc_object(obj)
         # I do things to the associated object here
      end

Reference:
https://apidock.com/ruby/Object/public_send

Shiko
  • 2,448
  • 1
  • 24
  • 31
  • 2
    The only thing I'd change about this code is to avoid the double iteration and simply do it like so: `parent_object.reflect_on_all_associations(:has_one).each do |association|` and then use `parent_object.public_send(association.class_name)`. – Clemens Kofler Feb 27 '20 at 23:54
  • @ClemensKofler You have reason, no need to include it – Shiko Feb 28 '20 at 01:52
  • @Shiko public_send was the trick, thanks. However, I had more work to do: The first ```parent_object``` should really be ```parent_class``` (reflection applies to classes, not instances), and I had to de-classify the association's class name: ```object.public_send(association.class_name.underscore)``` It's working in my application so I'm happy, but I'd like to think there's still a more robust approach that won't break for some class naming edge cases. – Superduper Feb 28 '20 at 05:46