Summoner Engine
An advanced, flexible, and powerful Feature Toggle, Dynamic Configuration, and Attribute-Based Access Control (ABAC) engine for Ruby on Rails.
Summoner Engine goes way beyond simple booleans. It allows you to manage global or Entity-specific configurations (e.g., User, Account), perform gradual rollouts based on attributes (like role or plan), attach editable descriptions to each feature, and create surgical exceptions (overrides) directly through an elegant Web UI. All wrapped in a lightning-fast Caching layer to protect your database.
Table of Contents
Key Features
- Dynamic Entity Injection: Features become native methods on your models (e.g.,
user.new_dashboard?). - Attribute-Based Access Control (ABAC): Automatically evaluate feature access by matching permission arrays against entity attributes (e.g., checking if a user's role is in the allowed list).
- Instant Global Rollouts: Use the wildcard
["*"]to release a feature to everyone in seconds, without touching the codebase. - Granular Overrides: The default rule might be X, but for User ID 42, the rule is Y. Summoner manages these exceptions flawlessly.
- Strong Typing: Native support for complex data types (
boolean,integer,float,json,string). - YAML Synchronization: Keep a
features.ymlfile as your single source of truth and sync it to the database on every deploy. YAML changes update the tracked defaults and descriptions, while manual dashboard edits remain in place until the YAML changes again. - Built-in Web Dashboard: A mountable Rails Engine for you and your team to manage everything visually.
Installation
Add this line to your application's Gemfile:
gem "summoner-engine"
The gem name is summoner-engine, but the internal Ruby namespace remains Summoner, so existing code and configuration can keep using Summoner::Entity, Summoner::Sync, and Summoner.configure.
Install the gem and run the installation generator:
bundle install
rails generate summoner:install
The generator will create the database migrations, an initializer, and your config/features.yml template. Then, build the tables:
rails db:migrate
Configuration
You can customize the engine's behavior, especially the built-in Cache, in config/initializers/summoner.rb:
Summoner.configure do |config|
# Enables or disables the use of Rails.cache (Default: true)
config.cache_enabled = true
# A safe namespace to prevent key collisions in Redis/Memcached
config.cache_namespace = 'summoner'
# Defines expiration time for the cache (Default: 1.hour)
config.cache_expires_in = 1.hour
end
The Web Dashboard
To manage your features and overrides visually, mount the UI inside your config/routes.rb:
Rails.application.routes.draw do
# ... your application routes ...
mount Summoner::Engine => "/summoner"
end
Now visit http://localhost:3000/summoner to see the magic happen!
Usage
Summoner's true power lies in its organization by Namespaces (Entities) and Data Types.
1. Defining your Source of Truth (features.yml)
Declare your application's base behavior in your config/features.yml. The structure uses the Entity name as the root key. Each feature can define a default value and an optional description:
# =========================================================================
# Whenever you update this file, run in your terminal: rails summoner:sync
# =========================================================================
user:
# Example 1: Simple Boolean injected into the model
"new_dashboard?":
default: false
description: Enables the new dashboard experience for users.
# Example 2: ABAC (Attribute-Based Access Control)
# Checks if the User's `role` method matches the values in the Array
"can_access_admin?":
default: ["admin", "manager"]
match_attribute: "role"
description: Allow admins and managers to access the admin area.
# Example 3: Global Release with Wildcard ("*")
# Everyone gets access, ignoring the `role` attribute entirely!
"can_access_beta?":
default: ["*"]
match_attribute: "role"
# Example 4: Dynamic Values (Integer, String, JSON)
max_items_per_page:
default: 20
description: Maximum number of items shown per page.
system:
# Features that do not belong to a specific entity
maintenance_mode:
default: false
description: Toggle the maintenance page for all users.
app:
# Global application configurations
api_timeout:
default: 30
description: Request timeout in seconds for external API calls.
Sync these definitions with your database by running:
rails summoner:sync
If you edit a feature through the dashboard, the manual change stays active until the same feature changes in features.yml again. At that point, Summoner updates the tracked YAML value and YAML description back to match the file.
2. Setting up your Entities
To allow Summoner to inject these configurations directly into your classes (like your User model), simply include the core module:
class User < ApplicationRecord
include Summoner::Entity
end
3. Checking Features in your Code
Summoner exposes an elegant, fluent API with automatic caching out of the box.
A. Checking Boolean Toggles and ABAC:
Keys ending with a ? in your YAML are converted into direct query methods on your entity:
user = User.first
# Respects the YAML default or a database Override
if user.new_dashboard?
render "dashboards/v2"
else
render "dashboards/v1"
end
# Automatically evaluates the ABAC array against user.role!
user.can_access_admin? # => true (if the role is "admin")
B. Getting Complex Typed Values: To retrieve integers, strings, or JSON, just call the exact key name:
# Returns 20 (or the specific overridden value for this user)
limit = user.max_items_per_page
Product.limit(limit)
C. Global Features (Without Entities):
Not every feature is tied to a user or an account. You can create arbitrary namespaces in your features.yml (like system:, app:, or global:) to group application-wide settings.
Since these don't belong to an Active Record model, you can query them directly through the Summoner module using the "namespace.feature_key" format:
# Checking a global boolean toggle
if Summoner.active?("system.maintenance_mode")
redirect_to maintenance_path
end
# Getting a global typed value
api_timeout = Summoner.get("app.api_timeout")
Targeted Overrides and Rollouts
Your YAML defines the "General Rule," but real-world apps require exceptions. Through the Web Dashboard, you can create Overrides for specific instances.
Common Use Cases:
- Beta Testing: The
user.new_dashboard?feature isfalsefor everyone, but you create an Override set totruestrictly for User ID:1and User ID:42. - Tenant Customization: The
max_items_per_pagelimit is20, but you sold a VIP plan to User ID:99and created an Override with the value100just for them. - ABAC Bypass:
can_access_admin?only allows["admin"], but you want to grant temporary access to a specific regular user.
Summoner's Evaluation Engine always respects the following priority hierarchy:
Entity-specific Override > ABAC / Wildcard Rule > Global Default Value.
Contributing
Bug reports and pull requests are warmly welcome on GitHub at https://github.com/feliperodrigs1/summoner.
License
The gem is available as open source under the terms of the MIT License.