Introduction
In modern applications, feature flags (or toggles) play a critical role in safely releasing new features, performing A/B testing, and managing rollouts — all without redeploying code. In this post, we’ll deep-dive into how we integrated Flipt, an open-source feature flag system, into our Ruby on Rails application with a production-grade wrapper that handles:
- Smart caching to reduce unnecessary API calls
- Graceful fallbacks during outages
- Downtime resilience
- Environment-based configuration
- Support for boolean flags (our current need), with a foundation for future expansion like percentage rollouts and thresholds. Let’s explore how we built it.
Why Feature Flags?
Feature flags let developers control feature rollout dynamically:
- Enable/disable features instantly from a dashboard
- Test in production safely with specific tenants or users
- Rollback instantly if an issue arises
- Progressively roll out to a small percentage of users This eliminates the need for redeploys and gives product and engineering teams fine-grained control over experimentation.
Why Flipt?
We chose Flipt because:
- It’s open-source and self-hostable
- It provides namespaces, boolean flags, and gradual rollouts
- It supports API-based evaluation, making it easy to integrate with any backend
- It’s designed for performance and scalability
Our Goal
We wanted a clean Ruby abstraction that:
- Could be dropped into any Rails app
- Works out-of-the-box with Flipt’s API
- Handles caching and fallback logic automatically
- Protects against Flipt downtime
- Keeps API tokens and host configuration securely managed via environment variables
The Architecture Overview
Here’s what our system looks like conceptually:
┌─────────────────────────────────────┐
│ Rails App │
│ │
│ Flipt::FeatureFlag (our wrapper) │
│ ├── Caching (Rails.cache) │
│ ├── Fallback Cache │
│ ├── Flipt API Client │
│ └── Figaro Env Config │
└─────────────────────────────────────┘
│
▼
┌────────────────┐
│ Flipt Service │
│ (3rd Party API)│
└────────────────┘Implementation: Flipt::FeatureFlag
Let’s walk through the full implementation of our Feature Flag Wrapper.
# frozen_string_literal: true
require 'singleton'
module Flipt
# Simple, production-ready wrapper for Flipt API using Rails cache.
# Handles high traffic, concurrent requests, fallbacks, and downtime gracefully.
class FeatureFlag
include Singleton
CACHE_TTL = 1.minutes.to_i # Short-term cache
FALLBACK_CACHE_TTL = 1.weeks.to_i # Long-term fallback cache
# Public method: check if a flag is enabled for a given entity
def self.enabled?(flag_key, entity)
instance.enabled?(flag_key, entity)
end
def enabled?(flag_key, entity)
return false if entity.nil?
flag_key = flag_key.to_s
entity_id = entity&.tenant_identification_code.to_s
cache_key = "flipt:flags:#{flag_key}:#{entity_id}"
fallback_key = "#{cache_key}:fallback"
# Try fetching from cache or Flipt
Rails.cache.fetch(cache_key, expires_in: CACHE_TTL) do
fetch_from_flipt(flag_key, entity_id, fallback_key)
end
rescue StandardError
# Gracefully fallback on any failure
get_fallback_value(fallback_key)
end
private
def fetch_from_flipt(flag_key, entity_id, fallback_key)
result = flipt_client.evaluate_boolean(flag_key: flag_key, entity_id: entity_id)
value = result.respond_to?(:enabled) && result.enabled
# Store a long-term fallback
Rails.cache.write(fallback_key, value, expires_in: FALLBACK_CACHE_TTL)
value
end
def get_fallback_value(fallback_key)
Rails.cache.read(fallback_key) || false
end
def flipt_client
@flipt_client ||= ::Flipt::Client.new(
namespace: Figaro.env.ffp_namespace,
url: Figaro.env.ffp_host,
authentication: ::Flipt::ClientTokenAuthentication.new(Figaro.env.ffp_token)
)
end
end
endNote: This wrapper follows a service-based pattern, meaning it’s reusable and modular. You can easily adapt or extend it across different projects — for example:
- Adjust cache duration as per system load
- Extend to support gradual rollouts or percentage-based targeting Think of this class as a reference implementation clean, generic, and production-ready for any Rails project.
Caching Strategy Explained
We introduced a two-layer caching system to minimize Flipt API calls and handle downtimes gracefully. Cache Type Expiry Purpose Primary Cache (CACHE_TTL) 1 minute Keeps responses fresh and avoids frequent Flipt calls Fallback Cache (FALLBACK_CACHE_TTL) 1 week Stores the last known good value in case Flipt is down How it works:
- Every time a flag is checked, Rails first looks in its short-term cache.
- If the cache is empty or expired, it calls Flipt API.
- On success, it stores the new result in both caches.
- If any error occurs (Flipt downtime, timeout, etc.), it returns the fallback cache value — ensuring no user-facing failure.
Caching Workflow Diagram
┌────────────────────────┐
│ Flipt::FeatureFlag │
│ (our wrapper service) │
└────────────┬───────────┘
│
▼
┌────────────────────────┐
│ Check Rails Cache │
│ (short-term TTL: 1min) │
└────────────┬───────────┘
Cache Hit? │ No
▼
┌────────────────────────┐
│ Call Flipt API │
│ (evaluate_boolean) │
└────────────┬───────────┘
│
▼
┌────────────────────────┐
│ Store in Rails Cache │
│ + Fallback Cache (1wk) │
└────────────┬───────────┘
│
▼
┌────────────────────────┐
│ Return flag value │
└────────────────────────┘
Fallback Strategy & Downtime Handling
We designed the wrapper to never fail open — meaning if Flipt is unreachable, the system:
- Returns the last known flag state (if available)
- Avoids breaking main app functionality This pattern ensures:
- Zero downtime due to external dependencies
- Predictable behavior in production
Secure Configuration with Figaro and ENV Variables We keep all Flipt credentials in environment variables for security and environment flexibility. config/application.yml:
ffp_namespace: "<%= ENV['FFP_NAMESPACE'] %>"
ffp_host: "<%= ENV['FFP_HOST'] %>"
ffp_token: "<%= ENV['FFP_TOKEN'] %>"Example environment setup:
This way, local, staging, and production environments can use separate namespaces or tokens safely.
Current Scope and Future Enhancements
Currently, our wrapper supports boolean flags — perfect for use cases like enabling/disabling new UI components or features per tenant. However, Flipt also supports:
- Thresholds
- Gradual rollouts (percentage-based)
- Segment-based targeting
- Add error logging to Rollbar for better failure monitoring. We plan to expand the wrapper to support these advanced scenarios as our use cases grow.
Example Usage
if Flipt::FeatureFlag.enabled?(:new_dashboard, current_office)
render 'dashboard/new_version'
else
render 'dashboard/old_version'
endThis simple interface allows any developer to use feature flags instantly without worrying about Flipt’s API or caching logic.
Benefits of Our Approach
Benefit Description Performance Reduced network calls with smart caching Resilience Survives Flipt downtime with graceful fallback Security All credentials safely managed via ENV Simplicity Clean, single-line flag checks Extensibility Designed for future rollout and targeting options Observability and Debugging Developers can verify flag behavior via:
Rails.cache.read('flipt:flags:<flag_key>:<entity_id>') And monitor logs or metrics for any fallback occurrences.
Feature Flag Removal Guidelines
Remove feature flags within 2 weeks after a feature is fully rolled out and verified stable. Ensure both flag logic and related cleanup (code paths, configs, tests) are deleted. Communicate removal in PR description for transparency. Avoid keeping long-lived flags unless they’re permanent toggles (like for permissions).
Pros:
Let us release features safely and gradually. Easy to turn a feature ON or OFF without redeploying. Help test features in production for selected users. Useful for quick rollback if something breaks.
Cons:
Can make code messy if old flags aren’t removed. Adds extra conditions that make debugging harder. Need to test both ON and OFF states properly.
Conclusion
Feature flags give us fine-grained control over our release process — but doing them right requires careful handling of performance and resilience. By wrapping Flipt in a robust, cache-aware Ruby service, we achieved:
- Fast evaluations
- Safe degradation
- Simple API for developers This wrapper is now our standard foundation for all new feature flag integrations.
Feedback and Suggestions
We’re continuously improving this implementation. If you have suggestions, ideas, or questions about optimizing feature flagging in Rails, we’d love to hear them!
