Upgrading to from rails 5 to 6 and zeitwerk - ruby-on-rails

We have a rails 5.2.8.1 app and a trying to upgrade to rails 6.1. We have managed to make the application run locally with only minor problems.
However, pushing to our ci (circleCI) we get errors in precompiling the assets.
The error is that the zeitwerk loader (load once) wants to manage actiontext-6.1.7/app/helpers which is already managed by the default main loader.
The weird thing is we do not use the load once loader at all..
loader setup in application.rb
config.autoload_paths += %W[
#{config.root}/lib
#{config.root}/lib/simple_form
#{config.root}/app/jobs
#{config.root}/app/jobs/concerns
#{config.root}/app/controllers/concerns
#{config.root}/app/mailers/concerns
#{config.root}/app/models/concerns
#{config.root}/app/models/reports
#{config.root}/app/services/pdf_data
#{config.root}/app/presenters
#{config.root}/app/assets/templates
#{config.root}/app/event_handlers
#{config.root}/app/queries
#{config.root}/app/concepts
#{config.root}/vendor/
#{config.root}/app/pdfs
]
config.eager_load_paths += %W[
#{config.root}/lib
]
error printout:
#!/bin/bash -eo pipefail
WEBPACKER_PRECOMPILE=false RAILS_NO_DB=true bundle exec rake assets:precompile --trace
** Invoke assets:precompile (first_time)
** Invoke assets:environment (first_time)
** Execute assets:environment
** Invoke environment (first_time)
** Execute environment
rake aborted!
Zeitwerk::Error: loader
#<Zeitwerk::Loader:0x000055eb81710b98
#autoloaded_dirs=[],
#autoloads={},
#collapse_dirs=#<Set: {}>,
#collapse_glob_patterns=#<Set: {}>,
#eager_load_exclusions=#<Set: {}>,
#eager_loaded=false,
#ignored_glob_patterns=#<Set: {}>,
#ignored_paths=#<Set: {}>,
#inflector=ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector,
#initialized_at=2022-12-07 14:27:29.123263169 +0000,
#logger=nil,
#mutex=#<Thread::Mutex:0x000055eb81710738>,
#mutex2=#<Thread::Mutex:0x000055eb81710710>,
#namespace_dirs={},
#on_load_callbacks={},
#on_setup_callbacks=[],
#on_unload_callbacks={},
#reloading_enabled=false,
#roots={},
#setup=false,
#shadowed_files=#<Set: {}>,
#tag="rails.once",
#to_unload={}>
wants to manage directory /home/circleci/flightlogger-rails/vendor/bundle/ruby/2.7.0/gems/actiontext-6.1.7/app/helpers, which is already managed by
#<Zeitwerk::Loader:0x000055eb81712f88
#autoloaded_dirs=[],
#autoloads={},
#collapse_dirs=#<Set: {}>,
#collapse_glob_patterns=#<Set: {}>,
#eager_load_exclusions=
#<Set: {"/home/circleci/flightlogger-rails/lib/simple_form",
"/home/circleci/flightlogger-rails/app/models/reports",
"/home/circleci/flightlogger-rails/app/assets/templates",
"/home/circleci/flightlogger-rails/vendor"}>,
#eager_loaded=false,
#ignored_glob_patterns=#<Set: {}>,
#ignored_paths=#<Set: {}>,
#inflector=ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector,
#initialized_at=2022-12-07 14:27:29.122727016 +0000,
#logger=nil,
#mutex=#<Thread::Mutex:0x000055eb81712ab0>,
#mutex2=#<Thread::Mutex:0x000055eb81712a88>,
#namespace_dirs={},
#on_load_callbacks={},
#on_setup_callbacks=[],
#on_unload_callbacks={},
#reloading_enabled=false,
#roots=
{"/home/circleci/flightlogger-rails/lib"=>Object,
"/home/circleci/flightlogger-rails/lib/simple_form"=>Object,
"/home/circleci/flightlogger-rails/app/jobs"=>Object,
"/home/circleci/flightlogger-rails/app/jobs/concerns"=>Object,
"/home/circleci/flightlogger-rails/app/controllers/concerns"=>Object,
"/home/circleci/flightlogger-rails/app/mailers/concerns"=>Object,
"/home/circleci/flightlogger-rails/app/models/concerns"=>Object,
"/home/circleci/flightlogger-rails/app/models/reports"=>Object,
"/home/circleci/flightlogger-rails/app/presenters"=>Object,
"/home/circleci/flightlogger-rails/app/assets/templates"=>Object,
"/home/circleci/flightlogger-rails/app/event_handlers"=>Object,
"/home/circleci/flightlogger-rails/app/queries"=>Object,
"/home/circleci/flightlogger-rails/app/concepts"=>Object,
"/home/circleci/flightlogger-rails/vendor"=>Object,
"/home/circleci/flightlogger-rails/app/pdfs"=>Object,
"/home/circleci/flightlogger-rails/app/associations"=>Object,
"/home/circleci/flightlogger-rails/app/channels"=>Object,
"/home/circleci/flightlogger-rails/app/controllers"=>Object,
"/home/circleci/flightlogger-rails/app/decorators"=>Object,
"/home/circleci/flightlogger-rails/app/forms"=>Object,
"/home/circleci/flightlogger-rails/app/graphql"=>Object,
"/home/circleci/flightlogger-rails/app/graphql/concerns"=>Object,
"/home/circleci/flightlogger-rails/app/helpers"=>Object,
"/home/circleci/flightlogger-rails/app/interactors"=>Object,
"/home/circleci/flightlogger-rails/app/mailers"=>Object,
"/home/circleci/flightlogger-rails/app/models"=>Object,
"/home/circleci/flightlogger-rails/app/permitters"=>Object,
"/home/circleci/flightlogger-rails/app/policies"=>Object,
"/home/circleci/flightlogger-rails/app/public_api"=>Object,
"/home/circleci/flightlogger-rails/app/serializers"=>Object,
"/home/circleci/flightlogger-rails/app/services"=>Object,
"/home/circleci/flightlogger-rails/app/validators"=>Object,
"/home/circleci/flightlogger-rails/vendor/bundle/ruby/2.7.0/gems/jquery-minicolors-rails-2.2.6.2/app/inputs"=>
Object,
"/home/circleci/flightlogger-rails/engines/sms/app/concerns"=>Object,
"/home/circleci/flightlogger-rails/engines/sms/app/controllers"=>Object,
"/home/circleci/flightlogger-rails/engines/sms/app/helpers"=>Object,
"/home/circleci/flightlogger-rails/engines/sms/app/inputs"=>Object,
"/home/circleci/flightlogger-rails/engines/sms/app/mailers"=>Object,
"/home/circleci/flightlogger-rails/engines/sms/app/models"=>Object,
"/home/circleci/flightlogger-rails/vendor/bundle/ruby/2.7.0/gems/sentry-rails-5.6.0/app/jobs"=>
Object,
"/home/circleci/flightlogger-rails/vendor/bundle/ruby/2.7.0/gems/react_on_rails-13.1.0/app/helpers"=>
Object},
#setup=false,
#shadowed_files=#<Set: {}>,
#tag="rails.main",
#to_unload={}>

