Conflicting constant lookup in rspec (Rails) - ruby-on-rails

On our project, we have the following:
module A
# several components belong to this namespace
end
module Databuilder::A
# several components also belong to this namespace
#
When running the full test suite, in which one of the tests is like this:
describe ::A::AnotherNamespace::MyService do
# code ommited
The following error shows up:
uninitialized constant DataBuilder::A::AnotherNamespace
Unfortunately, renaming the namespace A is not an option because the components belong to a legacy monolith.
Manually requiring the file which contains :A::AnotherNamespace::MyService leads to the same error.
The files on DataBuilder:: ... live inside the /lib folder. The ones from the A namespace live inside a app/"a" folder
I'm running rspec-rails 4.0.0 / rails 5.2.4 in a ruby 2.3.0 rbenv.
No customization has been done to the autoloading.

Related

How do I ensure assets are present with Rail 7, cssbundling-rails, jsbundling-rails in test mode (RSpec)?

I'm upgrading a large, commercial (proprietary) Rails 6 application to Rails 7. We never used Webpacker, and are instead going directly from bundled gems for things like Bootstrap, to the "Rails 7 way".
It turns out that the "no Node" workflow for Rails 7 has no good answer for components that consist of both a CSS and JS component. In our case, the most obvious offender there is Bootstrap. Faced with maintaining the JS "half" of Bootstrap through import maps and the CSS "half" through something like the old Bootstrap gem or manual vendoring (and yes, there really is no other solution without Node here) we end up back at a full Node workflow.
This is coming together. All front-end components that provide CSS and/or JS were already happily available in NPM, so now that's all managed via package.json & Yarn, with bin/dev driving Sass & esbuild compilation of the SCSS and JS components pulled from either app/assets, app/javascript or node_modules/...; the asset pipeline manifest.js contains only references to the build and images folders inside app/assets as a result.
It feels like a bit of backwards step with all the heavyweight manual maintenance of lists of filenames (wildcard imports no longer supported) along with the complexity of the multiple processes now running under Foreman vs just having things synchronously processed in Sprockets on a per-request basis, but with all that stuff being deprecated/abandonware, it was clearly time to update.
This all works fine in dev & production mode, but what about test? We use RSpec; in CI, there's no built assets and developers don't want to have to remember to run esbuild  or assets:precompile or whatever every time they're about to run rspec. Apart from anything else, it's quite slow.
What's the official, idiomatic Rails 7 solution in a Yarn/Node-based workflow specifically using cssbundling-rails and jsbundling-rails, when you want to run tests with up to date assets?
This is pretty ropey but better than nothing for now; it'll ensure CI always builds assets and also ensure that local development always has up-to-date assets, even if things have been modified when e.g. bin/dev isn't running.
# Under Rails 7 with 'cssbundling-rails' and/or the 'jsbundling-rails' gems,
# entirely external systems are used for asset management. With Sprockets no
# longer synchronously building assets on-demand and only when the source files
# changed, compiled assets might be (during local development) or will almost
# always be (CI systems) either out of date or missing when tests are run.
#
# People are used to "bundle exec rspec" and things working. The out-of-box gem
# 'cssbundling-rails' hooks into a vanilla Rails "prepare" task, running a full
# "css:build" task in response. This is quite slow and generates console spam
# on every test run, but points to a slightly better solution for RSpec.
#
# This class is a way of packaging that solution. The class wrapper is really
# just a namespace / container for the code.
#
# First, if you aren't already doing this, add the folllowing lines to
# "spec_helper.rb" somewhere *after* the "require 'rspec/rails'" line:
#
# require 'rake'
# YourAppName::Application.load_tasks
#
# ...and call MaintainTestAssets::maintain! (see that method's documentation
# for details). See also constants MaintainTestAssets::ASSET_SOURCE_FOLDERS and
# MaintainTestAssets::EXPECTED_ASSETS for things you may want to customise.
#
class MaintainTestAssets
# All the places where you have asset files of any kind that you expect to be
# dynamically compiled/transpiled/etc. via external tooling. The given arrays
# are passed to "Rails.root.join..." to generate full pathnames.
#
# Folders are checked recursively. If any file timestamp therein is greater
# than (newer than) any of EXPECTED_ASSETS, a rebuild is triggered.
#
ASSET_SOURCE_FOLDERS = [
['app', 'assets', 'stylesheets'],
['app', 'javascript'],
['vendor']
]
# The leaf files that ASSET_SOURCE_FOLDERS will build. These are all checked
# for in "File.join(Rails.root, 'app', 'assets', 'builds')". Where files are
# written together - e.g. a ".js" and ".js.map" file - you only need to list
# any one of the group of concurrently generated files.
#
# In a standard JS / CSS combination this would just be 'application.css' and
# 'application.js', but more complex applications might have added or changed
# entries in the "scripts" section of 'package.json'.
#
EXPECTED_ASSETS = %w{
application.js
application.css
}
# Call this method somewhere at test startup, e.g. in "spec_helper.rb" before
# tests are actually run (just above "RSpec.configure..." works reasonably).
#
def self.maintain!
run_build = false
newest_mtime = Time.now - 100.years
# Find the newest modificaftion time across all source files of any type -
# for simplicity, timestamps of JS vs CSS aren't considered
#
ASSET_SOURCE_FOLDERS.each do | relative_array |
glob_path = Rails.root.join(*relative_array, '**', '*')
Dir[glob_path].each do | filename |
next if File.directory?(filename) # NOTE EARLY LOOP RESTART
source_mtime = File.mtime(filename)
newest_mtime = source_mtime if source_mtime > newest_mtime
end
end
# Compile the built asset leaf names into full file names for convenience.
#
built_assets = EXPECTED_ASSETS.map do | leaf |
Rails.root.join('app', 'assets', 'builds', leaf)
end
# If any of the source files are newer than expected built assets, or if
# any of those assets are missing, trigger a rebuild task *and* force a new
# timestamp on all output assets (just in case build script optimisations
# result in a file being skipped as "already up to date", which would cause
# the code here to otherwise keep trying to rebuild it on every run).
#
run_build = built_assets.any? do | filename |
File.exist?(filename) == false || File.mtime(filename) < newest_mtime
end
if run_build
Rake::Task['javascript:build'].invoke()
Rake::Task[ 'css:build'].invoke()
built_assets.each { | filename | FileUtils.touch(filename, nocreate: true) }
end
end
end
(EDIT) As a commenter below points out, you'll need to make sure Rake tasks are loaded in your spec_helper.rb, e.g.:
require 'rake'
Rails.application.load_tasks
Both jsbundling-rails and cssbundling-rails append themselves into a rake task called test:prepare.
There are a few ways to cause test:prepare to run, depending on your overall build process.
Call it directly:
bundle exec rails test:prepare test
Or, if running rspec outside of the rails command:
bundle exec rails test:prepare && bundle exec rspec
Use a test task that already calls test:prepare.
Curiously, only some test tasks call (depend on) test:prepare, while others (including the default test task) don't. Example:
bundle exec rails test:all
Make test:prepare a dependency on your preferred test task.
For example, if you normally use the spec task by running bundle exec rails spec, add this to a new or existing task file (such as lib/tasks/tests.rake):
task spec: ['css:build', 'javascript:build']
Background
test:prepare is an empty task defined by Rails. Both cssbundling-rails and jsbundling-rails add themselves as dependencies of that task.
In general, test:prepare is a useful place to add any kind of dependency needed to run your tests, with the caveat that only some of Rails' default test tasks depend on it. But as mentioned above, you can always call it directly or add your own dependencies.
In most cases, calling test:prepare is going to be equivalent to calling both css:build and javascript:build, which is why I showed test:prepare in most of the above examples. On occasion, other gems or your app may an extended test:prepare with additional commands as well, in which case those will run as well (and would likely be wanted).
Also note that assets:precompile also depends on css:build and javascript:build. In my experience, test:prepare (or css:build and javascript:build separately) runs faster than assets:precompile, likely because we're running a lightweight configuration of sprockets-rails (as opposed to propshaft) and assets:precompile runs the entire sprockets compilation process.

