Loading class descendants in rails development - ruby-on-rails

I need to be able to see all of the class descendants from a controller when I go into the rails console locally. I have this Api::BaseController which all my Api controller inherit from. The issue I have is when I hop in to the rails console to check which Api controller are in the descendants, it comes up empty until I call them. This probably has something to do with how the classes in development aren't eager loaded, and they're not cached locally.
sample_app$ rails c
Loading development environment (Rails 4.2.0)
2.1.5 :001 > Api::BaseController.descendants
=> []
2.1.5 :002 > Api::V1::FoosController
=> Api::V1::FoosController
2.1.5 :003 > Api::BaseController.descendants
=> [Api::V1::FoosController]
From this example, you can see when I call descendants on the Api::BaseController the first time, it's an empty array. After calling one of the controllers that class will be then loaded and will show up as a descendant. In this case, there could be an any number of controllers in V1 as well as V2, V3, etc...
As a stupid ugly hack, I could do
Dir.glob("#{Rails.root.join('app', 'controllers', 'api', 'v1')}/**/*.rb").each(&method(:require_dependency))
but I don't want to have to write that each time I enter the console. I'm also working on a gem, and definitely don't want to put this sort of code in my gem.
The other option is caching classes in development, but that causes a huge issues on it's own. Anyone have any ideas?
Edit
Another option would be to call Rails.application.eager_load!. This option would work fine if I could specify only controllers in my API folder. Then I wouldn't have to eager load the entire app, but just a small subset of controllers that I need.

I found the following post: http://avinmathew.com/using-rails-descendants-method-in-development/
In short it says the following:
In enviroments/development.rb add the following:
config.eager_load_paths += Dir['path/to/files/*.rb']
ActionDispatch::Reloader.to_prepare do
Dir['path/to/files/*.rb'].each {|file| require_dependency file}
end
The first line adds the path that should be loaded when you start your app (or console)
and the rest tells rails to reload the classes on each request.

Works in Rails 5 & Rails 6:
In environments/development.rb:
Rails.application.reloader.to_prepare do
Dir["#{Rails.root}/app/models/my_models/*.rb"].each { |file| require_dependency file }
end
This builds on u/Aguardientico's answer above.
If requiring files not in config.eager_load_paths you may need to add yours as described above.
ActionDispatch::Reloader is replaced with Rails.application.reloader (thanks to u/jean-baptiste's comment).
#{Rails.root} was necessary as part of my argument to Dir for this to work.

Related

Store a list of .rb files inside model

colleagues! I have Document model. Document should be processed by one of the parsers (in my project they are called 'importers' and stored inside 'lib/importers' folder). The question is about what is the best way to implement entity Importer inside models layer? (for instance associate document with importer).
First idea is to create importers table, but then I will have 2 independent places where importer names will be saved (database and file system). Bad cases:
Case 1: I've aded a new importer, but forgot to add it to importers table = I can't associate document with this impoter
Case 2: Importer was renamed and we forgot to rename it inside database = error
I decided to define
def Document.importers
#importers ||= Dir.entries("#{Rails.root}/lib/importers/")
.select { |name| !File.directory?(name) && name != 'base_importer.rb'}
.map { |name| name.gsub(/\.rb$/, '') }
end
for f.association inout and add importer string attribute to the document model. So I can get importer class in following way -- 'importer.classify.constantize'. It works, but it looks creepy
Can you advice better solution for this situation? I will appreciate to hear any ideas ;)
I would model them in the database, and then have some system in place to update the database if the files change. This happens all the time with asset management systems: you have some physical files, and some data, and yes, if they go out of sync you've got problems. So, you put systems in place to try to keep them in sync. You can have a rake task to update the database based on the physical files for example, then the protocol is that you need to run the rake task after changing the files, and if you don't then you screwed up and the problems are your fault. Developers should be able to work within these sorts of rules.
Ultimately, Rails is about object-relational data, so work with that if you want to use rails. If you try to go down some route of building a load of instances on the fly every time, based on the contents of the folder, then you will just end up with a very complicated and inefficient system.
Solvation made during brain storm in my local ruby chat:
I haven't mentioned that all importers are inherited from the base_importer which is also placed in lib/importers. We decided to add 'inheritors' array to it and store all inheritors there using hook 'inherited' provided by the ruby core -> http://ruby-doc.org/core-2.2.0/Class.html#method-i-inherited.
class BaseImporter
#inheritors = []
def self.inheritors
#inheritors
end
def self.inherited(subclass)
#inheritors << subclass.name
end
# ...
end
We expected it to work well, but we forgot, that all classes are eager loaded in Rails. So BaseImporter.inheritors will return [] to you on the fresh system start.:
anton#anton:~/Projects/project$ rails c
Running via Spring preloader in process 16832
Loading development environment (Rails 4.2.5)
B2.2.3 :001 > BaseImporter.inheritors
=> []
To force loading of all importers I made an initializer:
# config/initializers/importers.rb
# Preload importers
require 'base_importer'
Dir.glob("#{Rails.root}/lib/importers/*.rb").each { |file| require file }
BaseImporter should be loaded first, because if any importer will be loaded before BaseImporter it will not fire 'inherited' hook:
anton#anton:~/Projects/project$ rails c
Running via Spring preloader in process 16846
Loading development environment (Rails 4.2.5)
2.2.3 :001 > require "xxx_importer"
=> true
2.2.3 :002 > require "base_importer"
=> true
2.2.3 :003 > require "yyy_importer"
=> true
2.2.3 :004 > BaseImporter.inheritors
=> ["XxxImporter"]
Feel free to post your feedback on this solution. I will be glad to hear it

