I have a rails application that patches ActiveRecord with a hand-coded validator.
The patch is made by adding the following lines in config/environment.rb
Rails::Initializer.run do |config|
...
end
class ActiveRecord::Base
include MtLib::DBValidations
end
This works fine in production mode i.e. with
config.cache_classes = true
however it does not work in development with cache_classes set to false.
The error thrown is
ArgumentError (A copy of MtLib::DBValidations has been removed from
the module tree but is still active!):
My question is what is the process that is followed when cache_class is set to false. Does Rails re-run any of the initialization methods? If not then where is the best place for me to put my patch to ensure that it is in all models and survives a classes reload?
I have tried adding the patch to config/initializers/active_record_patch, however this is not re-run when the classes are reloaded.
The solution to this, provided by Frederick Cheung on the Ruby On Rails google group add the directory containing the loaded class into the load_once_path array.
I edited environment.rb to look like this
config.load_paths +=
%W( #{RAILS_ROOT}/lib/soap_clients/carefone #{RAILS_ROOT}/lib/mt_lib)
# Make sure load_once_paths is a subset of load_paths
config.load_once_paths += %W( #{RAILS_ROOT}/lib/mt_lib)
And now this works in development mode without having to reload the server on every request
Related
I have a problem because my code does not work on the production server. When it comes to development environment, everything is fine. It looks like he doesn't read the classes. My ruby version is 2.6.6 and rails is set as 5.2.4.4.
lib/crm/api.rb
module CRM
class API
# some code
end
end
app/services/crm/changes.rb
module CRM
class Changes
def initialize
#api = API.new
end
# some code
end
end
The main service is running as a cronjob.
config/schedule.rb
every 5.minutes do
runner('CRM::CheckChanges.new.call', output: "#{path}/log/crm_check_changes.log")
end
And run this code
app/services/crm/check_changes.rb
module CRM
class CheckChanges
def initialize
#changes = Changes.new
end
# some code
end
end
At first I got errors log like 'Uninitialized constant CRM::Changes::API' after reload background jobs I got 'Unitialized constant CRM::API'
config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += %W(#{config.root}/app)
Dir.glob(Rails.root.join('lib/**')).inject(config.autoload_paths){ |autoload_paths, path| autoload_paths << path }
As I mentioned before everything works fine on development environment. Does anyone have any idea what this could be about? Thanks in advance for answer.
You need to add /lib to the eager load paths as well:
config.autoload_paths << Rails.root.join('lib')
config.eager_load_paths << Rails.root.join('lib')
The default setting for production uses eager loading (all the classes are loaded at startup) instead of autoloading.
In production, however, you want consistency and thread-safety and can
live with a longer boot time. So eager_load is set to true, and then
during boot (before the app is ready to receive requests) Rails loads
all files in the eager_load_paths and then turns off auto loading (NB:
autoloading may be needed during eager loading). Not autoloading after
boot is a good thing, as autoloading can cause the app to have
thread-safety problems.
See Autoloading and Reloading Constants (Classic Mode). Also note that /app and all its subdirecties are already on the autoloading paths.
I am adding a method to Mailboxer::Conversation to retrieve a link using mailboxer's emails to reply (i.e. reply_link).
I've decided to monkey patch mailboxer within my application. What I've done exactly is the following:
Created the folder structure lib/mailboxer/extensions.
Added files lib/mailboxer/extensions/conversation.rb, lib/mailboxer/extensions.rb, lib/mailboxer.rb.
The following is the content of the files:
# lib/mailboxer/extensions/conversation.rb
module Mailboxer
module Extensions
module Conversation
def reply_link
"/mail?notif_id=#{id}"
end
end
end
end
# lib/mailboxer/extensions.rb
require 'mailboxer/extensions/conversation'
# lib/mailboxer.rb
require 'mailboxer/extensions'
My config/application.rb has the following:
config.autoload_paths += %W(#{config.root}/lib)
Which gives me access to my lib folder.
Then what I do is include Mailboxer::Extensions::Conversation to Mailboxer::Conversation within the mailboxer initializer file initalizers/mailboxer.rb:
Mailboxer.setup do |config|
# ...
end
Mailboxer::Conversation.include Mailboxer::Extensions::Conversation
In my rails console, the code always works. However in the website, the reply_link method works at first, then becomes undefined randomly.
Couple of attempts later....
and it stops working until I restart the server...
Whenever I get an unrelated exception (i.e. typo, refactoring, etc.) the reply_link method becomes undefined. Could this be a development thing?
I could fork mailboxer, make my changes then go on. But the method is so custom to my application that I'd rather just patch.
If anyone has any suggestions, advice or questions I truly appreciate the advice.
First, I am still convinced that this is a development issue only. Whenever I have time to spare, I will test this out and post here.
Second, to ensure this never happened again I copied the source for the Mailboxer's Conversation and added an inclusion include MailboxerExt::Conversation.
I also structured my extension to not collide, reload Mailboxer's namespace.
The final result has the folders app/models/mailboxer, lib/mailboxer_ext.
The files are app/models/mailboxer/conversation.rb, lib/mailboxer_ext.rb and lib/mailboxer_ext/conversation.rb.
I use the following line in an initializer to autoload code in my /lib directory during development:
config/initializers/custom.rb:
RELOAD_LIBS = Dir[Rails.root + 'lib/**/*.rb'] if Rails.env.development?
(from Rails 3 Quicktip: Auto reload lib folders in development mode)
It works great, but it's too inefficient to use in production- Instead of loading libs on each request, I just want to load them on start up. The same blog has another article describing how to do this:
config/application.rb:
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
However, when I switch to that, even in development, I get NoMethodErrors when trying to use the lib functions.
Example of one of my lib files:
lib/extensions.rb:
Time.class_eval do
def self.milli_stamp
Time.now.strftime('%Y%m%d%H%M%S%L').to_i
end
end
Calling Time.milli_stamp will throw NoMethodError
I realize others have answered similar questions on SO but they all seem to deal with naming conventions and other issues that I didn't to have to worry about before- My lib classes already worked for per-request loading, I just want to change it to per-startup loading. What's the right way to do this?
I think this may solve your problem:
in config/application.rb:
config.autoload_paths << Rails.root.join('lib')
and keep the right naming convention in lib.
in lib/foo.rb:
class Foo
end
in lib/foo/bar.rb:
class Foo::Bar
end
if you really wanna do some monkey patches in file like lib/extensions.rb, you may manually require it:
in config/initializers/require.rb:
require "#{Rails.root}/lib/extensions"
P.S.
Rails 3 Autoload Modules/Classes by Bill Harding.
And to understand what does Rails exactly do about auto-loading?
read Rails autoloading — how it works, and when it doesn't by Simon Coffey.
Though this does not directly answer the question, but I think it is a good alternative to avoid the question altogether.
To avoid all the autoload_paths or eager_load_paths hassle, create a "lib" or a "misc" directory under "app" directory. Place codes as you would normally do in there, and Rails will load files just like how it will load (and reload) model files.
This might help someone like me that finds this answer when searching for solutions to how Rails handles the class loading ... I found that I had to define a module whose name matched my filename appropriately, rather than just defining a class:
In file lib/development_mail_interceptor.rb (Yes, I'm using code from a Railscast :))
module DevelopmentMailInterceptor
class DevelopmentMailInterceptor
def self.delivering_email(message)
message.subject = "intercepted for: #{message.to} #{message.subject}"
message.to = "myemail#mydomain.org"
end
end
end
works, but it doesn't load if I hadn't put the class inside a module.
Use config.to_prepare to load you monkey patches/extensions for every request in development mode.
config.to_prepare do |action_dispatcher|
# More importantly, will run upon every request in development, but only once (during boot-up) in production and test.
Rails.logger.info "\n--- Loading extensions for #{self.class} "
Dir.glob("#{Rails.root}/lib/extensions/**/*.rb").sort.each do |entry|
Rails.logger.info "Loading extension(s): #{entry}"
require_dependency "#{entry}"
end
Rails.logger.info "--- Loaded extensions for #{self.class}\n"
end
Someone who understands how rails caching works can really help me out here. Here's the code, nested inside of the Rails::Initializer.run block:
config.after_initialize do
SomeClass.const_set 'SOME_CONST', 'SOME_VAL'
end
Now if I run script/server and make a request, everything is dandy. However, on the second request to my Rails app, all goes to hell with an unitialized constant error. In production mode, I can make the second request successfully, meaning the constant is still there.
I've fixed the problem by changing the above to:
config.after_initialize do
require 'some_class' # in RAILS_ROOT/lib/some_class.rb
SomeClass.const_set 'SOME_CONST', 'SOME_VAL'
end
But now that means whenever I make a change to some_class.rb, I have to restart the server. Is there any way to set constants in an environment file and have them work correctly in development mode? Why does the constant exist on the first request, but not the following request?
UPDATE: Since environment.rb is only read when the Rails app is booted and I want both my lib files and models to be reloaded on each request, I was forced to move the constants into the some_class.rb file as follows:
if Rails.env.development?
const_set 'SOME_CONST', 'SOME_DEVELOPMENT_VAL'
end
And in environments/production.rb, I have the old const_set code.
UPDATE: An even better method using config.to_prepare is detailed below.
It only works on the first request in development mode because the classes are reloaded on each request. So on the first request the constant is set in the initializer, and all is good. Then on the next request, it reloads the class WITHOUT rerunning the bit from your initializer, so the constant isn't set from there on out.
It works in production mode because the classes aren't reloaded for each request, so you don't lose that bit of class state each time.
So you might want to set the constant either in the model, or in a config.to_prepare instead config.after_initialize. to_prepare is called before each request.
In the model:
class SomeClass < ActiveRecord::Base
MY_CONST = "whatever"
# You can access MY_CONST directly, but I tend to wrap them in a class
# method because literal constants often get refactored into the database.
def self.my_const
MY_CONST
end
end
In the config:
# This will run before every single request. You probably only want this in
# the development config.
config.to_prepare do
SomeClass.const_set 'SOME_CONST', 'SOME_VAL'
end
Production mode preloads all of the classes, whereas in development mode classes are loaded as needed, after the config files are read. Manually requiringing them in your configs forces the classes to be read before/during the config stage.
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.