I am working with a Rails project and don't quite understand how Rails autoloading works in my particular case. I read some articles about Rails' autoloading and its pitfalls but those didn't really help me
I am building a processor for tasks (exercises). Each task has its custom processor class in Tasks::<TaskName>::Processor that mixes in module Tasks::Processor that contain shared code for task processors. Processors contain class Get (for processing GET requests) located in Tasks::<TaskName>::Processor::Get that mixes in Tasks::Processor::Get containing generic Get's code.
I've simplified the code a little bit so it's easier to understand and removed all the business logic but it's still enough to reproduce the problem.
So the problem is:
when I run Tasks::TaskOne::Processor.new.get it works fine, but if I run Tasks::TaskTwo::Processor.new.get after that it throws an error: NoMethodError: undefined method `new' for Tasks::Processor::Get:Module. It also works the other way round: if I run TaskTwo's processor's code first then it works fine but the TaskOne's processor will throw the error. It just fails to find the specific implementation of Get and instead finds the generic module and tries to instantiate it which is obviously impossible.
Here is the code together with the structure.
Shared code:
app/models/tasks/processor.rb:
module Tasks
# generic Processor (mixed in by custom processors)
module Processor
# ...
end
end
app/models/tasks/processor/get.rb:
module Tasks
module Processor
# generic Get
module Get
# ...
end
end
end
TaskOne's code:
app/models/tasks/task_one/processor.rb:
module Tasks
module TaskOne
# processor for task_one
class Processor
include Tasks::Processor # mix in generic task processor
def get
Get.new.call
end
end
end
end
app/models/tasks/task_one/processor/get.rb:
module Tasks
module TaskOne
class Processor
# task_one's processor's custom Get
class Get
include Tasks::Processor::Get # mix in generic Get
def call
puts "in task_one's Processor's Get"
end
end
end
end
end
And practically identical code for the TaskTwo:
app/models/tasks/task_two/processor.rb:
module Tasks
module TaskTwo
# processor for task_two
class Processor
include Tasks::Processor # mix in generic task processor
def get
Get.new.call
end
end
end
end
app/models/tasks/task_two/processor/get.rb:
module Tasks
module TaskTwo
class Processor
# task_two's processor's custom Get
class Get
include Tasks::Processor::Get # mix in generic Get
def call
puts "in task_two's Processor's Get"
end
end
end
end
end
It has most likely something to do with Rails' autoloading, because when I use plain ruby and manually require all the files and try to run the code the problem doesn't happen.
Could you, please, explain why it works like this and tell me what the best way to avoid this problem is? Seems like Rails doesn't like the fact that I have a class and a module with same name and it gets confused, but I thought it shouldn't be a problem as they are in different namespaces.
I could have just named the generic class something different, but I'd really like to understand why using the same class name for both specific implementation and generic one only works for the first thing to load but not for the next. Thank you very much for your help!
P.S. my version of Ruby is 2.5.1 and Rails version is 5.2.1
I was literally reading about autoloading yesterday. Your problem is the same as the one outlined here:
https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#when-constants-aren-t-missed
Basically, any time you write Get.new.call, you need to be more specific. It doesn’t know which Get to use in the tree of possible Gets. The first time you call it, it hasn’t had to load up more than one Get class, and so it actually finds the right one. After that call, you’ve now auto loaded MORE classes, and now things start to get dicey. You need to either qualify your Get to be more specific, and/or use require_dependency to force the right classes to be loaded in. However given your case, I think require_dependency will just make it fail every time, since you’ll now have all of the classes loaded up.
Related
I am using a plugin in Rails, and I call its methods without problems:
plugin_module::class_inside_module.method_a(...)
I want to re-open the class_inside_module and add a new method, I tried in many different ways. I can't figure out why in this way doesn't work:
class plugin_module::class_inside_module
def new_method
puts 'new method'
end
end
I get the error: uninitialized constant plugin_module, but how is possible if I can call without problem plugin_module::class_inside_module.any_methods ?
Do you know why I get that error ? why "uninitialized constant" ? (it is a class declaration :-O )
Do you have any ideas how I can add a new methods in a class inside a module (that is part of a plugin) ?
Thank you,
Alessandro
If you have written your class and module-names like you did, so plugin_module instead of PluginModule this is against ruby/rails standards, and rails will not be able to automatically find the class and module.
If you write something like
module MyModule
class MyClass
end
end
Rails will expect this file to be located in lib\my_module\my_class.
But this can always easily be overwritten by explicitly doing a require.
So in your case, when you write
module plugin_module::class_inside_module
Rails will not know where to find the module plugin_module.
This way of writing only works if module plugin_module is previously defined (and loaded).
So either add the correct require, or rename your modules to standard rails naming, or write it as follows:
module plugin_module
class class_inside_module
This way will also work, because now the order no longer matters.
If the module is not known yet, this will define the module as well.
Either you are re-opening the class, or you define it first (and the actual definition will actually reopen it).
Hope this helps.
Have you tried reopening the module that's wrapping the class, rather than relying on ::?
module plugin_module
class class_inside_module
def new_method
puts 'new_method'
end
end
end
By the way, you know that the proper name for modules and classes is use CamelCase with a capital first letter?
module PluginModule
class ClassInsideModule
def new_method
puts 'new_method'
end
end
end
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 successfully created a gem having some classes and modules to be'ing able to to something like that in ANY kind of class in a Rails project:
class AnyRubyOrActiveModelClass
acts_as_anything [:foo, :bar]
def foo
.. do some foo
end
def self.bar
.. do some bar
end
end
To do so I created a Module in my gem that looked something like that:
module InstanceMagic
class << self.class.superclass
define_method(:acts_as_anything) do |methods|
self.class_eval do
include ClassMagic
.. do some alias_method with given methods
end
end
end
This module successfully aliased my method :foo from the example above, the second module ClassMagic did the same for my :bar class method (following the advice from here).
In a testproject that approach worked very well. In a real life project it led to interference with another gem taking a - maybe similar - approach. This gem complained about missing methods in a class even when I only integrated my gem into the project - not even integrated acts_as_anything into the class.
So I broke down my code to only that:
module InstanceMagic
class << self.class.superclass
define_method(:acts_as_anything) do |methods|
#really empty here
end
end
As a result the other gem still breaks.
I used class << self.class.superclass to explicitly extend Object, so that even non ActiveSomething classes but ALL classes are affected and my acts_as_anything is available. So I remain with three questions.
Why do the last 5 lines of code break another gem and making it complain about missing methods it's trying to dynamically create? I would like to understand.
Is there a better approach to achieve my goal?
When I use method_added and singleton_method_added (what I actually do inside my modules), should I look for these methods whether they already exist, alias the "original" ones, insert my ones and call the original ones after I have done my job?
Knowing this is a lot I still hope someone can point me into the right direction.
Thank you.
Felix
The only thing I can think of is some sort of order dependency (long shot). Try to put your code in an initializer in the rails app and see if it still causes the same problems.
Inside ActionController class (rails/actionpack/lib/action_controller.lib) I found several weird code. I don't really have a mentor to learn Ruby on Rails from, so this forum is my only hope:
Question #1: Could anyone help me explain these lines of codes?
begin
require 'active_support'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support'
end
end
Especially the line with $:.unshift activesupport_path
In my thought, it tries to require active_support class, and if that doesn't work, it looks if activesupport_path is a directory, if it is, then . . . I totally lost it.
Question #2: What autoload method is for?
module ActionController
# TODO: Review explicit to see if they will automatically be handled by
# the initilizer if they are really needed.
def self.load_all!
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
end
autoload :Base, 'action_controller/base'
autoload :Benchmarking, 'action_controller/benchmarking'
autoload :Caching, 'action_controller/caching'
autoload :Cookies, 'action_controller/cookies'
.
.
.
Question #3: If I later find a method I don't understand what for, how is the best way to find out? As for that autoload method case, I tried to find it across my project (I have my Rails code frozen there) but couldn't find any clue. I searched for "def autoload". Am I doing things wrong? Is my IDE, TextMate just doesn't cut it?
Thank you!
In order for a file to be required you have to ensure that the path to it is in the Ruby $LOAD_PATH variable. This is has a short-hand version $: for legacy reasons, inheriting this from Perl.
When you call require, the interpreter looks for a .rb file in each of the paths given there until it finds a match. If it finds one, it is loaded. If not you get an exception.
Often you will see lines like this in files:
# script/something
# This appends "script/../lib" to the $LOAD_PATH, but this expands to
# something like "/home/user/project/lib" depending on the details of
# your installation.
$: << File.expand_path(File.join('..', 'lib'), File.dirname(__FILE__))
You can use standard Array modifiers on $LOAD_PATH like unshift, push, and <<.
The first block of code is attempting to load active_support and only if that fails does it go about modifying the $LOAD_PATH to include the likely location of this file based on the path to the file making the require call. They do this because typically all gems from the Rails bundle are installed in the same base directory.
The reason for using unshift is to put that path at the highest priority, inserted at the front of the list. The << or push method adds to the end, lowest priority.
When you require a file it is loaded in, parsed, and evaluated, an operation which can take a small but measurable amount of time and will consume more memory to hold any class or method definitions inside the file, as well as any data such as string constants that may be declared. Loading in every single element of a library like ActiveRecord using require will require a considerable amount of memory, and this will import every database driver available, not just the ones that are actually used.
Ruby allows you to declare a class and a path to the file where it is defined, but with the advantage of not actually loading it in at that moment. This means that references to that class don't cause script errors in other parts of your application that make use of them.
You will often see declarations like this:
class Foo
# Declare the class Foo::Bar to be defined in foo/bar.rb
autoload(:Bar, 'foo/bar')
end
When using autoload you need to keep in mind that the class name is always defined within the scope of the module or class declaring it. In this example Bar is within Foo, or Foo::Bar using Ruby naming conventions.
When you make use of the Bar class, the foo/bar.rb file will be required. Think of it as creating a stub Bar class that transforms into the real class once it's actually exercised.
This is a great way of keeping a lot of options open, with many different modules ready to use, but without having to load everything into memory up front.
As for the third question, searchable documentation like APIDock will help you try and find more information on methods. The distinction between Ruby and Rails is often blurred, so you may have to check through both to be sure. Rails adds a lot of methods to core Ruby classes, so don't take the listing of methods available to be complete on either side. They work in conjunction.
Sometimes it pays to search for def methodname when trying to find out about where methodname originates, although this covers only conventional declarations. That method may be an alias from a mechanism like method_alias or may have been dynamically created using define_method, you can never really be sure until you dig around. At least 90% of the methods in Rails are declared the conventional way, though, so most of the time a simple search will yield what you want.
I'm trying to add a function that will be accessible throughout all parts of my program. I want something like:
def GlobalFunctions.my_function(x,y)
puts x + y
end
to be accessible for all models. Specifically I am trying to use a function like this in my seeds.rb file but I am most likely going to be reusing the code and don't want any redundancy. Now I know I can make a simple class, but I could also make a module. What are some reasons to go in either direction? And once I've decided on which type to use, how do I make it accessible throughout the whole program?
I have tried a module, but I keep getting " Expected app/[module file] to define [ModuleName]"
You'd define a class for something you'll want to make instances of. In this case, a module would probably be better, as modules basically just group code together:
module GlobalFunctions
def self.my_function(x,y)
puts x+y
end
end
GlobalFunctions.my_function(4,5) # => 9
Alternatively, you could define it on Kernel, and then just call it anywhere. Kernel is where methods like puts are defined.
def Kernel.my_function(x,y)
puts x + y
end
my_function(4,5) # => 9
Adding methods to Kernel (answer from PreciousBodilyFluids) is usually considered a bad smell and can lead to some really hard to find bugs in large projects.
It's much more accepted to relevantly namespace the code and put in to /lib/.
class Formatting
def self.bold(str)
return "<strong>#{str}</strong>"
end
end
You can then:
require 'formatting'
puts Formatting.bold("text")
or
require 'formatting'
include Formatting
puts bold("text")
It'll be clear to anyone who comes to the code later what you're using too. If you're using Rails you won't need the require.
PreciousBodilyFluids is correct, and if this GlobalFunctions is part of a RoR project, you may want to name the file global_functions.rb and place it in the lib/ directory to help you avoid the error message you posted at the end of your question.