This question already has an answer here:
`ClassName.constants` returning empty array in Rails app
(1 answer)
Closed 7 years ago.
I know that Rails has an opinion on what the name of my classes and modules should be. As such, I have tried to cohere with it.
In Rails.root/lib/query_finder directory, I have the following structure:
/lib
/query_finder
/adapters
active_record.rb
mongoid.rb
base.rb
/strategies
base.rb
In base.rb, I named my class like so:
module QueryFinder
class Base
end
end
In adapters/base.rb, I named my class like so:
module QueryFinder
module Adapters
class Base
end
end
end
In adapters/mongoid.rb, I named my class like so:
module QueryFinder
module Adapters
class Mongoid
end
end
end
In adapters/active_record.rb, I named my class like:
module QueryFinder
module Adapters
class ActiveRecord
end
end
end
But Rails is unable to find the adapters. I try to grab all the constants:
> QueryFinder::Adapters.constants
=> []
And it's giving me an empty array. I also added the following to autoload path:
config.autoload_paths += Dir["#{config.root}/lib/**/"]
What might I be doing wrong?
I just want to make a note that I am able to reference the constants and classes like so:
QueryFinder::Adapters::Base
=> QueryFinder::Adapters::Base
The problem is when I use the constants method, it gives an empty array.
The problem is lazy loading. Rails, in production mode, is usually set up to do lazy loading. That means that the constants are not actually defined until you reference them: At that time it notices you're referencing an undefined constant, finds the file, and loads it.
If you turned off lazy loading, then you could rely upon .constants to tell you what was available. But performance would suffer.
Another workaround would be for you to explicitly load the required files.
Related
This question may just be for Ruby, but it was from working on a Rails that spurred me asking this question.
Suppose I am creating a new module so that I can better organize correlated/coupled code. Let's call this module amazing_feature and all of it's classes/submodules are located in the app/services directory. So according to code loading principles, the entire module should be in the app/services/amazing_feature directory in order to be loaded properly.
Let's say that I have two classes for this module:
# app/services/amazing_feature/thing_one.rb
module AmazingFeature
class ThingOne
...
end
end
# app/services/amazing_feature/thing_two.rb
module AmazingFeature
module ThingTwo
...
end
end
There are some constants that I would like to be available for all of the classes/submodules within module AmazingFeature, as well as being available from the AmazingModule namespace for any external code (eg, other controllers and models, in the Rails point of view). For example, if I want to define MY_CONSTANT = 1, then it would be accessible as just MY_CONSTANT within the module and as AmazingFeature::MY_CONSTANT from outside the module.
So the question is, how can I actually accomplish this in Ruby or Rails? There are thoughts that I've had, approaches that think may work, or approaches that I have seen elsewhere, such as other SOF posts:
Make a file directly in app/services for the module that associate the constants directly to the module. I don't prefer this approach because it feels weird putting a file coupled to the module outside of its subdirectory.
# app/services/amazing_feature.rb
module AmazingFeature
MY_CONSTANT = 1
end
Load the constants globally as a Rails initializer (ie, in config/initializers). I also have the same dislike for this approach as above.
Create a Constants module in the subdirectory so that the constants are colocated with all other code for the module. I just don't know how to properly associate these constants to the parent module, so there is a missing piece in this code example.
# app/services/amazing_feature/constants.rb
module AmazingFeature
module Constants
MY_CONSTANT = 1
end
end
# Now what??? :(
Some other approach? I'm at a loss here.
Thank you.
You can do whatever you want of course, but take some inspiration from popular gems:
https://github.com/rails/rails/blob/master/activerecord/lib/active_record.rb
https://github.com/sparklemotion/nokogiri/blob/master/lib/nokogiri.rb
https://github.com/heartcombo/devise/blob/master/lib/devise.rb
It is normal to rely on Ruby autoloaders to map the names of your constants (AmazingFeature) to file names that contain those constants. So AmazingFeature could map to load/path/amazing_feature.rb and AmazingFeature::Greatness could map to load/path/amazing_feature/greatness.rb.
I'd advise doing this and pretty soon it won't feel weird :)
I think this is a good idea:
# app/services/amazing_feature/constants.rb
module AmazingFeature
module Constants
MY_CONSTANT = 1
end
include AmazingFeature::Constants
end
module AmazingModule
include AmazingFeature::Constants
end
# Then
AmazingFeature::MY_CONSTANT # => 1
AmazingModule::MY_CONSTANT # => 1
I've multiple issues to load / require classes under my app/services folder in a Rails 5 project and I'm starting to give up on this issue.
First of all and to be clear, services/ are simple PORO classes I use throughout my project to abstract most of the business logic from the controllers, models, etc.
The tree looks like this
app/
services/
my_service/
base.rb
funny_name.rb
my_service.rb
models/
funny_name.rb
Failure #1
First, when I tried to use MyService.const_get('FunnyName') it got FunnyName from my models directory. It does not seem to have the same behavior when I do MyService::FunnyName directly though, in most of my tests and changes this was working fine, it's odd.
I realised Rails config.autoload_paths does not load things recursively ; it would makes sense that the first FunnyName to be catch is the models/funny_name.rb because it's definitely loaded but not the other.
That's ok, let's find a workaround. I added this to my application.rb :
config.autoload_paths += Dir[Rails.root.join('app', 'services', '**/')]
Which will add all the subdirectories of services into config.autoload_paths. Apparently it's not recommended to write things like that since Rails 5 ; but the idea does look right to me.
Failure #2
Now, when I start my application it crashes and output something like this
Unable to autoload constant Base, expected
/.../backend/app/services/my_service/base.rb to define it (LoadError)
Names were changed but it's the matching path from the tree I wrote previously
The thing is, base.rb is defined in the exact file the error leads me, which contains something like
class MyService
class Base
end
end
Poor solution
So I try other workaround, lots of them, nothing ever works. So I end up totally removing the autoload_paths and add this directly in the application.rb
Dir[Rails.root.join('app', 'services', '**', '*.rb')].each { |file| require file }
Now the base.rb is correctly loaded, the MyService.const_get('FunnyName') will actually return the correct class and everything works, but it's a disgusting workaround. Also, it has yet not been tested in production but it might create problems depending the environment.
Requiring the whole tree from the application.rb sounds like a bad idea and I don't think it can be kept this way.
What's the cleanest way to add custom services/ directory in Rails ? It contains multiple subdirectories and classes with simple names which are also present in other parts of the app (models, base.rb, etc.)
How do you avoid confusing the autoload_paths ? Is there something else I don't know which could do the trick ? Why did base.rb even crash here ?
Working solution
After deeper investigation and attempts, I realised that I had to eager_load the services to avoid getting wrong constants when calling meta functionalities such as const_get('MyClassWithModelName').
But here's is the thing : the classic eager_load_paths won't work because for some reason those classes will apparently be loaded before the entire core of Rails is initialized, and simple class names such as Base will actually be mixed up with the core, therefore make everything crash.
Some could say "then rename Base into something else" but should I change a class name wrapped into a namespace because Rails tell me to ? I don't think so. Class names should be kept simple, and what I do inside a custom namespace is no concern of Rails.
I had to think it through and write down my own hook of Rails configuration. We load the core and all its functionalities and then service/ recursively.
On a side note, it won't add any weight to the production environment, and it's very convenient for development.
Code to add
Place this in config/environment/development.rb and all other environment you want to eager load without Rails class conflicts (such as test.rb in my case)
# we eager load all services and subdirectories after Rails itself has been initializer
# why not use `eager_load_paths` or `autoload_paths` ? it makes conflict with the Rails core classes
# here we do eager them the same way but afterwards so it never crashes or has conflicts.
# see `initializers/after_eager_load_paths.rb` for more details
config.after_eager_load_paths = Dir[Rails.root.join('app', 'services', '**/')]
Then create a new file initializers/after_eager_load_paths.rb containing this
# this is a customized eager load system
# after Rails has been initialized and if the `after_eager_load_paths` contains something
# we will go through the directories recursively and eager load all ruby files
# this is to avoid constant mismatch on startup with `autoload_paths` or `eager_load_paths`
# it also prevent any autoload failure dû to deep recursive folders with subclasses
# which have similar name to top level constants.
Rails.application.configure do
if config.respond_to?(:after_eager_load_paths) && config.after_eager_load_paths.instance_of?(Array)
config.after_initialize do
config.after_eager_load_paths.each do |path|
Dir["#{path}/*.rb"].each { |file| require file }
end
end
end
end
Works like a charm. You can also change require by load if you need it.
When I do this (which is in all of my projects), it looks something like this:
app
|- services
| |- sub_service
| | |- service_base.rb
| | |- useful_service.rb
| |- service_base.rb
I put all common method definitions in app/services/service_base.rb:
app/services/service_base.rb
class ServiceBase
attr_accessor *%w(
args
).freeze
class < self
def call(args={})
new(args).call
end
end
def initialize(args)
#args = args
end
end
I put any methods common to the sub_services in app/services/sub_service/service_base.rb:
app/services/sub_service/service_base.rb
class SubService::ServiceBase < ServiceBase
def call
end
private
def a_subservice_method
end
end
And then any unique methods in useful_service:
app/services/sub_service/useful_service.rb
class SubService::UsefulService < SubService::ServiceBase
def call
a_subservice_method
a_useful_service_method
end
private
def a_useful_service_method
end
end
Then, I can do something like:
SubService::UsefulService.call(some: :args)
With your tree,
app/
services/
my_class/
base.rb
funny_name.rb
my_class.rb
models/
funny_name.rb
services/my_class/base.rb should look similar to:
module MyClass
class Base
services/my_class/funny_name.rb should look similar to:
module MyClass
class FunnyName
services/my_class.rb should look similar to:
class MyClass
models/funny_name.rb should look similar to:
class FunnyName
I say "should look similar to" because class/module are interchangable; Rails is merely looking for these constants to be defined in these locations.
You don't need to add anything to your autoload path. Rails automatically picks up everything in app
Anecdotal: With your services directory, it's fairly common to treat their naming convention (both name of file and underlying constant) to be "_service.rb" or "ThingService" — just like how controllers look. Models don't get this suffix because they're treated as first-class objects.
GitLab has some great file structure that is very worth a look at. https://gitlab.com/gitlab-org/gitlab-ce
We're in the process of making a major database change to our Rails application. In order to be able to interop with the existing code, my plan is to do all the work in module namespaces to keep them separate from the existing models. However, I'm running into Rails autoload problems.
My file structure is like:
app/
models/
entity/
new_thing.rb
old_thing.rb
Where new_think.rb contains something like
module Entity
class NewThing
end
end
and old_thing.rb contains something like
class OldThing
end
OldThing gets autoloaded fine, but I keep getting errors like this:
Expected app/models/entity/new_thing.rb to define NewThing
Is there a way I can get it to correctly expect entity/new_thing.rb to define Entity::NewThing?
Try:
In your old_thing.rb
class OldThing
Extend Entity
end
or
class OldThing
require "entity/new_thing"
end
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
Let's say I have something like this
class Major < ActiveRecord::Base
def self.my_kids
self.subclasses.collect {|type| type.name}.sort
end
end
class MinorOne < Major
end
class MinorTwo < Major
end
In Rails 2.3 I could call Major.my_kids and get back an array of the subclass names, but in Rails 3.0.3 I get back an empty array, unless I load the subclasses first. This seems wrong to me, am I missing something or is this new to Rails 3?
There's no difference that I know of between Rails 2 and 3 concerning the use of the subclasses method. You might have believed it was working previously because the subclasses were already loaded. As Rails load most files dynamically a parent class couldn't know about any class derived from it unless it is defined in the same file. The easiest way to ensure all models are loaded, you can simply call require on all files in the app/models directory:
Dir.glob(RAILS_ROOT + '/app/models/*.rb').each { |file| require file }
One other thing to note is that the subclasses method doesn't work after issuing the reload! command in the Rails console.
The reason why you're getting an empty array in Rails 3 is likely because Rails 3 uses autoloading.
If you open the Rails console and you reference the name of the subclasses, then run your 'subclasses' method on the parent class, you'll see it works. This is because Rails 3 only loads the classes into memory that you've referenced, when you reference them.
The way I ended up forcing my classes to load from a library I created under /lib was with the following code I added into the method that depends on those classes:
# load feature subclasses
my_classes_path = File.expand_path(File.dirname(__FILE__)) + "/my_classes"
if File.directory?(my_classes_path)
Dir.glob(my_classes_path + "/*.rb").each do |f|
load f
end
end