I have set up a couple of modules that provide similar functionality, and I have another module that collects all of these, so that at runtime it is possible to determine what functionality is available.
This works fine, but whenever the code gets reloaded, Rails' autoloading functionality clobbers the instance variables on the collection module.
The code looks something like this:
module ServiceCollection
def self.available_services
#available_services ||= []
end
end
module ServiceProvider
extend ActiveSupport::Concern
included do |includer|
ServiceCollection.available_services.push(includer)
end
end
module MyService
include ServiceProvider
#some functionality here
end
Calling ServiceCollection.available_services will return the list of modules that include ServiceProvider, however on reload, the instance variable #available_services will be reset, and subsequent calls return an empty array.
Is there an easy way to get around this?
You can force the load of the modules putting a require call in your application.rb file.
Something like:
Dir["#{File.expand_path('../..', __FILE__)}/extras/*.rb"].each { |rb| require rb }
Thanks to #thaleshcv for the answer, but I decided to go a different route, partially inspired by his answer.
I changed the implementation of ServiceCollection to something like this:
module ServiceCollection
def self.available_services
Dir[Rails.root.join("app", "services", "*.rb")].each do |filename|
model_name = Pathname.new(filename.to_s).basename.to_s.chomp('.rb').camelcase
begin
Module.const_get(model_name)
rescue Exception => e
#Log something or whatever
end
end
#available_services ||= []
end
end
Calling Module.const_get triggers Rails' autoloading functionality. Assuming that I follow the module/class/filename naming convention, then this should work fine. Here's a gist that extracts this functionality into a module
So, I have a database of people on an external system, and I want to set up the code to easily create people records internal to our sysem based on the external system. The field names, of course, are not the same, so I've written some code which maps from one table to the next.
class PeopleController < ApplicationController
...
def new
#person = Person.new
if params[:external_id] then
initialize_from_external_database params[:external_id]
end
end
private
def initialize_form_external_database(external_id)
external = External::Person.find(external_id)
if external.nil?
...
else
#person.name_last = exteral.last_name
#person.name_first = external.first_name
#...
#person.valid?
end
end
end
Okay, so the stuff in the "else" statement I can write as a loop, which would use a hash something like:
FieldMappings = {
:name_last => :last_name,
:name_first => :first_name,
:calculated_field => lambda {|external_person| ... },
...
}
But where would you put this hash? Is it natural to put it in the External::Person class because the only reason we access those records is to do this initialization? Or would it go in the controller? Or a helper?
Added: Using Rails 2.3.5.
I'd put this code in the External::Person to avoid Person even having to know it exists. Use a 'to_person' method (or maybe 'to_internal_person') on External::Person. Keep the Hash in External::Person and use it to perform the generation. Either way as JacobM says, you want this code in your model, not controller.
class PeopleController < ApplicationController
def new
if external = External::Person.find_by_id params[:external_id]
#person = external.to_person
else
#person = Person.new
end
end
end
If you're in Rails 3.x (maybe also in 2.x, I'm not sure), you can put miscellaneous classes and modules in your /extras folder which is included in the autoloader path. This is where I always put things of this nature, but I' not aware of any Rails convention for this sort of thing.
First of all, I would do that work in your (internal) Person model -- give it a class method like create_person_from_external_person that takes the external person and does the assignments.
Given that, I think it would be OK to include the hash within that Person model, or somewhere else, as Josh suggests. What would be particularly cool would be to write a generic create_person_from_external_person method that would ask the external person for a hash and then do the mapping based on that hash; that approach could support more than one type of external person. But that may be overkill if you know this is the only type you have to deal with.
I wouldn't put it in the controller, but, again, I wouldn't do that work in the controller either.
You can put it on a module on the lib directory so you don't mess any of your classes that will be full of awesome code that will probably last many years. Another good reason is you can then include/require your mapping module everywhere you need it (maybe in your tests).
module UserMapping
FIELDS = { :last_name => :name_last, .... }
end
If you drop the module on the lib and you use rails 3 you should put this on your config/application.rb file:
config.autoload_paths += %W(#{config.root}/lib)
On Rails::VERSION::MAJOR < 3 the lib directory is automatically added to the autoload_path
I have a private method in my Rails app to connect to Amazon S3, execute a passed block of code, then close the connection to S3. It looks like so;
def S3
AWS::S3::Base.establish_connection!(
:access_key_id => 'Not telling',
:secret_access_key => 'Really not telling'
)
data = yield
AWS::S3::Base.disconnect
data
end
It is called like this (as an example);
send_data(S3 {AWS::S3::S3Object.value("#{#upload_file.name}",'bucket')}, :filename => #upload_file.name)
I call this method in a number of ways in my controller and model so have it included in both classes as a private method. This works fine and I'm happy with it but it's not very DRY.
How can I make this method accessible to both my model and controller but only have the code appear once? This is more of a Ruby question than a Rails question and reflects my newness to OOP. I'm guessing a module or a mix-in is the answer but I haven't really been using either of these up until now and need a little hand-holding.
Thanks.
Modules are used for 3 different things in ruby. First is namespacing. Having class or constant definitions inside a module won't collide with classes or constants outside that module. Something like this
class Product
def foo
puts 'first'
end
end
module Affiliate
class Product
puts 'second'
end
end
p = Product.new
p.foo # => 'first'
p = Affiliate::Product.new
p.foo # => 'second'
The second use for modules is as a place to stick methods that don't really have a place anywhere else. You can do this inside a class too, but using a module sort of tells people reading the code that it is not meant to be instanciated. Something like this
module Foo
def self.bar
puts 'hi'
end
end
Foo.bar #=> 'hi'
Finally (and the most confusing) is that modules can be included into other classes. Using them this way is also referred to as a mixin, because you are "mixing in" all the methods into whatever you are including.
module Foo
def bar
puts 'hi'
end
end
class Baz
include Foo
end
b = Baz.new
b.bar #=> 'hi'
Mixins are actually a way more complected topic then I am covering here, but going deeper would probably be confusing.
Now, to me, S3 seems to be something that really belongs in the controller, since controllers are usually the things dealing with incoming and outgoing connections. If that is the case, I would just have a protected method on application controller, since that will be accessible to all other controllers, but still be private.
If you do have a good reason for it being in the model too, I would go for a mixin. Something like
module AwsUtils
private
def S3
AWS::S3::Base.establish_connection!\
:access_key_id => 'Not telling',
:secret_access_key => 'Really not telling'
data = yield
AWS::S3::Base.disconnect
data
end
end
If you put that in lib/aws_utils.rb, you should be able to use it by adding include AwsUtils in both your controller and your model. Rails knows to look for classes and modules in lib, but only if the name matches (in wide case). I called it AwsUtils because I know what rails will look for when it sees that (aws_utils.rb), and to be honest, I have no idea what it will need for S3Utils ;-)
Feel free to ask for more info if I wasn't clear on something. Modules tend to be one of those things in ruby that while amazing, are downright baffling to newcomers.
Your hunch is correct: you can put a module in the lib directory. In
order to make these methods available to your models, simply include it
with:
class Model < ActiveRecord::Base
include MyModule
end
The included module's instance methods will become instance methods on your class. (This is known as a mixin)
module MyModule
def S3
#...
end
end
You can write a module as :
module MyModule
def self.S3(args*)
AWS::S3::Base.establish_connection!(
:access_key_id => 'Not telling',
:secret_access_key => 'Really not telling'
)
data = yield
AWS::S3::Base.disconnect
data
end
end
and then call it in your controller or model as
MyModule.S3(params*)
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
I have a module saved in /lib as test_functions.rb that looks like this
module TestFunctions
def abc
puts 123
end
end
Going into ruby script/runner, I can see that the module is loading automatically (good ol' convention over configuration and all that...)
>> TestFunctions.instance_methods
=> ["abc"]
so the method is known, let's try calling it
>> TestFunctions.abc
NoMethodError: undefined method `abc' for TestFunctions:Module from (irb):3
Nope. How about this?
>> TestFunctions::abc
NoMethodError: undefined method `abc' for TestFunctions:Module from (irb):4
Test
Nope again.
defined?(TestFunctions::abc) #=> nil, but
TestFunctions.method_defined? :abc #=> true
Like I said at the top, I know I'm being dumb, can anyone de-dumb me?
If you want Module-level functions, define them in any of these ways:
module Foo
def self.method_one
end
def Foo.method_two
end
class << self
def method_three
end
end
end
All of these ways will make the methods available as Foo.method_one or Foo::method_one etc
As other people have mentioned, instance methods in Modules are the methods which are available in places where you've included the Module
I'm going to try to summarise the various answers myself, since each had something valuable to say, but none really got to what I now realise is probably the best response:
I was asking the wrong question because I was doing it wrong.
For reasons I can no longer explain, I wanted a set of completely stand-alone functions in a library, which represented methods I was trying to DRY out of my classes. That can be achieved, using things like
module Foo
def self.method_one
end
def Foo.method_two
end
class << self
def method_three
end
end
def method_four
end
module_function :method_four
end
I could also include my module, either within a class, in which case the methods become part of the class or outside, in which case they are defined on whatever class I'm running inside (Object? Kernel? Irb, if I'm interactive? Probably not a great idea, then)
The thing is, there was no good reason not to have a class in the first place - I'd somehow got on to a train of thought that took me down an seldom-used and frankly slightly weird branch line. Probably a flashback to the days before OO became mainstream (I'm old enough that up to today I've spent a lot more years writing procedural code).
So the functions have moved into a class, where they seem pretty happy, and the class methods thus exposed are being cheerfully used wherever necessary.
You can also use module_function like so:
module TestFunctions
def abc
puts 123
end
module_function :abc
end
TestFunctions.abc # => 123
Now you can include TestFunctions in class and call "abc" from within TestFunctions module.
I messed with this for a while and learned several things. Hopefully this will help someone else out. I am running Rails 3.2.8.
My module (utilities.rb) looks like this and is in the /lib directory of my rails app:
module Utilities
def compute_hello(input_string)
return "Hello #{input_string}"
end
end
My test (my_test.rb) looks like this and is in the /test/unit directory of my rails app:
require "test_helper"
require "utilities"
class MyTest < ActiveSupport::TestCase
include Utilities
def test_compute_hello
x = compute_hello(input_string="Miles")
print x
assert x=="Hello Miles", "Incorrect Response"
end
end
Here are a few things to note: My test extends ActiveSupport::TestCase. This is important because ActiveSupport adds /lib to the $LOAD_PATH. (seehttp://stackoverflow.com/questions/1073076/rails-lib-modules-and)
Secondly, I needed to both "require" my module file, and also "include" the module. Lastly, it is important to note that the stuff that gets included from the module essentially gets placed in the test class. So... be careful that the module that you include doesn't start with "test_". Otherwise, Rails will attempt to run your module method as a test.
You can't call a method in a Module directly. You need to include it in a class. Try this:
>> class MyTest
>> include TestFunctions
>> end
=> MyTest
>> MyTest.new.abc
123
=> nil
You need to include the module
include Testfunctions
Then 'abc' will return something.
You need to prefix your function with the module name because modules are not classes:
Your /lib/test_functions.rb:
module TestFunctions
def TestFunctions.abc
puts 123
end
end
Your code using the module method:
require 'test_functions'
TestFunctions.abc
Today you can do it using module_function notation.
module TestFunctions
def abc
puts 123
end
end
Now TestFunctions.abc prints "123"
A little more about module_function: https://apidock.com/ruby/Module/module_function
Try this code:
service = Class.new { extend TestFunctions }
service.method_four