The problem is that vendor is added to the autoload paths of the main autoloader. It should not be there.
Action Text configures the once autoloader (here), but with your configuration it cannot do that because main manages vendor (and therefore all its descendants).
Your configuration should not be adding vendor, because vendor/bundle is not intended to define a Bundle module, right? That directory is not meant to be autoloaded by the application.
Dependencies load themselves.
In addition to removing vendor from that configuration, you should also remove these redundant directories that are automatically added by Rails:
#{config.root}/app/jobs
#{config.root}/app/controllers/concerns
#{config.root}/app/models/concerns
#{config.root}/app/presenters
#{config.root}/app/event_handlers
#{config.root}/app/queries
#{config.root}/app/concepts
#{config.root}/app/pdfs

Related

Heroku CI w/ Postgresql Extensions

I'm attempting to use Heroku's CI to run my Rails application's tests but it's running into a problem when attempting to load my structure.sql file.
-----> Preparing test database
Running: rake db:schema:load_if_ruby
db:schema:load_if_ruby completed (3.24s)
Running: rake db:structure:load_if_sql
psql:/app/db/structure.sql:28: ERROR: must be owner of extension plpgsql
rake aborted!
failed to execute:
psql -v ON_ERROR_STOP=1 -q -f /app/db/structure.sql d767koa0m1kne1
Please check the output above for any errors and make sure that `psql` is installed in your PATH and has proper permissions.
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/tasks/postgresql_database_tasks.rb:108:in `run_cmd'
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/tasks/postgresql_database_tasks.rb:80:in `structure_load'
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/tasks/database_tasks.rb:223:in `structure_load'
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/tasks/database_tasks.rb:236:in `load_schema'
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/tasks/database_tasks.rb:255:in `block in load_schema_current'
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/tasks/database_tasks.rb:304:in `block in each_current_configuration'
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/tasks/database_tasks.rb:303:in `each'
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/tasks/database_tasks.rb:303:in `each_current_configuration'
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/tasks/database_tasks.rb:254:in `load_schema_current'
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/railties/databases.rake:290:in `block (3 levels) in <top (required)>'
/app/vendor/bundle/ruby/2.4.0/gems/activerecord-5.1.1/lib/active_record/railties/databases.rake:294:in `block (3 levels) in <top (required)>'
/app/vendor/bundle/ruby/2.4.0/gems/rake-12.0.0/exe/rake:27:in `<top (required)>'
Tasks: TOP => db:structure:load
(See full trace by running task with --trace)
!
! Could not prepare database for test
!
The relevant line here is:
psql:/app/db/structure.sql:28: ERROR: must be owner of extension plpgsql
rake aborted!
Structure.sql contains this line:
COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
Any ideas on how to get this working on Heroku's CI?
Ended up overriding db:structure:dump to remove the COMMENT ON ... statements:
namespace :db do
namespace :structure do
task dump: [:environment, :load_config] do
filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
sql = File.read(filename).each_line.grep_v(/\ACOMMENT ON EXTENSION.+/).join
File.write(filename, sql)
end
end
end
Another workaround would be to add something like
if Rails.env.development?
ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = ["-v", "ON_ERROR_STOP=0"]
end
anywhere in the initialisation / tasks pipeline before the db:structure:load is executed.
If Kyle's solution isn't enough and the errors aren't caused only by comments on extensions, but actual extensions installations, you can still go the hard way and add this to an initializer:
# This is a temporary workaround for the Rails issue #29049.
# It could be safely removed when the PR #29110 got merged and released
# to use instead IGNORE_PG_LOAD_ERRORS=1.
module ActiveRecord
module Tasks
class PostgreSQLDatabaseTasks
ON_ERROR_STOP_1 = 'ON_ERROR_STOP=0'.freeze
end
end
end
Note: This isn't specific to Heroku but a broader Rails 5.1 issue
There are two solutions to this problem. First, as it was previously noted, is disabling the ON_ERROR_STOP feature. It'd help regardless of the environment. Custom rake task:
namespace :db do
namespace :structure do
# This little task is a workaround for a problem introduced in Rails5. Most specificaly here
# https://github.com/rails/rails/blob/5-1-stable/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb#L77
# When psql encounters an error during loading of the structure it exits at once with error code 1.
# And this happens on heroku. It renders review apps and heroku CI unusable if you use a structure instead of a schema.
# Why?
# Our `db/structure.sql` contains entries like `CREATE EXTENSION` or `COMMENT ON EXTENSION`.
# Zylion of extensions on heroku are loaded in template0, so "our" db also has them, but because of that
# only a superuser (or owner of template0) has access to them - not our heroku db user. For that reason
# we can neither create an extension (it already exists, but that is not a problem, because dump contains IF NOT EXIST)
# nor comment on it (and comments don't have IF NOT EXIST directive). And that's an error which could be safely ignored
# but which stops loading of the rest of the structure.
desc "Disable exit-on-error behaviour when loading db structure in postgresql"
task disable_errors: :environment do
ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = ["-v", "ON_ERROR_STOP=0"]
end
end
end
# And use it like so:
bin/rails db:structure:disable_errors db:structure:load
Another option, in my opinion, superior if it comes only to Heroku, would be using PostgreSQL in in-dyno plan (https://devcenter.heroku.com/articles/heroku-ci-in-dyno-databases), which is basically a DB instance sitting within dyno, thus we have full access to it. Also, the test suite should be significantly faster because we use a localhost connection, not over the wire. To enable it, change your app.json content to have entries like so:
{
"environments": {
"test": {
"addons": [
"heroku-postgresql:in-dyno"
]
}
}
}

rake aborted! Don't know how to build task 'sandbox'

I'm trying to add the sandbox to my rails spree application and have run into this error
(using windows 8/powershell with Rails 4.1.6). I'm going by this manual: https://github.com/spree/spree/issues/411
This link Use older version of Rake
seems to have a similar issue but I am not sure how to take the necessary steps to achieve it.
When I try:
C:\Ruby193\openUpShop> bundle exec rake sandbox
I get:
rake aborted!
Don't know how to build task 'sandbox'
I'm am new to rails and am still not sure how everything works so a throughout explanation
with step by step instructions would be greatly appreciated! Thanks.
you can use a file sandbox.rb
# use example: rake task:sub_task -- --sandbox
if ARGV.any? {|arg| arg == '--sandbox' }
puts "** << USING SANDBOX!! >> **"
# beginning
$sandbox = -> do
ActiveRecord::Base.connection.begin_transaction
end
# end
at_exit do
ActiveRecord::Base.connection.rollback_transaction
end
end
then only you need add at the beginning of your task.rake file
require_relative 'path_to_your/sandbox.rb'
..and add at the beggining of your task code
desc "description task"
task example_task: :environment do
$sandbox.call if $sandbox.present?
...

run rake task inside rails application

I want to run asset precompile task inside the rails application,As I had many dependencies who will change the code,in that case all the time whenever they change i need to run the script as I cant give server access to them so I am providing the GUI for them from that they alone can run the script,so,I have built UI to run the task with some parameter like
system("Template='#{params[:template]}' Theme='#{params[:theme]}' rake assets:precompile)
I am getting two values from UI(params[:template],params[:theme]).Another thing i want to run this task in another path(site path) means Admin side UI is there that task should execute in Site directory,
if(params[:theme_script] == "true")
template=Template.where(:name => params[:template]).first
if template
theme = template.themes.where(:name => params[:theme]).first
if theme
# Dir.chdir "#{THEMEPATH}"do
# `Template="#{template.name}" Theme="#{theme.name}" rake assets:precompile`
# end
# sleep 10
# system("#{Rails.root.to_s}/lib/shell_script.sh")
# RunRake.run_rake(template.name,theme.name)
# Dir.chdir "#{THEMEPATH}"do
# Rake::Task['assets:precompile'].invoke
# end
ENV["Template"] = template.name
ENV["Theme"] = theme.name
precompile_task = "bundle exec rake assets:precompile --trace 2>&1"
output = Dir.chdir(THEMEPATH) { %x[ #{precompile_task} ] }
flash[:notice] = "Asset created successfully"
else
flash[:notice] = "U have enter invalid data"
end
else
flash[:notice] = "U have enter invalid data"
end
end
This is my code am checking multiple condition and am allowing to execute the task.
I have tried this code by putting in controller and lib, but this is not working.
I have tried with shell script also.
Could please anyone can help me.
You can just setup an environment variable for rails, and then issue #invoke method from a controller. So, prepare the files:
gemfile
gem 'rake'
config/initializers/rake.rb:
Rake.load_rakefile Rails.root.join( 'Rakefile' )
app/controllers/your_controller:
ENV["Template"] = template.name
ENV["Theme"] = theme.name
Rake::Task[ 'assets:precompile' ].invoke
Issue bundle install, then run console rails c, and type:
Rake::Task.tasks.map(&:name).grep 'assets:precompile'
# => ["assets:precompile"]
As you can see, the task assets:precompile is loaded successfully. Then just issue the action for the controller.
To run the task for an other rails app you shell run also the other ruby instance, similar to as you had done:
system( "other_app_run.sh '#{template.name}' '${theme.name}'" )
other_app_run.sh:
#!/bin/bash
source "$HOME/.rvm/scripts/rvm"
cd /other/app/path
export Template="$1"
export Theme="$2"
rake assets:precompile

How do I use Rails clockwork gem to run rake tasks?

What is the syntax for calling rake tasks from clockwork? I've tried all kinds of syntax, and nothing seems to work. (I'm specifically interested in clockwork because Heroku's supporting it.)
Here's my clock.rb, using the same syntax that the whenever gem uses:
module Clockwork
puts "testing clockwork!"
every(30.seconds, 'Send Messages') {
rake 'scheduler:send_messages'
}
end
And here's my rake task in scheduler.rake:
task :send_messages => :environment do
puts "rake task run successfully!"
end
And here's what happens when I start a clockwork process:
$ clockwork lib/clock.rb
testing clockwork!
I, [2012-07-16T14:42:58.107245 #46427] INFO -- : Starting clock for 1 events: [ Send Messages ]
I, [2012-07-16T14:42:58.107364 #46427] INFO -- : Triggering 'Send Messages'
attempting to run rake task!
E, [2012-07-16T14:42:58.107437 #46427] ERROR -- : undefined method `rake' for Clockwork:Module (NoMethodError)
This runs every 30 seconds. As you can see, the clock.rb is executed successfully. But I can't for the life of me figure out the syntax to run a rake task. The clockwork readme is no help, unfortunately:
https://github.com/tomykaira/clockwork
rake is not a method, so you can't invoke it like that here.
You can either shell out and invoke it, something like
every(30.seconds, 'Send Messages') {
`rake scheduler:send_messages`
}
or rather invoke a new detached process using the heroku API. This is my preferred method right now:
Heroku::API.new.post_ps('your-app', 'rake scheduler:send_messages')
Heroku::API is available from heroku.rb: https://github.com/heroku/heroku.rb
You can add the following method to your clock.rb file:
def execute_rake(file,task)
require 'rake'
rake = Rake::Application.new
Rake.application = rake
Rake::Task.define_task(:environment)
load "#{Rails.root}/lib/tasks/#{file}"
rake[task].invoke
end
and then call
execute_rake("your_rake_file.rake","your:rake:task")
in your handler
You can pass in a block to every that executes your rake task:
every(1.day, "namespace:task") do
ApplicationName::Application.load_tasks
Rake::Task['namespace:task'].invoke
end
Invoking the task using Rake::Task['...'].invoke works well the first time, but the task need to be reenabled to be invoked again later.
ApplicationName::Application.load_tasks
module Clockwork do
every(10.minutes, "namespace:task") do
Rake::Task['namespace:task'].invoke
Rake::Task['namespace:task'].reenable
end
end
Otherwise the task will be invoked the first time, but no more after that until the clockwork process is restarted.

Capistrano: deploy.rb file refactoring

I have following code in my deploy.rb
namespace :app do
desc "copies the configuration frile from ~/shared/config/*.yml to ~/config"
task :copy_config_files,:roles => :app do
run "cp -fv #{deploy_to}/shared/config/hoptoad.rb #{release_path}/config/initializers"
run "cp -fv #{deploy_to}/shared/config/app_config.yml #{release_path}/config/app_config.yml"
end
end
I thought it would be a good idea to keep my deploy.rb file clean and I attempted to move above code to capistrano_utilities.rb under config. I am using Rails application. And I added following line of code to deploy.rb
require File.expand_path(File.dirname(__FILE__) + "/../lib/capistrano_utilities")
Now I am getting following error.
undefined method `namespace' for main:Object (NoMethodError)
The value of self in the deploy.rb is Capistrano::Configuration . While the value of self in capistrano_utilities is Main. So I understand why I am getting namespace method error. What is the fix for this problem?
In your config/deploy.rb, try load instead of require. Also, capistrano already runs as if you're at the RAILS_ROOT, so there's no need to use __FILE__:
load "lib/capistrano_utilities"
In a capistrano config file, load is redefined to load another configuration file into the current configuration. When passing a path to it, it actually calls load_from_file (a private method defined by capistrano) that just reads the file from disk and instance_eval's it.
Check your Capfile on Rails.root.
if you use capistrano 3, you see this line;
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }
Now, put your file on "lib/capistrano/tasks/capistrano_utilities.cap" and it will be loaded.

Resources