How to include a plugin/engine in a rails application?

This is my first time interaction with a rails plugin and I'm unable to wrap my head around how this thing works. I'm going to make a rails plugin/engine. I want to include this plugin in my rails application(a different project), how can I do it? I found this which suggests to
At the root of this brand new engine's directory lives a plugin_name.gemspec file. When you include the engine into an application later on, you will do so with this line in the Rails application's Gemfile:
gem 'plugin_name', path: 'engines/plugin_name'
My question is that how exactly do I do it? I mean when I create a rails application I do not find any engines directory in the folder structure. And also which directories from the plugin project do I have to place in the engines/ directory?
I created a plugin by doing
rails plugin new plugin_name --mountable
and it generated following files/directories:
|-app/
|-bin/
|-config/
|-demo_project.gemspec
|-Gemfile
|-Gemfile.lock
|-lib/
|-MIT-LICENSE
|-Rakefile
|-README.md
|-test
|-dummy
|-(contains a rails application)
Now once I'm done with this plugin, how do I include this in another rails application? What directories do I need to put in engines/ in addition to including the plugin in the gemfile?
In addition to this I also want some info about what the following is indicating
Additionally, the --mountable option tells the generator to mount the engine inside the dummy testing application located at test/dummy by adding the following to the dummy application's routes file at test/dummy/config/routes.rb:
mount PluginName::Engine => "/plugin_name"
What does the mount point indicate and what will be it's role once the plugin is included in a rails application?
Rails engine is an isolated rails app, that is mounted inside main app. Two main reasons to create an engine are isolation of an application part (with possible intent to split the app into pieces) and code reuse (when you use same engine in several apps)
Technically it is a ruby gem, but often engines start (and live) as an isolated part of a larger app, without being completely extracted into own repository, gem publishing etc.
For example, if you have admin part of your app, you can make it an engine:
# in gemfile:
gem 'admin', path: 'engines/admin'
# routes:
mount Admin::Engine => '/admin'
# engine's routes:
resources :foo # => because of being in an engine this will be /admin/foo
directory structure can look like:
|-app/
|-bin/
|-...
|-config/
|-application.rb
|-routes.rb
|-engines/
|-admin_engine/
|-app/
|-controllers/admin/
|- foo_controller.rb
|-config/
|-routes.rb # <- this is engine's routes
|-lib/
|-admin/
|-engine.rb
|- admin.gemspec

