Conditions are lambda-based rules that determine when features should be evaluated. They provide a way to add business logic that controls whether feature actions should execute.
Use the condition method with a lambda:
class User::OnboardingFeature < ApplicationFeature
prefix :user_onboarding
resource :user, type: User, option: true
condition ->(resources:) { resources.user.onboarding_awaiting? }
feature :passage
endThe condition lambda receives a resources: parameter that provides access to all resources defined in the feature class.
An object that provides access to all resources via method calls:
class User::OnboardingFeature < ApplicationFeature
resource :user, type: User, option: true
resource :account, type: Account, option: true
condition(lambda do |resources:|
resources.user.active? && resources.account.premium?
end)
endAccess resources by name:
resources.user— Returns the User instanceresources.account— Returns the Account instance
When a condition is defined, it acts as a guard for feature actions:
class User::OnboardingFeature < ApplicationFeature
resource :user, type: User, option: true
condition ->(resources:) { resources.user.onboarding_awaiting? }
feature :passage
action :enabled?, web: :enabled? do |features:, **options|
features.all? { |feature| Flipper.enabled?(feature, *options.values) }
end
end
# If user.onboarding_awaiting? returns false:
User::OnboardingFeature.enabled?(user: user)
# The action is not executed, returns false immediately
# If user.onboarding_awaiting? returns true:
User::OnboardingFeature.enabled?(user: user)
# The action executes normallyclass User::ProfileEditingFeature < ApplicationFeature
resource :user, type: User, option: true
condition ->(resources:) { resources.user.email_verified? }
feature :profile_editing
endclass OrganizationFeature < ApplicationFeature
resource :organization, type: Organization, option: true
resource :user, type: User, option: true
condition(lambda do |resources:|
resources.organization.active? && resources.user.admin?
end)
feature :admin_panel
endWhen using required: false, check for resource presence:
class AnalyticsFeature < ApplicationFeature
resource :user, type: User, option: true, required: false
condition(lambda do |resources:|
# Check if user is provided before accessing it
resources.user.nil? || resources.user.analytics_enabled?
end)
feature :tracking
endclass BillingFeature < ApplicationFeature
resource :user, type: User, option: true
condition(lambda do |resources:|
resources.user.subscription&.active? &&
resources.user.subscription&.plan&.premium?
end)
feature :api
endclass SeasonalFeature < ApplicationFeature
resource :user, type: User, option: true
condition(lambda do |resources:|
Date.current.month.in?([11, 12]) && resources.user.active?
end)
feature :holiday_theme
endclass ExperimentalFeature < ApplicationFeature
resource :user, type: User, option: true
condition(lambda do |resources:|
ExperimentService.user_enrolled?(resources.user.id)
end)
feature :beta_ui
endConditions and feature flags serve different purposes:
Conditions — Business logic that determines if a feature should be evaluated
condition ->(resources:) { resources.user.onboarding_awaiting? }
# "Should we even check the feature flag?"Feature Flags — Configuration that enables/disables features
action :enabled? do |features:, **options|
features.all? { |feature| Flipper.enabled?(feature, *options.values) }
end
# "Is the feature flag turned on?"class User::OnboardingFeature < ApplicationFeature
resource :user, type: User, option: true
# Condition: Only proceed if user is in onboarding state
condition ->(resources:) { resources.user.onboarding_awaiting? }
feature :passage
# Feature flag: Check if feature is enabled for this user
action :enabled?, web: :enabled? do |features:, **options|
features.all? { |feature| Flipper.enabled?(feature, *options.values) }
end
end
# Flow:
# 1. Check condition: Is user.onboarding_awaiting? true?
# - If false: Return false immediately
# - If true: Continue to step 2
# 2. Check feature flag: Is Flipper.enabled?(:user_onboarding_passage, user) true?
# - Return the resultConditions apply to the current class and do not cascade to nested groups:
class User::OnboardingFeature < ApplicationFeature
resource :user, type: User, option: true, nested: true
condition ->(resources:) { resources.user.onboarding_awaiting? }
feature :passage
group BillingFeature
end
class BillingFeature < ApplicationFeature
resource :user, type: User, option: true
# This class has NO condition
# It will always execute its actions
feature :api
end
# The condition only applies to :user_onboarding_passage
# BillingFeature actions always execute
User::OnboardingFeature.enabled?(user: user)To add conditions to nested groups, define them in each class:
class BillingFeature < ApplicationFeature
resource :user, type: User, option: true
condition ->(resources:) { resources.user.billing_enabled? }
feature :api
endclass AdminFeature < ApplicationFeature
resource :user, type: User, option: true
condition ->(resources:) { resources.user.admin? }
feature :admin_panel
endclass PremiumFeature < ApplicationFeature
resource :user, type: User, option: true
condition(lambda do |resources:|
resources.user.subscription&.tier&.in?(["premium", "enterprise"])
end)
feature :advanced_analytics
endclass OnboardingStepFeature < ApplicationFeature
resource :user, type: User, option: true
condition(lambda do |resources:|
resources.user.onboarding_state == "step_2"
end)
feature :step_2_completion
endclass RegionalFeature < ApplicationFeature
resource :user, type: User, option: true
condition(lambda do |resources:|
resources.user.country_code.in?(["US", "CA", "GB"])
end)
feature :regional_payment_method
endWhen testing, verify that conditions control action execution:
RSpec.describe User::OnboardingFeature do
let(:user) { User.new }
context "when user is awaiting onboarding" do
before do
allow(user).to receive(:onboarding_awaiting?).and_return(true)
allow(Flipper).to receive(:enabled?).with(:user_onboarding_passage, user).and_return(true)
end
it "checks the feature flag" do
expect(Flipper).to receive(:enabled?).with(:user_onboarding_passage, user)
User::OnboardingFeature.enabled?(user: user)
end
end
context "when user is not awaiting onboarding" do
before { allow(user).to receive(:onboarding_awaiting?).and_return(false) }
it "returns false without checking feature flag" do
expect(Flipper).not_to receive(:enabled?)
expect(User::OnboardingFeature.enabled?(user: user)).to be(false)
end
end
end- Learn about Actions and how conditions affect action execution
- Review Resources for accessing resource data in conditions
- See Examples for real-world condition patterns
- Explore Best Practices for condition design