Skip to content

Commit

Permalink
Allow passing blocks to is_enabled? to determine default_result (#33)
Browse files Browse the repository at this point in the history
- add docs relevant to feature in README
- add unit tests
  • Loading branch information
rarruda committed Jun 3, 2021
1 parent e3d47f5 commit b5a4dbb
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ Style/HashTransformKeys:
Style/HashTransformValues:
Enabled: true

Style/DoubleNegation:
Enabled: false

Style/IfInsideElse:
Exclude:
- 'bin/unleash-client'
Expand Down
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,40 @@ if UNLEASH.is_enabled? "AwesomeFeature", @unleash_context, true
end
```

Another possibility is to send a block, [Lambda](https://ruby-doc.org/core-3.0.1/Kernel.html#method-i-lambda) or [Proc](https://ruby-doc.org/core-3.0.1/Proc.html#method-i-yield)
to evaluate the default value:

```ruby
net_check_proc = proc do |feature_name, context|
context.remote_address.starts_with?("10.0.0.")
end

if UNLEASH.is_enabled?("AwesomeFeature", @unleash_context, &net_check_proc)
puts "AwesomeFeature is enabled by default if you are in the 10.0.0.* network."
end
```

or

```ruby
awesomeness = 10
@unleash_context.properties[:coolness] = 10

if UNLEASH.is_enabled?("AwesomeFeature", @unleash_context) { |feat, ctx| awesomeness >= 6 && ctx.properties[:coolness] >= 8 }
puts "AwesomeFeature is enabled by default if both the user has a high enought coolness and the application has a high enough awesomeness"
end
```

Note:
- The block/lambda/proc can use feature name and context as an arguments.
- The client will evaluate the fallback function once per call of `is_enabled()`.
Please keep this in mind when creating your fallback function!
- The returned value of the block should be a boolean.
However the client will coerce the result to boolean via `!!`.
- If both a `default_value` and `fallback_function` are supplied,
the client will define the default value by `OR`ing the default value and the output of the fallback function.


Alternatively by using `if_enabled` you can send a code block to be executed as a parameter:

```ruby
Expand All @@ -209,6 +243,8 @@ UNLEASH.if_enabled "AwesomeFeature", @unleash_context, true do
end
```

Note: `if_enabled` only supports `default_value`, but not `fallback_function`.

##### Variations

If no variant is found in the server, use the fallback variant.
Expand All @@ -232,6 +268,7 @@ Method Name | Description | Return Type |
`shutdown` | Save metrics to disk, flush metrics to server, and then kill ToggleFetcher and MetricsReporter threads. A safe shutdown. Not really useful in long running applications, like web applications. | nil |
`shutdown!` | Kill ToggleFetcher and MetricsReporter threads immediately. | nil |

For the full method signatures, please see [client.rb](lib/unleash/client.rb)

## Local test client

Expand Down
8 changes: 7 additions & 1 deletion lib/unleash/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@ def initialize(*opts)
start_metrics unless Unleash.configuration.disable_metrics
end

def is_enabled?(feature, context = nil, default_value = false)
def is_enabled?(feature, context = nil, default_value_param = false, &fallback_blk)
Unleash.logger.debug "Unleash::Client.is_enabled? feature: #{feature} with context #{context}"

if block_given?
default_value = default_value_param || !!fallback_blk.call(feature, context)
else
default_value = default_value_param
end

if Unleash.configuration.disable_client
Unleash.logger.warn "unleash_client is disabled! Always returning #{default_value} for feature #{feature}!"
return default_value
Expand Down
118 changes: 115 additions & 3 deletions spec/unleash/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,19 +212,131 @@
expect(WebMock).not_to have_requested(:post, 'http://test-url//client/metrics')
end

it "should return default results if running with disable_client" do
it "should return default results via block or param if running with disable_client" do
Unleash.configure do |config|
config.disable_client = true
end
unleash_client = Unleash::Client.new

expect(
unleash_client.is_enabled?('any_feature')
).to be false

expect(
unleash_client.is_enabled?('any_feature', {}, true)
).to eq(true)
).to be true

expect(
unleash_client.is_enabled?('any_feature2', {}, false)
).to eq(false)
).to be false

expect(
unleash_client.is_enabled?('any_feature3') { true }
).to be true

expect(
unleash_client.is_enabled?('any_feature3') { false }
).to be false

expect(
unleash_client.is_enabled?('any_feature3', {}) { true }
).to be true

expect(
unleash_client.is_enabled?('any_feature3', {}) { false }
).to be false

expect(
unleash_client.is_enabled?('any_feature5', {}) { nil }
).to be false

# should never really send both the default value and a default block,
# but if it is done, we OR the two values
expect(
unleash_client.is_enabled?('any_feature3', {}, true) { true }
).to be true

expect(
unleash_client.is_enabled?('any_feature3', {}, false) { true }
).to be true

expect(
unleash_client.is_enabled?('any_feature3', {}, true) { false }
).to be true

expect(
unleash_client.is_enabled?('any_feature3', {}, false) { false }
).to be false

expect(
unleash_client.is_enabled?('any_feature5', {}) { 'random_string' }
).to be true # expect "a string".to be_truthy

expect do |b|
unleash_client.is_enabled?('any_feature3', &b).to yield_with_no_args
end

expect do |b|
unleash_client.is_enabled?('any_feature3', {}, &b).to yield_with_no_args
end

expect do |b|
unleash_client.is_enabled?('any_feature3', {}, true, &b).to yield_with_no_args
end

number_eight = 8
expect(
unleash_client.is_enabled?('any_feature5', {}) do
number_eight >= 5
end
).to be true

expect(
unleash_client.is_enabled?('any_feature5', {}) do
number_eight < 5
end
).to be false

context_params = {
session_id: 'verylongsesssionid',
remote_address: '127.0.0.2',
properties: {
env: 'dev'
}
}
unleash_context = Unleash::Context.new(context_params)
expect(
unleash_client.is_enabled?('any_feature6', unleash_context) do |feature, context|
feature == 'any_feature6' && \
context.remote_address == '127.0.0.2' && context.session_id.length == 18 && context.properties[:env] == 'dev'
end
).to be true

proc = proc do |_feat, ctx|
ctx.remote_address.starts_with?("127.0.0.")
end
expect(
unleash_client.is_enabled?('any_feature6', unleash_context) { proc }
).to be true
expect(
unleash_client.is_enabled?('any_feature6', unleash_context, true) { proc }
).to be true
expect(
unleash_client.is_enabled?('any_feature6', unleash_context, false) { proc }
).to be true

proc_feat = proc do |feat, _ctx|
feat != 'feature6'
end
expect(
unleash_client.is_enabled?('feature6', unleash_context, &proc_feat)
).to be false
expect(
unleash_client.is_enabled?('feature6', unleash_context, true, &proc_feat)
).to be true
expect(
unleash_client.is_enabled?('feature6', unleash_context, false, &proc_feat)
).to be false
end

it "should not connect anywhere if running with disable_client" do
Expand Down

0 comments on commit b5a4dbb

Please sign in to comment.