-
Notifications
You must be signed in to change notification settings - Fork 0
Custom Rule Types & Rules
Togls is specifically architected on top of a generic concept of a
Togls::Rule
. This empowers both Togls proper and the users to define
any custom rule types, and use them to control their feature
toggles. For example, you could use them to do A/B testing, define various groups (internal users, alpha
testers, beta testers), manage percentage based rollouts, etc.
Custom rule types exist in code as classes that inherit from Togls::Rule
that are then registered with Togls via the Togls.rule_types
block method.
To implement a custom rule type, a new rule type class must be defined that abides by the following.
-
inherits from
Togls::Rule
or a decendent of it -
must not define its own constructor
-
has an instance method named
run
that takes a required feature key and either a default value parameter oftarget
or a required parameter oftarget
. This method must determine if the toggle should be on(true)/off(false) based on thetarget
, featurekey
, and initialization data@data
and return the appropriate boolean value.def run(key, target = 'foo') ... end # or def run(key, target) ... end
-
has a class level method
title
which returns a string title of the rule type. This is required as of v3.0.0. -
has a class level method
description
which returns a string description of the rule type. This is required as of v3.0.0. -
has a class level method
target_type
which returns a symbol identifying the abstract target type that this rule type expects to be passed when being evaluated. Note: This is optional in the case that you have defined a rule type so generic you can't identify the abstract target type. A good example of this is theTogls::Rules::Group
rule. It is worth noting that if you don't define the target type in the rule type class it will have to be set when constructing a rule instance.
As of v3.0.0, once you have defined a new rule type class you are required to register the rule type before you can use it. This is done as follows, generally in whatever file you define your feature toggles in.
Togls.rule_types do
register(:my_custom_rule, MyCustomRule)
end
Registering your target type associates your rule type class with the symbol identifier you provide in the register(rule_type_id, rule_type_class)
method. This allows Togls data to be represented in a platform agnostic manner.
Since v3.0.0 we provide a DSL to construct custom rules.
Once you have defined a custom rule type you can construct an instance of that rule type by calling the Togls.rule(rule_id, type_id, data, target_type: Togls::TargetTypes::NOT_SET)
method. Having a rule instance allows you to associate that rule instance with a feature toggle when defining your toggles using Togls.feature(:foo, 'desc').on(my_rule_instance)
. The following is an example of how one would construct an instance of the :my_custom_rule
type from above.
Togls.rule(:the_rule, :my_custom_rule, 'some_initialization_data', target_type: :special_target_type) # => instance of MyCustomRule
# or
Togls.rule(:the_rule, :my_custom_rule, 'some_initialization_data') # => instance of MyCustomRule
Prior to v3.0.0 you would have to construct them manually without the DSL. The following is an example.
MyCustomRule.new('some_initialization_data') # => instance of MyCustomRule
You will likely note that in the above example the rule_id
, type_id
, and target_type
are missing. This is because those concepts were added in v3.0.0.
Internally Togls uses these custom rule types to provide functionality and will evolve to contain more official rule types over time. If you have a generic custom rule type you think should be part of Togls please make a pull request.
A prime example of a custom rule type provided by Togls is the
Togls::Rules::Group
rule. You can see it below.
module Togls
module Rules
class Group < Togls::Rule
def self.title
"Group"
end
def self.description
%Q{
The Group rule allows you to define an arbitrary collection of objects to be
used in evaluating against the target. Specify the initialization data as an
array of inclusive identifiers for the group. When the feature toggle is
evaluated if the passed in target is included in the group then it is evaluated
to on. If not, it evaluates to off. Examples:
# Group defined by user ids
alpha_testers = Togls::Rules::Group.new(:group, [23, 343, 222, 123], target_type: :user_id)
# Group defined by email addresses
beta_testers = Togls::Rules::Group.new(:group, ['bob@example.com', 'cindy@example.com'], target_type: :user_email_address)
Togls.release do
feature(:foo, 'some foo desc', :user_email_address).on(beta_testers)
end
Togls.feature(:foo).on?('jack@example.com') # evalutes to false (a.k.a. off)
}
end
def run(_key, target)
@data.include?(target)
end
end
end
end
Lets take a closer look at exactly what is going on here.
- it is inheriting from the
Togls::Rule
class which meets one of the minimum requirements for defining a custom rule type. - it uses the base class (
Togls::Rule
) defined constructor which meets one of the minimum requirements for defining a custom rule type. - it defines a
run
method that returns a boolean value identifying if the feature should be on(true)/off(false) for the givenkey
andtarget
. It does so by identifying if the array passed in at construction timeinclude?
the giventarget
. This meets one of the minimum requirement for a rule. - the
run
method signature requires akey
andtarget
. This meets a minimum requirement for a rule as well. It also makes sense in the case of group based rule as it has to have something to compare against the group. - the
self.title
method properly returns a string as the title as required - the
self.description
method properly returns a string as the description - the
self.target_type
method is not defined, so it falls back to theself.target_type
method provided byTogls::Rule
which returnsTogls::TargetTypes::NOT_SET
indicating that the rule type isn't able to specify its target type. This in turn, forces the target type to be assigned during instantiation of the rule.
Custom Rules Types & Rules are a simple yet extremely powerful concept that you shouldn't hesitate to use.