Rails engine concerns autoload paths - ruby-on-rails

I have some rails engine 'Core', and I have:
# core/app/models/core/concerns/user_helper.rb
module Core
module UserHelper
extend ActiveSupport::Concern
included do
# some methods
end
end
end
# core/app/models/core/user.rb
module Core
class User < ActiveRecord::Base
include Core::UserHelper
end
end
however it says uninitialized constant Core::UserHelper. So it seems engine doesn't load its concerns by default, so I added it in the autoload paths
module Core
class Engine < ::Rails::Engine
config.autoload_paths += %W(#{Core::Engine.root}/app/models/core/concerns)
isolate_namespace Core
end
end
And now I end up this error: Unable to autoload constant UserHelper, expected myapp/core/app/models/core/concerns/user_helper.rb to define it
So what is wrong here? When I checked the guide http://edgeguides.rubyonrails.org/engines.html and it didn't have the concerns in concerns directory, but rather under lib/concerns and had all reference to concern using Core::Concerns::MyConcern, so is this where to put concerns in engine?
Thanks
Edit
Yury comment explained the issue, it seems that in rails engines concerns directory don't get any special treatment, and it is treated as a normal directory under models, so modules in it must be within Concerns namespace, and when including a concern, you have to include it with Concerns namesapace as well, if I understand right. I 'm surprised this is not mentioned in the docs.

The concern must reside inside the app/models|controllers/concerns/engine_name/concern_name.rb. This will autoload the concern.
To include the concern, include EngineName::ConcernName.

I had the same issue. Your mistake is that you are putting the concerns directory in the app/{models|controllers}/core directory, when it should be the other way round.
Instead of doing
app/{models/controllers}/core/concerns/user_helper.rb
change it to be
app/{models/controllers}/concerns/core/user_helper.rb
It took me a bit to figure out, because I intuitively thought it should also be under the engine_name directory.
Hope this helps.

Related

Zeitwerk and Modules Nested in Classes

I'm having some trouble switching from the classic autoloader to Zeitwerk with a Rails app that's seen the light of day back in Rails 3 days – so there's some crust there.
Some model code has been extracted to modules and these modules are nested in the model class (which acts as the namespace):
# app/models/donation
class Donation < ApplicationRecord
(...)
end
# app/models/donation/download
class Donation
module Download
def csv
(...)
end
end
end
The modules are then used on the fly when needed:
donation = Donation.find(...)
donation.extend(Donation::Download).csv
Since the subdirs in app/models are not added by default, it's done explicitly in application.rb:
Dir[
"#{config.root}/app/models/*/"
].then do |paths|
config.autoload_paths += paths
config.eager_load_paths += paths
end
The eager_load_paths are required by Zeitwerk (as per the Rails guides), however, Zeitwerk doesn't seem to like this constellation:
% rails zeitwerk:check
Hold on, I am eager loading the application.
expected file app/models/donation/download.rb to define constant Download
Strange, because Download is defined there. Any idea what's going on here and how best to refactor things to work with Zeitwerk?
Thanks for your hints!
Hmmm, that should work out of the box, looks like a regular setup to me.
Since app/models is in the autoload paths, Donation and Donation::Download are going to be autoloaded just fine, no custom configuration is needed.
If they do not, the app has to be doing something funky. We could debug it.

How can I add autoload_paths in my Rails4 Engine?

Usually I add the following in config/application.rb to add autload_paths:
config.autoload_paths += Dir[Rails.root.join('app', 'poros', '{**}')]
How can I achieve the same in an engine?
It seems to work when I just use the same code in application.rb in the host app, however I think it's ugly that the code is not in the engine and needs to be added to the host app to make things work.
The only solution I found to add the load path through the engine is by adding this to lib/engine/engine.rb:
config.to_prepare do
Dir.glob(Rails.root + "../../app/poros/**/*.rb").each do |c|
require_dependency(c)
end
end
However there seems to be something fundamentally wrong with this as this leads to problems when I'm doing console reloads (e.g. it tells me that constants are already defined or that concerns can't execute the include block twice)
What is the right way to do this in the engine itself? (can't believe this is so hard/uncommon, I have really googled a lot but I can't find a solution)
According to the Rails::Engine documentation, you can add autoload paths in your Railtie like this:
class MyEngine < Rails::Engine
# Add a load path for this specific Engine
config.autoload_paths << File.expand_path("../lib/some/path", __FILE__)
initializer "my_engine.add_middleware" do |app|
app.middleware.use MyEngine::Middleware
end
end
If poros is a subdirectory of app, you do not need add it again.
All subdirectories of app in the application and engines present at boot time. For example, app/controllers. They do not need to be the default ones, any custom directories like app/workers belong automatically to autoload_paths.

Cannot define multiple 'included' blocks for a Concern (ActiveSupport::Concern::MultipleIncludedBlocks) with cache_classes = true