Ruby on Rails: Difference between behaviour within the console / in a .rb file

I would like to use information stored within a the credentials.yml.enc file for a Rails 5.2 app. However, I am struggling to get a command which works perfectly within the console to behave in the same way when inserted into a .rb file.
In the Rails console (on my local development computer) Rails.application.credentials.username returns "my_username"
If I insert this line within a very simple db_backup.rb file as shown below, I get the error:
NameError: uninitialized constant #<Class:#<Backup::Config::DSL:0x00007fb0db941d10>>::Rails
db_backup.rb:
Model.new(:db_backup, 'Description for db_backup') do
##
# PostgreSQL [Database]
#
database PostgreSQL do |db|
db.username = Rails.application.credentials.username
end
end
Please could you explain why I get the different behaviour when using exactly the same line of code in the Rails console / within a .rb file?
The context in which the code is executed is not the same. One is the rails console and the other is the backup command
What happens when you load the Rails console
Launching the rails console means you launch all of the rails stack before executing your code against it. The Rack applications like Sinatra, Rails etc. use the config.ru file as a convention for which file should be run to boot. (You can explore the rabbit hole if you want to have a deep understanding of this)
It means that the vast majority of errors you can encounter when will occur during the console boot, preventing you from executing anything in the console (because boot failed). Instead it will print the stack trace errors for you to figure out what went wrong so you can correct and give it another try.
TL; DR Rails.application.credentials.username in console is executed after all of the Rails stack (models, dependencies, initializers) has loaded in a particular order
What happens when you run the backup command
The backup command is defined here in the bin repo of the backup repo
It goes like this
#!/usr/bin/env ruby
# encoding: utf-8
require File.expand_path("../../lib/backup", __FILE__)
Backup::CLI.start
If you open the required file lib/backup.rb and look around in the Gemfile, you won't fine a place where you have a dependency or a define for the Rails constant.
Thus when you run the backup command and execute your db_backup.rb, the constant Rails called here is ... not defined. Ruby being kind will try to find a nested version of this constant in the current scope which is the Model.new do; end block.
It is still not defined which ruby tells you about with NameError: uninitialized constant #<Class:#<Backup::Config::DSL:0x00007fb0db941d10>>::Rails.
#giglemad gives a great explanation of the issue of class resolution in the execution context (rails console vs. running of the backup ruby file).
To fix your error, just let the code know to use the top-level class lookup (::Rails):
Model.new(:db_backup, 'Description for db_backup') do
##
# PostgreSQL [Database]
#
database PostgreSQL do |db|
db.username = ::Rails.application.credentials.username
end
end
If you're still seeing the missing Rails constant, you'll need to put your script in either a rake task, or require your rails environment.
I ended up resolving this by simply adding the line below to the top of my db_backup.rb:
require './config/environment' # added to enable credentials to be read from Rails environment

