Rails custom environment: keep certain subdirectories in app/models from loading - ruby-on-rails

I've created a custom environment in the Rails app I'm working on called nhl_test. The models for this environment are in app/models/nhl namespace. There are a number of other models in app/models/[other_subdir] that I don't want to autoload in this environment. So far I tried modifying the config.paths in my environment file similarly to how I modified the db/migrate location, like so:
config/environments/nhl_test.rb
MyApp::Application.configure do
config.eager_load = false
config.paths["db/migrate"] = ["db/migrate/nhl"]
config.paths["app/models"] = ["app/models/nhl"]
end
However, the other subdirectories of app/models are still being loaded. I can tell because there is code in those models that will break in this environment and I'm unable to run my tests using RAILS_ENV=nhl_test m test/models/nhl - they break with a stack trace that points to app/models/mlb/base.rb.
How can I keep any of the models except what's in app/models/nhl from being loaded in this environment??
EDIT It turns out it's this line in test_helper.rb that is causing the problem:
class ActiveSupport::TestCase
fixtures :all
end
Rather than loading all fixtures, I just need to load the fixtures in test/fixtures/nhl somehow...
I've tried the following but it doesn't seem to be working:
class ActiveSupport::TestCase
fixture_path = Rails.root.join('test', 'fixtures', 'nhl')
fixtures :all
end

I was able to fix the problem by setting the fixture_path this way:
# test_helper.rb
ActiveSupport::TestCase.fixture_path = Rails.root.join('test', 'fixtures', Rails.env.gsub('_test', ''))
class ActiveSupport::TestCase
fixtures :all
end

Related

Rails 7 controller decorator uninitialised constant error in production only