I have a certain module which is used in a Rails 4.1.1 application
module A
extend ActiveSupport::Concern
included do
#Some code
end
end
which is included in a class
class Some
include A
end
This works great with cache_classes=true in application.rb. Now, if I turn off the caching of classes, I get Cannot define multiple 'included' blocks for a Concern (ActiveSupport::Concern::MultipleIncludedBlocks) exception upson starting the server.
How should one deal with such an issue since reloading the classes is done by Rails?
For anyone hitting the same wall to read, the solution to this is to strictly respect Rails autoloading rules. That is
Removing all the require / require_relative
Add needed paths to Rails autoload paths
Put files at the right places with the right names so Rails can infer where to look for code to load.
More info here: https://github.com/rails/rails/issues/15767
It's also possible that you have two concerns with same name.
In my case I faced this error while running rails swagger:docs SD_LOG_LEVEL=1 .
$ rails swagger:docs SD_LOG_LEVEL=1
Cannot define multiple 'included' blocks for a Concern
1.0: 19 processed / 49 skipped
Since I had two swagger files with same name.
module SwaggerDocs::TrackerPhases
extend ActiveSupport::Concern
included do
end
end
module SwaggerDocs::TrackerPhases
extend ActiveSupport::Concern
included do
end
end
I renamed second file as:
module SwaggerDocs::ClientTrackerPhases
extend ActiveSupport::Concern
included do
end
end
In my case, I had a gem installed which was using the same module name as our concern. For several years, this wasn't causing any known issues in development or production, but we started getting this error the first time we installed Sorbet and were trying to run sorbets init command.
More specifically,addressable (2.7.0) was listed in our Gemfile.lock (installed as a dependency of another gem, we were not explicitly using this gem). And we also had a concern as:
module Addressable
extend ActiveSupport::Concern
included do
end
end
Renaming our concern fixed the issue.

Organizing models/controllers and classes in rails in subfolders

I am working in a Rails project in which i have used the below names for model/controller and class files
/app/models/friends/friend.rb
/app/controllers/friends/friends_controller.rb
/lib/classes/friends/friend.rb
I tried to add all the models, controllers and class files in autoload path in application.rb.
But i am facing issues since the class names are same.
How should i handle this? and organize files in such a way that files are organized with name spaces.
Thanks,
Balan
A much better approach would be to use Rails Engines & divide your app in isolated modules.
rails plugin new friends --full --mountable --dummy-path spec/dummy
the above command will generate a full mountable engine with isolated namespace, meaning that all the controllers and models from this engine will be isolated within the namespace of the engine. For instance, the Post model later will be called Friends::Post, and not simply Post. to mount this app inside your main rails app, you need do two things:
Add entry to Gemfile
gem 'friends', path: "/path/to/friends/engine"
And then add route to config/routes.rb
mount Friends::Engine, at: "/friends"
For more information on this approch, checkout:
Rails Guide to Engines
Taming Rails Apps with Engines
RailsCast #277 Mountable Engines
The class names are same but path's are different, and you don't need to add classes to autoload except /lib/classes/friends/friend.rb
Did you tried the following way:
# app/models/friends/friend.rb
class Friends::Friends
#...
end
# Friends::Friends.new
# app/controllers/friends/friends_controller.rb
class Friends::FriendsController < ApplicationController
#...
end
# lib/classes/friends/friend.rb
module Classes
module Friends
class Friends
#...
end
end
end
# Classes::Friends::Friends.new
To add lib files to autoload add following to your applicaion.rb
config.autoload_paths += %W(#{config.root}/lib)

Rails include module in model trouble

I have module in /lib/models/scopes.rb
module Models
module Scopes
extend ActiveSupport::Concern
...
end
end
I'm trying to include it from model:
class User < ActiveRecord::Base
include Models::Scopes
end
And getting error:
NameError: uninitialized constant User::Models
How to solve this trouble? Maybe it`s wrong to keep this types of files in /lib?
Environment:
Rails v3.1
Ruby v1.9.3
Rails doesn't require files in the lib directory automatically, but you can add to the autoloaded paths in config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Restart the server to pick up the new settings.
This will now load the file automatically when the module name is first used. In development mode, you might want to reload the module after every change in order to see the changes without restarting the server. To do that, add it as an eager load path instead:
config.eager_load_paths += %W(#{config.root}/lib)
The scope shouldn't be a problem as long as you don't have a Models class or module within User or anywhere else.
when you define your class, you're "opening" a new scope. So when you do Models::Scopes, ruby is looking for User::Models::Scopes. You can fix this by using ::Models::Scopes, the :: telling ruby to look in the global scope.
FYI: I'm not sure about the terms I used or even if my train of thought if correct; but the solution should be good anyway. I'd think Ruby would try for ::Models::Scope after failing to find User::Models::Scope, but it doesn't.. Maybe there is a User::Models scope defined somewhere? Anyway, as you can see, I'm not yet familiar with those. You might want to dig on the subject if that interests you

Resources