Togglefy is a simple feature management solution to help you control which features an user or a group has access to.
Togglefy is free, open source and you are welcome to help build it.
Add the gem manually to your Gemfile:
gem 'togglefy', '~> 1.0', '>= 1.1.1'
Or install it and add to the application's Gemfile by executing:
bundle add togglefy
If bundler is not being used to manage dependencies, install the gem by executing:
gem install togglefy
After adding the gem to your project, you need to run the generate command to add the necessary files:
rails generate togglefy:install
This command will create the migrations to create the tables inside your project.
If you use an older version of Rails (< 5), then the migration files don't need you to specify the version.
To fix this, you will have to manually go to the two migration files of Togglefy: create_feature
and create_feature_assignments
and do the following:
Change these lines from this:
rails_version = "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}"
class CreateTogglefyFeatures < ActiveRecord::Migration[rails_version]
rails_version = "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}"
class CreateTogglefyFeatureAssignments < ActiveRecord::Migration[rails_version]
To this:
class CreateTogglefyFeatures < ActiveRecord::Migration
class CreateTogglefyFeatureAssignments < ActiveRecord::Migration
Please, don't remove/change anything else that's there or Togglefy may not work as expected.
Run the migration to create these in your datase:
rails db:migrate
Or if you're using a legacy codebase:
rake db:migrate
The models are stored inside Togglefy, so you don't need to create them inside your project unless you want to.
If that's something you want, you can check them following this path: app/models/togglefy/
.
After that, the next steps are also pretty simple.
Add the following to your model that will have a relation with the features. It can be an User
, Account
or something you decide:
include Togglefy::Assignable
This will add the relationship between Togglefy's models and yours. Yours will be referred to as assignable throughout this documentation. If you want to check it in the source code, you can find it here: lib/togglefy/assignable.rb
inside de included
block.
Older versions (<= 1.0.2
) had the Featureable
instead of the Assignable
. The Featureable
is now deprecated. You can still use it and it won't impact old users, but we highly recommend you to use the Assignable
as it is semantically more accurate about what it does.
With that, everything is ready to use Togglefy, welcome!
To create features it's as simple as drinking a nice cold beer after a hard day or drinking the entire bottle of coffee in a span of 1 hour:
Togglefy::Feature.create(
name: "Magic",
description: "You're a Wizard, Harry"
) # To create a simple Feature
If you have tenant, groups (or roles), difference between environments, you can do the following:
Togglefy::Feature.create(
name: "Super Powers",
description: "With great power comes great responsibility",
tenant_id: "123abc",
group: :admin,
environment: :production
)
You can also use:
Togglefy.create(
name: "Teleportation",
description: "Allows the assignable to teleport to anywhere",
group: :jumper
)
# Or
Togglefy.create_feature(
name: "Teleportation",
description: "Allows the assignable to teleport to anywhere",
group: :jumper
)
You don't have to fill all fields, the only one that is mandatory is the name, because is by using the name that we will create the unique identifier, which is the field we'll use to find, delete and more.
The identifier is the name, downcased and snake_cased 🐍
Whenever you create a Togglefy::Feature
, you can expect something like this:
id: 1,
name: "Super Powers",
identifier: "super_powers",
description: "With great power comes great responsibility",
created_at: "2025-04-12 01:39:10.176561000 +0000",
updated_at: "2025-04-12 01:39:46.818928000 +0000",
tenant_id: "123abc",
group: "admin",
environment: "production",
status: "inactive"
To update a feature is as simple as riding on a monocycle while balancing a cup of water on the top of a really tall person that's on your shoulders.
Here's how you can do it:
Togglefy.update(:super_powers, tenant_id: "abc123")
Togglefy.update_feature(:super_powers, tenant_id: "abc123")
Or by finding the feature manually and then updating it like you always do with Rails:
feature = Togglefy::Feature.find_by(identifier: :super_powers)
# or
feature = Togglefy.feature(:super_powers) # This is explained more in the "Finding a specific feature" section of this README
feature.update(tenant_id: "123abc")
It looks like you're mean 😈
So here's how you can destroy a feature:
Togglefy.destroy(:super_powers)
Togglefy.destroy_feature(:super_powers)
Togglefy::Feature.identifier(:super_powers).destroy
Togglefy::Feature.find_by(identifier: :super_powers).destroy
You can toggle a feature status to active or inactive by doing this:
Togglefy.toggle(:super_powers)
Togglefy.toggle_feature(:super_powers)
As you can see, the Togglefy::Feature
also has a status and it is default to inactive
. You can change this during creation.
The status holds the inactive
or active
values. This status is not to define if a assignable (any model that has the include Togglefy::Assignable
) either has ou hasn't a feature, but to decide if this feature is available in the entire system.
It's up to you to define how you will implement it.
- Is it disabled? Then this feature is likely unrelease
- Or maybe if it is disabled, you can see the flag to active to an assignable but can't change the values?
Again, it's up to you!
You can change the status by:
- Sending a value during creation
- Updating the column
- Doing a:
Togglefy::Feature.find_by(identifier: :super_powers).active! Togglefy.feature(:super_powers).active! Togglefy.active!(:super_powers) Togglefy.activate_feature(:super_powers) Togglefy::Feature.find_by(identifier: :super_powers).inactive! Togglefy.feature(:super_powers).inactive! Togglefy.inactive!(:super_powers) Togglefy.inactivate_feature(:super_powers)
Now that we know how to create features, let's check how we can manage them.
An assignable has some direct methods thanks to the include Togglefy::Assignable
, which are (and let's use an user as an example of an assignable):
user.has_feature?(:super_powers) # Checks if user has a single feature
user.add_feature(:super_powers) # Adds a feature to user
user.remove_feature(:super_powers) # Removes a feature from an user
user.clear_features # Clears all features from an user
The assignable <-> feature relation is held by the Togglefy::FeatureAssignment
table/model.
But there's another way to manage assignables <-> features by using the FeatureAssignableManager
. It's up to you to decide which one.
Here are the examples:
Togglefy.for(assignable).has?(:super_powers) # Checks if assignable (user || account || anything) has a single feature
Togglefy.for(assignable).enable(:super_powers) # Enables/adds a feature to an assignable
Togglefy.for(assignable).disable(:super_powers) # Disables/removes a feature from an assignable
Togglefy.for(assignable).clear # Clears all features from an assignable
You can also supercharge it by chaining methods, like:
# Instead of doing this:
Togglefy.for(assignable).disable(:alpha_access)
Togglefy.for(assignable).enable(:beta_access)
# You can go something like this:
Togglefy.for(assignable).disable(:alpha_access).enable(:beta_access)
This second method may look strange, but it's the default used by the gem and you will see that right now!
You can mass enable/disable features to/from Assignables.
Doing that is simple! Let's assume that your assignable is an User model.
Togglefy.mass_for(User).bulk.enable(:super_powers) # To enable a specific feature to all users
Togglefy.mass_for(User).bulk.enable([:super_powers, :magic, ...]) # To enable two or more features to all users
Togglefy.mass_for(User).bulk.enable(:super_powers, percentage: 20) # To enable a feature to 20% of all users
Togglefy.mass_for(User).bulk.enable([:super_powers, :magic, ...], percentage: 50) # To enable two or more features to 50% of all users
The same applies to the disable:
Togglefy.mass_for(User).bulk.disable(:super_powers) # To disable a specific feature to all users
Togglefy.mass_for(User).bulk.disable([:super_powers, :magic, ...]) # To disable two or more features to all users
Togglefy.mass_for(User).bulk.disable(:super_powers, percentage: 5) # To disable a feature to 5% of all users
Togglefy.mass_for(User).bulk.disable([:super_powers, :magic, ...], percentage: 75) # To disable two or more features to 75% of all users
There are a few things to pay attention:
- Whenever you do a enable/disable, it will only query for valid assignables, so:
- If you do a enable, it will query all assignables that don't have the feature(s) enabled
- If you do a disable, it will query all assignables that do already have the feature(s) enabled
- You can also use filters for:
group || role
environment || env
tenant_id
- You can check about filters aliases at Aliases table
These will be applied to query features that match the identifiers + the filters sent.
So it would be something like:
Togglefy.mass_for(User).bulk.enable(:super_powers, group: :admin, percentage: 20)
Togglefy.mass_for(User).bulk.disable(:magic, group: :dev, env: :production, percentage: 75)
Remember when I told you a looooong time ago that the strange way is the default using by the gem? If you don't, no worries. It was a really loooong time ago, like 1.minute.ago
.
It's actually pretty simple. Each line of each code block will show you a way to query to achieve the same result in the context. You don't need to use all of options listed in each code block.
To query Togglefy::Feature
from a specific group (the same applies to environment and tenant), you would do something like this:
Togglefy::Feature.where(group: :dev)
Togglefy::Feature.for_group(:dev)
Togglefy.for_group(:dev)
Togglefy.for_role(:dev)
Togglefy.for_filters(filters: {group: :dev})
Togglefy.for_filters(filter: {role: :dev})
The for_filters
is recommended when you want to use more than one filter, like:
Togglefy.for_filters(filters: {group: :admin, environment: :production})
This will query me all Togglefy::Feature
s that belongs to group admin and the production environment.
The Togglefy.for_filters
can have the following filters:
filters: {
group:,
role:, # Acts as a group, explained in the Aliases section
environment:,
env:, # Acts as a group, explained in the Aliases section
tenant_id:,
status:,
identifier:
}
The Togglefy.for_filters
will only apply the filters sent that are nil
or !value.blank?
.
You can send nil
values too, like:
Togglefy.for_tenant(nil) # This will query me all Togglefy::Features with tenant_id nil
Togglefy.for_filters(filters: {group: :admin, environment: nil, tenant_id: nil})
There's also another way to filter for nil
values:
Togglefy.without_group
Togglefy.without_role
Togglefy.without_environment
Togglefy.without_env
Togglefy.without_tenant
To query Togglefy::Features by status (the same applies to inactive).
Togglefy::Feature.where(status: :active)
Togglefy::Feature.active
Togglefy.with_status(:active)
Togglefy.feature(:super_powers)
Togglefy::Feature.identifier(:super_powers)
Togglefy::Feature.find_by(identifier: :super_powers)
Togglefy.feature([:super_powers, :magic])
Togglefy::Feature.identifier([:super_powers, :magic])
Togglefy::Feature.where(identifier: [:super_powers, :magic])
Let's pretend that your assignable is an User.
user = User.find(1)
user.features
Again, let's pretend that your assignable is an User. This is the only case you need to send the feature id and not the identifier.
User.with_features(1)
User.with_features([2, 3])
User.without_features(1)
User.without_features([2, 3])
Togglefy.features(:super_powers)
Togglefy::Feature.all
Let's assume that you have two different assignables: User and Account.
You want to list all features being used by assignables of User type:
Togglefy.for_type(User) # This returns all current FeatureAssignment with a User assignable
By the way, did you notice that I wrote group
and role
to get group?
There are aliases for both group and environment that can be used outside of Togglefy::Feature
. If you want to query Togglefy::Feature
directly, use only the default name.
group
can be written asrole
outside ofTogglefy::Feature
environment
can be written asenv
out side ofTogglefy::Feature
Togglefy.for_group(:dev)
Togglefy.for_role(:dev)
Togglefy.for_filters(filters: {group: :dev})
Togglefy.for_filters(filter: {role: :dev})
Togglefy.for_environment(:production)
Togglefy.for_env(:production)
Togglefy.for_filters(filters: {environment: :production})
Togglefy.for_filters(filter: {env: :production})
Here's a table of all aliases available on Togglefy.
You can use either, as long as you respect the rules listed.
Original | Alias | Rules |
---|---|---|
for_group |
for_role |
Can't be used if doing a direct Togglefy::Feature |
without_group |
without_role |
Can't be used if doing a direct Togglefy::Feature |
for_environment |
for_env |
Can't be used if doing a direct Togglefy::Feature |
without_environment |
without_env |
Can't be used if doing a direct Togglefy::Feature |
group |
role |
Used inside methods that receives filters |
environment |
env |
Used inside methods that receives filters |
create |
create_feature |
None |
update |
update_feature |
None |
toggle |
toggle_feature |
None |
active! |
activate_feature |
None |
deactive! |
inactivate_feature |
None |
destroy |
destroy_feature |
None |
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
.
Bug reports and pull requests are welcome on GitHub at https://github.com/azeveco/togglefy. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
There's a PR Template. Its use is highly encouraged.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the togglefy project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.