abstract_feature_branch 1.3.1 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52ff8ed098aa83bdd70eda839fa74b291c95993fb9859e2cc5ed8c0752bfa213
4
- data.tar.gz: 9b5e6915905904be0b87690a73b1dfe14ec8893b2659383545bac23b0fe76978
3
+ metadata.gz: 5a8c7180bcc59c08383c35f7aaaa993c8d64684d8d5f52174f5cd27c6fbca1da
4
+ data.tar.gz: f22086f5d839b540864caa62ab5fc3f31baf8f99d194c5516135ec0ddc85f032
5
5
  SHA512:
6
- metadata.gz: 2f948fd18d057bed300d4cb9eb89e36b784ec2e8a445edaeaf2d742866884828795e8873de02ea9d1794604757dee8d6af2b63934a4f7fcd5b7c0d65aa1171ec
7
- data.tar.gz: c4a1ddc94547f390e2960ddc97a20034cf7bd3dbaa378e17060ae38509b019897d0364913573856254269217c22731de15e7e1e8c8825b1d30c0615521ee6610
6
+ metadata.gz: 44908f37b872e59a8160d87b7a598949635489553b82a8cb56fcaa28ca5eac43679f9334ef273908aacaa90d6e2b7fe255913d1805eaff05cca2a9792c3d5f6e
7
+ data.tar.gz: 8723b3f9ed9ce9131cbdc4c259e3700ad708925f50226c736115ee2f65da5a897f0f08537e1bac7b24d39741a0ef710ae732f8196e6ee7388b22058075318479
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.3.3
4
+
5
+ - Redis network failure error handling for per-user feature enablement to default to `nil` value instead of crashing
6
+ - Error logging upon encountering Redis network failure errors in loading Redis Overrides live or not and in per-user feature enablement
7
+
8
+ ## 1.3.2
9
+
10
+ - Ensure better performance, fetch Redis Overrides at app/server startup time only by default while providing option to fetch live by setting `AbstractFeatureBranch.feature_store_live_fetching` to `true`
11
+ - Do not automatically pre-init `AbstractFeatureBranch.feature_store` with `Redis.new` if it was `nil` as that is a bad default.
12
+ - Fix issue with crashing when not including the `connection_pool` gem manually if needed with a version of `redis` older than 5
13
+
3
14
  ## 1.3.1
4
15
 
5
16
  - Support Redis `ConnectionPool` `AbstractFeatureBranch::Configuration#feature_store`
@@ -11,5 +22,5 @@
11
22
  - Remove `redis` gem from required dependencies to allow using `abstract_feature_branch` without Redis
12
23
  - Make configuration of Redis optional in generated Rails initializer
13
24
  - Provide alias of `AbstractFeatureBranch::Configuration#feature_store` to `AbstractFeatureBranch::Configuration#user_features_storage` (plus corresponding aliases `feature_store=` and `initialize_feature_store`)
