How to specify environment dependencies in Rails? - ruby-on-rails

Where does one list the environment variable dependencies for a Rails application?
I don't want the app to run if the user hasn't specified the variables or at a minimum output some form of notice that says ***Don't run until you've set the following environment variables..."

I'd put something like that in config/boot.rb:
# usual boot.rb stuff...
raise 'Set PANCAKES in your environment!' unless ENV.has_key? 'PANCAKES'
The nice thing about boot.rb is that it is run very early in the start up process so you don't have to wait for all the Rails machinery to start (which can take a long time) before you know there's a problem.

Related

Scope of Models(?) in Rails

I'm new to Ruby on Rails and am trying to access my site's database. I generated and set up a model and controller called Machine, and noticed that in places like the Machine view I could iterate through all the machines in my database simply using #machines.each. However, this doesn't appear to be universal, as when I created a new Ruby file directly in my project's outermost directory, both #machines.each and the attempted assignment #machines = Machine.all threw errors (a NoMethodError and NameError respectively). Here's an example of code I could try to run:
#machines = Machine.all
#machines.each do |machine|
puts machine.created_at
end
Perhaps I need some kind of import statement?
If you are writing a script in plain Ruby -- then yes, you'll have to import everything manually, establish a connection to the DB, etc.
The code would roughly look like this:
require 'active_support'
require 'active_record'
your_db_config = {
# your DB config goes here
}
ActiveSupport::Dependencies.autoload_paths += File.join(__dir__, "app/models")
ActiveRecord::Base.establish_connection(your_db_config)
machines = Machine.all
Consider creating a task if you want Rails to take care of all that and don't want to be doing all that stuff manually.
When you start a rails server (or a rails console) it preloads your Rails application so that your models, constants, etc. are automatically in scope. If you want to access your application's resources from a separate script you still need to load the app. The simplest way to do that is with the rails runner command, which loads your app and then executes a script. So if your script above is in lib/show_machines you'd run:
$ bin/rails runner lib/show_machines
If you like self-executing scripts you can also use runner as a 'shebang' line:
#!/usr/bin/env <your_project_path>/rails/runner
#machines = Machine.all
#machines.each do |machine|
puts machine.created_at
end

How to test the presence of environnements variables in Rspec

Question
There are many ways to achieve that goal but I would like to know what is the best way to tests the presence of the environmental variables inside a Ruby on Rails project.
Context
We recently had a production issue related to a missing environment variable in one of our Rails project.
To prevent this from happening again, I would like to test the presence of the environments variable in the application.yml configuration file.
I am using Ruby 2.5, Rails 4.2, Spring 2.
Unless checking this specifically in application.yml is an absolute requirement (why?), here is my take. Make a config/initializers/env.rb and put in there something like
%i[FOO BAR BAZ].each do |var|
ENV[var] = ENV.fetch(var)
end
What this does is reads all of your required environment variables (FOO, BAR, BAZ etc.) and Hash#fetch them which will 'raise' if this variable is not set at boot time.

Ruby on rails, run a method on server start 2.3

I want to run a method, on the startup of the rails server. It's a model method.
I tried using config/initializers/myfile.rb, but the method was invoked during migrations, so it SELECTed from a nonexistant table.
Tried environment.rb also, but the class does not exist yet (and will probably have the same problem with migrations)
I don't know where to put that method, so it'll run only on server startup and not during migrations.
There are some things you could do to actually improve this a bit. The issue is that you are running this code when rake loads your environment, but you really only want to run this when the environment is loaded by an instance of your web server. One way to get around this is to set a value when rake loads your environment, and when that value is set, to not execute your initializer code. You can do this as follows:
task :environment => :disable_initializer
task :disable_initializer do
ENV['DISABLE_INITIALIZER_FROM_RAKE'] = 'true'
end
#In your initializer:
ENV['DISABLE_INITIALIZER_FROM_RAKE'] || MyModel.method_call
There is no way to avoid this from my understanding. You can put the initializer code that relies on the new table in a rescue block to quiet things down so others can run migrations.
Try putting your method call in boot.rb, in the run method after the Rails::initializer call. I don't have rails in front of me right now because I'm at work but I think that the whole environment should be loaded by that point and you can run methods on the framework.
I found this to work quite well:
if File.basename($0) == "rails" && ARGV == []
It also detects "rails generate .."

Determine if script/server is being started

