Rails Guide
Environment-specific Settings
Default approach: Use an initializer!
Especially if a gem or library already provides a configuration interface (e.g. ActionMailer, CarrierWave, …), you should just do the configuration right there with Ruby without introducing a second configuration layer (e.g. with a .yml
file) above it.
# config/intializers/foo.rb
if Rails.env.production?
...
else
...
end
If you build your own “module/library” you should should implement a configuration interface for that “module/library”.
Advantages:
- easy to find configuration (Where is the config for ‘foo’? –> initializers/foo.rb)
- configuration for all environments of one lib withinin a couple of lines of code
- more flexible than .yml, because it’s Ruby
Exception: configuration used by different “libs”
It can make sense to introduce another configuration level above the configuration of your lib or gem, if configuration options need to be used by different gems or in completely different parts of the app.
Example: The Google Analytics account id of an app is used: 1) in the application layout view, 2) in a tracking gem (e.g. ga_events
), 3) and in a gem retrieving/displaying metrics from GA. This is a good usecase for storing the configuration in a separate place.
However, it’s better to simple use the configuration code that already comes with rails instead of implementing your own .yml configuration loader code.
Here is an example for setting a global default configuration value and overriding it only for the production enviroment, without using any non-Rails code:
config/application.rb
:
AppName::Application.configure do
config.google_analytics = ActiveSupport::OrderedOptions.new
config.google_analytics.account_id = 'UA-12345-23' # dev & staging acccount
end
config/environments/production.rb
:
AppName::Application.configure do
congfig.google_analytics.account_id = 'UA-12345-1' # production account
end
Infrastructure configuration
One other important aspect concerning app configuration, is that configuration concerning internal infrastructure parts of your architecture (= stuff & services that you control), should not live inside your apps. This type of configuration should be generated by your provisioning tools (=> CHEF) instead, so that they will be updated when something changes. Examples for such parts are database server addresses, interal API endpoints, etc.
This does not apply to external parts of the infrastructure, e.g. address of your Redis-to-go server, Sendgrid, S3, etc.
Verify time zone and locale settings
Many applications allow the user to change locale and time zone settings. This is often handled by providing a url parameter or by setting a cookie value. But one has to be aware, that the user might modify those settings and therefore might brake the app by providing malicious data. In order to check the data, we can validate the input before setting the value:
# Only allow supported time zones
def valid_time_zone_or_nil(zone)
zone = zone.to_s
ActiveSupport::TimeZone::MAPPING.values.include?(zone) ? zone : nil
end
# Only allow supported locales
def valid_locale_or_nil(locale)
I18n.available_locales.map(&:to_s).include?(locale.to_s) ? locale : nil
end
You can use this in you application_controller.rb
like this:
around_filter :set_timezone
before_filter :set_locale
def set_locale
I18n.locale = valid_locale_or_nil(params[:locale]) || I18n.default_locale
end
def set_time_zone(&block)
time_zone = valid_time_zone_or_nil(params[:time_zone]) || 'Europe/Berlin'
Time.use_zone(time_zone, &block)
end
Handle a model attribute with a set of allowed values (incl. dropdown & i18n)
Use case: We have a model (e.g. User
) with an attribute (e.g. gender
that can take one of the values from a certain finite set of values (e.g. 'female', 'male', 'neutral'
)
In the Model:
class User
GENDER_VALUES = %w(female male neutral)
validates :gender, inclusion: { in: GENDER_VALUES }
end
In i18n:
de:
activerecord:
attributes:
user:
gender: 'Geschlecht'
values: # this is not rails standard, but follows their pattern
user:
gender:
female: 'weiblich'
male: 'männlich'
neutral: 'unentschieden'
In the decorator:
class UserDecorator
def gender_options_for_select
options = User::GENDER_VALUES.map do |value|
[h.t(value, scope: "activerecord.values.user.gender"), value]
end
h.options_for_select options, self.gender
end
end
In the view
= form_for @user
= f.select :gender, @user.gender_options_for_select