I store a value in a class variable inside of a module, such as:
module TranslationEnhancer
def self.install! klass
#dictionaries ||= [] << klass
end
...
end
I call this from an initializer in config/initializers:
require Rails.root + "lib" + "translation_enhancer.rb"
TranslationEnhancer::install! TranslationDictionary
Now, if I start the server in development environment, everything is ok during the first request. However, after that request, #dictionaries are suddenly nil. I have commented all other code in TranslationEnhancer, so I am absolutely sure the whole module must get reloaded every time I do a request.
I tried to move the module outside of the lib directory (moved it to lib_unloadable), then I tried:
ActiveSupport::Dependencies.explicitly_unloadable_constants << "TranslationEnhancer"
but failed again. I have no idea how to solve this, please help.
Got Ruby 1.9.2 # Rails 3.1.rc4.
EDIT: I know I could set the dictionaries as a constant. But I would like to use TranslationEnhancer as a library - so I could use it unchanged in a different project and install different Directories, such as:
TranslationEnhancer.install! EnglishDirectory, FrenchDirectory
These values won'd change during the runtime, they will just change project to project.
Solved!
I realized that the whole application.rb and environment.rb files are reloaded along with all other files. The only thing that does not get reloaded are initializers (config/initializers/*). The solution was to move the initialization to application.rb.
#dictionaries is not a "class variable". It is a "class-level instance variable".
Look here for a better explanation: Class and instance variables
Try using ##dictionaries instead.
Related
I'm trying to extend the String class in my Rails 4.2 app. I created a lib/string.rb file to hold the extensions.
class String
def testing
"testing"
end
end
I added lib to autoload_paths in application.rb.
config.autoload_paths << Rails.root.join('lib')
When I startup the rails console and execute "".testing, I get NoMethodError: undefined method 'testing' for "":String
Can anyone explain why this method isn't getting picked up? I have a hunch that it's because the String constant is already loaded, so Rails doesn't need to autoload the constant. As a result, it never tries to load the lib/string.rb file and my method never gets added to String.
When I explicitly require the file in an initializer, I can get the method loaded, but if I change the method, I have to restart the server to get rails to see the change. It feels like I'm missing something. It seems like there should be a way to get Rails to automatically read core extension classes and reload them when the file changes.
Yep, you're right. It will not autoload string since it's already defined. I usually put core class extensions inside an initializer. So config/initializers/string.rb
I've got a library module I'd like to override based on the rails environment I'm running in
Module is located in lib/package/my_module.rb:
module Package
module MyModule
puts "Defining original module"
def foo
puts "This is the original foo"
end
end
end
I have been able to partially solve with the info at Overriding a module method from a gem in Rails - specifically, in my environments/dev_stub.rb:
Package::MyModule.module_eval do
puts "Defining override"
def foo
puts "This is foo override"
end
end
(The other solution at that link seems to cause errors when rails tries to lookup other classes related to package)
Now, this seems to get me most of the way there, and works if I set
config.cache_classes = true
...but I want to use this as a stub development environment, and the comment recommendation on this value for a dev environment is to use false... in which case the override only works the first time the module is included, and any subsequent times, it uses the original.
My question: Am I going about this the right way? I could hack up the lib module itself to conditionally override based on RAILS_ENV, but I'd like to keep it cleaner than that...
Edit
My use case for this is to reference it from a controller function. If I have
class SomethingController < ApplicationController
def show
Package::MyModule.foo
end
end
and config.cache_classes=false (which I ideally want since it is a development environment), and access the action through my web browser (http://localhost/something/show) then the first time I hit it, my override is loaded and it works, but the second and any subsequent times, the original library class is reloaded (outputs "Defining original module" on my console without "Defining override"), and the override is lost.
Another alternative I tried was add something like config.load_paths += %W( #{RAILS_ROOT}/lib_patch/#{RAILS_ENV}) to environment.rb - but defining the same module/class didn't quite work without putting in an explicit hook in the original library to basically load the patch if it existed
Edit 2 (in response to #apneadiving answer)
I've tried doing this without module_eval, and just using the following in development_stub.rb:
require 'package/my_module'
module Package
module MyModule
puts "Defining override"
def foo
puts "This is foo override"
end
end
end
The problem I initially had with doing this is that Rails no longer automatically finds all content in my lib directory, and I need to sprinkle 'require' statements throughout all other lib files (and my controllers that reference the libs) to cover all of their dependencies. Although this is all done, it does work, but it also has a similar effect as config.cache_classes=true does, in that all the lib classes are not reloaded on change, even in my regular development environment that does not have a monkey-patch (since all the 'require' statements are added).
Setting config.cache_classes=true in dev_stub.rb and using module_eval to define the patch as described in the question seems the way to go for what the goal is here - to create an environment specific patch for a module that doesn't impact the other environments in both code path and Rails class loading behavior.
you could simply override the module and it's instance without module_eval.
I guess your module is included as a Mixin and it's methods aren't impacted by your monkey patch.
That's where alias_method_chain comes in action.
Look at this great article to get how you should use it to fit your needs.
I have a few methods I am adding to Ruby's Array class for my Rails application. Where is the best place to put them?
Right now I have them in config/environment.rb.
config/environment.rb isn't really the best place, since you can run into serious load ordering-problems if try to extend classes that haven't been resolved at the time environment.rb is executed.
Better to put a file into config/initializers. Any script placed there will be executed after the rails runtime is loaded.
What you could do is to create a file lib/my_extensions.rb
module MyExtensions
end
then in lib/my_extensions/array.rb :
module MyExtensions::Array
def join_with_commas
join(", ")
end
end
and in config/initializers/load_my_extensions.rb
class Array
include MyExtensions::Array
end
This will cause MyExtensions::Array to be auto-reloaded each time you invoke a request in development mode. This is nicer than restarting your application everytime you make a change to your code.
It would probably be cleaner to add a lib/ directory with all your extensions. Then add a line in config/environment.rb that loads the file:
require File.join(RAILS_ROOT, 'lib', 'array.rb')
I'm trying to write a plugin that will extend InheritedResources.
Specifically I want to rewrite some default helpers.
And I'd like it to "just work" once installed, w/o any changes to application code.
The functionality is provided in a module which needs to be included in a right place. The question is where? :)
The first attempt was to do it in my plugin's init.rb:
InheritedResources::Base.send :include, MyModule
It works in production, but fails miserably in development since InheritedResource::Base declared as unloadable and so its code is reloaded on each request. So my module is there for the first request,
and then its gone.
InheritedResource::Base is 'pulled' in again by any controller that uses it:
Class SomeController < InheritedResource::Base
But no code is 'pulling in' my extension module since it is not referenced anywhere except init.rb which is not re-loaded on each request
So right now I'm just including the module manually in every controller that needs it which sucks.
I can't even include it once in ApplicationController because InheritedResources inherites from it and so it will override any changes back.
update
I'm not looking for advice on how to 'monkey patch'. The extension is working in production just great. my problem is how to catch moment exactly after InheritedResources loaded to stick my extension into it :)
update2
another attempt at clarification:
the sequence of events is
a) rails loads plugins. my plugin loads after inherited_resources and patches it.
b) a development mode request is served and works
c) rails unloads all the 'unloadable' code which includes all application code and also
inherited_resources
d) another request comes in
e) rails loads controller, which inherites from inherited resources
f) rails loads inherited resources which inherit from application_controller
g) rails loads application_contrller (or may be its already loaded at this stage, not sure)
g) request fails as no-one loaded my plugin to patch inherited_resources. plugin init.rb files are not reloaded
I need to catch the point in time between g and h
The Rails::Configuration, config in the environment files, allows registering a callback on the dispatcher that runs before each request in development mode, or once when in production mode.
config.to_prepare do
# do something here
end
The problem is, I don't think your plugin has access to config when the init.rb file is run. Here is a way to register your callback directly in the dispatcher. Just put this in the init.rb file.
require 'dispatcher'
::Dispatcher.to_prepare do
puts "hi there from a plugin"
end
Warning: I don't know what side effects this may have. If possible, try to get access to config and register the callback tha right way.
What you are attempting to do is usually called "MonkeyPatch" - changing the way one module or class is working by "overriding" methods.
It is a common practice in Rails, but it doesn't mean it is the best way to do things - when possible, it is better to use common inheritance (it is more explicit about the changes you make).
Regarding your questions about "where to put the files": it is usually the lib/ directory. This can mean the lib of the rails app, or a lib directory inside a gem or plugin, if you are into that sort of thing.
For example, if the file you want to change is lib/generators/rails/templates/controller.rb of inherited resources, the first thing you have to do is replicate that directory structure inside your lib/ folder ('lib/generators/rails/templates/controller.rb')
Inside that new file of yours, (empty at the beginning) you can override methods. However, you must also the modules/classes hierarchy. So if the original gem had this:
module foo
module bar
def f1
...
end
def f2
...
end
end
def f3
...
end
end
And you wanted to modify f1, you would have to respect the foo-bar modules.
module foo
module bar
def f1
... # your code here
end
end
end
Now the last thing you need is to make sure this code is executed at the right time. If you are using the application's lib/ folder, you will need to create an entry on the initializers/ folder and require your new file. If you are developing a gem/plugin, you will have a init.rb file on the "root" folder of that plugin. Put the 'require' there.
I'm not very familiar with this unloadable stuff; maybe I'm asking something obvious but- have you tried making your extension module unloadable, too? (You shouldn't need this if you monkeypatched the module instead of creating a new one)
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.