Finding all by Polymorphic Type in Rails?
Asked Answered
W

5

10

Is there a way to find all Polymorphic models of a specific polymorphic type in Rails? So if I have Group, Event, and Project all with a declaration like:

has_many :assignments, :as => :assignable

Can I do something like:

Assignable.all

...or

BuiltInRailsPolymorphicHelper.all("assignable")

That would be nice.

Edit:

... such that Assignable.all returns [Event, Group, Product] (array of classes)

Wan answered 23/2, 2010 at 0:2 Comment(1)
I have changed my answer based on the additional information you gave. I think it will address your need.Bradstreet
B
16

There is no direct method for this. I wrote this monkey patch for ActiveRecord::Base. This will work for any class.

class ActiveRecord::Base

  def self.all_polymorphic_types(name)
    @poly_hash ||= {}.tap do |hash|
      Dir.glob(File.join(Rails.root, "app", "models", "**", "*.rb")).each do |file|
        klass = File.basename(file, ".rb").camelize.constantize rescue nil
        next unless klass.ancestors.include?(ActiveRecord::Base)

        klass.
          reflect_on_all_associations(:has_many).
          select{ |r| r.options[:as] }.
          each do |reflection|
            (hash[reflection.options[:as]] ||= []) << klass
          end
      end
    end
    @poly_hash[name.to_sym]
  end

end

Now you can do the following:

Assignable.all_polymorphic_types(:assignable).map(&:to_s)
# returns ['Project', 'Event', 'Group']
Bradstreet answered 23/2, 2010 at 1:2 Comment(9)
How do I implement this? do I go to class ActiveRecord::Base to modify? cant seem to find this class in my Project..Tartuffe
@Tartuffe refer to this answer: #2329484Bradstreet
This is beautiful! Ain't there a way to avoid having to open the model files? Doesn't Rails knows all the classes already?Alienor
@AugustinRiedinger In the prod mode you can use ActiveRecord::Base.subclasses, in dev mode the classes are loaded only after they are referred explicitly. So it is not too reliable. My approach reads the file names(not the content) and it is done once for the life time of the process. So it is tolerable..Bradstreet
Yeah I googled it too. The fact that it is read only once makes it acceptable indeed. But it would be even nicer that it loads on startup instead of at first call because the first one is actually much slower...Alienor
@AugustinRiedinger, you can add an initializer file( config/initializers/active_record.rb) to your project and call ActiveRecord::Base. all_polymorphic_types('foo') in it.Bradstreet
Yeah, but I meant for all classe. Which means opening the models folder again, unless it is loaded at first.Alienor
@AugustinRiedinger calling ActiveRecord::Base. all_polymorphic_types loads all classes.Bradstreet
Awesome. This really should be part of AR itself. Tremendously helpful for example for building forms.Supererogation
C
1

You can also try this way.cause above solution doesn't work for me cause i had some mongo's model.

def get_has_many_associations_for_model(associations_name, polymorphic=nil)

  associations_name = associations_name.to_s.parameterize.underscore.pluralize.to_sym
  active_models = ActiveRecord::Base.descendants
  get_model = []
  active_models.each do |model|

    has_many_associations =model.reflect_on_all_associations(:has_many).select{|a|a.name==associations_name }
    has_many_associations = has_many_associations.select{ |a| a.options[:as] == polymorphic.to_s.to_sym} if polymorphic.present?
    get_model << model if has_many_associations.present?

  end
  get_model.map{|a| a.to_s}
end

Anb call it like

get_has_many_associations_for_model("assignments", "assignable")

Here Second parameters is optional for if you want polymorphic records than pass it otherwise leave it as blank.

It will Return Array of Model name as String.

Culch answered 27/9, 2019 at 11:39 Comment(0)
S
1

Harish Shetty's solution will not work for namespaced model files which are not stored directly in Rails.root/app/models but in a subdirectory. Although it correctly globs files in subdirectories, it then fails to include the subdir when turning the file name into a constant. The reason for this is, that the namespacing subdir is removed by this line:

klass = File.basename(file, ".rb").camelize.constantize rescue nil

Here is what I did to retain the namespacing subdir:

file.sub!(File.join(Rails.root, "app", "models"), '')
file.sub!('.rb', '')
klass = file.classify.constantize rescue nil

Here's the full modified solution:

  def self.all_polymorphic_types(name)
    @poly_hash ||= {}.tap do |hash|
      Dir.glob(File.join(Rails.root, "app", "models", "**", "*.rb")).each do |file|
        file.sub!(File.join(Rails.root, "app", "models"), '')
        file.sub!('.rb', '')
        klass = file.classify.constantize rescue nil
        next unless klass.ancestors.include?(ActiveRecord::Base)

        klass.
          reflect_on_all_associations(:has_many).
          select{ |r| r.options[:as] }.
          each do |reflection|
            (hash[reflection.options[:as]] ||= []) << klass
          end
      end
    end
    @poly_hash[name.to_sym]
  end

Now, the method will turn /models/test/tensile.rb correctly into Test::Tensile before reflecting on its associations.

Just a minor improvement, all credit still goes to Harish!

Sung answered 10/5, 2020 at 7:14 Comment(0)
R
0

I created a polymorphic model class with a method 'all' to test this.

class Profile
  # Return all profile instances
  # For class return use 'ret << i' instead of 'ret << i.all'
  def self.all
    ret = []
    subclasses_of(ActiveRecord::Base).each do |i|
      unless i.reflect_on_all_associations.select{|j| j.options[:as] == :profile}.empty?
        ret << i
      end
    end
    ret.flatten
  end

  def self.all_associated
    User.all.map{|u| u.profile }.flatten
  end
end

Here is my app setup:

User < ActiveRecord::Base
  belongs_to :profile, :polymorphic => true
end

Student < ActiveRecord::Base
  has_one :user, :as => :profile
end
Rainbolt answered 23/2, 2010 at 14:58 Comment(2)
To get this work in development enviroment, you've to require the models files.Rainbolt
This is a special class, doesn't descend from ActiveRecord.Rainbolt
I
-1

You should be able to just use the associated collection:

model.assignments.all
Intellection answered 23/2, 2010 at 0:50 Comment(3)
This will return all the assignments for a model instance. I think he wants to return all the assignments for a type.Bradstreet
But can't you just say Event.assignments.all?Intellection
Rails doesn't add static association helpers. So Event.assignments will throw NoMethodError.Bradstreet

© 2022 - 2024 — McMap. All rights reserved.