Rails 环境变量设置

什么是环境变量(Environment Variables)

所有的操作系统都提供了各自的机制可以设置环境变量,所有的 Unix类Unix 系统中的每个进程都有各自的环境变量设置,很多 PaaS 的开发者平台(比如Heroku)也提供了配置环境变量的机制。我的理解是,环境变量就是可以在不同环境中单独配置(对环境有依赖),可以让进程在运行时加载并使用,一些底层的基础变量。常见的环境变量比如:PATH、HOME 等等。

为什么在 Rails 中使用环境变量

主要有两方面原因,

  • 应用程序通常都是设计成可以在多种不同环境下部署和运行的,所以代码中不应该出现针对特定环境的内容(比如文件绝对路径),而是应该在运行时根据当前环境使用合适的内容。那么对与这些对环境有所依赖的部分,就可能需要使用环境变量来进行配置。
  • 当应用程序中需要使用一些涉及到安全和隐私的内容时(比如邮箱密码),把这类内容直接写在代码里是很不明智的,因为可能会有很多人可以浏览这部分代码,或者这些代码根本就是开源的。为了保护此类数据,就有必要把它们配置在只有你自己可以访问的环境中,让程序在运行时根据变量名去获取。

怎样在 Rails 中获取环境变量

Ruby 提供了一个很方便的访问环境变量的方式:

1
ENV["VARIABLE_NAME"]

比如要在一个 Rails 应用中配置一个 Gmail 账号来发邮件:

1
2
3
4
5
6
7
8
9
config.action_mailer.smtp_settings = {
address: "smtp.gmail.com",
port: 587,
domain: "example.com",
authentication: "plain",
enable_starttls_auto: true,
user_name: ENV["GMAIL_USERNAME"]
password: ENV["GMAIL_PASSWORD"]
}

这个配置对 Rails 开发者肯定不陌生,这里就是使用环境变量对 Gmail 的用户名和密码进行了保护。

如何配置环境变量

配置环境变量对大多数人来说也并不陌生,比如 Windows 系统属性设置里面就可以找到配置环境变量的按钮,在 Linux/Unix/Mac 之类的操作系统中可以通过配置 .bashrc 或者 .zshrc (根据使用shell来定)之类文件来设置。

比如在 ~/.bashrc 中加入下面内容:

1
2
export GMAIL_USERNAME="foobar@gmail.com"
export GMAIL_PASSWORD="password"

我认为这种配置方式对一个运行在 production 环境的应用来说是不错的选择。我这么认为的理由是,假如我在 Linux 上部署应用的 production 环境,为了很好的控制操作权限,我会新建一个系统用户,然后用这个用户部署和运行这个应用,那么我在这个用户的 ~/.bashrc 中配置应用所需要的环境变量,即保证了安全性,又可以完全与应用隔离,方便维护且不会轻易丢失。

这也是对各种应用程序最通用的一种配置环境变量的方式,下面再说两个针对 Rails 的环境变量配置方式。

自建 YAML 文件配置

该方式一共有三步,

  1. 在 Rails App 路径下新建一个 YAML 文件,比如 config/local_env.yml,配置需要的环境变量,例如:
1
2
GMAIL_USERNAME: 'foobar@gmail.com'
GMAIL_PASSWORD: 'password'
  1. 要让 Rails App 启动时首先加载配置的环境变量,需要在 config/application.rb 中添加如下代码:
1
2
3
4
5
6
config.before_configuration do
env_file = File.join(Rails.root, 'config', 'local_env.yml')
YAML.load(File.open(env_file)).each do |key, value|
ENV[key.to_s] = value
end if File.exists?(env_file)
end
  1. config/local_env.yml 文件加入到 VCS 的忽略列表中,比如 .gitignore
1
/config/local_env.yml

这样就完成了整个配置,在 config/local_env.yml 中,每一行配置了一个环境变量,如果 config/local_env.yml 文件存在,其中的配置就会覆盖 shell 中的同名环境变量,需要注意第 3 步是非常重要的,否则就根本起不到对配置数据的保护作用了。

使用 Figaro Gem

如果你希望能够同时针对 development、test、production 配置不同的环境变量,又不介意多使用一个 Gem 的话,那么 Figaro Gem 就是个很好的选择。

要使用 Figaro Gem,要先在 Gemfile 中添加:

1
gem 'figaro'

运行 $ bundle install

然后执行 Figaro 提供的生成器 $ rails generate figaro:install

这将会为你生成一个 config/application.yml 文件,并将其添加到 .gitignore 中。

然后你可以像上面讲到的 config/local_env.yml 那样配置 config/application.yml 文件,如果你想针对 development、test、production 配置不同的环境变量,可以这样:

1
2
3
4
5
6
7
8
9
10
development:
GMAIL_USERNAME: 'develop@gmail.com'
test:
GMAIL_USERNAME: 'test@gmail.com'
production:
GMAIL_USERNAME: 'product@gmail.com'
GMAIL_PASSWORD: 'foobar'

在测试代码或其他一些情况下有可能无法使用 ENV 变量,这时可以使用 Figaro 提供的方法来获取配置的变量:

1
Figaro.env.gmail_username

是不是很方便?对 Figaro 的更多说明可以参考它的Github页面

补充

上面提到的三种配置方式如果要说那种更好,不如说那种更适合。就像我前面说的在 shell 中直接配置的方式可能更适合发布 production 环境时使用,因为后面两种方式中的 YAML 文件都是在 Rails App 路径下的,所以没有完全隔离,一旦重新部署就需要重新处理一次配置,多少有些麻烦。所以还是要根据具体情况选择合适的配置方式才对。

Figaro 提供的可以为不同环境分别添加各自配置的能力很多人一定喜欢,难道前两种配置方式就无法做到这一点么?其实也有办法,只不过稍稍麻烦一点,需要在代码里面去控制。首先,配置变量时可以在变量名后面添加后缀来区分不同环境的配置,就拿 config/local_env.yml 来示范一下:

1
2
3
GMAIL_USERNAME: 'foobar@gmail.com'
GMAIL_USERNAME_DEV: 'develop@gmail.com'
GMAIL_USERNAME_TEST: 'test@gmail.com'

然后在使用配置变量的地方分别做判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if Rails.env.development?
config.action_mailer.smtp_settings = {
user_name: ENV["GMAIL_USERNAME_DEV"]
}
end
if Rails.env.test?
config.action_mailer.smtp_settings = {
user_name: ENV["GMAIL_USERNAME_TEST"]
}
end
if Rails.env.production?
config.action_mailer.smtp_settings = {
user_name: ENV["GMAIL_USERNAME"]
}
end