Rails LoadError for Models/Controllers with different namespaces - ruby-on-rails

I decided to do a bit of refactoring on my code since one of my project starts to get really big. I thus decided to namespace some models in quite independent modules, for clarity.
However, my controllers have an extra outer module since I have different subdomains or different kinds of controllers for all those models.
Therefore, I have a model A::MyModel in app/models/a/my_model.rb
and controllers X::A::MyModelsController and Y::A::MyModelsController in app/controllers/x/a/my_models_controller.rb and `app/controllers/y/a/my_models_controller.rb.
I happen to get, on some endpoints only, an exception LoadError: Unable to autoload constant MyModel, expected [...]/app/models/a/my_model.rb to define it. Exception is pretty clear except that it should be looking for A::MyModel.
It only happens on some of the endpoints, I even managed to make it work for an endpoint with AJAX, but not in HTML. However, it works properly with Rspecs as none of my tests fail.
I suspect this has something to do with Autoload as when it fails, it does not even get into the controller: There is no stack and no Processed By xxx in the logs.
I am using Ruby 2.4.2, Rails 5.2.0, and here is my application.rb
require_relative "boot"
require "rails"
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie"
require "rails/test_unit/railtie"
Bundler.require(*Rails.groups)
module MonApp; end
class MonApp::Application < Rails::Application
config.action_view.embed_authenticity_token_in_remote_forms = true
config.generators do |generate|
generate.assets false
end
config.active_job.queue_adapter = :sidekiq
config.autoload_paths += Dir[Rails.root.join("app", "models", "**", "*")]
config.middleware.use I18n::JS::Middleware
config.exceptions_app = self.routes
end
Surprisingly, it seems to fail when using Ajax with a contentType: "application/json". Same endpoints without ajax or without this contentType seem to work.
My controller is
class X::A::MyModelsController
def create; end
end
Has anyone some insights on this?
Thanks!

Related

How to autoload classes to use in controller

I have a class in libs and try to use it in a controller. However I cannot access it. I tried to use the autoload function, but that doesn't work, and it shouldn't work in rails 5 in production mode, so I guess I don't need to try this one.. I also tried to require it in my controller, but I don't get the syntax correct I guess. I'm also wondering where to put my class, since I have read several different opinions..
config/application.rb
require_relative 'boot'
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Qipmatedevel
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 5.1
config.active_job.queue_adapter = :sidekiq
config.autoload_paths += %W(#{config.root}/lib)
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
end
end
app/controller/imports_controller
class ImportsController < ApplicationController
require 'lib/class_qip'
adding
require './lib/class_qip.rb'
fixed it
Put your classes in lib directory only & load as,
config.eager_load_paths << Rails.root.join('lib')
It works on both development and production environment.

Rails 5 API - error autoloading helper when using the api_only flag

I'm trying to use a module from the folder 'app/helpers/transaction_helper.rb'. (It's important to note that the application is using the api_only flag, and helpers aren't being generated or loaded by default.)
module TransactionHelper
module Generator
def self.code_alphanumeric(params)
end
end
end
I got this error:
NameError: uninitialized constant TransactionHelper
I tried to add the following line to the application.rb
config.autoload_paths += %W(#{config.root}/app/helpers)
require_relative 'boot'
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
# require "sprockets/railtie"
require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module SistemaControleContasApi
class Application < Rails::Application
config.autoload_paths += %W(#{config.root}/app/helpers)
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true
end
end
But this until not work. If I try to put this file in the app/models directory, this work. However, this is not the right local to place a helper.
Could someone help, please?
Normally everything under app is autoloaded by default if you follow the Rails™ naming conventions. See here Docs.
set_autoload_paths: This initializer runs before bootstrap_hook. Adds
all sub-directories of app and paths specified by
config.autoload_paths, config.eager_load_paths and
config.autoload_once_paths to
ActiveSupport::Dependencies.autoload_paths.
So in the first step you should remove config.autoload_paths += %W(#{config.root}/app/helpers) from your config.
Then for every module you should create a subfolder.
So for your problem you should put your module under app/modules/transaction_helper/generator.rb
Also you don't need self in a module scope.
Personally I would create the helper as follows:
module TransactionHelper
def generate_alphanumeric_code
# params are automatically available in helpers.
end
end
And put it under app/modules/transaction_helper.rb.

How to properly load lib modules and classes in Rails 5 app

How do I include a 'lib/' class or module in my models, Grape API and tests? For example, I have a class:
ROOT/lib/links/link.rb
module Links
class Link
...
end
end
And I want to include that class in my User model (app/models/user.rb), User Grape API (app/api/v1/users.rb), and testing suites (test/models/user_test.rb and test/api/v1/users/users_links_test.rb)
For example, I tried accessing it in my tests through
link = Links::Link.new(LINK_NAME, LINK_SITE)
but I get:
uninitialized constant API::V1::Users::APITest::Links
I've tried adding this to my config/application.rb:
config.autoload_paths += Dir["#{Rails.root}/lib/**"]
require_relative 'boot'
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
# require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie"
require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module ArbitraryAppName
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Auto-load API and its subdirectories
config.paths.add 'app/api', glob: '**/*.rb'
config.autoload_paths += Dir["#{Rails.root}/app/api/*"]
config.autoload_paths << "#{Rails.root}/lib"
# For Cross-Origin Resource Sharing (rack-cors)
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
end
end
but it doesn't work. What am I missing? How do I include lib files and how should I be calling those classes?
EDIT:
It seems like the only way I can access it is if I do:
require "#{Rails.root}/lib/links/link"
Is there a better, more conventional way?
There seems to be another issue, adding config.autoload_paths << whatever does not seem to be doing anything. For example, when I puts ActiveSupport::Dependencies.autoload_paths in rails console, my changes do not appear.
No, you shouldn't resort to using require (if you want to follow Rails conventions). Rails autoloading is based on paths and namespacing, as those two things have to match up. Besides the initial problem with namespacing that was fixed, the way you modify the autoload paths is incorrect. It should be done this way:
config.autoload_paths << "#{Rails.root}/lib"
This is if you want to leave lib/ at the root of your project. As mentioned in the comments, you could move it into your app/ directory.
Paul Hoffer's answer is the accepted answer but for anyone who notices that any changes to config/application.rb isn't persisting:
There seems to have been an issue with Spring, where any changes to the config/application.rb file wasn't persisting. I had to run spring stop to get things to work again. If anyone can explain why this happens sometimes, that'd be nice.
You should not put your lib into autoload_paths (as Paul Hoffer suggested), since doing so will prevent it being eager loaded in production (when config.eager_load = true).
You will find extensive guide on this topic here: http://hakunin.com/rails3-load-paths

How is this an un-initialized constant. (Mixins in rails)

under lib/ I have 'aisis_writer/loader.rb' which, inside that file, looks like:
module AisisWriter
module Loader
end
end
I then have, in my application.rb the following set up:
require File.expand_path('../boot', __FILE__)
# Pick the frameworks you want:
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "sprockets/railtie"
# require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env)
module AisisWriter
class Application < Rails::Application
# Load Lib
config.autoload_paths += %W(#{config.root}/lib)
# Use Rack Attack For Throttling
config.middleware.use Rack::Attack
end
end
From there I did, in the ApplicationController.rb: include AisisWriter::Loader and then I ran my tests and got:
'<class:ApplicationController>': uninitialized constant AisisWriter::Loader (NameError)
Either I cannot do what I am doing because of naming conflicts or I am doing something wrong. Any one care to tell me what I might be doing wrong?
I don't think your config.autoload_paths is broad enough -- it's not including subfiles of the lib directory.
This should do the trick:
config.autoload_paths += Dir[Rails.root.join('lib', '{**/}')]
Try defining like this within 'aisis_writer/loader.rb'
module AisisWriter::Loader
end

Fast Testing: How to test Mailer without Loading Rails

I've got some work to do refactoring a mailer class. I don't want to spend all my time loading rails and waiting to see if tests are passing. I'd like to just not include spec_helper and speed up my tests.
How do I just include ActionMailer?
I tried this:
require 'action_mailer'
but I stil end up with this error:
uninitialized constant ActionMailer (NameError)
Any ideas?
I'm using MiniTest, and the following works fine for me. You may be able to extrapolate from this, or provide some more info in your question.
require 'minitest/autorun'
require 'mocha'
require "minitest-matchers"
require 'action_mailer'
require "email_spec"
require File.expand_path('../../../../app/mailers/my_mailer', __FILE__)
class MyMailerTest < MiniTest::Unit::TestCase
include EmailSpec::Helpers
include EmailSpec::Matchers
def test_it_is_sent_from_me
email = MyMailer.refund_processed(42)
email.must be_delivered_from("me#example.com")
end
end

Resources