Rails.root - uninitialized constant - ruby-on-rails

I am writing a script located in /bin of a rails 5 project. Basically I am trying to iterate through various files and paths. Something to help me with this effort is Rails.root so I don't have to worry about relative paths etc
The problem is, when I am attempting to use Rails.root in my script, I am getting an error:
uninitialized constant Rails (NameError)
My script looks something like this
bin/my_class.rb
class MyClass
def initialize
...
end
def my_function
...
Rails.root.to_s
end
end
MyClass.new.my_function
Then i call my script like
ruby bin/template_check.rb foo=bar
And it's breaking on the line where I am calling Rails.root with the error message:
uninitialized constant Rails (NameError)
I have tried
moving Rails.root to a constant above the class (thought maybe it was a name conflict or something?). didn't work
changing the name to ::Rails.root.to_s. did not work either
Does anyone know why I cannot access Rails.root inside of my script?
edit
I was able to get this resolved by adding
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
to the top of my script. This loads rails. as a default, the script is slower, and I'm not a huge fan of loading all of rails just to use Rails.root, but for now it works.
Other solutions like the answers below are to either use rails runner to run the script, or to wrap everything in a rake task.

You can try with
rails r bin/my_class.rb if your file is under bin
other wise you can use
rails r #{file_name}.rb specify the file name

Run with $ rails runner bin/my_class.rb. You can find out more about rails runner here

Related

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

LoadError for custom Ruby module

I am new to Ruby. I am dealing with a codebase of the following nature.
I have a main/ directory containing my entire codebase, and inside it I have files like:
main/lib/foo/test1.rb and
main/app/bar/test2.rb
Inside main/app/bar/test2.rb there is the line: require 'test1'
However, if I am in the main/ directory and I run ruby main/app/bar/test2.rb I get the following error: require': cannot load such file -- access_control (LoadError)
Now, upon Googling, I think this has something to do with /config/application.rb and adding the line: config.autoload_paths += Dir["#{config.root}/lib", "#{config.root}/lib/**/". I do this, but it doesn't seem to make any difference.
Presumably my production environment knows where to look in the require 'test1.rb' statement, while my environment does not. How can I fix this?
Thanks!
It sounds like you have a Ruby on Rails application.
Given that, you probably don't want to run any contained script directly with ruby. If it's designed to be executed directly, try running it with rails runner path/to/file.rb (from inside the directory that contains app/, lib/, config/, etc).
If it's inside app/ or lib/, though, it's more likely intended to be loaded as a library from some other script elsewhere in the application... those would both be unusual places to keep a stand-alone executable.

ActiveSupport Autoloading not working outside of Rails

Given a file structure of
lib
app/
feature.rb
app.rb
And given the file app.rb that consists of:
require 'active_support'
require 'active_support/dependencies'
module App
extend ActiveSupport::Autoload
autoload :Feature, 'app/feature.rb'
def self.start
p Feature.new
end
end
It complains that LoadError: cannot load such file -- app/feature.rb
I've tried passing the absolute system path, no path, relative path to feature.rb from the module and the relative path from the execution point (one dir above lib).
This appears to be exactly the same thing that Rails is doing in the source.
Seems like this should be pretty intuitive, no?
The solution is the add the files to the Ruby interpreter load path like this: $LOAD_PATH.unshift(File.dirname(__FILE__)).
What that does is basically, allow Ruby to lookup those files. In Rails that is configured already when a new app is generated. When building a gem, in the spec you can add files or dirs to the load path. Outside of those, say a script in this instance, you must manually do that. autoload does not do that by default so you must specify (somewhat confusingly to me) the paths that Ruby can access.
I'm sure someone can clean up this explanation but that is how I understand it.
I don't believe Rails has lib on its autoload paths by default. We've got the following in config/application.rb:
config.eager_load_paths << Rails.root.join('lib')

How can I set the Rails environment for my somewhat stand alone Ruby script?

I have a Ruby script in my Rails app that I use to load some data from Twitter.
In the future I will make it an automatic background process, but for now I run it manually like:
ruby /lib/twitter/twitterLoad.rb
In order to use the Rails model classes and such, I have the following as the top line of the script:
require "#{File.dirname(__FILE__)}/../../config/environment.rb"
By default, the development environment is used. But, I'd like to be able to choose the production environment at some point.
Update #1: The RAILS_ENV constant is getting set in the environment.rb file. So, I was able to put ENV['RAILS_ENV'] = 'production' at the very top (before the environment.rb) line and solve my problem somewhat. So, my new question is, can do pass in env vars through the command line?
If you're going to be using the rails environment, your best bet would be to make this a rake script. To do this, put a twitter.rake file into lib/tasks and begin and end it like this:
task(:twitter_load => :environment) do
# your code goes here
end
That way, you're doing it by "following conventions" and it doesn't have that 'orrible smell associated with it.
I currently use the following method, and I know the environment doesn't have the rb extension, it's not needed. You can also set it before running it to overwrite the ENV["RAILS_ENV"].
#!/usr/bin/env ruby
# Set your environment here.
ENV["RAILS_ENV"] ||= "production"
require File.dirname(__FILE__) + "/../../config/environment"
puts "Rails was loaded!"
Then to change the environment, just run it with:
rb /lib/tasks/file.rb RAILS_ENV=development
Don't forget script/runner.
Set your environment variable from the command line and
ruby script/runner your_script_here.rb
The accepted answer to use rake is well-taken, but you may still want to manually set the environment for testing utility classes.
Here's what I use to set up the test environment for utility classes in /lib. For these I tend to use the Ruby convention of making my class file execute its tests when it gets run from the command line. This way I can do TDD outside of Rails' web-centric test harnesses, but still use the class within rake without affecting the environment that it sets.
This goes at the top:
if (__FILE__ == $0)
ENV['RAILS_ENV'] ||= 'test'
require File.join(File.dirname(__FILE__),'../config/environment.rb')
end
and this goes at the bottom:
if (__FILE__ == $0)
require 'test/unit/ui/console/testrunner'
Test::Unit::UI::Console::TestRunner.run(MyClassTest)
end
You can also do
script/console development < path/to/your/script.rb
Admiteddly cumbersome -and will spit out lots of irb garbage after evaluating each and every line of your script- but works for quickies and you dont have to remember that damned require line.
And don't forget that maybe the most elegant way to extend your app with scripts that do useful things is writing Rake tasks!
add the line:
RAILS_ENV = '<your environment of choice>'
http://wiki.rubyonrails.org/rails/pages/Environments#script
pantulis: It's cool that that works, but for quickies I just use RAILS_ENV = '<your environment of choice>' ruby path/to/script.rb. This is how you set environment variables in the console.
require 'bundler'
require 'bundler/setup'
ENV['RAILS_ENV'] ||= 'development'
RAILS_ROOT = Pathname.new(File.expand_path('~/path/to/root'))
puts "Loading Rails Environment from #{RAILS_ROOT}..."
require RAILS_ROOT.join('config/environment').to_s
This works but only if the Gemfile of your script contains all the dependencies of your rails app. You might be able to load one Gemfile from another, e.g just eval it, to overcome this copy/paste.

`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