Rails form_for route module undefined method - ruby-on-rails

I'm having a issue with scopes.
I've defined
# routes.rb
resources :asientos, module:'asientos'
# app/models/asientos/asiento.rb
module Asientos
class Asiento < ActiveRecord:Base
end
end
# app/controllers/asientos/asientos_controller.rb
module Asientos
class AsientosController < ApplicationController
def new
#asiento = Asientos::Asiento.new
end
end
end
# app/views/asientos/asientos/new
<%= form_for(#asiento) do |f| %>
...
rake routes
asientos GET /asientos(.:format) asientos/asientos#index
POST /asientos(.:format) asientos/asientos#create
new_asiento GET /asientos/new(.:format) asientos/asientos#new
edit_asiento GET /asientos/:id/edit(.:format) asientos/asientos#edit
asiento GET /asientos/:id(.:format) asientos/asientos#show
PATCH /asientos/:id(.:format) asientos/asientos#update
PUT /asientos/:id(.:format) asientos/asientos#update
DELETE /asientos/:id(.:format) asientos/asientos#destroy
Now whenever the form tries to render, i get
undefined method `asientos_asiento_index_path' for #<#<Class:0x000000065b3b40>:0x00000006ba5f30>
I've seen some of the answers like
form_for and scopes, rails 3
Module route in Rails with form_for(#object)
But none of them present a clear solution, o suggest some kind of patching.
Furthermore, form_for now generates asientos_ prefix, and in my controller now i have to rename also params.require(:asientos) to params.require(:asientos_asientos) ... not pretty...
Any suggestions (besides undoing namespacing) would be much appreciated. Thanks in advance.
Edit
It seems like by adding the following to the module definition, the route is generated as expected, without the "scope"
module Asientos
def self.use_relative_model_naming?
true
end
...
end
But it still wants an asientos_index_path... "undefined method `asientos_index_path'"

Well, after diving into tons of articles, and code i found the answer and as some posts pointed it has to do with Inflections.
My app has custom inflections for spanish, in which inflections look like:
Usuario -> Usuarios
Asiento -> Asientos
ItemAsiento -> ItemsAsiento
you'll notice it pluralizes the first word instead. That being said, the following piece of code from rails source extracted from rails/activemodel/lib/active_model/naming.rb shows the internals of whats happening
def initialize(klass, namespace = nil, name = nil)
#name = name || klass.name
raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if #name.blank?
#unnamespaced = #name.sub(/^#{namespace.name}::/, "") if namespace
#klass = klass
#singular = _singularize(#name)
#plural = ActiveSupport::Inflector.pluralize(#singular)
#element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(#name))
#human = ActiveSupport::Inflector.humanize(#element)
#collection = ActiveSupport::Inflector.tableize(#name)
#param_key = (namespace ? _singularize(#unnamespaced) : #singular)
#i18n_key = #name.underscore.to_sym
#route_key = (namespace ? ActiveSupport::Inflector.pluralize(#param_key) : #plural.dup)
#singular_route_key = ActiveSupport::Inflector.singularize(#route_key)
#route_key << "_index" if #plural == #singular
end
Since my class is under Asientos namespace, the #name becomes Asientos::Asiento, #unnamespaced = "Asiento", #singular = "asientos_asiento", #plural = "asientos_asiento" and here lays the issue. #route_key gets suffixed if plural and singular are both equal.
But why are those equal? Since Spanish inflections pluralize first word, and it does not know of namespaces, the "asientos_asiento" is considered plural (true from an inflections stand point but wrong since first part is namespace not model name).
I guess here conventions played me, since convetion seems to assume last part is always the model name, thus english plural will always work just fine.
This should not happen since rails is already detecting the namespace, and it should not rely on the #name itself but strip the namespace and then singularize and pluralize without the namespace.
Monkey patch... here we go...
Thanks to everybody.

you need to specify the module/namespace in the form_for helper like this:
<% form_for [:asientos, #asiento] do |f| %>

Related

Rails creates routes from objects

I have a routes file that looks something like this (It's very deeply nested...I know):
scope :admin, module: :admin do
namespace :breadth do
resources :areas, as: 'areas' do
resources :sequences, as: 'sequences'
end
end
end
When running rake routes I get a back all of the routes including one called breatdh_area_sequences (which is exactly what I want). The problem is that when I create a form rails builds up the wrong url based on the parameters I'm giving it:
= form_for [:breadth, breadth_sequence.area, breadth_sequence] do |f|
...
This gives me:
undefined method `breadth_breadth_area_breadth_sequences_path' for #<#<Class:0x007fe55bcf16b8>:0x007fe55d9a3f68>
Exactly how does rails take this array of parameters and create a URL path out of it? I'm assuming it calls a method on each object? Is this something I can override in order to get the named route I'm expecting (not overriding :url on form_for)
Since you want to name your objects differently from the routes, it won't work. Your routes/controllers need to be named just as your models are named, if your model is BreadthArea (as the route believes it is) your route should also be named in the same way.
In order to make this work I had to override the self.model_name methods on both BreadthArea and BreadthSequence. This method is how form_for figures out the URL when an object (or an array of objects) is used at the first argument.
class BreadthArea < ActiveRecord::Base
def self.model_name
ActiveModel::Name.new(self, nil, "Area")
end
...
end
class BreadthSequence < ActiveRecord::Base
def self.model_name
ActiveModel::Name.new(self, nil, "Sequence")
end
...
end

Dynamic namespaced controllers w/ fallback in Rails

I have a somewhat bizarre requirement for a new Rails application. I need to build an application in which all routes are defined in multiple namespaces (let me explain). I want to have an application in which school subjects (math, english, etc) are the namespaces:
%w[math english].each do |subject|
namespace subject.to_sym do
resources :students
end
end
This is great and it works but it requires me to create a namespaced StudentsController for each subject which means if I add a new subject then I need to create a new controller.
What I would like is to create a Base::StudentsController and if, let's say the Math::StudentsController exists then it will be used and if it doesn't exist, then we can dynamically create this controller and inherit from Base::StudentsController.
Is this something that is possible? If so then how would I go about implementing this?
With routes defined this way:
%w[math english].each do |subject|
scope "/#{subject}" do
begin
"#{subject.camelcase}::StudentsController".constantize
resources :students, controller: "#{subject}::students", only: :index
rescue
resources :students, controller: "base::students", only: :index
end
end
end
rake routes outputs:
students GET /math/students(.:format) base::students#index
GET /english/students(.:format) english::students#index
if english/students_controller.rb is present and math/students_controller. is absent.
To restate your requirements:
Minimal declarations per subject/resource pair
Use dedicated controller (Math::StudentsController) if it exists, otherwise use base controller (StudentsController)
Rails expects each route to have a dedicated controller, and doesn't really have a good way to support the second requirement. So, this is how I would do it:
Dynamicroutes::Application.routes.draw do
SUBJECTS = [ "math", "english", "chemistry" ]
RESOURCES = [ "assignments", "students" ]
class DedicatedSubjectResourceControllerConstraint
def initialize(subject, resource)
#subject = subject
#resource = resource
end
def matches?(request)
begin
defined?("#{#subject.capitalize}::#{#resource.capitalize}")
return true
rescue NameError
Rails.logger.debug "No such class: #{#subject.capitalize}::#{#resource.capitalize}"
return false
end
end
end
class ValidSubjectConstraint
def matches?(request)
return SUBJECTS.include?(request.path_parameters[:subject])
end
end
SUBJECTS.each do |subject|
RESOURCES.each do |resource|
namespace subject, :constraints => DedicatedSubjectResourceControllerConstraint.new(subject, resource) do
resources resource
end
end
end
RESOURCES.each do |resource|
scope "/:subject", :constraints => ValidSubjectConstraint.new do
resources resource
end
end
end
This sounds like a use for const_missing. If what you want to do is
to create a Base::StudentsController
and if, let's say the Math::StudentsController exists
then it will be used
and if it doesn't exist, then we can dynamically create this controller and inherit from Base::StudentsController
You can achieve that by adding dynamic constant lookup (const_missing) and dynamic constant definition with inheritance (Object.const_set).
I imagine something like this; with a few tweaks and more rigorous checking, would work:
# initializers/dynamic_controllers.rb
class ActionDispatch::Routing::RouteSet
SUBJECTS = [ "math", "english", "chemistry" ]
def const_missing(name, *args, &block)
if SUBJECTS.any?{ |subject| name.include? subject.uppercase }
Object.const_set name, Class.new(Base::StudentsController)
else
super
end
end
end
That'll add dynamic constant lookups to ActionDispatch::Routing::RouteSet, from which Dynamicroutes::Application.routes inherits, so undefined constants in Dynamicroutes::Application.routes.draw will generate the corresponding classes subclassed from Base::StudentsController.
I believe this will do it:
%w[math english].each do |subject|
namespace subject.to_sym do
resources :students
end
end
match ':subject/students(/:action(/:id))' => 'base/students'
With these combined routes, /math/students goes to the Math::StudentsController, /english/students/ goes to the English::StudentsController, and all other subjects (e.g. /physics/students and /cs/students) go to the Base::StudentsController.
Which I think is exactly what you want and only adds one line of code to your original solution.
All the routing helpers like resources, scope, etc are just functions inside your application's routes. You could just define a custom function as follows:
YourApplication.routes.draw do
# Let's define a custom method that you're going to use for your specific needs
def resources_with_fallback(*args, &block)
target_module = #scope[:module].camelize.constantize
target_controller = "#{args.first.to_s}_controller".camelize
fallback_controller = args.last.delete(:fallback).to_s.camelize.constantize
# Create the target controller class
# using fallback_controller as the superclass
# if it doesn't exist
unless target_module.const_defined?(target_controller)
target_module.const_set target_controller, Class.new(fallback_controller)
end
# Call original resources method
resources *args, &block
end
# Now go ahead and define your routes!
namespace "test" do
namespace "new" do
# Use our custom_resources function and pass a fallback parameter
custom_resources :photos, :fallback => 'base/some_controller'
end
end
end
I tested this in Rails 3.2, but it should equally work well in all 3.x versions.
I included no null checks or begin/rescue blocks anywhere. Since you're going to use this custom function only when required, I'm assuming that you will pass the correct and necessary parameters. If say you passed a fallback controller that doesn't exist, I'd rather that the routes parsing fail with an exception, rather than trying to handle it.
Edit: Typo in function arguments
Edit 2: Forgot &block in function arguments
Edit 3: Add "_controller" to the target_controller variable
I ended up writing some custom logic into ActionDispatch::Routing::RouteSet::Dispatcher.controller_reference. I attempt to look up all of the constants required for the given controller and create them if they're missing. This code is FAR from perfect so please feel free to edit w/ improvements.
class ActionDispatch::Routing::RouteSet::Dispatcher
private
def controller_reference(controller_param)
const_name = #controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
obj = Object
const_name.split('::').each do |cn|
begin
obj = obj.const_get(cn)
rescue
if obj == Object
obj = obj.const_set(cn, Class.new(ApplicationController))
else
puts "Creating #{obj}::#{cn} based on Generic::#{cn}"
obj = obj.const_set(cn, Class.new("Generic::#{cn}".constantize))
end
end
end
ActiveSupport::Dependencies.constantize(const_name)
end
end

Pretty Paths in Rails

I have a category model and I'm routing it using the default scaffolding of resources :categories. I'm wondering if there's a way to change the paths from /category/:id to /category/:name. I added:
match "/categories/:name" => "categories#show"
above the resources line in routes.rb and changed the show action in the controller to do:
#category = Category.find_by_name(params[:name])
it works, but the 'magic paths' such as link_to some_category still use the :id format.
Is there a way to do this? If this is a bad idea (due to some possible way in which rails works internally), is there another way to accomplish this? So that /categories/music, for example, and /categories/3 both work?
Rails has a nifty model instance method called to_param, and it's what the paths use. It defaults to id, but you can override it and produce something like:
class Category < ActiveRecord::Base
def to_param
name
end
end
cat = Category.find_by_name('music')
category_path(cat) # => "/categories/music"
For more info, check the Rails documentation for to_param.
EDIT:
When it comes to category names which aren't ideal for URLs, you have multiple options. One is, as you say, to gsub whitespaces with hyphens and vice versa when finding the record. However, a safer option would be to create another column on the categories table called name_param (or similar). Then, you can use it instead of the name for, well, all path and URL related business. Use the parameterize inflector to create a URL-safe string. Here's how I'd do it:
class Category < ActiveRecord::Base
after_save :create_name_param
def to_param
name_param
end
private
def create_name_param
self.name_param = name.parameterize
end
end
# Hypothetical
cat = Category.create(:name => 'My. Kewl. Category!!!')
category_path(cat) # => "/categories/my-kewl-category"
# Controller
#category = Category.find_by_name_param(param[:id]) # <Category id: 123, name: 'My. Kewl. Category!!!'>
If you don't want to to break existing code that relying on model id you could define your to_param like this:
def to_param
"#{id}-#{name}"
end
so your url will be: http://path/1-some-model and you still can load your model with Model.find(params[:id]) because:
"123-hello-world".to_i
=> 123
Although possibly more than you need, you may also want to look into 'human readable urls' support like friendly_id or one of the others (for instance, if you need unicode support, etc.) that are described here at Ruby Toolbox.

Legacy table with column named "class" in Rails

I've got a legacy table that my rails application shares with another application. It has a column called "class". The first time I reference any attribute in that model, I get an error. Subsequent references to attributes work. Is there a good workaround for this, or should I just go modify the other application that uses this table (ugh)?
>> Member::Ssg.find(:first)
=> #<Member::Ssg ssg_key: #<BigDecimal:10b169688,'0.253E3',4(8)>, org_id: 2, academic_year: 2006, class: true, next_due_date: "2011-06-01", submitted_date: "2006-02-13", notes: nil, owner_id: "1">
>> Member::Ssg.find(:first).notes
NoMethodError: undefined method `generated_methods' for true:TrueClass
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/attribute_methods.rb:247:in `method_missing'
from (irb):2
>> Member::Ssg.find(:first).notes
=> nil
SOLUTION:
I went with a combination of the Bellmyer solution and adding the code below to my model
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
NOTE: Please see the updated solution at the end of this answer. Leaving the original outdated solution for historic reasons.
This has come up often enough (legacy column names interfering with ruby/rails) that I might just make a plugin out of this. Here's how you can fix it right away, though. Create this file in your app:
# lib/bellmyer/create_alias.rb
module Bellmyer
module CreateAlias
def self.included(base)
base.extend CreateAliasMethods
end
module CreateAliasMethods
def create_alias old_name, new_name
define_method new_name.to_s do
self.read_attribute old_name.to_s
end
define_method new_name.to_s + "=" do |value|
self.write_attribute old_name.to_s, value
end
end
end
end
end
And now, in your model:
class Member < ActiveRecord::Base
include Bellmyer::CreateAlias
create_alias 'class', 'class_name'
end
The first parameter to create_alias is the old method name, and the second parameter is the new name you want to call it, that won't interfere with rails. It basically uses the read_attribute and write_attribute methods to interact with the column instead of the ruby methods that get defined by ActiveRecord. Just be sure to use the new name for the field everywhere, like so:
member.class_name = 'helper'
This works with ruby 1.8, but I haven't tested with ruby 1.9 yet. I hope this helps!
UPDATE: I've found a better solution that works in Rails 3, the safe_attributes gem. I've written a blog post explaining how to use it, with example code snippets, and a full sample app you can download from github and play around with. Here's the link:
Legacy Database Column Names in Rails 3
The following works in Rails 6.0.2.2
class ReasonCode < ApplicationRecord
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
def as_json(options={})
add_class = attributes.keys.include?('class')
if add_class
if options[:only]
add_class = Array(options[:only]).map(&:to_s).include?('class')
elsif Array(options[:except])
add_class = Array(options[:except]).map(&:to_s).exclude?('class')
end
end
options[:except] = Array(options[:except])
options[:except].push('class')
json = super(options)
json['class'] = attributes['class'] if add_class
json
end
end
Adapted from this answer https://www.ruby-forum.com/t/activerecord-column-with-reserved-name-class/125705/2. The as_json method was added because rendering the record as json gave a SystemStackError (stack level too deep). I followed the serialization code in the Rails repo to only render the class attribute if specified in the as_json options.

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

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
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.
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)
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
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
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!
I looked for ways to do this and ended up choosing this way:
in the controller:
#data_tables = ActiveRecord::Base.connection.tables
in the view:
<% #data_tables.each do |dt| %>
<br>
<%= dt %>
<% end %>
<br>
source: http://portfo.li/rails/348561-how-can-one-list-all-database-tables-from-one-project
I think #hnovick's solution is a cool one if you dont have table-less models. This solution would work in development mode as well
My approach is subtly different though -
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact
classify is well supposed to give you the name of the class from a string properly. safe_constantize ensures that you can turn it into a class safely without throwing an exception. This is needed in case you have database tables which are not models. compact so that any nils in the enumeration are removed.
If you want just the Class names:
ActiveRecord::Base.descendants.map {|f| puts f}
Just run it in Rails console, nothing more. Good luck!
EDIT: #sj26 is right, you need to run this first before you can call descendants:
Rails.application.eager_load!
With Rails 6, Zetiwerk became the default code loader.
For eager loading, try:
Zeitwerk::Loader.eager_load_all
Then
ApplicationRecord.descendants
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 %>
On one line: Dir['app/models/\*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }
ActiveRecord::Base.connection.tables
Yes there are many ways you can find all model names but what I did in my gem model_info is , it will give you all the models even included in the gems.
array=[], #model_array=[]
Rails.application.eager_load!
array=ActiveRecord::Base.descendants.collect{|x| x.to_s if x.table_exists?}.compact
array.each do |x|
if x.split('::').last.split('_').first != "HABTM"
#model_array.push(x)
end
#model_array.delete('ActiveRecord::SchemaMigration')
end
then simply print this
#model_array
In just one line:
ActiveRecord::Base.subclasses.map(&:name)
I can't comment yet, but I think sj26 answer should be the top answer. Just a hint:
Rails.application.eager_load! unless Rails.configuration.cache_classes
ActiveRecord::Base.descendants
To avoid pre-load all Rails, you can do this:
Dir.glob("#{Rails.root}/app/models/**/*.rb").each {|f| require_dependency(f) }
require_dependency(f) is the same that Rails.application.eager_load! uses. This should avoid already required file errors.
Then you can use all kind of solutions to list AR models, like ActiveRecord::Base.descendants
This works for Rails 3.2.18
Rails.application.eager_load!
def all_models
models = Dir["#{Rails.root}/app/models/**/*.rb"].map do |m|
m.chomp('.rb').camelize.split("::").last
end
end
Dir.foreach("#{Rails.root.to_s}/app/models") do |model_path|
next unless model_path.match(/.rb$/)
model_class = model_path.gsub(/.rb$/, '').classify.constantize
puts model_class
end
This will give to you all the model classes you have on your project.
Module.constants.select { |c| (eval c).is_a?(Class) && (eval c) < ActiveRecord::Base }
Here's a solution that has been vetted with a complex Rails app (the one powering Square)
def all_models
# must eager load all the classes...
Dir.glob("#{RAILS_ROOT}/app/models/**/*.rb") do |model_path|
begin
require model_path
rescue
# ignore
end
end
# simply return them
ActiveRecord::Base.send(:subclasses)
end
It takes the best parts of the answers in this thread and combines them in the simplest and most thorough solution. This handle cases where your models are in subdirectories, use set_table_name etc.
Just came across this one, as I need to print all models with their attributes(built on #Aditya Sanghi's comment):
ActiveRecord::Base.connection.tables.map{|x|x.classify.safe_constantize}.compact.each{ |model| print "\n\n"+model.name; model.new.attributes.each{|a,b| print "\n#{a}"}}
This worked for me. Special thanks to all the posts above. This should return a collection of all your models.
models = []
Dir.glob("#{Rails.root}/app/models/**/*.rb") do |model_path|
temp = model_path.split(/\/models\//)
models.push temp.last.gsub(/\.rb$/, '').camelize.constantize rescue nil
end
I've tried so many of these answers unsuccessfully in Rails 4 (wow they changed a thing or two for god sakes) I decided to add my own. The ones that called ActiveRecord::Base.connection and pulled the table names worked but didn't get the result I wanted because I've hidden some models (in a folder inside of app/models/) that I didn't want to delete:
def list_models
Dir.glob("#{Rails.root}/app/models/*.rb").map{|x| x.split("/").last.split(".").first.camelize}
end
I put that in an initializer and can call it from anywhere. Prevents unnecessary mouse-usage.
The Rails implements the method descendants, but models not necessarily ever inherits from ActiveRecord::Base, for example, the class that includes the module ActiveModel::Model will have the same behavior as a model, just doesn't will be linked to a table.
So complementing what says the colleagues above, the slightest effort would do this:
Monkey Patch of class Class of the Ruby:
class Class
def extends? constant
ancestors.include?(constant) if constant != self
end
end
and the method models, including ancestors, as this:
The method Module.constants returns (superficially) a collection of symbols, instead of constants, so, the method Array#select can be substituted like this monkey patch of the Module:
class Module
def demodulize
splitted_trail = self.to_s.split("::")
constant = splitted_trail.last
const_get(constant) if defines?(constant)
end
private :demodulize
def defines? constant, verbose=false
splitted_trail = constant.split("::")
trail_name = splitted_trail.first
begin
trail = const_get(trail_name) if Object.send(:const_defined?, trail_name)
splitted_trail.slice(1, splitted_trail.length - 1).each do |constant_name|
trail = trail.send(:const_defined?, constant_name) ? trail.const_get(constant_name) : nil
end
true if trail
rescue Exception => e
$stderr.puts "Exception recovered when trying to check if the constant \"#{constant}\" is defined: #{e}" if verbose
end unless constant.empty?
end
def has_constants?
true if constants.any?
end
def nestings counted=[], &block
trail = self.to_s
collected = []
recursivityQueue = []
constants.each do |const_name|
const_name = const_name.to_s
const_for_try = "#{trail}::#{const_name}"
constant = const_for_try.constantize
begin
constant_sym = constant.to_s.to_sym
if constant && !counted.include?(constant_sym)
counted << constant_sym
if (constant.is_a?(Module) || constant.is_a?(Class))
value = block_given? ? block.call(constant) : constant
collected << value if value
recursivityQueue.push({
constant: constant,
counted: counted,
block: block
}) if constant.has_constants?
end
end
rescue Exception
end
end
recursivityQueue.each do |data|
collected.concat data[:constant].nestings(data[:counted], &data[:block])
end
collected
end
end
Monkey patch of String.
class String
def constantize
if Module.defines?(self)
Module.const_get self
else
demodulized = self.split("::").last
Module.const_get(demodulized) if Module.defines?(demodulized)
end
end
end
And, finally, the models method
def models
# preload only models
application.config.eager_load_paths = model_eager_load_paths
application.eager_load!
models = Module.nestings do |const|
const if const.is_a?(Class) && const != ActiveRecord::SchemaMigration && (const.extends?(ActiveRecord::Base) || const.include?(ActiveModel::Model))
end
end
private
def application
::Rails.application
end
def model_eager_load_paths
eager_load_paths = application.config.eager_load_paths.collect do |eager_load_path|
model_paths = application.config.paths["app/models"].collect do |model_path|
eager_load_path if Regexp.new("(#{model_path})$").match(eager_load_path)
end
end.flatten.compact
end
Make sure to eager load your app before calling descendants so that all the classes are loaded:
Rails.application.eager_load! unless Rails.application.config.eager_load
ApplicationRecord.descendants.each do |clazz|
# do something with clazz, e.g. User, Event, Attendance, etc.
end
def load_models_in_development
if Rails.env == "development"
load_models_for(Rails.root)
Rails.application.railties.engines.each do |r|
load_models_for(r.root)
end
end
end
def load_models_for(root)
Dir.glob("#{root}/app/models/**/*.rb") do |model_path|
begin
require model_path
rescue
# ignore
end
end
end
can check this
#models = ActiveRecord::Base.connection.tables.collect{|t| t.underscore.singularize.camelize}
Assuming all models are in app/models and you have grep & awk on your server (majority of the cases),
# extract lines that match specific string, and print 2nd word of each line
results = `grep -r "< ActiveRecord::Base" app/models/ | awk '{print $2}'`
model_names = results.split("\n")
It it faster than Rails.application.eager_load! or looping through each file with Dir.
EDIT:
The disadvantage of this method is that it misses models that indirectly inherit from ActiveRecord (e.g. FictionalBook < Book). The surest way is Rails.application.eager_load!; ActiveRecord::Base.descendants.map(&:name), even though it's kinda slow.
I'm just throwing this example here if anyone finds it useful. Solution is based on this answer https://stackoverflow.com/a/10712838/473040.
Let say you have a column public_uid that is used as a primary ID to outside world (you can findjreasons why you would want to do that here)
Now let say you've introduced this field on bunch of existing Models and now you want to regenerate all the records that are not yet set. You can do that like this
# lib/tasks/data_integirity.rake
namespace :di do
namespace :public_uids do
desc "Data Integrity: genereate public_uid for any model record that doesn't have value of public_uid"
task generate: :environment do
Rails.application.eager_load!
ActiveRecord::Base
.descendants
.select {|f| f.attribute_names.include?("public_uid") }
.each do |m|
m.where(public_uid: nil).each { |mi| puts "Generating public_uid for #{m}#id #{mi.id}"; mi.generate_public_uid; mi.save }
end
end
end
end
you can now run rake di:public_uids:generate

Resources