14
- - Document support for Rails 7 and Redis Server 7
25
+ - Document support for Ruby 3.1, Rails 7 and Redis Server 7
15
26
  - Add gem post install instructions, including how to run the Rails generators and install/use Redis
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012-2022 Andy Maleh
1
+ Copyright (c) 2012-2023 Andy Maleh
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,18 +1,17 @@
1
- # Abstract Feature Branch 1.3.1
1
+ # Abstract Feature Branch 1.3.3
2
2
  [![Gem Version](https://badge.fury.io/rb/abstract_feature_branch.png)](http://badge.fury.io/rb/abstract_feature_branch)
3
3
  [![Build Status](https://api.travis-ci.org/AndyObtiva/abstract_feature_branch.png?branch=master)](https://travis-ci.org/AndyObtiva/abstract_feature_branch)
4
4
  [![Coverage Status](https://coveralls.io/repos/AndyObtiva/abstract_feature_branch/badge.png?branch=master)](https://coveralls.io/r/AndyObtiva/abstract_feature_branch?branch=master)
5
5
  [![Code Climate](https://codeclimate.com/github/AndyObtiva/abstract_feature_branch.png)](https://codeclimate.com/github/AndyObtiva/abstract_feature_branch)
6
6
 
7
- abstract_feature_branch is a Rails gem that enables developers to easily branch by abstraction as per this pattern:
8
- http://paulhammant.com/blog/branch_by_abstraction.html
7
+ [`abstract_feature_branch`](https://rubygems.org/gems/abstract_feature_branch) is a Ruby gem that provides a variation on the [Branch by Abstraction Pattern](http://paulhammant.com/blog/branch_by_abstraction.html) by [Paul Hammant](https://paulhammant.com/) and the [Feature Toggles Pattern](https://martinfowler.com/bliki/FeatureToggle.html) by [Martin Fowler](https://martinfowler.com/).
9
8
 
10
9
  It is a productivity and fault tolerance enhancing team practice.
11
10
 
12
11
  It provides the ability to wrap blocks of code with an abstract feature branch name, and then
13
- specify in a configuration file which features to be switched on or off.
12
+ [specify in a configuration file](#instructions) which features to be switched on or off.
14
13
 
15
- The goal is to build out upcoming features in the same source code repository branch, regardless of whether all are
14
+ The goal is to build out upcoming features in the same source code repository branch (i.e. continuous integration), regardless of whether all are
16
15
  completed by the next release date or not, thus increasing team productivity by preventing integration delays.
17
16
  Developers then disable in-progress features until they are ready to be switched on in production, yet enable them
18
17
  locally and in staging environments for in-progress testing.
@@ -20,13 +19,17 @@ locally and in staging environments for in-progress testing.
20
19
  This gives developers the added benefit of being able to switch a feature off after release should big problems arise
21
20
  for a high risk feature.
22
21
 
23
- abstract_feature_branch additionally supports [DDD](http://www.domaindrivendesign.org)'s pattern of
24
- [Bounded Contexts](http://dddcommunity.org/uncategorized/bounded-context/) by allowing developers to configure
22
+ [`abstract_feature_branch`](https://rubygems.org/gems/abstract_feature_branch) additionally supports [Domain Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design)'s pattern of
23
+ [Bounded Contexts](https://www.domainlanguage.com/wp-content/uploads/2016/05/DDD_Reference_2015-03.pdf) by allowing developers to configure
25
24
  context-specific feature files if needed.
26
25
 
26
+ [`abstract_feature_branch`](https://rubygems.org/gems/abstract_feature_branch) is one of the simplest and most minimalistic "Feature Flags" Ruby gems out there as it enables you to get started very quickly by simply leveraging YAML files without having to set up a data store if you do not need it (albeit, you also have the option to use Redis as a very fast in-memory data store).
27
+
28
+
29
+
27
30
  Requirements
28
31
  ------------
29
- - Ruby (between `~> 3.1.0` and `~> 1.8.7`) ([click for a list of tested Ruby versions](https://travis-ci.org/AndyObtiva/abstract_feature_branch))
32
+ - Ruby (between `~> 3.1.0` and `~> 1.8.7`)
30
33
  - [Optional] Rails (between `~> 7.0` and `~> 2.0`)
31
34
  - [Optional] Redis Server (between `~> 7.0` and `~> 2.0`)
32
35
  - [Optional] Redis client gem (between `~> 5.0` and `~> 3.0`)
@@ -37,17 +40,17 @@ Setup
37
40
  ### Rails Application Use
38
41
 
39
42
  1. Configure Rubygem
40
- - With `rails` between `~> 7.0` and `~> 2.0`: Add the following to Gemfile <pre>gem 'abstract_feature_branch', '~> 1.3.1'</pre>
41
- - With `rails` `~> 2.0` only: Add the following to config/environment.rb <pre>config.gem 'abstract_feature_branch', :version => '1.3.1'</pre>
43
+ - With `rails` between `~> 7.0` and `~> 2.0`: Add the following to Gemfile <pre>gem 'abstract_feature_branch', '~> 1.3.3'</pre>
44
+ - With `rails` `~> 2.0` only: Add the following to config/environment.rb <pre>config.gem 'abstract_feature_branch', :version => '1.3.3'</pre>
42
45
  2. Generate <code>config/initializers/abstract_feature_branch.rb</code>, <code>lib/tasks/abstract_feature_branch.rake</code>, <code>config/features.yml</code> and <code>config/features.local.yml</code> in your Rails app directory by running <pre>rails g abstract_feature_branch:install</pre>
43
46
  3. [Optional] Generate <code>config/features/[context_path].yml</code> in your Rails app directory by running <pre>rails g abstract_feature_branch:context context_path</pre> (more details under [**instructions**](#instructions))
44
- 4. [Optional] Customize configuration in <code>config/initializers/abstract_feature_branch.rb</code> (can be useful for changing location of feature files in Rails application, configuring Redis with a Redis or ConnectionPool instance to use for overrides, and per-user feature enablement, or troubleshooting a specific Rails environment feature configuration)
45
- 5. [Optional] Redis Server (between `~> 7.0` and `~> 3.0`): Install view [Homebrew](https://brew.sh/) with `brew install redis`
46
- 6. [Optional] `redis` client gem (between `~> 5.0` and `~> 3.0`): Add the following to Gemfile above `abstract_feature_branch` <pre>gem 'redis', '~> 5.0.5'</pre>
47
+ 4. [Optional] Customize configuration in <code>config/initializers/abstract_feature_branch.rb</code> (can be useful for changing location of feature files in Rails application, configuring Redis with a Redis or ConnectionPool instance to use for overrides, and [per-user feature enablement](#per-user-feature-enablement), or troubleshooting a specific Rails environment feature configuration)
48
+ 5. [Optional] Redis Server (between `~> 7.0` and `~> 3.0`): On the Mac, you can install simply via [Homebrew](https://brew.sh/) with `brew install redis`
49
+ 6. [Optional] `redis` client gem (between `~> 5.0` and `~> 3.0`): Add the following to Gemfile above [`abstract_feature_branch`](https://rubygems.org/gems/abstract_feature_branch) <pre>gem 'redis', '~> 5.0.5'</pre>
47
50
 
48
51
  ### Ruby Application General Use
49
52
 
50
- 1. <pre>gem install abstract_feature_branch -v 1.3.1</pre>
53
+ 1. <pre>gem install abstract_feature_branch -v 1.3.3</pre>
51
54
  2. Add code <code>require 'abstract_feature_branch'</code>
52
55
  3. Create <code>config/features.yml</code> under <code>AbstractFeatureBranch.application_root</code> and fill it with content similar to that of the sample <code>config/features.yml</code> mentioned under [**instructions**](#instructions).
53
56
  4. [Optional] Create <code>config/features.local.yml</code> under <code>AbstractFeatureBranch.application_root</code> (more details under [**instructions**](#instructions))
@@ -57,7 +60,8 @@ Setup
57
60
  8. [Optional] Add code <code>AbstractFeatureBranch.logger = "[your_application_logger]"</code> (it defaults to a new instance of Ruby <code>Logger</code>. Must use a logger with <code>info</code> and <code>warn</code> methods).
58
61
  9. [Optional] Add code <code>AbstractFeatureBranch.cacheable = {[environment] => [true/false]}</code> to indicate cacheability of loaded feature files for enhanced performance (it defaults to true for every environment other than development).
59
62
  10. [Optional] Add code <code>AbstractFeatureBranch.load_application_features</code> to pre-load application features for improved first-use performance
60
- 11. [Optional] Add code <code>AbstractFeatureBranch.feature_store = Redis.new(options)</code> to configure Redis for overrides or per-user feature enablement
63
+ 11. [Optional] Add code <code>AbstractFeatureBranch.feature_store = Redis.new(options)</code> to configure Redis for overrides and/or [per-user feature enablement](#per-user-feature-enablement)
64
+ 12. [Optional] Set <code>AbstractFeatureBranch.feature_store_live_fetching = true</code> to enable live fetching of features from store (e.g. Redis) to avoid need for app/server restart upon feature changes, with the trade-off of slightly more latency due to making calls to feature store over the network
61
65
 
62
66
  Instructions
63
67
  ------------
@@ -124,9 +128,14 @@ Note that <code>feature_branch</code> returns nil and does not execute the block
124
128
 
125
129
  Note that <code>feature_enabled?</code> 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).
126
130
 
131
+ - List all configured features for a particular environment:
132
+
133
+ > AbstractFeatureBranch.environment_features('development')
134
+ > # => {"feature1"=>true, "feature2"=>false, "feature3"=>false, "feature4"=>true}
135
+
127
136
  ### Per-User Feature Enablement
128
137
 
129
- It is possible to restrict enablement of features per specific users by setting a feature value to <code>per_user</code>.
138
+ It is possible to restrict enablement of features per specific users (or per entities of any kind) by setting a feature value to <code>per_user</code>, and then toggling features for specific users (or other entities).
130
139
 
131
140
  1. Use <code>toggle_features_for_user</code> in Ruby code to enable features per user ID (e.g. email address or database ID). This loads Redis client gem into memory and stores per-user feature configuration in Redis.
132
141
  In the example below, current_user is a method that provides the current signed in user (e.g. using Rails [Devise] (https://github.com/plataformatec/devise) library).
@@ -262,19 +271,23 @@ application more easily.
262
271
  Redis Overrides
263
272
  ---------------
264
273
 
265
- Prerequisites: Redis server and client (`redis` gem) and optional Redis configuration of `AbstractFeatureBranch.feature_store` in `config/initializers/abstract_feature_branch.rb` (`Redis` `ConnectionPool` instance is recommended for Production environments)
274
+ Prerequisites: Redis server and client (`redis` gem) and Redis configuration of `AbstractFeatureBranch.feature_store` in `config/initializers/abstract_feature_branch.rb` (`Redis` `ConnectionPool` instance is recommended for Production environments)
266
275
 
267
276
  To be able to override feature configuration in a production environment, you can utilize Redis Overrides.
268
277
 
269
278
  Alternatively, you may use Redis Overrides as your main source of feature configuration if you prefer that instead of relying on YAML files.
270
279
 
271
- You can override feature configuration with Redis hash values by calling `AbstractFeatureBranch#set_store_feature` in `rails console` (or `irb` after requiring `redis` and `abstract_feature_branch`):
280
+ Keep in mind that by default, Redis Overrides are fetched on app/server start to pre-cache for better performance.
281
+
282
+ To enable live fetching of Redis Overrides, set `AbstractFeatureBranch#feature_store_live_fetching` to `true` (e.g. in `config/initializers/abstract_feature_branch.rb`), but keep in mind the trade-off with more latency due to making calls to Redis Server over the network.
283
+
284
+ You can override feature configuration with Redis hash values by calling `AbstractFeatureBranch#set_store_feature` in `rails console` (or `irb` after requiring `redis` and [`abstract_feature_branch`](https://rubygems.org/gems/abstract_feature_branch)):
272
285
 
273
286
  ```ruby
274
287
  AbstractFeatureBranch.set_store_feature('feature1', true)
275
288
  ```
276
289
 
277
- Behind the scenes, that is the equivalent of the following Redis client invocation, which stores a hash value in a `abstract_feature_branch` key:
290
+ Behind the scenes, that is the equivalent of the following Redis client invocation, which stores a hash value in a `'abstract_feature_branch'` key:
278
291
 
279
292
  ```ruby
280
293
  AbstractFeatureBranch.configuration.feature_store.hset('abstract_feature_branch', 'feature1', 'true')
@@ -358,6 +371,10 @@ Here is the content of the generated initializer [with `redis` client gem added]
358
371
  > # The following example line works with Heroku Redis To Go while still operating on local Redis for local development
359
372
  > # AbstractFeatureBranch.feature_store = Redis.new(:url => ENV['REDISTOGO_URL'])
360
373
  >
374
+ > # Enable live fetching of feature configuration from storage system, to update features without app/server restart.
375
+ > # false by default to only load features on app/server start for faster performance (requires restart on change)
376
+ > AbstractFeatureBranch.feature_store_live_fetching = false
377
+ >
361
378
  > # Application root where config/features.yml or config/features/ is found
362
379
  > AbstractFeatureBranch.application_root = Rails.root
363
380
  >
@@ -478,7 +495,7 @@ Although feature branches and branching by abstraction are similar, there are di
478
495
 
479
496
  Feature branching leverages your version control software (VCS) to create a branch that is independent of your main branch. Once you write your feature, you integrate it with the rest of your code base. Feature branching is ideal for developing features that can be completed within the one or two iterations. But it can become cumbersome with larger features due to the fact your code is isolated and quickly falls out of sync with your main branch. You will have to regularly rebase with your main branch or devote substantial time to resolving merge conflicts.
480
497
 
481
- Branching by abstraction, on the other hand, is ideal for substantial features, i.e. ones which take many iterations to complete. This approach to branching takes place outside of your VCS. Instead, you build your feature, but wrap the code inside configurable flags. These configuration flags will allow for different behavior, depending on the runtime environment. For example, a feature would be set to "on" when your app runs in development mode, but "off" when running in "production" mode. This approach avoids the pain of constantly rebasing or resolving a myriad of merge conflict when you do attempt to integrate your feature into the larger app.
498
+ Branching by abstraction, on the other hand, is ideal for substantial features, i.e. ones which take many iterations to complete. This approach to branching takes place outside of your VCS. Instead, you build your feature, but wrap the code inside configurable flags. These configuration flags will allow for different behavior, depending on the runtime environment. For example, a feature would be set to "on" when your app runs in development mode, but "off" when running in "production" mode. This approach avoids the pain of constantly rebasing or resolving a myriad of merge conflicts when you do attempt to integrate your feature into the larger app.
482
499
 
483
500
  Contributing to abstract_feature_branch
484
501
  ---------------------------------------
@@ -502,5 +519,5 @@ Contributors
502
519
  Copyright
503
520
  ---------------------------------------
504
521
 
505
- Copyright (c) 2012-2022 Andy Maleh. See [LICENSE.txt](LICENSE.txt) for
522
+ Copyright (c) 2012-2023 Andy Maleh. See [LICENSE.txt](LICENSE.txt) for
506
523
  further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.1
1
+ 1.3.3
@@ -2,17 +2,17 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: abstract_feature_branch 1.3.1 ruby lib
5
+ # stub: abstract_feature_branch 1.3.3 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "abstract_feature_branch".freeze
9
- s.version = "1.3.1"
9
+ s.version = "1.3.3"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
- s.authors = ["Annas \"Andy\" Maleh".freeze]
14
- s.date = "2022-12-12"
15
- s.description = "abstract_feature_branch is a Rails gem that enables developers to easily branch by abstraction as per this pattern:\nhttp://paulhammant.com/blog/branch_by_abstraction.html\n\nIt is a productivity and fault tolerance enhancing team practice.\n\nIt provides the ability to wrap blocks of code with an abstract feature branch name, and then\nspecify in a configuration file which features to be switched on or off.\n\nThe goal is to build out upcoming features in the same source code repository branch, regardless of whether all are\ncompleted by the next release date or not, thus increasing team productivity by preventing integration delays.\nDevelopers then disable in-progress features until they are ready to be switched on in production, yet enable them\nlocally and in staging environments for in-progress testing.\n\nThis gives developers the added benefit of being able to switch a feature off after release should big problems arise\nfor a high risk feature.\n\nabstract_feature_branch additionally supports DDD's pattern of\nBounded Contexts by allowing developers to configure\ncontext-specific feature files if needed.\n".freeze
13
+ s.authors = ["Andy Maleh".freeze]
14
+ s.date = "2023-01-15"
15
+ s.description = "abstract_feature_branch is a Ruby gem that provides a variation on the Branch by Abstraction Pattern by Paul Hammant and the Feature Toggles Pattern by Martin Fowler.\n\nIt is a productivity and fault tolerance enhancing team practice.\n\nIt provides the ability to wrap blocks of code with an abstract feature branch name, and then specify in a configuration file which features to be switched on or off.\n\nThe goal is to build out upcoming features in the same source code repository branch (i.e. continuous integration), 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.\n\nThis gives developers the added benefit of being able to switch a feature off after release should big problems arise for a high risk feature.\n\nabstract_feature_branch additionally supports Domain Driven Design's pattern of Bounded Contexts by allowing developers to configure context-specific feature files if needed.\n\nabstract_feature_branch is one of the simplest and most minimalistic \"Feature Flags\" Ruby gems out there as it enables you to get started very quickly by simply leveraging YAML files without having to set up a data store if you do not need it (albeit, you also have the option to use Redis as a very fast in-memory data store).\n".freeze
16
16
  s.extra_rdoc_files = [
17
17
  "CHANGELOG.md",
18
18
  "LICENSE.txt",
@@ -40,8 +40,8 @@ Gem::Specification.new do |s|
40
40
  s.homepage = "http://github.com/AndyObtiva/abstract_feature_branch".freeze
41
41
  s.licenses = ["MIT".freeze]
42
42
  s.post_install_message = "\nRails-only post-install instructions:\n\n1) Run the following command to generate the Rails initializer and basic feature files:\n\nrails g abstract_feature_branch:install\n\n2) Optionally, you may run this command to generate feature files per context:\n\nrails g abstract_feature_branch:context context_path\n \n3) Optionally, install Redis server with [Homebrew](https://brew.sh/) by running:\n\nbrew install redis\n\n4) Optionally, install redis client gem (required with Redis server) by adding the following line to Gemfile above abstract_feature_branch:\n\ngem 'redis', '~> 5.0.5'\n\nAfterwards, run:\n\nbundle\n\n5) Optionally, customize configuration in config/initializers/abstract_feature_branch.rb\n\n(can be useful for changing location of feature files in Rails application,\nconfiguring Redis with a Redis or ConnectionPool instance to use for overrides and per-user feature enablement,\nand/or troubleshooting specific Rails environment feature configurations)\n\n".freeze
43
- s.rubygems_version = "3.3.6".freeze
44
- s.summary = "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".freeze
43
+ s.rubygems_version = "3.1.4".freeze
44
+ s.summary = "abstract_feature_branch is a Ruby gem that provides a variation on the Branch by Abstraction Pattern by Paul Hammant and the Feature Toggles Pattern by Martin Fowler (aka Feature Flags).".freeze
45
45
 
46
46
  if s.respond_to? :specification_version then
47
47
  s.specification_version = 4
@@ -45,7 +45,7 @@ module AbstractFeatureBranch
45
45
  end
46
46
 
47
47
  def feature_store
48
- @feature_store ||= initialize_feature_store
48
+ @feature_store
49
49
  end
50
50
  alias user_features_storage feature_store
51
51
 
@@ -53,18 +53,28 @@ module AbstractFeatureBranch
53
53
  if feature_store.nil?
54
54
  @feature_store = nil
55
55
  else
56
- @feature_store = feature_store.is_a?(::ConnectionPool) ? AbstractFeatureBranch::Redis::ConnectionPoolToRedisAdapter.new(feature_store) : feature_store
56
+ begin
57
+ @feature_store = feature_store.is_a?(::ConnectionPool) ? AbstractFeatureBranch::Redis::ConnectionPoolToRedisAdapter.new(feature_store) : feature_store
58
+ rescue NameError => e
59
+ logger.debug { "connection_pool gem is not available" }
60
+ @feature_store = feature_store
61
+ end
57
62
  end
58
63
  end
59
64
  alias user_features_storage= feature_store=
60
65
 
61
- def initialize_feature_store
62
- self.feature_store = ::Redis.new
63
- rescue => e
64
- logger.debug { "Redis is not enabled!" }
65
- logger.debug { e.full_message }
66
- nil
67
- end
68
- alias initialize_user_features_storage initialize_feature_store
66
+ def feature_store_live_fetching
67
+ initialize_feature_store_live_fetching if @feature_store_live_fetching.nil?
68
+ @feature_store_live_fetching
69
+ end
70
+ alias feature_store_live_fetching? feature_store_live_fetching
71
+
72
+ def feature_store_live_fetching=(value)
73
+ @feature_store_live_fetching = value
74
+ end
75
+
76
+ def initialize_feature_store_live_fetching
77
+ @feature_store_live_fetching = false
78
+ end
69
79
  end
70
80
  end
@@ -23,23 +23,28 @@ module AbstractFeatureBranch
23
23
 
24
24
  class << self
25
25
  extend Forwardable
26
- def_delegators :configuration, :application_root, :application_root=, :initialize_application_root, :application_environment, :application_environment=, :initialize_application_environment, :logger, :logger=, :initialize_logger, :cacheable, :cacheable=, :initialize_cacheable, :feature_store, :feature_store=, :initialize_feature_store, :user_features_storage, :user_features_storage=, :initialize_user_features_storage
26
+ def_delegators :configuration, :application_root, :application_root=, :initialize_application_root, :application_environment, :application_environment=, :initialize_application_environment,
27
+ :logger, :logger=, :initialize_logger, :cacheable, :cacheable=, :initialize_cacheable, :feature_store, :feature_store=, :user_features_storage, :user_features_storage=,
28
+ :feature_store_live_fetching, :feature_store_live_fetching=
27
29
 
28
30
  def configuration
29
31
  @configuration ||= Configuration.new
30
32
  end
31
33
 
32
34
  def redis_overrides
33
- load_redis_overrides
35
+ @redis_overrides ||= load_redis_overrides
34
36
  end
35
37
  def load_redis_overrides
36
- return {} if feature_store.nil?
38
+ return (@redis_overrides = {}) if feature_store.nil?
37
39
 
38
40
  redis_feature_hash = get_store_features.inject({}) do |output, feature|
39
41
  output.merge(feature => get_store_feature(feature))
40
42
  end
41
43
 
42
- downcase_keys(redis_feature_hash)
44
+ @redis_overrides = downcase_keys(redis_feature_hash)
45
+ rescue Exception => error
46
+ AbstractFeatureBranch.logger.error "AbstractFeatureBranch encounter an error in loading Redis Overrides!\n\nError:#{error.full_message}\n\n"
47
+ @redis_overrides = {}
43
48
  end
44
49
 
45
50
  def environment_variable_overrides
@@ -76,19 +81,22 @@ module AbstractFeatureBranch
76
81
  local_features[environment] ||= {}
77
82
  @environment_features[environment] = features[environment].
78
83
  merge(local_features[environment]).
79
- merge(environment_variable_overrides)
84
+ merge(environment_variable_overrides).
85
+ merge(redis_overrides)
80
86
  end
81
87
  def application_features
82
88
  unload_application_features unless cacheable?
83
89
  environment_features(application_environment)
84
90
  end
85
91
  def load_application_features
92
+ AbstractFeatureBranch.load_redis_overrides
86
93
  AbstractFeatureBranch.load_environment_variable_overrides
87
94
  AbstractFeatureBranch.load_features
88
95
  AbstractFeatureBranch.load_local_features
89
96
  AbstractFeatureBranch.load_environment_features(application_environment)
90
97
  end
91
98
  def unload_application_features
99
+ @redis_overrides = nil
92
100
  @environment_variable_overrides = nil
93
101
  @features = nil
94
102
  @local_features = nil
@@ -1,29 +1,55 @@
1
1
  class Object
2
- raise 'Abstract feature branch conflicts with another Ruby library' if respond_to?(:feature_branch)
3
- def self.feature_branch(feature_name, user_id = nil, &feature_work)
4
- if feature_enabled?(feature_name, user_id)
5
- feature_work.call
2
+ class << self
3
+ raise 'Abstract feature branch conflicts with another Ruby library having Object::feature_branch' if respond_to?(:feature_branch)
4
+ def feature_branch(feature_name, user_id = nil, &feature_work)
5
+ if feature_enabled?(feature_name, user_id)
6
+ feature_work.call
7
+ end
6
8
  end
7
- end
8
-
9
- raise 'Abstract feature branch conflicts with another Ruby library' if respond_to?(:feature_enabled?)
10
- def self.feature_enabled?(feature_name, user_id = nil)
11
- normalized_feature_name = feature_name.to_s.downcase
12
-
13
- redis_override_value = AbstractFeatureBranch.get_store_feature(normalized_feature_name) rescue nil
14
- value = !redis_override_value.nil? ? redis_override_value : AbstractFeatureBranch.application_features[normalized_feature_name]
15
- if value == 'per_user'
16
- value = !user_id.nil? && AbstractFeatureBranch.user_features_storage.sismember("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}#{normalized_feature_name}", user_id)
9
+
10
+ raise 'Abstract feature branch conflicts with another Ruby library having Object::feature_enabled?' if respond_to?(:feature_enabled?)
11
+ def feature_enabled?(feature_name, user_id = nil)
12
+ normalized_feature_name = feature_name.to_s.downcase
13
+
14
+ redis_override_value = feature_enabled_reddis_override_value(normalized_feature_name)
15
+ value = !redis_override_value.nil? ? redis_override_value : AbstractFeatureBranch.application_features[normalized_feature_name]
16
+ if value == 'per_user'
17
+ value = !user_id.nil? && feature_enabled_per_user_value(feature_name, user_id)
18
+ end
19
+ value
20
+ end
21
+
22
+ private
23
+
24
+ raise 'Abstract feature branch conflicts with another Ruby library having Object::feature_enabled_reddis_override_value' if Object.new.respond_to?(:feature_enabled_reddis_override_value, true)
25
+ def feature_enabled_reddis_override_value(normalized_feature_name)
26
+ if AbstractFeatureBranch.configuration.feature_store_live_fetching?
27
+ begin
28
+ AbstractFeatureBranch.get_store_feature(normalized_feature_name)
29
+ rescue Exception => error
30
+ AbstractFeatureBranch.logger.error "AbstractFeatureBranch encountered an error in retrieving Redis Override value for feature \"#{normalized_feature_name}\"! Defaulting to YAML configuration value...\n\nError: #{error.full_message}\n\n"
31
+ nil
32
+ end
33
+ end
34
+ end
35
+
36
+ raise 'Abstract feature branch conflicts with another Ruby library having Object::feature_enabled_per_user_value' if Object.new.respond_to?(:feature_enabled_per_user_value, true)
37
+ def feature_enabled_per_user_value(normalized_feature_name, user_id)
38
+ begin
39
+ AbstractFeatureBranch.user_features_storage.sismember("#{AbstractFeatureBranch::ENV_FEATURE_PREFIX}#{normalized_feature_name}", user_id)
40
+ rescue Exception => error
41
+ AbstractFeatureBranch.logger.error "AbstractFeatureBranch encountered an error in retrieving Per-User value for feature \"#{normalized_feature_name}\" and user_id #{user_id}! Defaulting to nil value...\n\nError: #{error.full_message}\n\n"
42
+ nil
43
+ end
17
44
  end
18
- value
19
45
  end
20
-
21
- raise 'Abstract feature branch conflicts with another Ruby library' if Object.new.respond_to?(:feature_branch)
46
+
47
+ raise 'Abstract feature branch conflicts with another Ruby library having Object#feature_branch' if Object.new.respond_to?(:feature_branch)
22
48
  def feature_branch(feature_name, user_id = nil, &feature_work)
23
49
  Object.feature_branch(feature_name.to_s, user_id, &feature_work)
24
50
  end
25
51
 
26
- raise 'Abstract feature branch conflicts with another Ruby library' if Object.new.respond_to?(:feature_enabled?)
52
+ raise 'Abstract feature branch conflicts with another Ruby library having Object#feature_enabled?' if Object.new.respond_to?(:feature_enabled?)
27
53
  def feature_enabled?(feature_name, user_id = nil)
28
54
  Object.feature_enabled?(feature_name.to_s, user_id)
29
55
  end
@@ -7,6 +7,10 @@
7
7
  # The following example line works with Heroku Redis To Go while still operating on local Redis for local development
8
8
  # AbstractFeatureBranch.feature_store = Redis.new(:url => ENV['REDISTOGO_URL'])
9
9
 
10
+ # Enable live fetching of feature configuration from storage system, to update features without app/server restart.
11
+ # false by default to only load features on app/server start for faster performance (requires restart on change)
12
+ AbstractFeatureBranch.feature_store_live_fetching = false
13
+
10
14
  # Application root where config/features.yml or config/features/ is found
11
15
  AbstractFeatureBranch.application_root = Rails.root
12
16
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abstract_feature_branch
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.3
5
5
  platform: ruby
6
6
  authors:
7
- - Annas "Andy" Maleh
7
+ - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-12 00:00:00.000000000 Z
11
+ date: 2023-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deep_merge
@@ -95,25 +95,19 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: 3.3.4
97
97
  description: |
98
- abstract_feature_branch is a Rails gem that enables developers to easily branch by abstraction as per this pattern:
99
- http://paulhammant.com/blog/branch_by_abstraction.html
98
+ abstract_feature_branch is a Ruby gem that provides a variation on the Branch by Abstraction Pattern by Paul Hammant and the Feature Toggles Pattern by Martin Fowler.
100
99
 
101
100
  It is a productivity and fault tolerance enhancing team practice.
102
101
 
103
- It provides the ability to wrap blocks of code with an abstract feature branch name, and then
104
- specify in a configuration file which features to be switched on or off.
102
+ It provides the ability to wrap blocks of code with an abstract feature branch name, and then specify in a configuration file which features to be switched on or off.
105
103
 
106
- The goal is to build out upcoming features in the same source code repository branch, regardless of whether all are
107
- completed by the next release date or not, thus increasing team productivity by preventing integration delays.
108
- Developers then disable in-progress features until they are ready to be switched on in production, yet enable them
109
- locally and in staging environments for in-progress testing.
104
+ The goal is to build out upcoming features in the same source code repository branch (i.e. continuous integration), 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.
110
105
 
111
- This gives developers the added benefit of being able to switch a feature off after release should big problems arise
112
- for a high risk feature.
106
+ 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.
113
107
 
114
- abstract_feature_branch additionally supports DDD's pattern of
115
- Bounded Contexts by allowing developers to configure
116
- context-specific feature files if needed.
108
+ abstract_feature_branch additionally supports Domain Driven Design's pattern of Bounded Contexts by allowing developers to configure context-specific feature files if needed.
109
+
110
+ abstract_feature_branch is one of the simplest and most minimalistic "Feature Flags" Ruby gems out there as it enables you to get started very quickly by simply leveraging YAML files without having to set up a data store if you do not need it (albeit, you also have the option to use Redis as a very fast in-memory data store).
117
111
  email:
118
112
  executables: []
119
113
  extensions: []
@@ -168,9 +162,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
168
162
  - !ruby/object:Gem::Version
169
163
  version: '0'
170
164
  requirements: []
171
- rubygems_version: 3.3.6
165
+ rubygems_version: 3.1.4
172
166
  signing_key:
173
167
  specification_version: 4
174
- summary: 'abstract_feature_branch is a Rails gem that enables developers to easily
175
- branch by abstraction as per this pattern: http://paulhammant.com/blog/branch_by_abstraction.html'
168
+ summary: abstract_feature_branch is a Ruby gem that provides a variation on the Branch
169
+ by Abstraction Pattern by Paul Hammant and the Feature Toggles Pattern by Martin
170
+ Fowler (aka Feature Flags).
176
171
  test_files: []