Actions are custom methods that define how your features interact with your feature flag system. They receive feature names and options, and return results based on your system's API.
Use the action method in your base class:
class ApplicationFeature < Featury::Base
action :enabled?, web: :enabled? do |features:, **options|
features.all? { |feature| Flipper.enabled?(feature, *options.values) }
end
action :disabled?, web: :regular do |features:, **options|
features.any? { |feature| !Flipper.enabled?(feature, *options.values) }
end
action :enable, web: :enable do |features:, **options|
features.all? { |feature| Flipper.enable(feature, *options.values) }
end
action :disable, web: :disable do |features:, **options|
features.all? { |feature| Flipper.disable(feature, *options.values) }
end
action :add, web: :regular do |features:, **options|
features.all? { |feature| Flipper.add(feature, *options.values) }
end
endEach action block receives two parameters:
An array of feature flag names (symbols) that the action should operate on:
action :enabled? do |features:, **options|
puts features.inspect
# => [:user_onboarding_passage, :billing_api, :billing_webhooks]
endWhen a feature class has:
- Direct features defined with
feature - Nested groups defined with
group
The features: array contains all feature names from the current class and all nested groups.
A hash of resources passed as options when the action is called:
class User::OnboardingFeature < ApplicationFeature
resource :user, type: User, option: true
resource :account, type: Account, option: true, required: false
feature :passage
end
action :enabled? do |features:, **options|
puts options.inspect
# => { user: #<User:0x00007f9b1c8b3e00>, account: #<Account:0x00007f9b1c8b3f00> }
puts options.values.inspect
# => [#<User:0x00007f9b1c8b3e00>, #<Account:0x00007f9b1c8b3f00>]
end
User::OnboardingFeature.enabled?(user: user, account: account)Only resources marked with option: true are included in **options. See Resources for details.
The web: parameter specifies how the action should be represented in web contexts:
action :enabled?, web: :enabled? do |features:, **options|
# Implementation
endweb: :enabled? — For checking feature status (read-only)
web: :enable — For enabling features (write)
web: :disable — For disabling features (write)
web: :regular — For actions that don't fit the above categories
Web mappings are accessible via the .info.actions.web API:
class ApplicationFeature < Featury::Base
action :enabled?, web: :enabled? do |features:, **options|
features.all? { |feature| Flipper.enabled?(feature, *options.values) }
end
action :disabled?, web: :regular do |features:, **options|
features.any? { |feature| !Flipper.enabled?(feature, *options.values) }
end
action :enable, web: :enable do |features:, **options|
features.all? { |feature| Flipper.enable(feature, *options.values) }
end
action :disable, web: :disable do |features:, **options|
features.all? { |feature| Flipper.disable(feature, *options.values) }
end
action :add, web: :regular do |features:, **options|
features.all? { |feature| Flipper.add(feature, *options.values) }
end
end
# Access web mappings
info = ApplicationFeature.info
info.actions.web.enabled # => :enabled?
info.actions.web.enable # => :enable
info.actions.web.disable # => :disable
info.actions.web.all # => [:enabled?, :disabled?, :enable, :disable, :add]Web mappings allow you to:
- Build admin UIs that know which actions are read vs. write
- Generate API endpoints based on action types
- Create permission systems based on action categories
See Info and Introspection for complete details.
Execute code before all actions:
class ApplicationFeature < Featury::Base
before do |action:, features:|
Slack::API::Notify.call!(action: action, features: features)
end
action :enable, web: :enable do |features:, **options|
features.all? { |feature| Flipper.enable(feature, *options.values) }
end
endThe before callback receives:
action:— Symbol name of the action being called (e.g.,:enable)features:— Array of feature flag names
Execute code after specific actions:
class ApplicationFeature < Featury::Base
after :enabled?, :disabled? do |action:, features:|
Slack::API::Notify.call!(action: action, features: features)
end
action :enabled?, web: :enabled? do |features:, **options|
features.all? { |feature| Flipper.enabled?(feature, *options.values) }
end
action :disabled?, web: :regular do |features:, **options|
features.any? { |feature| !Flipper.enabled?(feature, *options.values) }
end
endThe after callback receives the same parameters as before.
before — Runs before all actions (no scope specification)
after :action1, :action2 — Runs after specific actions only
class ApplicationFeature < Featury::Base
# Runs before ALL actions
before do |action:, features:|
Logger.info("Starting action #{action} for #{features}")
end
# Runs after ONLY :enabled? and :disabled?
after :enabled?, :disabled? do |action:, features:|
Logger.info("Completed check action #{action}")
end
# Runs after ONLY :enable, :disable, :add
after :enable, :disable, :add do |action:, features:|
Logger.info("Completed mutating action #{action}")
Cache.clear_for(features)
end
endaction :enabled?, web: :enabled? do |features:, **options|
features.all? { |feature| Flipper.enabled?(feature, *options.values) }
end
# Returns true only if ALL features are enabledaction :any_enabled?, web: :enabled? do |features:, **options|
features.any? { |feature| Flipper.enabled?(feature, *options.values) }
end
# Returns true if ANY feature is enabledaction :disabled?, web: :regular do |features:, **options|
features.any? { |feature| !Flipper.enabled?(feature, *options.values) }
end
# Returns true if ANY feature is disabledaction :status, web: :regular do |features:, **options|
features.map do |feature|
{ feature: feature, enabled: Flipper.enabled?(feature, *options.values) }
end
end
# Returns array of hashes with status for each featureaction :percentage_enabled, web: :regular do |features:, **options|
enabled_count = features.count { |feature| Flipper.enabled?(feature, *options.values) }
(enabled_count.to_f / features.size * 100).round(2)
end
# Returns percentage of features enabledActions defined in the base class are available to all feature classes:
class ApplicationFeature < Featury::Base
action :enabled?, web: :enabled? do |features:, **options|
features.all? { |feature| Flipper.enabled?(feature, *options.values) }
end
end
class BillingFeature < ApplicationFeature
feature :api
end
class PaymentFeature < ApplicationFeature
feature :stripe
end
# Both classes have the :enabled? action
BillingFeature.enabled?(user: user)
PaymentFeature.enabled?(user: user)Actions can be called in two ways:
User::OnboardingFeature.enabled?(user: user)
User::OnboardingFeature.enable(user: user)
User::OnboardingFeature.disable(user: user)feature = User::OnboardingFeature.with(user: user)
feature.enabled?
feature.enable
feature.disableSee Working with Features for detailed usage patterns.
- Learn about Resources and the
option: trueparameter - Add Conditions to control when actions are evaluated
- Review Examples for real-world action patterns
- Explore Info and Introspection for runtime action discovery