views:

1529

answers:

5

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

Thanks in advance.

+19  A: 

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 seach 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
Vincent Robert
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.
Urf
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! :)
Urf
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 :)
nilbus
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.
nilbus
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.
動靜能量
+6  A: 

This seems to work for me:

  Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
  @models = Object.subclasses_of(ActiveRecord::Base)

Rails only loads models when they are used, so the Dir.glob line "requires" all the files in the models directory.

Once you have the models in an array, you can do what you were thinking (e.g. in view code):

<% @models.each do |v| %>
  <li><%= h v.to_s %></li>
<% end %>
bhousel
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).
Urf
+9  A: 

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).each do |model|
  puts model.name
end
egarcia
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.
動靜能量
A: 

Is there a way to find out which migration you're on from the console / application?

Ivanoats
Please ask your questions in separate thread. It does not relate to this question.
Deepak N
A: 

On one line: Dir['app/models/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }

:-)

vjt