rails current_teacher keyword not set

I'm new to Ruby on Rails and I'm looking at an application that has a variable called current_teacher. I cannot seem to find where this is set. Everywhere I look the code seems to read from it but where is it set. Is this one of those things that Rails does for you. There is a mode and a table called teachers, so I'm sure this has something to do with it.
I'm very confused by statements like the following, can someone tell me how Rails does this?
if current_teacher.can_request_fieldtrip
Suppose you have a controller like :
class ClientsController < ApplicationController
  def new
if current_teacher.can_request_fieldtrip
# code
end
  end
end
Here is debugging tips :
(a) put this in your Gemfile and do bundle install :
`gem 'pry-rails', :group => :development`
(b) Put the line binding.pry just before the if statement.
(c) Start rails server using rails s.
(d) Hit the browser like http://localhost:3000/new
(e) Now you will be in the Pry console. Just do in the console,
method(:current_teacher).source_location
And the above line tell you where the method has been defined.
Documentation of Method#source_location
Returns the Ruby source filename and line number containing this method or nil if this method was not defined in Ruby (i.e. native)
Rails does not support authentication by itself, however there are a lot of 'add-ons' that rails can use. These 'add-ons' are called gems. This can be a little confusing because you can't actually see their code inside your project folder.
If you open a file called "Gemfile" (it should be in your project folder) you can see a list of gems that you use. Try searching their names on google, you will probably find official web page that contains it's documentation. That way can learn what they do and how to use them.
current_teacher method smells like "Devise" gem
https://github.com/plataformatec/devise
I'm not sure about can_request_fieldtrip, this could be a custom method defined in Teacher model.

How can I keep my initializer configuration from being lost in development mode?

