Abstract Feature Branch ======================= [](http://badge.fury.io/rb/abstract_feature_branch) [](https://travis-ci.org/AndyObtiva/abstract_feature_branch) abstract_feature_branch is a Rails gem that enables developers to easily branch by abstraction as per this pattern: http://paulhammant.com/blog/branch_by_abstraction.html It is a productivity and fault tolerance enhancing team practice that has been utilized by professional software development teams at large corporations, such as Sears and Groupon. It gives ability to wrap blocks of code with an abstract feature branch name, and then specify which features to be switched on or off in a configuration file. The goal is to build out upcoming features in the same source code repository branch, regardless of whether all are completed by the next release date or not, thus increasing team productivity by preventing integration delays. Developers then disable in-progress features until they are ready to be switched on in production, yet enable them locally and in staging environments for in-progress testing. This gives developers the added benefit of being able to switch a feature off after release should big problems arise for a high risk feature. abstract_feature_branch additionally supports [DDD](http://www.domaindrivendesign.org)'s pattern of [bounded contexts](http://dddcommunity.org/uncategorized/bounded-context/) by allowing developers to configure context-specific feature files if needed. Requirements ------------ - Ruby ~> 2.0.0, ~> 1.9 or ~> 1.8.7 - (Optional) Rails ~> 4.0.0, ~> 3.0 or ~> 2.0 Setup ----- ### Rails Application 1. Configure Rubygem - Rails (~> 4.0.0 or ~> 3.0): Add the following to Gemfile
gem 'abstract_feature_branch', '0.7.0'- Rails (~> 2.0): Add the following to config/environment.rb
config.gem 'abstract_feature_branch', :version => '0.7.0'2. Generate
config/initializers/abstract_feature_branch.rb
, config/features.yml
and config/features.local.yml
in your Rails app directory by running rails g abstract_feature_branch:install3. (Optional) Generate
config/features/[context_path].yml
in your Rails app directory by running rails g abstract_feature_branch:context context_path(more details under **instructions**) 4. (Optional and rarely needed) Customize configuration in
config/initializers/abstract_feature_branch.rb
(can be useful for changing location of feature files in Rails application or troubleshooting a specific Rails environment feature configuration)
### Ruby Application (general use)
1. gem install abstract_feature_branch -v 0.7.02. Add code
require 'abstract_feature_branch'
3. Create config/features.yml
under AbstractFeatureBranch.application_root
and fill it with content similar to that of the sample config/features.yml
mentioned under **instructions**.
4. (Optional) Create config/features.local.yml
under AbstractFeatureBranch.application_root
(more details under **instructions**)
5. (Optional) Create config/features/[context_path].yml
under AbstractFeatureBranch.application_root
(more details under **instructions**)
6. (Optional) Add code AbstractFeatureBranch.application_root = "[your_application_path]"
to configure the location of feature files (it defaults to '.'
)
7. (Optional) Add code AbstractFeatureBranch.application_environment = "[your_application_environment]"
(it defaults to 'development'
). Alternatively, you can set ENV['APP_ENV']
before the require
statement or an an external environment variable.
8. (Optional) Add code AbstractFeatureBranch.load_application_features
to pre-load application features for improved first-use performance
Instructions
------------
config/features.yml
contains the main configuration for the application features.
config/features.local.yml
contains local overrides for the configuration, ignored by git, thus useful for temporary
local feature switching for development/testing/troubleshooting purposes.
Optional context specific config/features/[context_path].yml
contain feature configuration for specific application contexts.
For example: admin, public, or even internal/wiki. Useful for better organization especially once config/features.yml
grows too big (e.g. 20+ features)
Optional context specific config/features/[context_path].local.yml
contain local overrides for context-specific feature configuration.
These files are rarely necessary as any feature (even a context feature) can be overridden in config/features.local.yml
,
so these additional *.local.yml
files are only recommended to be utilized once config/features.local.yml
grows
too big (e.g. 20+ features).
Here are the contents of the generated sample config/features.yml
, which you can modify with your own features, each
enabled (true) or disabled (false) per environment (e.g. production).
> defaults: &defaults
> feature1: true
> feature2: true
> feature3: false
>
> development:
> <<: *defaults
>
> test:
> <<: *defaults
>
> staging:
> <<: *defaults
> feature2: false
>
> production:
> <<: *defaults
> feature1: false
> feature2: false
Notice in the sample file how the feature "feature1" was configured as true (enabled) by default, but
overridden as false (disabled) in production. This is a recommended practice.
- Declaratively feature branch logic to only run when feature1 is enabled:
multi-line logic:
> feature_branch :feature1 do
> # perform logic
> end
single-line logic:
> feature_branch(:feature1) { # perform logic }
Note that feature_branch
returns nil and does not execute the block if the feature is disabled or non-existent.
- Declaratively feature branch two paths of logic, one that runs when feature1 is enabled and one that runs when it is disabled:
> feature_branch :feature1,
> :true => lambda {
> # perform logic
> },
> :false => lambda {
> # perform alternate logic
> }
Note that feature_branch
executes the false branch if the feature is non-existent.
- Imperatively check if a feature is enabled or not:
> if feature_enabled?(:feature1)
> # perform logic
> else
> # perform alternate logic
> end
Note that feature_enabled?
returns false if the feature is disabled and nil if the feature is non-existent (practically the same effect, but nil can sometimes be useful to detect if a feature is referenced).
Initializer
-----------
Here is the content of the generated initializer (config/initializers/abstract_feature_branch.rb
), which contains instructions on how to customize:
> # Application root where config/features.yml or config/features/ is found
> AbstractFeatureBranch.application_root = Rails.root
>
> # Application environment (e.g. "development", "staging" or "production")
> AbstractFeatureBranch.application_environment = Rails.env.to_s
>
> # Pre-loads application features to improve performance of first web-page hit
> AbstractFeatureBranch.load_application_features
Recommendations
---------------
- Wrap routes in routes.rb with feature blocks to disable entire MVC feature elements by
simply switching off the URL route to them. Example:
> feature_branch :add_business_project do
> resources :projects
> end
- Wrap visual links to these routes in ERB views. Example:
> <% feature_branch :add_business_project do %>
> > Please submit a business idea for review. >
>config/features.yml
into multiple context-specific feature files once it grows too big (e.g. 20+ features) by
utilizing the context generator mentioned above: rails g abstract_feature_branch:context context_path- When working on a new feature locally that the developer does not want others on the team to see yet, the feature can be enabled in
config/features.local.yml
only as it is git ignored, and disabled in config/features.yml
- When troubleshooting a deployed feature by simulating a non-development environment (e.g. staging or production) locally,
the developer can disable it temporarily in config/features.local.yml
(git ignored) under the non-development environment,
perform tests on the feature, and then remove the local configuration once done.
Environment Variable Overrides
------------------------------
You can override feature configuration with environment variables by setting an environment variable with
a name matching this convention (case-insensitive):
ABSTRACT_FEATURE_BRANCH_[feature_name] and giving it the case-insensitive value "TRUE" or "FALSE"
Example:
> export ABSTRACT_FEATURE_BRANCH_FEATURE1=TRUE
> rails s
The first command adds an environment variable override for feature1
that enables it regardless of any
feature configuration, and the second command starts the rails server with feature1
enabled.
To remove an environment variable override, you may run:
> unset ABSTRACT_FEATURE_BRANCH_FEATURE1
> rails s
The benefits can be achieved more easily via config/features.local.yml
mentioned above.
However, environment variable overrides are implemented to support overriding feature configuration for a Heroku deployed
application more easily.
Heroku
------
Environment variable overrides can be extremely helpful on Heroku as they allow developers to enable/disable features
at runtime without a redeploy.
Examples:
Enabling a new feature without a redeploy:
heroku config:add ABSTRACT_FEATURE_BRANCH_FEATURE3=true -a heroku_application_nameDisabling a buggy recently deployed feature without a redeploy:
heroku config:add ABSTRACT_FEATURE_BRANCH_FEATURE2=false -a heroku_application_nameRemoving an environment variable override:
heroku config:remove ABSTRACT_FEATURE_BRANCH_FEATURE2 -a heroku_application_nameRecommendation: It is recommended that you use environment variable overrides on Heroku only as an emergency or temporary measure. Afterward, make the change officially in config/features.yml, deploy, and remove the environment variable override for the long term. Gotcha with abstract feature branching in CSS and JS files: If you've used abstract feature branching in CSS or JS files via ERB, setting environment variable overrides won't affect them as you need asset recompilation in addition to it, which can only be triggered by changing a CSS or JS file and redeploying on Heroku (hint: even if it's just a minor change to force it). In any case, environment variable overrides have been recommended above as an emergency or temporary measure. If there is a need to rely on environment variable overrides to alter the style or JavaScript behavior of a page back and forth without a redeploy, one solution is to do additional abstract feature branching in HTML templates (e.g. ERB or HAML templates) to link to different CSS classes or invoke different JavaScript methods per branch of HTML for example. Feature Configuration Load Order -------------------------------- For better knowledge and clarity, here is the order in which feature configuration is loaded, with the latter sources overriding the former if overlap in features occurs: 1. Context-specific feature files:
config/features/**/*.yml
2. Main feature file: config/features.yml
3. Context-specific local feature file overrides: config/features/**/*.local.yml
4. Main local feature file override: config/features.local.yml
5. Environment variable overrides
Release Notes
-------------
Version 0.7.0:
- Added support for general Ruby use (without Rails) by externalizing AbstractFeatureBranch.application_root and AbstractFeatureBranch.application_environment. Added initializer to optionally configure them. Supported case-insensitive feature names.
Version 0.6.1 - 0.6.4:
- Fixed issues including making feature configuration files optional (in case one wants to get rid of features.local.yml
or even features.yml
)
Version 0.6.0:
- Added a context generator and support for reading feature configuration from context files config/features/**/*.yml
and config/features/**/*.local.yml
Version 0.5.0:
- Added support for local configuration feature ignored by git + some performance optimizations via configuration caching and better algorithms.
Version 0.4.0:
- Added support for overwriting feature configuration with environment variable overrides. Very useful on Heroku to quickly enable/disable features without a redeploy.
Version 0.3.6:
- Fixed feature_branch issue with invalid feature name, preventing block execution and returning nil instead
Version 0.3.5:
- Fixed issue with generator not allowing consuming client app to start Rails server successfully
Version 0.3.4:
- Added abstract_feature_branch:install
generator to easily get started with a sample config/features.yml
Version 0.3.3:
- Removed version from README title
Version 0.3.2:
- Added AbstractFeatureBranch.features
to delay YAML load until Rails.root
has been established
Version 0.3.1:
- Removed dependency on the rails_config gem
Version 0.3.0:
- Simplified features.yml
requirement to have a features header under each environment
- Moved feature storage from Settings object to AbstractFeatureBranch::FEATURES
Version 0.2.0:
- Support an "else" block to execute when a feature is off (via :true
and :false
lambda arguments)
- Support ability to check if a feature is enabled or not (via feature_enabled?
)
Upcoming
--------
- Add rake task to reorder feature entries in feature.yml alphabetically
- Support runtime read of features yml in development for easy testing purposes (trading off performance)
- Support configuring per environment whether features yml is read at runtime or not (given performance trade-off)
Contributing to abstract_feature_branch
---------------------------------------
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
* Fork the project.
* Start a feature/bugfix branch.
* Commit and push until you are happy with your contribution.
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
Committers
---------------------------------------
[Annas "Andy" Maleh (Author)](https://github.com/AndyObtiva)
Contributors
---------------------------------------
[Christian Nennemann](https://github.com/XORwell)
Copyright
---------------------------------------
Copyright (c) 2013 Annas "Andy" Maleh. See LICENSE.txt for
further details.