I am getting the following error zeitwerk/loader/helpers.rb:95:in const_get': uninitialized constant Controllers::BasePublicDecorator (NameError)
This is an error in a local production console using rails c -e production but not an issue in development which works perfectly.
In an engine, CcsCms::PublicTheme, I have a decorator I am using to extend the controller of another CcsCms::Core engine and it is this decorator that is causing the error.
public_theme/app/decorators/decorators/controllers/base_public_decorator.rb
CcsCms::BasePublicController.class_eval do
before_action :set_theme #ensure that #current_theme is available for the
#header in all public views
private
def set_theme
#current_theme = CcsCms::PublicTheme::Theme.current_theme
end
end
This functionality is working perfectly in development but fails in production with an error as follows
The controller I am trying to decorate in the CcsCms::Core engine is CcsCms::BasePublicController.rb
module CcsCms
class BasePublicController < ApplicationController
layout "ccs_cms/layouts/public"
protected
def authorize
end
end
end
in the theme engine with the decorator I am trying to use I have a Gemfile that defines the core engine as follows
gem 'ccs_cms_core', path: '../core'
In the ccs_cms_public_theme.gemspec I am requiring the core engine as a dependency
spec.add_dependency "ccs_cms_core"
in the engine.rb I am requiring the core engine and loading the decorator paths in a config.to_prepare do block
require "deface"
require 'ccs_cms_admin_dashboard'
require 'ccs_cms_custom_page'
require 'ccs_cms_core'
require 'css_menu'
#require 'tinymce-rails'
require 'delayed_job_active_record'
require 'daemons'
require 'sprockets/railtie'
require 'sassc-rails'
module CcsCms
module PublicTheme
class Engine < ::Rails::Engine
isolate_namespace CcsCms::PublicTheme
paths["app/views"] << "app/views/ccs_cms/public_theme"
initializer "ccs_cms.assets.precompile" do |app|
app.config.assets.precompile += %w( public_theme_manifest.js )
end
initializer :assets do |config|
Rails.application.config.assets.paths << root.join("")
end
initializer :append_migrations do |app|
unless app.root.to_s.match?(root.to_s)
config.paths['db/migrate'].expanded.each do |p|
app.config.paths['db/migrate'] << p
end
end
end
initializer :active_job_setup do |app|
app.config.active_job.queue_adapter = :delayed_job
end
config.to_prepare do
Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
config.generators do |g|
g.test_framework :rspec,
fixtures: false,
request: false,
view_specs: false,
helper_specs: false,
controller_specs: false,
routing_specs: false
g.fixture_replacement :factory_bot
g.factory_bot dir: 'spec/factories'
end
end
end
end
Given that my decorator is given the same name as the controller it is decorating from the core engine but with the .decorator extension I am pretty certain that is everything hooked up correctly, as mentioned, this works perfectly in development but I am unable to start a rails console in a production environment due to this error.
It seems that the class_eval is failing somehow and I can only think that this may be a path issue but I can not figure it out
UPDATE
After quite a big learning curve, thank's muchly to #debugger comments and #Xavier Noria
answer it is clear that my issue comes down to Zeitworks autoload functionality
Rails guides here has an interesting and appealing solution to me
Another use case are engines decorating framework classes:
initializer "decorate ActionController::Base" do
> ActiveSupport.on_load(:action_controller_base) do
> include MyDecoration end end
There, the module object stored in MyDecoration by the time the
initializer runs becomes an ancestor of ActionController::Base, and
reloading MyDecoration is pointless, it won't affect that ancestor
chain.
But maybe this isn't the right solution, I again failed to make it work with the following
initializer "decorate CcsCms::BasePublicController" do
ActiveSupport.on_load(:ccs_cms_base_public_controller) do
include CcsCms::BasePublicDecorator
end
end
Generating the following error
zeitwerk/loader/callbacks.rb:25:in `on_file_autoloaded': expected file /home/jamie/Development/rails/comtech/r7/ccs_cms/engines/public_theme/app/decorators/controllers/ccs_cms/base_public_decorator.rb to define constant Controllers::CcsCms::BasePublicDecorator, but didn't (Zeitwerk::NameError)
So back to the solution provided here, thank's again for the answer below I tried the following which did work finally
config.to_prepare do
overrides = Engine.root.join("app", "decorators")
Rails.autoloaders.main.ignore(overrides)
p = Engine.root.join("app", "decorators")
loader = Zeitwerk::Loader.for_gem
loader.ignore(p)
Dir.glob(Engine.root.join("app", "decorators", "**", "*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
end
end
Problem here is that when lazy loading, nobody is referencing a constant called ...::BasePublicDecorator. However, Zeitwerk expects that constant to be defined in that file, and the mismatch is found when eager loading.
The solution is to configure the autoloader to ignore the decorators, because you are handling their loading, and because they do not define constants after their names. This documentation has an example. It needs to be adapted to your engine, but you'll see the idea.
For completeness, let me also explain that in Zeitwerk, eager loading is a recursive const_get, not a recursive require. This is to guarantee that if you access the constant, loading succeeds or fails consistently in both modes (and it is also a tad more efficient). Recursive const_get still issues require calls via Module#autoload, and if you ran one for some file idempotence also applies, but Zeitwerk detects the expected constant is not defined anyway, which is an error condition.

Fixtures for Apartment Gem

I'm trying to do some tests with my multi-tenancy rails app but getting into trouble with my fixtures. In my controller test I have the following setup:
setup do
Apartment::Tenant.create('test')
Apartment::Tenant.switch! 'test'
host! "test:3000"
sign_in users(:admin)
end
When running the test I get this Error:
ActiveRecord::RecordNotFound: Couldn't find User with 'id'=255947101
I think the problem is that the fixtures are being created before switching to the test tenant. How do I create the fixtures after switching the tenant?
Ran into this same problem this morning ... I was able to get around it, at least for now, with this change in my test_helper.rb file:
# Customizing the base TestCase
module ActiveSupport
class TestCase
Apartment::Tenant.create('the-tenant')
Apartment::Tenant.switch! 'the-tenant'
fixtures :all
ActiveRecord::Migration.check_pending!
def setup
Capybara.app_host = 'http://www.my-somain.local'
DatabaseCleaner.strategy = :transaction
end
def teardown
Apartment::Tenant.drop('the-tenant')
Apartment::Tenant.reset
DatabaseCleaner.clean
end
end
end
Basically, I moved the tenant creation outside the setup method, where it previously resided. This seems to have fixed the issue with the fixtures being created outside the tenant on which I am testing.
I hope this helps ... not 100% sure it's ideal but it does have my tests running today :)

Fixture Class Not found error on simple rails testing

I am trying to set up the rails testing framework but am facing some issues. My setup is as follows
test/models/clinic_test.rb
require 'test_helper'
class ClinicTest < ActiveSupport::TestCase
test "sample" do
clinic = clinics(:myclinic)
assert(clinic.name == 'Krishan')
end
end
test/fixtures/clinics.yml
myclinic:
name: Krishan
But when I run the clinic_test rake process I get the following error:
ActiveRecord::FixtureClassNotFound: No class attached to find
test/models/clinic_test.rb:5:in `block in <class:ClinicTest>'
I see that the database is actually populated with the sample data from the clinics.yml file.
Where is the problem? Is this some configuration issue?
Add following lines in test_helper.rb file, before fixtures :all
class ActiveSupport::TestCase
self.use_transactional_fixtures = true
set_fixture_class clinics: Clinic
fixtures :all
...
end
clinics is the name of yml file where Clinic is the name of model.