In Rails, in an initializer/environment.rb Whats the pefered way to detemrine if the webapp itself is being loaded (script/server).
All the initializers are loaded for migrations script/console and other rails task as well, but in my case some stuff only has to be loaded when the server itself is being initialized.
My ideas: checking $0
Thanks!
Reto
Because there are multiple application servers, each with their own initialization strategy, I would recommend the only way to reliably hook into the server boot process: ActionController::Dispatcher.
The dispatcher has some callbacks; namely:
prepare_dispatch (added with to_prepare)
before_dispatch
after_dispatch
The "prepare" callbacks are run before every request in development mode, and before the first request in production mode. The Rails configuration object allows you to add such callbacks via its own to_prepare method:
Rails::Initializer.run do |config|
config.to_prepare do
# do your special initialization stuff
end
end
Unfortunately, to my knowledge this callback will always be run since Rails initializer calls Dispatcher.run_prepare_callbacks regardless of if we're booting up with a server or to a script/console or even a rake task. You want to avoid this, so you might try this in your environment.rb:
Rails::Initializer.run do |config|
# your normal stuff
end
if defined? ActionController::Dispatcher
ActionController::Dispatcher.to_prepare do
# your special stuff
end
end
Now, your "special stuff" will only execute before first request in production mode, but before every request in development. If you're loading extra libraries, you might want to avoid loading something twice by putting an if statement around load or require. The require method will not load a single file twice, but I still recommend that you put a guard around it.
There is probably a better way to do this, but since I am not aware of one, I would probably alter script/server to set an environment variable of some kind.
Then I would have my initializer check for that environment variable.

Supplying an argument at Rails runtime

I'm looking for a way to supply an argument to a ruby on rails project at runtime. Essentially, our project uses public key cryptography to encrypt some sensitive client data and we want the ability to supply the password to the private key file at runtime.
Any Ruby script has access to local environment variables through the ENV hash.
puts ENV['PATH']
So with any posix system (Linux, Unix, Mac OS) you can simply set it when calling the script, like this:
MY_ARG=supersecret ruby script.rb
The same is also valid for rails. If you put puts ENV['MY_ARG'] in your environment.rb and start your server:
$ MY_ARG=supersecret mongrel_rails start
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
supersecret
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart).
** Rails signals registered. HUP => reload (without restart). It might not work well.
** Mongrel 1.1.5 available at 0.0.0.0:3000
** Use CTRL-C to stop.
An environment variable is by far the simplest solution in my opinion.
An easy way to do this would be to create a Rails plugin that takes arguments using 'gets' in its 'init.rb'. Allow me to cook-up a quick code sample:
Make a directory: '$railsRoot/vendor/plugins/startup_args/lib'
Create an object to store argument data in '$railsRoot/vendor/plugins/startup_args/lib/startup_args.rb':
module StartupArgs
##argHash = {}
def self.setArg(key, value)
##argHash[key.to_sym] = value
end
def self.getArg(key)
return ##argHash[key.to_sym]
end
end
Load the StartupArgs module into the Rails project's namespace and populate it with arguments in '$railsRoot/vendor/plugins/startup_args/init.rb':
require "startup_args"
promptString = "Enter arg name (type nothing to continue):"
puts promptString
while (newArg = gets.chomp) != ""
puts "Enter value for '#{newArg}':"
newVal = gets.chomp
StartupArgs.setArg(newArg, newVal)
puts promptString
end
Now, during the Rails project's start-up process, it will try to take key-value pairs from the console. These pairs will be stored in the global-namespace object StartupArgs for later access (via 'StartupArgs.getArg()').
If you anticipate that your Rails project might be deployed in scenarios where the daemon will not have access to the console during startup-time, you could read from a named-pipe instead of the console's standard input.
Going one step further, you could remove all parts of 'init.rb' except for the 'require' statement and add an action that performs this setup to a controller that would take the relevant parameters as a post over the web. Make sure to configure Rails to prevent potentially sensitive parameters (e.g. passwords) from being entered into log files recording accesses or errors (especially if it might be used as an HTTP GET, with parameters in the URL).
(You get the same effect this way as with the system described above if you configure your other Rails actions to ignore requests until the setup action has stored the appropriate parameters to the global object.)
A Note For Micah: I don't have the reputation to comment directly on your post, so I will include my comment here. There might be a few reasons to consider a system that never requires the password to be represented in the filesystem. For example, a developer might be planning a Rails project that could be deployed on varying operating systems and in varying environments. If the developer determines that there could be scenarios in which an administrative or root user could be compromised, cannot be trusted, or cannot be asked to sign confidentiality and security agreements, the developer might decide to add the additional obfuscation of placing the password in memory only (in the interest of requiring a slightly less secure system or a slightly more clever attack to steal the password). Perhaps it could be considered a matter of relative costs: at low cost, the password can be hidden in a way that requires more expensive knowledge in order to retrieve.
What is wrong with putting the password in a file that is chmod'ed to only be readable by the web server user?

Resources