Usually application data is stored in the DB. We use controllers and models to read and write it. But sometimes that data is static (system settings, list of countries, etc) so it does not make sense to put it in DB. Plus storing data in file(s) guarantees that when we deploy the application, the data will be there. Otherwise we have enter it manually via UI or load via SQL script.

System settings

There are several gems our there such as rails-settings and config. Usually I am big fan of using robust libraries instead of implementing complex functionality myself. But when the need is simple it can be better to create few config files / POROs to do exactly what we need.

Let’s imagine a CMS where users belong to various roles (admin, editor, author). We could create Role model/table and have UserRole mapping. Or we can store config values in application.rb and have User.roles array.

# config/application.rb
class Application < Rails::Application
  ...
  config.roles = [:admin, :editor, :author]
end
# app/models/user.rb
class User
  ...
  extend Enumerize
  field :roles, type: Array
  enumerize :roles, in: Rails.application.config.roles, multiple: true
end

But with time application.rb gets bigger and harder to manage. Why not create a custom initializer?

# config/initializers/system_settings.rb
CMS_ROLES = [:admin, :editor, :author]
# app/models/user.rb
class User
  enumerize :roles, in: CMS_ROLES
end

CMS_ROLES is a constant so we want to give it descriptive name or namespace it.

Static data

What if the amount of data is much larger than a few strings? We might need to store the list of US states, zipcodes, countries, etc. Why not create data folder in application root? We can put CSV, TXT, JSON, XML or YML files in appropriate subfolder structure.

# data/us_states.txt
Alabama
Alaska
...
# config/initializers/system_settings.rb
STATES_PROVINCES = File.readlines('data/us_states.txt').map {|line| line.strip}
# app/models/user.rb
class User
  extend Enumerize
  field :region
  enumerize :region, in: STATES_PROVINCES
end

Sharing data across multiple applications

Config values might need to be shared by several applications running on separate servers. But we do not want to store the same data files in multiple applications because they can get out of sync. We want a canonical source that gets refreshed everytime we deploy the main app. We could use Redis as a shared cache storage.

# config/initializers/redis.rb
redis = Redis.new(host: 'localhost', port: 6379, db: 0)
REDIS_SETTINGS = Redis::Namespace.new(:sys_set, redis: redis)
# config/initializers/system_settings.rb
sp_data = File.readlines('data/us_states.txt').map {|line| line.strip}
# remove current cache
REDIS_SETTINGS.del('states_provinces')
# cache new data in Redis SET with descriptive key
REDIS_SETTINGS.sadd('states_provinces', sp_data)
# read it into constant so data is retained if Redis connection is lost
STATES_PROVINCES = REDIS_SETTINGS.smembers('states_provinces')

Now all we have do do in the second application is connect to Redis and read data.

# config/initializers/redis.rb
redis = Redis.new(host: 'localhost', port: 6379, db: 0)
REDIS_SETTINGS = Redis::Namespace.new(:sys_set, redis: redis)
# config/initializers/system_settings.rb
STATES_PROVINCES = REDIS_SETTINGS.smembers('states_provinces')

One downside with this approach is that we need to restart the application(s) to force reload of STATES_PROVINCES from Redis. We might want a hybrid option where we can edit data in Redis for live update and separately store canonical data in config file that gets deployed. We can use REDIS_SETTINGS.smembers('states_provinces') in our code as is but that’s a little verbose.

# app/services/red_set.rb
class RedSet
  def self.states_provinces
    REDIS_SETTINGS.smembers('states_provinces')
  end
end

Now we just call RedSet.states_provinces from our application(s) and it will fetch data from Redis.

Depending on the type of data that needs to be cached in Redis we might use different data types. To store array of US states we used Redis SET with SADD and SMEMBERS commands. Other data might be better stored in Redis hashes, lists or strings.

If this approach does not work, there is redis-settings gem.