I'm working on a Rails app that uses an engine. I'm using an initializer to configure one of my engine's controllers so that it will trigger an action in the host app. The code looks something like this:
# config/initializers/my_engine.rb
MyEngine::SomeController.after_filter proc {
# Do something in the host app
}, :only => :update
This works fine in production, but in development mode, the proc is only called on the first request. This is because the classes are getting reloaded and this configuration is lost, because it was stored in a class variable. (For example, MyEngine::SomeController is reloaded from the file it's in, and since the after_filter isn't declared there, it isn't added back on.)
Some Rails background
In development mode, Rails uses the following load strategy:
Code in the app directory is reloaded on each request, on the assumption that you're actively changing it.
Code in the lib directory, along with config/initializer files, are loaded once, when the application boots.
Initializer files are generally used for configuring gems. In the past, gems have mostly had code in the lib directory, so running their configuration once was sufficient.
How engines change things
However, Rails engines have code in the app directory: controllers, models, etc. These files are reloaded in development mode on each request. Therefore, configuration like my example above is lost.
Enter to_prepare
Rails provides config.to_prepare specifically to solve this problem: it run once in production, and on every request in development.
For example, we have this in application.rb, which works fine:
config.to_prepare do
# set up class variables (after_filters, etc)
end
However, if I have to put all my engines' configuration in application.rb, this defeats the point of config/initializers in keeping things organized.
So, for any configuration of classes in my engines' app directories, I want to put that code in files under config/initializers.
Here are my questions.
I'm unclear how to get config into scope in an initializer file. I'm thinking it would be Rails.application.config. Is that right?
Can I add add multiple to_prepare blocks? I'm afraid that calling it multiple times will overwrite previous blocks.
Update
As #Frederick Cheung mentioned, Rails.application.config.to_prepare does work in config/initializer files, and one can use as many of these as needed in the various files; each call appends its block to an array, so nothing is overwritten.
So the solution to this problem is:
# config/initializers/my_engine.rb
Rails.application.config.to_prepare do
MyEngine::SomeController.after_filter proc {
# Do something in the host app
}, :only => :update
end
One thing that still seems odd: I expected the to_prepare block to be called on every request in development mode, but instead it seems to be called randomly every 3rd request or so. I added block:
Rails.application.config.to_prepare do
Rails.logger.info "Running the prepare block!"
end
... restarted my app, and refreshed the page nine times. I only saw the message on the 1st, 5th, 7th and 9th requests. I'm not sure what explains this behavior, but it does explain why my code without the to_prepare worked intermittently in development.
You can add as many to_prepare blocks as you want - when you do config.to_prepare, Rails is doing (in configuration.rb in railties)
def to_prepare(&blk)
to_prepare_blocks << blk if blk
end
and then iterates over those blocks handing them over to ActionDispatch::Reloader, where to_prepare is implemented using ActiveSupport::Callbacks (i.e. the same thing that is used for before_save and so on). Multiple to_prepare blocks are fine.
Currently it looks like Rails iterates over to_prepare_blocks after reading application initialisers so adding to Rails.application.configuration.to_prepare should work. You may prefer to use ActionDispatch::Reloader.to_prepare.
There's nothing to stop you from doing initializer code in a file that lives in app/models.
for example
class MyClass
def self.run_me_when_the_class_is_loaded
end
end
MyClass.run_me_when_the_class_is_loaded
MyClass.run_me... will run when the class is loaded .... which is what we want, right?
Not sure if its the Rails way.... but its extremely straightforward, and does not depend on the shifting winds of Rails.

Converting Rails 2 plugin to Rails 3 gem

So there's this great plugin I've gotten used to using in my Rails 2 projects called Bootstrapper. It essentially duplicates the functionality of the seeds.rb file, but I like it because it lets you break up your bootstrap process into concise chunks.
Anyway, I've gone so far as to fork the project and attempt to turn it into a Rails 3 gem. I've been able to get the gem to initialize and register the rake tasks and generators OK. However, I'm running into a problem with the Bootstrapper class itself. It won't load in the Rails project unless it's in a module.
That is, if I place the Bootstrapper class in a file by itself and require that file in my Railtie, then in my Rails app, it can't find the Bootstrapper class. If I put the class in a module and call Bootstrapper::Bootstrapper everything is peachy.
The code that actually requires the Bootstrapper class is this:
ActiveSupport.on_load :active_record do
require 'bootstrapper/bootstrapper'
end
The source is available here:
http://github.com/jrmehle/bootstrapper/tree/make_gem
Autoload paths actually has an annoying feature of following filesystem paths. For example in your lib or extras (depending on what you autoload) you might have the following file structure:
lib/bootstrapper/bootstrapper.rb
# in this case, Bootstrapper::Bootstrapper.class = Class in rails c
# ie: you don't get a NameError exception
More specifically,
lib/bootstrappers/bootstrapper.rb
# Bootstrapper::Bootstrapper => NameError
# Bootstrappers::Bootstrapper => works
If you really want the other way, you can move everything into your lib/bootstrapper.rb source file but meh, I don't like doing that, that's not how gems are organized. In rails3, you'll find the autoloading pretty nice once you use modules everywhere (which can be painful).
Rails3 uses /extras instead of /lib but it's not required, it's just the default (commented out) from rails new. To switch, you just autoload extras instead of lib.

Rails: filter defined in lib file required in environment.rb disappears from filter_chain in production environment. Why?

In my rails application, I have a file in lib that, among other things, sets up a filter that runs on all controllers.
When running under development environment, everything runs fine. However, under production the filter goes missing. Funny thing is, by inspecting the filter_chain, I noticed other filters remain, eg. those defined in plugins, or later in the specific controller class.
I've tested this with both rails edge and v2.3.0.
Testing update:
I've now tested with older rails and found the issue to be present back to v2.1.0, but not in v2.0.5, I've bisect them and found the 986aec5 rails commit to be guilty.
I've isolated the behavior to the following tiny test case:
# app/controllers/foo_controller.rb
class FooController < ApplicationController
def index
render :text => 'not filtered'
end
end
# lib/foobar.rb
ActionController::Base.class_eval do
before_filter :foobar
def foobar
render :text => 'hi from foobar filter'
end
end
# config/environment.rb (at end of file)
require 'foobar'
Here's the output I get when running under the development environment:
$ script/server &
$ curl localhost:3000/foo
> hi from foobar filter
And here's the output for the production environment:
$ script/server -e production &
$ curl localhost:3000/foo
> not filtered
As alluded to before, it works fine for any environment when I do the same thing via plugin. All I need is to put what's under lib/foobar.rb in the plugin's init.rb file.
So in a way I already have a workaround, but I'd like to understand what's going on and what's causing the filter to go missing when in production.
I conjecture it's something in the different ways Rails handles loading in the different environments, but I need to dig deeper.
update
Indeed, I've now narrowed it down to the following config line:
config.cache_classes = false
If, in production.rb, config.cache_classes is changed from true to false, the test application works properly.
I still wonder why class reloading is causing such thing.
Frederick Cheung on the Rails list had the answer:
Because cache_classes does a little more than just that. The way
before filters work, if Foo < Bar then only those filters defined in
Bar at that point will be inherited by Foo - adding them to Bar at a
later date will not do anythingn
In development mode, the app starts, your file is required, the filter
added to ActionController::Base. Later the first request comes along,
the controller is loaded and it inherits that filter.
When cache_classes is true then all of your application classes are
loaded ahead of time. This happens before your file is required, so
all of your controllers already exist when that file is run and so it
has no effect. You could solve this by requiring this file from an
initializer (ensuring it runs before app classes are loaded), but
really why wouldn;t you just put this in application.rb ?
Fred
My real case was actually way more involved, and this is the way I found to solve the issue:
config.after_initialize do
require 'foobar'
end
The after_initialize block runs after the framework has been initialized but before it loads the application files, hence, it'll affect ActionPack::Base after it's been loaded, but before the application controllers are.
I guess that's the generally safe way to deal with all the preloading that goes on in production.
Drop a ruby script in
RAILS_ROOT\config\initializers
that contains
require "foobar.rb"
This invokes the before_filter for me.

Resources