A lightweight Rails engine for managing entity based features with built-in caching support. "Pin features" to any ActiveRecord model with ease, providing a slightly different take on traditional feature flags.
- 🚀 Polymorphic "feature tags" which can be tied to any ActiveRecord Model
- ⚡ Built-in caching with configurable expiry
- 🎨 An admin UI that stays out of your way with minified BulmaCSS and AlpineJS
- 🔒 Isolated namespace to avoid conflicts
- 💉 No dependency on Stimulus
- Rails 8.0 or higher with Turbo
- Ruby 3.0 or higher
- Rails app is using a relational database (e.g., PostgreSQL, MySQL, SQLite)
Add this line to your application's Gemfile:
gem "pin_flags"And then execute:
$ rails g pin_flags:installRun the migration to create the necessary database tables:
$ rails db:migrateConfigure PinFlags in an initializer (e.g., config/initializers/pin_flags.rb):
PinFlags.config do |config|
config.cache_prefix = "my_app_flags" # Default: "pin_flags"
config.cache_expiry = 1.hour # Default: 12.hours
endPinFlags includes built-in HTTP Basic Authentication to protect the admin interface. Configure it in your initializer:
PinFlags.config do |config|
config.cache_prefix = "budget_tracker_pin_flags"
config.cache_expiry = 1.hour
config.http_basic_auth_enabled = true # Default: true
config.http_basic_auth_user = "admin" # Default: "pin_flags_admin"
config.http_basic_auth_password = "password" # Default: "please_change_me"
endSecurity Note: Always change the default credentials in production environments.
PinFlags.config do |config|
config.http_basic_auth_user = ENV["PIN_FLAGS_USER"] || "pin_flags_admin"
config.http_basic_auth_password = ENV["PIN_FLAGS_PASSWORD"] || "please_change_me"
endPinFlags.config do |config|
config.http_basic_auth_user = Rails.application.credentials.pin_flags[:user] || "pin_flags_admin"
config.http_basic_auth_password = Rails.application.credentials.pin_flags[:password] || "please_change_me"
endTo disable authentication entirely:
PinFlags.config do |config|
config.http_basic_auth_enabled = false
endclass SomeModel < ApplicationRecord
include PinFlags::FeatureTaggable
endIncluding PinFlags::FeatureTaggable in your model adds the following capabilities:
- Polymorphic associations: Your model gets
feature_subscriptionsandfeature_tagsassociations - Feature flag checking: Methods to check if specific features are enabled/disabled for instances
- Pin/Unpin operations: Ability to add or remove feature tags from individual records
- Cached lookups: Feature flag status is cached for performance
# Include the module in your models
class User < ApplicationRecord
include PinFlags::FeatureTaggable
end
class Organization < ApplicationRecord
include PinFlags::FeatureTaggable
end
# Create some feature tags
PinFlags::FeatureTag.create!(name: "premium_features", enabled: true)
PinFlags::FeatureTag.create!(name: "beta_features", enabled: true)
PinFlags::FeatureTag.create!(name: "deprecated_ui", enabled: false)
# Working with individual records
user = User.find(1)
# Pin a feature to a user (creates the tag if it doesn't exist)
user.feature_tag_pin("premium_features")
# Check if user has a specific feature tag
user.feature_tag?("premium_features") # => true
user.feature_tag?("nonexistent_feature") # => false
# Check if a feature is enabled for the user
user.feature_tag_enabled?("premium_features") # => true
user.feature_tag_enabled?("deprecated_ui") # => false (tag exists but disabled)
# Check if a feature is disabled for the user
user.feature_tag_disabled?("deprecated_ui") # => true
# Remove a feature tag from a user
user.feature_tag_unpin("premium_features")
# Access all feature tags for a user
user.feature_tags # => ActiveRecord collection
user.feature_tags.enabled # => Only enabled tags
user.feature_tags.disabled # => Only disabled tags
# Bulk operations using the model directly
PinFlags::FeatureSubscription.create_in_bulk(
feature_tag: PinFlags::FeatureTag.find_by(name: "beta_features"),
feature_taggable_type: "User",
feature_taggable_ids: User.where(some_boolean_attribute: true).pluck(:id)
)
# Global feature flag management
PinFlags::FeatureTag.enable("new_dashboard") # Enable globally
PinFlags::FeatureTag.enabled?("new_dashboard") # Check if globally enabled
PinFlags::FeatureTag.disable("old_feature") # Disable globally
PinFlags::FeatureTag.disabled?("old_feature") # Check if globally disabled
# Check if a feature is enabled for a specific subscriber
PinFlags::FeatureTag.enabled_for_subscriber?("premium_features", user)Instance methods added to your model:
feature_tag_pin(tag_name)- Pin a feature tag to this recordfeature_tag_unpin(tag_name)- Remove a feature tag from this recordfeature_tag?(tag_name)- Check if this record has a feature tagfeature_tag_enabled?(tag_name)- Check if feature is enabled for this recordfeature_tag_disabled?(tag_name)- Check if feature is disabled for this recordfeature_tags- Get all feature tags for this recordfeature_subscriptions- Get all feature subscriptions for this record
Class methods for feature management:
PinFlags::FeatureTag.enable(tag_name)- Enable a feature globallyPinFlags::FeatureTag.disable(tag_name)- Disable a feature globallyPinFlags::FeatureTag.enabled_for_subscriber?(tag_name, record)- Check if enabled for specific record
Add PinFlags to your routes:
# config/routes.rb
Rails.application.routes.draw do
mount PinFlags::Engine, at: "/pin_flags"
# your other routes...
end| Option | Description | Default |
|---|---|---|
cache_prefix |
Prefix for cache keys | "pin_flags" |
cache_expiry |
Cache expiration time | 12.hours |
http_basic_auth_enabled |
Enable HTTP Basic Authentication | true |
http_basic_auth_user |
Username for admin interface | "pin_flags_admin" |
http_basic_auth_password |
Password for admin interface | "secure_password" |
Please prefer to test against an actual Rails application rather than using the built-in test/dummy app.
-
git clonethe PinFlags repository locally on your machine: -
Add the gem to your Rails application's Gemfile:
gem "pin_flags", path: "path/to/pin_flags"- Mount the engine in your application's routes:
# config/routes.rb
Rails.application.routes.draw do
mount PinFlags::Engine, at: "/pin_flags"
# your other routes...
end- Run the generator to install the engine:
$ rails g pin_flags:install- Run the migrations to create the necessary database tables:
$ rails db:migrate- Start your Rails server
- Navigate to the root of the PinFlags gem directory where you cloned the repository.
$ rails test- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -am 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please make sure to:
- Add tests for new features
- Update documentation as needed
- Follow the existing code style
- Write clear commit messages
This project uses Semantic Versioning. For available versions, see the tags on this repository.
The gem is available as open source under the terms of the MIT License.
If you encounter any issues or have questions:
- Check the Issues page
- Create a new issue if your problem isn't already reported
- Provide as much detail as possible including:
- Rails version
- Ruby version
- Steps to reproduce
- Expected vs actual behavior

