Set Rails Variable to Different Values for Different Capistrano Environments - ruby-on-rails

I have a rails app which is set up to deploy with Capistrano to one of two different servers (depending on which the user chooses to deploy to). I want to create an environment variable for the rails app which changes depending on which server the website is deployed to.
In my Capistrano deploy (config/deploy.rb) file, I have a variable "stages" that has the two options for the user to deploy to:
set :stages, %w(production staging)
I have tried to use this variable in an if statement in my environment (config/environment.rb) file to set the variable like so:
if (:stages == "staging")
ENV['GLOBAL_EMAIL'] = "email1#domain.com"
else
ENV['GLOBAL_EMAIL'] = "email2#domain.com"
end
However, when I test this, rails fails to render #{ENV['GLOBAL_EMAIL']} (as far as I can tell because the variable is not set or reachable.
My question is if there is a reason that :stages is not reachable, and also if there is a way/better way to set the variable according to the environment?
Note that even though one is called staging and one is called production, both environments deploy the production environment of rails (not test and production environments of rails), thus I cannot do a test to see which environment rails is running to then set the variable.

For starters you are trying to equate a symbol with a string, which will always return false.
Also within capistrano, to get the environment your deploying in you want the stage variable. This variable is only available after loading and not when the file is read by ruby. What this means is that you need to provide a block that gets lazy loaded. The problem is that these are evaluated in the context of setting a local variable such as deploy_to.
set(:deploy_to) { "/var/www/#{application}/#{stage}" }
This is a common setup within my deploy scripts where staging and production are deployed to the same server.
You could possibly try something like:
set(:global_email) do
if stage == 'production'
ENV['GLOBAL_EMAIL'] = 'email1#example.com'
else
ENV['GLOBAL_EMAIL'] = 'email2#example.com'
end
end
Or another way of handling this is to use the deploy stage specific files.
# config/deploy/production.rb
ENV['GLOBAL_EMAIL'] = 'email1#example.com'
# config/deploy/staging.rb
ENV['GLOBAL_EMAIL'] = 'email2#example.com'
Update
I hadn't noticed the question mentioned rendering the value in Rails. This of course has nothing to do with Rails, nor is anything set in a Capistrano config file available when Rails is initialized since capistrano should really be only available in development environments.
A proper way to do this would be using a rails initalizer and setting the variable when Rails boots up.
# config/initializer/global_email.rb
if Rails.env.production?
GLOBAL_EMAIL = 'email1#example.com'
else
GLOBAL_EMAIL = 'email2#example.com'
end

The final solution I ended up with was to create a file external from my Capistrano deploy (for instance in /home/[user]/.var/.emails) on each server where each file contains just the desired email (e.g. email1#domain.com). Then in environments.rb:
ENV['GLOBAL_EMAIL'] = begin IO.read("/home/[user]/.vars/.email") rescue "" end

Related

Is there a way to find current environment in a Ruby application similar to rails.env?

I need to implement a feature that sets some Env vars conditionally based on current environment(dev,test,prod). I know in rails this can be accomplished with rails.env. Is there a similar method for Ruby?
No, vanilla ruby doesn't have a concept of "environment". You will have to build this yourself. One simple way is to use an environment variable and read it.
For example, you can require a MYAPP_ENVIRONMENT environment variable. Then you read it with myapp_environment = ENV['MYAPP_ENVIRONMENT']. Then you might have hashes or some other data structure to determine values that are specific to that environment:
ENDPOINT_A = {
prod: 'https://prod.my_company.com',
stage: 'https://stage.my_company.com'
}
Similarly for other variables. Note that the endpoint string is a ruby variable, not an environment variable. You should NOT set environment variables from the ruby code that uses them. The whole point of environment variables is that they are set externally to your app and your app takes them as input to configure how it behaves.
If you want to specify the endpoint through an environment variable, you should set it in the operating system where your ruby app runs. You can do this manually on the machine or through a deployment pipeline or script.

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.

How can I reuse environment specific variables?

Say we have 2 logical environments "Staging" and "Production".
How or where can I define a variable that will have the value X on staging and Y on production AND be able to reuse those variables when I create more than 1 release definition?
An example would be the URL of a Web API to be used by all applications released on that environment.
For example I have these environment specific variables:
Staging
ApiUrl: https://staging.api.com
Production
ApiUrl: https://production.api.com
And I want to create 2 release definitions to deploy products (that both use that Api) to both environments.
If I add the ApiUrl as environment variables I need to add and maintain those variables on ALL release definition environments. Not very maintainable?
If I create a variable group "StagingVariables" and "ProductionVariables" I don't think I can switch/choose which group to use on which environment? You can link multiple variable groups to a release definition, but not to a specific environment, right?
You can define release definition variables which would combine the worst of the above 2 options.
If there's no decent way to solve this, are there way to help reduce the maintainance burden? Bulk editing environment variables for example?
Your assessment seems to be correct. The only way I can think of is to programmatically overwrite the variable values from a PowerShell script at the start of an environment.
Or, I think you can create a variable in the environment and set the value to a variable in the variable group, eg:
Variable Group Staging
- Staging.ApiUri
Variable Group Prod
- Prod.ApiUri
Environment Staging
- ApiUri = $(Staging.ApiUri)
Environment Prod
- ApiUri = $(Prod.ApiUri)
That way the value is still defined in one location, but explicitly scoped at the release level.

How to specify environment dependencies in 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.

Resources