Rails autoloading behaves strangely in RSpec with modules and subclasses with spork

In my Rails app I've added the following files:
app/models/baz.rb
lib/presenters/foo_presenter.rb
lib/presenters/foo_presenter/bar.rb
spec/models/baz_spec.rb
spec/lib/presenters/foo_presenter/bar_spec.rb
The contents of lib/presenters/foo_presenter.rb is something like:
module Presenters
module FooPresenter
def self.render
# do stuff
end
end
end
The contents of lib/presenters/foo_presenter/bar.rb is like:
module Presenters
class FooPresenter::Bar
def baz
# do stuff
end
end
end
The contents of spec/lib/presenters/foo_presenter/bar_spec.rb is like:
require 'spec_helper'
module Presenters::FooPresenter
describe Bar do
# some tests
end
end
Then I have a spec file in spec/models/baz_spec.rb:
require 'spec_helper'
describe Baz do
it 'works' do
Presenters::FooPresenter.render
end
end
(The contents of app/models/baz.rb is not relevant to this issue)
The problem is when I run rspec spec/models/baz_spec.rb it works fine without spork, but when spork is running, I get an error like:
NameError: undefined method `render' for Presenters::FooPresenter:Module
I traced through the code a bit and noticed that when rspec loads spec/lib/presenters/foo_presenter/bar_spec.rb it causes Rails to autoload lib/presenters/foo_presenter/bar.rb and so at that point Presenters::FooPresenter::Bar is loaded, but then when baz_spec.rb runs, lib/presenters/foo_presenter.rb has never been loaded and thus the exception. But this only happens if spork is running. The quick fix was to require 'foo_presenter' in a file in config/initializers, but is there a cleaner solution that doesn't need the explicit require? My guess is the issue here is that Rails doesn't autoload lib/presenters/foo_presenter.rb because Presenters::FooPresenter has already been defined by bar_spec.rb.
A co-worker and I were faced with this problem today and we eventually found we needed Spork to reload the classes on every run. We used the each_run() method to do this:
Spork.each_run do
Dir[Rails.root.join("app/classes/**/*.rb")].each {|f| require f}
end

RAILS: running code once after loading all global fixtures

Where can I write code to be executed only once after loading of all global fixtures, and before running any tests/specs
I tried before(:suite) with rspec 1.3.1 on Rails 2.3.11 and that seems to get executed before fixtures.
How about a rake task(/lib/tasks) ? For instance, i have one(reset_db.rake) that loads fixtures, resets db and more :
namespace :db do
desc "Drop, create, migrate, seed the database and prepare the test database for rspec"
task :reset_db => :environment do
Rake::Task['db:drop'].invoke
Rake::Task['db:create'].invoke
Rake::Task['db:migrate'].invoke
Rake::Task['db:fixtures:load'].invoke
Rake::Task['db:test:prepare'].invoke
end
end
I run into the same issue, but still haven't found any way to hook up some code after loading fixtures ... so i used the SpyrosP solution. However the problem with this way of doing is that you can't benefit anymore of the fixtures accessors helpers, since you don't load your fixtures from the rspec config but from a rake task.
So basically you neeed to recreate theese helpers like that (code is a bit dirty but seems to work for me :
#spec_helper.rb
module CustomAccessors
# Remplacement de fixtures :all
%w{yml csv}.each do |format|
paths = Dir.
glob(::Rails.root.join("spec/fixtures/*.#{format}")).
map! { |path| path.match(/\/([^\.\/]*)\./)[1] }
paths.each do |path|
define_method path do |*args|
path.singularize.camelcase.constantize.find(ActiveRecord::Fixtures.identify(args[0]))
end
end
end
end
RSpec.configure do |config|
#config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.include(CustomAccessors)
end

Resources