Run engine RSpec/Cucumber tests in the client app

I have mountable rails engine Users:
users/
...
features/
...
specs/
dummy/
controllers/
models/
factories/
...
I have a client app MySite, which uses the Users engine:
my_site/
app/
...
features/
...
specs/
controllers/
models/
factories/
...
Gemfile
Gemfile:
gem "users", git: "..."
Is it possible to run engine rspec/cucumber tests in the MySite app instead of the mock?
If so, what is the best way?
You should not do this for two reasons:
The User engine is its own project. It should work in isolation in a certain way. Adding these tests to any project the User engine is included in does not make sense. Why not run all the specs of all the gems you include in your project as well?
If you have specific acceptance criteria for your application, add cucumbers for that to your project. That way, if you decide to ditch the User engine in the future with something else, you can still verify your application behaving as expected. If you'd included the cucumbers from the engine, then those criteria would have been lost as well.

`const_missing': uninitialized constant (NameError)

Every time I try to run any class from my rails 2.2 app's lib directory using "script/runner -e production ClassName.run" I get the following error:
/usr/lib/ruby/gems/1.8/gems/rails-2.2.2/lib/commands/runner.rb:47:
/usr/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/dependencies.rb:89:in `const_missing': uninitialized constant ClassName (NameError)"
For some reason I don't get this error when I run it as test instead of production, and everything works fine.
O yeah run is a class method i.e. def self.run .... end
Any suggestions?
That error occurs when ruby can't find a Class or Module. I'd start out by:
Checking gem dependencies (are they same for all environments?)
Search your code for anything that defines ClassName, particularly Modules, Classes and plugins.
disable each of your plugins in dev, does any plugin suddenly cause that error?
if the code is in a lib add the lib require statement to your production.rb to force the lib to be loaded in production.
Hope that helps.
update Just to summarise the comments it was option 4.
Unless you only want to load the lib in production you should think about making sure all environments load the lib by doing one of the following:
Create a rails initializer (a .rb file under config/initializers) with the require in it
Add the path to the lib in config/environment.rb by enabling and modifying the config.load_paths variable.
I had multiple class definition(all STI classes) on the same file. and when I separated the definition into their respective files, It works

Resources