是否可以在我的代码中为 Rails 开发环境设置 ENV 变量?

我知道我可以在 bash via 中设置 ENV 变量

export admin_password = "secret"

但是有没有办法在我的 Rails 源代码中找到呢?我的第一次尝试是在 environment/development.rb中的类似的东西

ENV['admin_password'] = "secret"

但没成功,有办法吗?

122948 次浏览

The system environment and rails' environment are different things. ENV let's you work with the rails' environment, but if what you want to do is to change the system's environment in runtime you can just surround the command with backticks.

# ruby code
`export admin_password="secret"`
# more ruby code

[Update]

While the solution under "old answer" will work for general problems, this section is to answer your specific question after clarification from your comment.

You should be able to set environment variables exactly like you specify in your question. As an example, I have a Heroku app that uses HTTP basic authentication.

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate


def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == ENV['HTTP_USER'] && password == ENV['HTTP_PASS']
end
end
end


# config/initializers/dev_environment.rb
unless Rails.env.production?
ENV['HTTP_USER'] = 'testuser'
ENV['HTTP_PASS'] = 'testpass'
end

So in your case you would use

unless Rails.env.production?
ENV['admin_password'] = "secret"
end

Don't forget to restart the server so the configuration is reloaded!

[Old Answer]

For app-wide configuration, you might consider a solution like the following:

Create a file config/application.yml with a hash of options you want to be able to access:

admin_password: something_secret
allow_registration: true
facebook:
app_id: application_id_here
app_secret: application_secret_here
api_key: api_key_here

Now, create the file config/initializers/app_config.rb and include the following:

require 'yaml'


yaml_data = YAML::load(ERB.new(IO.read(File.join(Rails.root, 'config', 'application.yml'))).result)
APP_CONFIG = HashWithIndifferentAccess.new(yaml_data)

Now, anywhere in your application, you can access APP_CONFIG[:admin_password], along with all your other data. (Note that since the initializer includes ERB.new, your YAML file can contain ERB markup.)

The way I am trying to do this in my question actually works!

# environment/development.rb


ENV['admin_password'] = "secret"

I just had to restart the server. I thought running reload! in rails console would be enough but I also had to restart the web server.

I am picking my own answer because I feel this is a better place to put and set the ENV variables

Never hardcode sensitive information (account credentials, passwords, etc.). Instead, create a file to store that information as environment variables (key/value pairs), and exclude that file from your source code management system. For example, in terms of Git (source code management system), exclude that file by adding it to .gitignore:

-bash> echo '/config/app_environment_variables.rb' >> .gitignore

/config/app_environment_variables.rb

ENV['HTTP_USER'] = 'devuser'
ENV['HTTP_PASS'] = 'devpass'

As well, add the following lines to /config/environment.rb, between the require line, and the Application.initialize line:

# Load the app's custom environment variables here, so that they are loaded before environments/*.rb
app_environment_variables = File.join(Rails.root, 'config', 'app_environment_variables.rb')
load(app_environment_variables) if File.exists?(app_environment_variables)

That's it!

