Is there a way to get a collection of all the Models in your Rails app?

119,815

Solution 1

EDIT: Look at the comments and other answers. There are smarter answers than this one! Or try to improve this one as community wiki.

Models do not register themselves to a master object, so no, Rails does not have the list of models.

But you could still look in the content of the models directory of your application...

Dir.foreach("#{RAILS_ROOT}/app/models") do |model_path|
  # ...
end

EDIT: Another (wild) idea would be to use Ruby reflection to search for every classes that extends ActiveRecord::Base. Don't know how you can list all the classes though...

EDIT: Just for fun, I found a way to list all classes

Module.constants.select { |c| (eval c).is_a? Class }

EDIT: Finally succeeded in listing all models without looking at directories

Module.constants.select do |constant_name|
  constant = eval constant_name
  if not constant.nil? and constant.is_a? Class and constant.superclass == ActiveRecord::Base
    constant
  end
end

If you want to handle derived class too, then you will need to test the whole superclass chain. I did it by adding a method to the Class class:

class Class
  def extend?(klass)
    not superclass.nil? and ( superclass == klass or superclass.extend? klass )
  end
end

def models 
  Module.constants.select do |constant_name|
    constant = eval constant_name
    if not constant.nil? and constant.is_a? Class and constant.extend? ActiveRecord::Base
    constant
    end
  end
end

Solution 2

The whole answer for Rails 3, 4 and 5 is:

If cache_classes is off (by default it's off in development, but on in production):

Rails.application.eager_load!

Then:

ActiveRecord::Base.descendants

This makes sure all models in your application, regardless of where they are, are loaded, and any gems you are using which provide models are also loaded.

This should also work on classes that inherit from ActiveRecord::Base, like ApplicationRecord in Rails 5, and return only that subtree of descendants:

ApplicationRecord.descendants

If you'd like to know more about how this is done, check out ActiveSupport::DescendantsTracker.

Solution 3

Just in case anyone stumbles on this one, I've got another solution, not relying on dir reading or extending the Class class...

ActiveRecord::Base.send :subclasses

This will return an array of classes. So you can then do

ActiveRecord::Base.send(:subclasses).map(&:name)

Solution 4

ActiveRecord::Base.connection.tables.map do |model|
  model.capitalize.singularize.camelize
end

will return

["Article", "MenuItem", "Post", "ZebraStripePerson"]

Additional information If you want to call a method on the object name without model:string unknown method or variable errors use this

model.classify.constantize.attribute_names

Solution 5

For Rails5 models are now subclasses of ApplicationRecord so to get list of all models in your app you do:

ApplicationRecord.descendants.collect { |type| type.name }

Or shorter:

ApplicationRecord.descendants.collect(&:name)

If you are in dev mode, you will need to eager load models before:

Rails.application.eager_load!
Share:
119,815

Related videos on Youtube

mr_urf
Author by

mr_urf

Ruby developer for a private invite-only, social network.

Updated on November 14, 2021

Comments

  • mr_urf
    mr_urf over 2 years

    Is there a way that you can get a collection of all of the Models in your Rails app?

    Basically, can I do the likes of: -

    Models.each do |model|
      puts model.class.name
    end
    
    • Andrei
      Andrei over 13 years
      If you need to collect all models including models of Rails engines/railties, see the answer by @jaime
    • aks
      aks about 6 years
      Doesn't work on rails 5.1
  • mr_urf
    mr_urf over 15 years
    Funnily enough, I went the other way round on this! I started looking at reflections but couldn't figure out how to get the list of classes either! Just after I posted I had a headslap moment and went off to look at Dir. Thanks for the answer. Much appreciated.
  • mr_urf
    mr_urf over 15 years
    Wow! Kudos earned and then some! Thanks for this, I went with the Dir suggestion and ended up having to scan, strip and capitalize! the resulting file name. However I think that your solution is much more elegant so I'll use this instead. If I could vote you up again, I would! :)
  • mr_urf
    mr_urf over 15 years
    Thanks bhousel. I originally went with this style of approach but ended up using the solution that Vincent posted above as it meant that I didn't have to "Modelize" the file name as well (i.e. strip out any _, capitalize! each word and then join them again).
  • Edward Anderson
    Edward Anderson almost 14 years
    FYI, I timed both methods just for fun. Looking up the directories is an order of magnitude faster than searching though the classes. That was probably obvious, but now you know :)
  • Edward Anderson
    Edward Anderson almost 14 years
    Also, it's important to note that searching for models via the constants methods will not include anything that hasn't been referenced since the app started, since it only loads the models on demand.
  • nonopolarity
    nonopolarity over 13 years
    why don't you use ActiveRecord::Base.subclasses but have to use send? Also, it seems like you have to "touch" the model before it will show up, for example c = Category.new and it will show up. Otherwise, it won't.
  • nonopolarity
    nonopolarity over 13 years
    I think there has got to be a better way than to use the filenames in a directory. When we use a model, Rails know it is valid or not, so the info must be kept some where.
  • hraada
    hraada over 13 years
    I prefer 'Kernel.const_get constant_name' to 'eval constant_name'.
  • kikito
    kikito over 13 years
    I don't remember why I used send anymore, but probably there was a reason. Maybe on the rails version I was using at the time subclasses was private. You were right about the "having to touch" part.
  • Tobias Cohen
    Tobias Cohen over 13 years
    In Rails 3, this has been changed to ActiveRecord::Base.descendants
  • Andrei
    Andrei over 13 years
    This is the only way I can get ALL models, including models of Rails engines used in the app. Thanks for the tip!
  • Andrei
    Andrei over 13 years
    A few useful methods: ActiveRecord::Base.connection.tables.each{|t| begin puts "%s: %d" % [t.humanize, t.classify.constantize.count] rescue nil end} Some of the models may be not activated therefore you need to rescue it.
  • wbharding
    wbharding over 13 years
    This one is nice since, in Rails 3, your models aren't auto-loaded by default, so many of the above methods won't return all possible models. My permutation also captures models in plugins and subdirectories: Dir['**/models/**/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
  • artemave
    artemave about 13 years
    There is build in kind_of? method that does exactly what your extend? does.
  • artemave
    artemave about 13 years
    with subdirectories: ...'/app/models/**/*.rb'
  • Kevin Rood
    Kevin Rood about 13 years
    You have to use "send" because the :subclasses member is protected.
  • Max Williams
    Max Williams almost 13 years
    Adapting @Andrei's a bit: model_classes = ActiveRecord::Base.connection.tables.collect{|t| t.classify.constantize rescue nil }.compact
  • Chris
    Chris almost 13 years
    Thank you so much! That saved my bacon that tip.
  • Charles Enrick Cajan
    Charles Enrick Cajan almost 13 years
    Thanks for the Rails 3 tip. For anyone else who comes along, you still need to "touch" the models before ActiveRecord::Base.descendants will list them.
  • so_mv
    so_mv over 12 years
    For rails 2.3.x, this seems a reliable approach over the list of tables. list of tables returns schame_migrations tables as well, which is not a model, and also it does not include STI models.
  • courtsimas
    courtsimas about 12 years
    This will get you all the tables though, not just the models, since some tables don't always have associated models.
  • fanaugen
    fanaugen about 12 years
    RAILS_ROOT is no longer available in Rails 3. Instead, use Dir.glob(Rails.root.join('app/models/*'))
  • sj26
    sj26 about 12 years
    Technically in Rails 3 you have subclasses and descendants, they mean different things.
  • David J.
    David J. almost 12 years
    Object.subclasses_of is deprecated after v2.3.8.
  • Mark Locklear
    Mark Locklear almost 12 years
    Also a nice followup is <table_name>.column_names to list all columns in the table. So for your user table you would execute User.column_names
  • Jo Liss
    Jo Liss almost 12 years
    Awesome! This should be the accepted answer. For anybody using this in a rake task: Make your task depend on :environment for the eager_load! to work.
  • sj26
    sj26 almost 12 years
    Actually, the models do register themselves as descendants of ActiveRecord::Base now, so if you eager load all models then you can iterate them easily—see my answer below.
  • Ajedi32
    Ajedi32 almost 12 years
    Or, as a slightly faster alternative to Rails.application.eager_load!, you can just load the models: Dir.glob(Rails.root.join('app/models/*')).each do |x| require x end
  • Ajedi32
    Ajedi32 almost 12 years
    @wbharding That's pretty nice, but it errors out when it tries to constantize the names of my rspec model tests. ;-)
  • lightyrs
    lightyrs over 11 years
    That's awesome @Aditya Sanghi. I didn't know about safe_constantize.
  • Joe Goggins
    Joe Goggins over 11 years
    I agree with Jo Liss: this should be the top answer! 2 lines instead of 14 is 7 times more awesome.
  • sj26
    sj26 over 11 years
    @Ajedi32 that is not complete, models can be defined outside those directories, especially when using engines with models. Slightly better, at least glob all Rails.paths["app/models"].existent directories. Eager loading the whole application is a more complete answer and will make sure there is absolutely nowhere left for models to be defined.
  • rcd
    rcd about 11 years
    Definitely the Rails 3 answer :] Great job.
  • user1558501
    user1558501 about 11 years
    Added another answer to a similar, but what feels like a better targeted question at which may actually get accepted: stackoverflow.com/q/5236932/15585
  • courtsimas
    courtsimas about 11 years
    That doesn't show all the models for me. Not sure why. It's a couple short, in fact.
  • courtsimas
    courtsimas about 11 years
    This will get you all the tables though, not just the models, since some tables don't always have associated models.
  • Marcus Mansur
    Marcus Mansur over 10 years
    @wbharding nice solution but it breaks when you have namespaced models
  • masciugo
    masciugo over 10 years
    I got what sj26 means but maybe there is a little mistake: as far as I know in development environment cache_classes is off (false) that's why you need to manually eager load the application to access all models. explained here
  • boulder_ruby
    boulder_ruby over 10 years
    worked for me. 'just a little late to answer thats all. give it time.
  • sj26
    sj26 over 10 years
    Thanks @masciugo, fixed.
  • sj26
    sj26 over 10 years
    @Ajedi32 again, not the complete answer. If you want to eager load only models then try: Rails.application.paths["app/models"].eager_load!
  • snowangel
    snowangel over 10 years
    throws TypeError: no implicit conversion of Symbol into String in the console.
  • Admin
    Admin about 10 years
    @sj26 Is absolutely right. For large apps, "edge cases" and guessing become major headaches and wrong answers.
  • sunsations
    sunsations almost 10 years
    Just what I wanted. Thank!
  • iheggie
    iheggie over 9 years
    For rails 2.3.x, use: ActiveRecord::Base.connection.tables.map{|x|x.classify.const‌​antize rescue nil}.compact
  • Pokechu22
    Pokechu22 over 9 years
    @iheggie It is generally better to post that as a separate answer than editing it into the existing post.
  • ianstarz
    ianstarz over 9 years
    I agree this should be the answer. Here's the one liner I used for my purposes: (Rails.application.eager_load!) && (ActiveRecord::Base.descendants.map {|klass| [klass.name, klass.count]})
  • PJSCopeland
    PJSCopeland over 9 years
    Not quite, @artemave - only instances of the class will be a kind_of the superclass.
  • newdark-it
    newdark-it over 9 years
    ActiveRecord::Base.descendants.map(&:name) this has helped me get the named spaced Model Names
  • illusionist
    illusionist about 9 years
    thanks, I found you answer best suited for me #adiya
  • equivalent8
    equivalent8 about 9 years
    and not are terrible choice for logical operators. They sound like they are logical operators but they are not. Please don't use them in boolean operations. use rather &&, ! stackoverflow.com/questions/1426826/…
  • equivalent8
    equivalent8 about 9 years
    upvolt for that Rails.application.eager_load! idea
  • equivalent8
    equivalent8 about 9 years
    in case of class MyConstant < ActiveRecord::Base don't compare MyConstant.superclass == ActiveRecord::Base as that is just checking direct ancestor. So For example in case of STI MyAnother < MyConstant the sperclass is MyConstant therefore you will miss this model to avoid this check rather entire ancestor tree MyConstant.ancestors.include?(ActiveRecord::Base)
  • denis.peplin
    denis.peplin over 8 years
    It's probably needs Rails.application.eager_load! before execution in development mode.
  • Nuno Costa
    Nuno Costa over 8 years
    calling map with puts? I don't get the point should be ActiveRecord::Base.descendants.map(&:model_name)
  • Jordan Michael Rushing
    Jordan Michael Rushing over 8 years
    You can do it that way, but they'll be in a single array, instead of line by line, in a much easier to read format.
  • shadowbq
    shadowbq over 8 years
    config.eager_load = true
  • lorefnon
    lorefnon about 8 years
    I take it that this would require that classes are already loaded and would give incomplete results in development environment with autoloading enabled. I will not downvote but perhaps this should be mentioned in the answer.
  • lorefnon
    lorefnon about 8 years
    This answer should be considered incorrect as it is feasible (and common in legacy setups) to configure the name of the table to be something other than pluralized name of the model. This answer gives the correct answer even when the setup deviates from the default configuration.
  • Nimir
    Nimir about 8 years
    fare enough, updating
  • Marklar
    Marklar over 7 years
    What about in Rails 5 where all models will inherit from ApplicationRecord?
  • sj26
    sj26 over 7 years
    ApplicationRecord still inherits ActiveRecord::Base so ActiveRecord::Base.descendants still works, but you can also use ApplicationRecord.descendants for only descendants of ApplicationRecord, too.
  • gtournie
    gtournie about 7 years
    It shouldn't depend on the environment but on the cache_classes flag: Rails.application.eager_load! unless Rails.application.config.cache_classes
  • sj26
    sj26 about 7 years
    That's what the answer says? "If cache_classes is off (by default it's off in development, but on in production)" (the environment is just a hint).
  • Tilo
    Tilo about 7 years
    in some cases this works better than ActiveRecord::Base.send :subclasses - looking for the table names is a good idea. Automatically generating the model names might be problematic as lorefnon mentioned.
  • Maxim
    Maxim about 7 years
    .capitalize.singularize.camelize can be replaced to .classify.
  • ypresto
    ypresto almost 7 years
    I tried this but one of the models is missing from result... (even it extends ApplicationRecord)
  • Yakob Ubaidi
    Yakob Ubaidi over 6 years
    the first one is the best for me because I have namespaced models.
  • Ibrahim Tencer
    Ibrahim Tencer almost 5 years
    Be careful though, this may also return ActiveRecord::SchemaMigration when it exists. It also doesn't seem to be loaded by eager_load.
  • sj26
    sj26 almost 5 years
    @IbrahimTencer to ignore the schema migrations model and only return your application's models then you might like to use ApplicationRecord.descendants :-)
  • jgomo3
    jgomo3 over 4 years
    I'm on Rails 6.0.2 and the eager_load! didn't make the descendants method to return anything but an empty array.
  • Mohamed AbuIssa
    Mohamed AbuIssa about 3 years
    Better to use c.constantize instead of eval c.
  • PKul
    PKul over 2 years
    Great, this work with mongodb as well as those activerecord.