Sail
This Rails engine brings a setting model into your app to be used as feature flags, gauges, knobs and other live controls you may need.
It saves configurations to the database so that they can be changed while the application is running, without requiring a deploy.
Having this ability enables live experiments and tuning to find an application's best setup.
Enable/Disable a new feature, turn ON/OFF ab testing for new functionality, change jobs' parameters to tune performance, you name it.
It comes with a lightweight responsive admin dashboard for searching and changing configurations on the fly.
Sail assigns to each setting a relevancy score. This metric is calculated while the application is running and is based on the relative number of times a setting is invoked and on the total number of settings. The goal is to have an indicator of how critical changing a setting's value can be based on its frequency of usage.
Contents
- Installation
- Configuration
- Populating the database
- Searching
- Manipulating settings
- Localization
- Contributing
Installation
Add this line to your application's Gemfile:
gem "sail"
And then execute:
$ bundle
Adding the following line to your routes file will make the dashboard available at <base_url>/sail
mount Sail::Engine => "/sail"
Running the install generator will create necessary migrations for having the settings in your database.
$ bin/rails g sail:install
When going through a major version upgrade, be sure to check the changelog and run the update generator. It will create whatever migrations are needed to move from any other major version to the latest.
$ bin/rails g sail:update
If you wish to customize the settings' card, the views can be copied to the main app by using the view generator.
$ bin/rails g sail:views
Configuration
Available configurations and their defaults are listed below
Sail.configure do |config|
config.cache_life_span = 6.hours
config.array_separator = ";"
config.dashboard_auth_lambda = nil
config.back_link_path = "root_path"
config.enable_search_auto_submit = true
config.days_until_stale = 60
config.enable_logging = true
config.failures_until_reset = 50
end
A possible authorization lambda is defined below.
Sail.configure do |config|
config.dashboard_auth_lambda = -> { redirect_to("/") unless session[:current_user].admin? }
end
Populating the database
In order to create settings, use the config/sail.yml file (or create your own data migrations).
If the sail.yml file was not created, it can be generated with the current state of the database using the following rake task.
$ rake sail:create_config_file
After settings have been created a first time, they will not be updated with the values in the yaml file (otherwise it would defeat the purpose of being able to configure the application without requiring a deploy).
Removing the entries from this file will cause settings to be deleted from the database.
Settings can be aggregated by using groups. Searching by a group name will return all settings for that group.
first_setting:
description: My very first setting
value: some_important_string
cast_type: string
group: setting_group_1
second_setting:
description: My second setting, this time a boolean
value: false
cast_type: boolean
group: feature_flags
To clear the database and reload the contents of your sail.yml file, invoke this rake task.
$ rake sail:load_defaults
Searching
Searching for settings in the dashboard can be done in the following ways:
- By name: matches a substring of the setting's name
- By group: matches all settings with the same group (exact match)
- By cast type: matches all settings with the same cast type (exact match)
- By stale: type 'stale' and get all settings that haven't been updated in X days (X is defined in the configuration)
- By recent: type 'recent X' where X is the number of hours and get all settings that have been updated since X hours ago
Manipulating settings in the code
Settings can be read or set via their interface. Notice that when reading a setting's value, it will be cast to the appropriate type using the "cast_type" field.
All possible cast types as well as detailed examples of usage can be found in the wiki.
Sail.get(:name)
Sail.get(:name) do |setting_value|
my_code(setting_value)
end
Sail.get(:name, expected_errors: [ExampleError]) do |value|
code_that_can_raise_example_error(value)
end
Sail.set(:name, "value")
Sail.reset(:name)
Sail.switcher(
positive: :setting_name_for_true,
negative: :setting_name_for_false,
throttle: :throttle_setting_name
)
Sail also comes with a JSON API for manipulating settings. It is simply an interface for the methods described above.
GET sail/settings/:name
Response
{
"value": true
}
PUT sail/settings/:name?value=something
Response
200 OK
GET sail/settings/switcher/:positive/:negative/:throttled_by
Response
{
"value": "Some value that depends on the setting combination passed"
}
Switching to a specific settings profile
PUT sail/profiles/:name/switch
Response
200 OK
GraphQL
For GraphQL APIs, types and mutations are defined for convenience. Include Sail's Graphql modules to get the appropriate fields.
module Types
class QueryType < Types::BaseObject
include Sail::Graphql::Types
end
end
module Types
class MutationType < Types::BaseObject
include Sail::Graphql::Mutations
end
end
To query settings via GraphQL, use the following pattern.
query {
sailGet(name: "my_setting")
sailSwitcher(
positive: "positive_case_setting"
negative: "negative_case_setting"
throttledBy: "throttle_setting"
)
}
mutation {
sailSet(name: "my_setting", value: "value") {
success
}
sailProfileSwitch(name: "my_profile") {
success
}
}
Localization
Sail's few strings are all localized for English in en.yml. Using the same YAML keys for other languages should work for localizing the dashboard.
Make sure to pass in the desired locale as a parameter.
Credits
The awesome icons used by Sail's admin panel are all made by Font Awesome.
Contributing
Contributions are very welcome! Don't hesitate to ask if you wish to contribute, but don't yet know how.
Please refer to this simple guideline.