As the comment above says, by doing this you will be loading your environment variables before environments/*.rb, which means that you will be able to refer to your variables inside those files (e.g. environments/production.rb). This is a great advantage over putting your environment variables file inside /config/initializers/.

Inside app_environment_variables.rb there's no need to distinguish environments as far as development or production because you will never commit this file into your source code management system, hence it is for the development context by default. But if you need to set something special for the test environment (or for occasions when you test production mode locally), just add a conditional block below all the other variables:

if Rails.env.test?
ENV['HTTP_USER'] = 'testuser'
ENV['HTTP_PASS'] = 'testpass'
end


if Rails.env.production?
ENV['HTTP_USER'] = 'produser'
ENV['HTTP_PASS'] = 'prodpass'
end

Whenever you update app_environment_variables.rb, restart the app server. Assuming you are using the likes of Apache/Passenger or rails server:

-bash> touch tmp/restart.txt

In your code, refer to the environment variables as follows:

def authenticate
authenticate_or_request_with_http_basic do |username, password|
username == ENV['HTTP_USER'] && password == ENV['HTTP_PASS']
end
end

Note that inside app_environment_variables.rb you must specify booleans and numbers as strings (e.g. ENV['SEND_MAIL'] = 'false' not just false, and ENV['TIMEOUT'] = '30' not just 30), otherwise you will get the errors can't convert false into String and can't convert Fixnum into String, respectively.

Storing and sharing sensitive information

The final knot to tie is: how to share this sensitive information with your clients and/or partners? For the purpose of business continuity (i.e. when you get hit by a falling star, how will your clients and/or partners resume full operations of the site?), your clients and/or partners need to know all the credentials required by your app. Emailing/Skyping these things around is insecure and leads to disarray. Storing it in shared Google Docs is not bad (if everyone uses https), but an app dedicated to storing and sharing small titbits like passwords would be ideal.

How to set environment variables on Heroku

If you have a single environment on Heroku:

-bash> heroku config:add HTTP_USER='herouser'
-bash> heroku config:add HTTP_USER='heropass'

If you have multiple environments on Heroku:

-bash> heroku config:add HTTP_USER='staguser' --remote staging
-bash> heroku config:add HTTP_PASS='stagpass' --remote staging


-bash> heroku config:add HTTP_USER='produser' --remote production
-bash> heroku config:add HTTP_PASS='prodpass' --remote production

Foreman and .env

Many developers use Foreman (installed with the Heroku Toolbelt) to run their apps locally (as opposed to using the likes of Apache/Passenger or rails server). Foreman and Heroku use Procfile for declaring what commands are run by your application, so the transition from local dev to Heroku is seamless in that regard. I use Foreman and Heroku in every Rails project, so this convenience is great. But here's the thing.. Foreman loads environment variables stored in /.env via dotenv but unfortunately dotenv essentially only parses the file for key=value pairs; those pairs don't become variables right there and then, so you can't refer to already set variables (to keep things DRY), nor can you do "Ruby" in there (as noted above with the conditionals), which you can do in /config/app_environment_variables.rb. For instance, in terms of keeping things DRY I sometimes do stuff like this:

ENV['SUPPORT_EMAIL']='Company Support <support@company.com>'
ENV['MAILER_DEFAULT_FROM'] = ENV['SUPPORT_EMAIL']
ENV['MAILER_DEFAULT_TO']   = ENV['SUPPORT_EMAIL']

Hence, I use Foreman to run my apps locally, but I don't use its .env file for loading environment variables; rather I use Foreman in conjunction with the /config/app_environment_variables.rb approach described above.

As an aside to the solutions here, there are cleaner alternatives if you're using certain development servers.

With Heroku's Foreman, you can create per-project environment variables in a .env file:

ADMIN_PASSOWRD="secret"

With Pow, you can use a .powenv file:

export ADMIN_PASSOWRD="secret"

I think the best way is to store them in some yml file and then load that file using this command in intializer file

APP_CONFIG = YAML.load_file("#{Rails.root}/config/CONFIG.yml")[Rails.env].to_hash

you can easily access environment related config variables.

Your Yml file key value structure:

development:
app_key: 'abc'
app_secret: 'abc'


production:
app_key: 'xyz'
app_secret: 'ghq'

Script for loading of custom .env file: Add the following lines to /config/environment.rb, between the require line, and the Application.initialize line:

# Load the app's custom environment variables here, so that they are loaded before environments/*.rb


app_environment_variables = File.join(Rails.root, 'config', 'local_environment.env')
if File.exists?(app_environment_variables)
lines = File.readlines(app_environment_variables)
lines.each do |line|
line.chomp!
next if line.empty? or line[0] == '#'
parts = line.partition '='
raise "Wrong line: #{line} in #{app_environment_variables}" if parts.last.empty?
ENV[parts.first] = parts.last
end
end

And config/local_environment.env (you will want to .gitignore it) will look like:

# This is ignored comment
DATABASE_URL=mysql2://user:psw@0.0.0:3307/database
RACK_ENV=development

(Based on solution of @user664833)

Add In your Gemfile gem 'dotenv-rails' run command bundle install add .env file in you root directory and write your environment variable like

export RAILS_ENV=production && rails c

export AUTH="xyz 891f5089ba563cd1ea55b73467"

call the env from anywhere in you application also you rails console run rails console write ENV['AUTH'] then press enter, you will find your result xyz 891f5089ba563cd1ea55b73467

You can also use the dotenv or dotenv-rails rails gems, e.g.

Have a .env with:

HTTP_USER=user
HTTP_PASSWORD=password

The nice thing is you can have .env.development, .env.production, .env.development.local, etc.

https://github.com/bkeepers/dotenv

For many years I relied on the Figaro gem, but it has unfortunately not been maintained. The latest problem there, Psych::BadAlias led me to roll my own solution.

This turned out to be pretty simple and I continue to use the application.yml file originally defined for Figaro.

config/environments/development.rb

Load the variables when the app boots. The same code can be used in test.rb for local tests and CI.

Rails.application.configure do
env_file = File.join(Rails.root, 'config', 'application.yml')
YAML.load_file(env_file).symbolize_keys[:development].each do |key, value|
ENV[key.to_s] = value
end
...


lib/tasks/heroku.rake

namespace :myapp do
desc 'Push from `application.yml to Heroku`'
task push_config: :environment do
app = 'myapp-prd'


env_file = File.join(Rails.root, 'config', 'application.yml')
YAML.load_file(env_file).symbolize_keys[:production].each do |key, value|
sh "heroku config:add #{key.to_s}='#{value}' --app #{app}"
end
end
end

The only issue is it reloads the Heroku environment after every variable is loaded. This can be optimised, but in reality ENV variables are not changed much anyway.