Module: Featuring::Declarable

Defined in:
lib/featuring/declarable.rb

Overview

public

Adds the ability to declare feature flags on a module or class.

module Features
  extend Featuring::Declarable

  feature :some_feature
end

class User < ActiveRecord::Base
  extend Featuring::Declarable

  feature :some_feature
end

Each feature flag has a corresponding method to check its value:

module Features
  extend Featuring::Declarable

  feature :some_feature
end

Features.some_feature?
=> false

When using feature flags on an object, checks are available through the features instance method:

class ObjectWithFeatures
  extend Featuring::Declarable

  feature :some_feature
end

instance = ObjectWithFeatures.new
instance.features.some_feature?
=> false

When using feature flag blocks, values can be passed through the check method:

module Features
  extend Featuring::Declarable

  feature :some_feature do |value|
    value == :some_value
  end
end

Features.some_feature?(:some_value)
=> true

Features.some_feature?(:some_other_value)
=> false

Check methods are guaranteed to only return true or false:

module Features
  extend Featuring::Declarable

  feature :some_feature do
    :foo
  end
end

Features.some_feature?
=> true

Check methods have access to their context:

class ObjectWithFeatures
  extend Featuring::Declarable

  feature :some_feature do
    enabled?
  end

  def enabled?
    true
  end
end

instance = ObjectWithFeatures.new
instance.features.some_feature?
=> true

Note that this happens through delegators, which means that instance variables are not accessible to the feature flag. For cases like this, define an attr_accessor.

Feature flags can be defined in various modules and composed together:

module Features
  extend Featuring::Declarable
  feature :some_feature, true
end

module AllTheFeatures
  extend Features

  extend Featuring::Declarable
  feature :another_feature, true
end

class User < ActiveRecord::Base
  include AllTheFeatures
end

instance = ObjectWithFeatures.new

instance.some_feature?
=> true

instance.another_feature?
=> true

Super is fully supported! Here’s an example of how it can be useful:

module Features
  extend Featuring::Declarable

  feature :some_feature do
    [true, false].sample
  end
end

class User < ActiveRecord::Base
  include Features

  extend Featuring::Declarable
  feature :some_feature do
    persisted?(:some_feature) || super()
  end
end

User.find(1).features.some_feature?
=> true/false at random

User.find(1).features.enable :some_feature

User.find(1).features.some_feature?
=> true (always)

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(object) ⇒ Object

Called when an object is extended by Featuring::Declarable.



176
177
178
179
180
# File 'lib/featuring/declarable.rb', line 176

def self.extended(object)
  super

  Flaggable.setup_flaggable_object(object)
end

Instance Method Details

#extended(object) ⇒ Object

Called when an object is extended by a module that is extended by Featuring::Declarable.



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/featuring/declarable.rb', line 184

def extended(object)
  super

  case object
  when Class
    raise "extending classes with feature flags is not currently supported"
  when Module
    Flaggable.setup_flaggable_object(object)

    # Add the feature check methods to the module that was extended.
    #
    object.internal_feature_checks_module.include internal_feature_checks_module

    # Because we added feature check methods above, extend again to make them available.
    #
    object.extend internal_feature_checks_module

    # Add the feature methods to the module's internal feature module.
    #
    object.internal_feature_module.include internal_feature_module

    # Add our feature flags to the object's feature flags.
    #
    object.feature_flags.concat(feature_flags)
  end
end

#feature(name, default = false, &block) ⇒ Object

public

Define a named feature with a default value, or a block that returns the default value.

By default, a feature flag is disabled. It can be enabled by specifying a value:

module Features
  extend Featuring::Declarable

  feature :some_feature, true
end

Feature flags can also compute a value using a block:

module Features
  extend Featuring::Declarable

  feature :some_feature do
    # perform some complex logic
  end
end

The truthiness of the block’s return value determines if the feature is enabled or disabled.



170
171
172
# File 'lib/featuring/declarable.rb', line 170

def feature(name, default = false, &block)
  define_feature_flag(name, default, &block)
end

#included(object) ⇒ Object

Called when a module extended by Featuring::Declarable is included into an object.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/featuring/declarable.rb', line 213

def included(object)
  super

  Flaggable.setup_flaggable_object(object)

  # Add the feature check methods to the object's internal feature class.
  #
  object.instance_feature_class.internal_feature_checks_module.include internal_feature_checks_module

  # Because we added feature check methods above, include again to make them available.
  #
  object.instance_feature_class.include object.instance_feature_class.internal_feature_checks_module

  # Add the feature methods to the object's internal feature class.
  #
  object.instance_feature_class.internal_feature_module.include internal_feature_module

  # Add our feature flags to the object's feature flags.
  #
  object.instance_feature_class.feature_flags.concat(feature_flags)
end