Code Monkey home page Code Monkey logo

unleash-client-ruby's Introduction

Unleash::Client

Build Status Coverage Status Gem Version

Unleash client so you can roll out your features with confidence.

Leverage the Unleash Server for powerful feature toggling in your ruby/rails applications.

Supported Ruby Interpreters

  • MRI 3.3
  • MRI 3.2
  • MRI 3.1
  • MRI 3.0
  • MRI 2.7
  • MRI 2.6
  • MRI 2.5
  • jruby 9.4
  • jruby 9.3
  • jruby 9.2

Installation

Add this line to your application's Gemfile:

gem 'unleash', '~> 5.0.0'

And then execute:

$ bundle

Or install it yourself as:

$ gem install unleash

Configure

It is required to configure:

  • url of the unleash server
  • app_name with the name of the runninng application.
  • custom_http_headers with {'Authorization': '<API token>'} when using Unleash v4.0.0 and later.

Please substitute the example 'https://unleash.herokuapp.com/api' for the url of your own instance.

It is highly recommended to configure:

  • instance_id parameter with a unique identifier for the running instance.
Unleash.configure do |config|
  config.app_name            = 'my_ruby_app'
  config.url                 = 'https://unleash.herokuapp.com/api'
  config.custom_http_headers = {'Authorization': '<API token>'}
end

or instantiate the client with the valid configuration:

UNLEASH = Unleash::Client.new(url: 'https://unleash.herokuapp.com/api', app_name: 'my_ruby_app', custom_http_headers: {'Authorization': '<API token>'})

Dynamic custom HTTP headers

If you need custom HTTP headers that change during the lifetime of the client, the custom_http_headers can be given as a Proc.

Unleash.configure do |config|
  config.app_name            = 'my_ruby_app'
  config.url                 = 'https://unleash.herokuapp.com/api'
  config.custom_http_headers =  proc do
    {
      'Authorization': '<API token>',
      'X-Client-Request-Time': Time.now.iso8601
    }
  end
end

List of Arguments

Argument Description Required? Type Default Value
url Unleash server URL. Y String N/A
app_name Name of your program. Y String N/A
instance_id Identifier for the running instance of program. Important so you can trace back to where metrics are being collected from. Highly recommended be be set. N String random UUID
environment Unleash context option. Could be for example prod or dev. Not yet in use. Not the same as the SDK's Unleash environment. N String default
project_name Name of the project to retrieve features from. If not set, all feature flags will be retrieved. N String nil
refresh_interval How often the unleash client should check with the server for configuration changes. N Integer 15
metrics_interval How often the unleash client should send metrics to server. N Integer 60
disable_client Disables all communication with the Unleash server, effectively taking it offline. If set, is_enabled? will always answer with the default_value and configuration validation is skipped. Will also forcefully set disable_metrics to true. Defeats the entire purpose of using unleash, except when running tests. N Boolean false
disable_metrics Disables sending metrics to Unleash server. If the disable_client option is set to true, then this option will also be set to true, regardless of the value provided. N Boolean false
custom_http_headers Custom headers to send to Unleash. As of Unleash v4.0.0, the Authorization header is required. For example: {'Authorization': '<API token>'} N Hash/Proc {}
timeout How long to wait for the connection to be established or wait in reading state (open_timeout/read_timeout) N Integer 30
retry_limit How many consecutive failures in connecting to the Unleash server are allowed before giving up. The default is to retry indefinitely. N Float::INFINITY 5
backup_file Filename to store the last known state from the Unleash server. Best to not change this from the default. N String Dir.tmpdir + "/unleash-#{app_name}-repo.json
logger Specify a custom Logger class to handle logs for the Unleash client. N Class Logger.new(STDOUT)
log_level Change the log level for the Logger class. Constant from Logger::Severity. N Constant Logger::WARN
bootstrap_config Bootstrap config on how to loaded data on start-up. This is useful for loading large states on startup without (or before) hitting the network. N Unleash::Bootstrap::Configuration nil
strategies Strategies manager that holds all strategies and allows to add custom strategies N Unleash::Strategies Unleash::Strategies.new

For a more in-depth look, please see lib/unleash/configuration.rb.

Environment Variable Description
UNLEASH_BOOTSTRAP_FILE File to read bootstrap data from
UNLEASH_BOOTSTRAP_URL URL to read bootstrap data from

Usage in a plain Ruby Application

require 'unleash'
require 'unleash/context'

@unleash = Unleash::Client.new(app_name: 'my_ruby_app', url: 'https://unleash.herokuapp.com/api', custom_http_headers: { 'Authorization': '<API token>' })

feature_name = "AwesomeFeature"
unleash_context = Unleash::Context.new
unleash_context.user_id = 123

if @unleash.is_enabled?(feature_name, unleash_context)
  puts " #{feature_name} is enabled according to unleash"
else
  puts " #{feature_name} is disabled according to unleash"
end

if @unleash.is_disabled?(feature_name, unleash_context)
  puts " #{feature_name} is disabled according to unleash"
else
  puts " #{feature_name} is enabled according to unleash"
end

Usage in a Rails Application

Add Initializer

Put in config/initializers/unleash.rb:

Unleash.configure do |config|
  config.app_name = Rails.application.class.parent.to_s
  config.url      = 'https://unleash.herokuapp.com/api'
  # config.instance_id = "#{Socket.gethostname}"
  config.logger   = Rails.logger
end

UNLEASH = Unleash::Client.new

# Or if preferred:
# Rails.configuration.unleash = Unleash::Client.new

For config.instance_id use a string with a unique identification for the running instance. For example: it could be the hostname, if you only run one App per host. Or the docker container id, if you are running in docker. If it is not set the client will generate an unique UUID for each execution.

To have it available in the rails console command as well, also add to the file above:

Rails.application.console do
  UNLEASH = Unleash::Client.new
  # or
  # Rails.configuration.unleash = Unleash::Client.new
end

Add Initializer if using Puma in clustered mode

That is, multiple workers configured in puma.rb:

workers ENV.fetch("WEB_CONCURRENCY") { 2 }
with preload_app!

Then you may keep the client configuration still in config/initializers/unleash.rb:

Unleash.configure do |config|
  config.app_name    = Rails.application.class.parent.to_s
  config.url                 = 'https://unleash.herokuapp.com/api'
  config.custom_http_headers = {'Authorization': '<API token>'}
end

But you must ensure that the unleash client is instantiated only after the process is forked. This is done by creating the client inside the on_worker_boot code block in puma.rb as below:

#...
preload_app!
#...

on_worker_boot do
  # ...

  ::UNLEASH = Unleash::Client.new
end

on_worker_shutdown do
  ::UNLEASH.shutdown
end
without preload_app!

By not using preload_app!:

  • the Rails constant will NOT be available.
  • but phased restarts will be possible.

You need to ensure that in puma.rb:

  • loading unleash sdk with require 'unleash' explicitly, as it will not be pre-loaded.
  • all parameters must be explicitly set in the on_worker_boot block, as config/initializers/unleash.rb is not read.
  • there are no references to Rails constant, as that is not yet available.

Example for puma.rb:

require 'unleash'

#...
# no preload_app!

on_worker_boot do
  # ...

  ::UNLEASH = Unleash::Client.new(
    app_name: 'my_rails_app',
    url: 'https://unleash.herokuapp.com/api',
    custom_http_headers: {'Authorization': '<API token>'},
  )
end

on_worker_shutdown do
  ::UNLEASH.shutdown
end

Note that we also added shutdown hooks in on_worker_shutdown, to ensure a clean shutdown.

Add Initializer if using Phusion Passenger

The unleash client needs to be configured and instantiated inside the PhusionPassenger.on_event(:starting_worker_process) code block due to smart spawning:

The initializer in config/initializers/unleash.rb should look like:

PhusionPassenger.on_event(:starting_worker_process) do |forked|
  if forked
    Unleash.configure do |config|
      config.app_name    = Rails.application.class.parent.to_s
      # config.instance_id = "#{Socket.gethostname}"
      config.logger      = Rails.logger
      config.url                 = 'https://unleash.herokuapp.com/api'
      config.custom_http_headers = {'Authorization': '<API token>'}
    end

    UNLEASH = Unleash::Client.new
  end
end

Add Initializer hooks when using within Sidekiq

Note that in this case we require that the code block for Unleash.configure is set beforehand. For example in config/initializers/unleash.rb.

Sidekiq.configure_server do |config|
  config.on(:startup) do
    UNLEASH = Unleash::Client.new
  end

  config.on(:shutdown) do
    UNLEASH.shutdown
  end
end

Set Unleash::Context

Be sure to add the following method and callback in the application controller to have @unleash_context set for all requests:

Add in app/controllers/application_controller.rb:

  before_action :set_unleash_context

  private
  def set_unleash_context
    @unleash_context = Unleash::Context.new(
      session_id: session.id,
      remote_address: request.remote_ip,
      user_id: session[:user_id]
    )
  end

Or if you see better fit, only in the controllers that you will be using unleash.

Sample usage

Then wherever in your application that you need a feature toggle, you can use:

if UNLEASH.is_enabled? "AwesomeFeature", @unleash_context
  puts "AwesomeFeature is enabled"
end

or if client is set in Rails.configuration.unleash:

if Rails.configuration.unleash.is_enabled? "AwesomeFeature", @unleash_context
  puts "AwesomeFeature is enabled"
end

If you don't want to check a feature is disabled with unless, you can also use is_disabled?:

# so instead of:
unless UNLEASH.is_enabled? "AwesomeFeature", @unleash_context
  puts "AwesomeFeature is disabled"
end

# it might be more intelligible:
if UNLEASH.is_disabled? "AwesomeFeature", @unleash_context
  puts "AwesomeFeature is disabled"
end

If the feature is not found in the server, it will by default return false. However, you can override that by setting the default return value to true:

if UNLEASH.is_enabled? "AwesomeFeature", @unleash_context, true
  puts "AwesomeFeature is enabled by default"
end
# or
if UNLEASH.is_disabled? "AwesomeFeature", @unleash_context, true
  puts "AwesomeFeature is disabled by default"
end

Another possibility is to send a block, Lambda or Proc to evaluate the default value:

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

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 enough 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 ORing the default value and the output of the fallback function.

Alternatively by using if_enabled (or if_disabled) you can send a code block to be executed as a parameter:

UNLEASH.if_enabled "AwesomeFeature", @unleash_context, true do
  puts "AwesomeFeature is enabled by default"
end

Note: if_enabled (and if_disabled) only support default_value, but not fallback_function.

Variations

If no variant is found in the server, use the fallback variant.

fallback_variant = Unleash::Variant.new(name: 'default', enabled: true, payload: {"color" => "blue"})
variant = UNLEASH.get_variant "ColorVariants", @unleash_context, fallback_variant

puts "variant color is: #{variant.payload.fetch('color')}"

Bootstrapping

Bootstrap configuration allows the client to be initialized with a predefined set of toggle states. Bootstrapping can be configured by providing a bootstrap configuration when initializing the client.

@unleash = Unleash::Client.new(
    url: 'https://unleash.herokuapp.com/api',
    app_name: 'my_ruby_app',
    custom_http_headers: { 'Authorization': '<API token>' },
    bootstrap_config: Unleash::Bootstrap::Configuration.new({
        url: "https://unleash.herokuapp.com/api/client/features",
        url_headers: {'Authorization': '<API token>'}
    })
)

The Bootstrap::Configuration initializer takes a hash with one of the following options specified:

  • file_path - An absolute or relative path to a file containing a JSON string of the response body from the Unleash server. This can also be set though the UNLEASH_BOOTSTRAP_FILE environment variable.
  • url - A url pointing to an Unleash server's features endpoint, the code sample above is illustrative. This can also be set though the UNLEASH_BOOTSTRAP_URL environment variable.
  • url_headers - Headers for the GET http request to the url above. Only used if the url parameter is also set. If this option isn't set then the bootstrapper will use the same url headers as the Unleash client.
  • data - A raw JSON string as returned by the Unleash server.
  • block - A lambda containing custom logic if you need it, an example is provided below.

You should only specify one type of bootstrapping since only one will be invoked and the others will be ignored. The order of preference is as follows:

  • Select a data bootstrapper if it exists.
  • If no data bootstrapper exists, select the block bootstrapper.
  • If no block bootstrapper exists, select the file bootstrapper from either parameters or the specified environment variable.
  • If no file bootstrapper exists, then check for a URL bootstrapper from either the parameters or the specified environment variable.

Example usage:

First saving the toggles locally:

curl -H 'Authorization: <API token>' -XGET 'https://unleash.herokuapp.com/api' > ./default-toggles.json

Now using them on start up:

custom_boostrapper = lambda {
  File.read('./default-toggles.json')
}

@unleash = Unleash::Client.new(
    app_name: 'my_ruby_app',
    url: 'https://unleash.herokuapp.com/api',
    custom_http_headers: { 'Authorization': '<API token>' },
    bootstrap_config: Unleash::Bootstrap::Configuration.new({
        block: custom_boostrapper
    })
)

This example could be easily achieved with a file bootstrapper, this is just to illustrate the usage of custom bootstrapping. Be aware that the client initializer will block until bootstrapping is complete.

Client methods

Method Name Description Return Type
is_enabled? Check if feature toggle is to be enabled or not. Boolean
enabled? Alias to the is_enabled? method. But more ruby idiomatic. Boolean
if_enabled Run a code block, if a feature is enabled. yield
is_disabled? Check if feature toggle is to be enabled or not. Boolean
disabled? Alias to the is_disabled? method. But more ruby idiomatic. Boolean
if_disabled Run a code block, if a feature is disabled. yield
get_variant Get variant for a given feature Unleash::Variant
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

Local test client

# cli unleash client:
bundle exec bin/unleash-client --help

# or a simple sample implementation (with values hardcoded):
bundle exec examples/simple.rb

Available Strategies

This client comes with the all the required strategies out of the box:

  • ApplicationHostnameStrategy
  • DefaultStrategy
  • FlexibleRolloutStrategy
  • GradualRolloutRandomStrategy
  • GradualRolloutSessionIdStrategy
  • GradualRolloutUserIdStrategy
  • RemoteAddressStrategy
  • UnknownStrategy
  • UserWithIdStrategy

Custom Strategies

Client allows to add custom activation strategies using configuration. In order for strategy to work correctly it should support two methods name and is_enabled?

class MyCustomStrategy
  def name
    'myCustomStrategy'
  end

  def is_enabled?(params = {}, context = nil)
    true
  end
end

Unleash.configure do |config|
  config.strategies.add(MyCustomStrategy.new)
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

This SDK is also built against the Unleash Client Specification tests. To run the Ruby SDK against this test suite, you'll need to have a copy on your machine, you can clone the repository directly using:

git clone --depth 5 --branch v5.0.2 https://github.com/Unleash/client-specification.git client-specification

After doing this, rake spec will also run the client specification tests.

To install this gem onto your local machine, run bundle exec rake install.

Releasing

Choose a new version number following Semantic Versioning semantics and then:

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/unleash/unleash-client-ruby.

Be sure to run both bundle exec rspec and bundle exec rubocop in your branch before creating a pull request.

Please include tests with any pull requests, to avoid regressions.

Check out our guide for more information on how to build and scale feature flag systems

unleash-client-ruby's People

Contributors

aldent95 avatar anakinj avatar andreas-unleash avatar ben3eee avatar epicatization avatar gardleopard avatar gen1321 avatar gingermusketeer avatar imaximix avatar ivarconr avatar jamesyang124 avatar jdurkee-mdsol avatar malclocke avatar petergoldstein avatar rarruda avatar serhii-bodnaruk avatar shieldo avatar sighphyre avatar simenb avatar tanmayj28 avatar thomasheartman avatar thorion3006 avatar tmaier avatar tooooooooomy avatar volodymyr-mykhailyk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

unleash-client-ruby's Issues

feat: Custom stickiness for variants

This is kind of similar to #41, but for variants.

The stickiness will be defined as a field on all the variants in the variants array, this to ensure we are not breaking our protocol. In the example we can see the stickiness is configured to "tenantId".

{"variants": [
     {
		"name": "yellow",
		"weight": 166,
		"weightType": "variable",
		"payload": {
			"type": "string",
			"value": "yellow submarine"
		},
		"overrides": [],
		"stickiness": "tenantId"
	}, {
		"name": "salmon",
		"weight": 166,
		"weightType": "variable",
		"payload": {
			"type": "string",
			"value": "salmon"
		},
		"overrides": [],
		"stickiness": "tenantId"
	}
]}

How it looks in Unleash Admin UI:
image

Edge cases:
If no stickiness is defined the SDK should assume "default" stickiness as today.

To guide the implementation we have:

  • added new client-specifications, see pr-11
  • implemented PoC in Node SDK see pr-202

Feature Toggles not working in Rails

Hi,
I am created a new feature flag and with a strategy to only work for users that have a specific property, but for some reason this isn't working for me. What am I doing wrong?

I did the following:

1. I created a feature toggle called 'nogah_test'
Screen Shot 2019-10-30 at 14 04 22

2. I added a strategy called companyId which is a list of companyIds
Screen Shot 2019-10-30 at 14 03 17

3. I added the strategy to my feature toggle with a specific companyId of 10 (my company Id)
Screen Shot 2019-10-30 at 14 06 22

4. My code looks like this:
This is my @unleash_context
Screen Shot 2019-10-30 at 14 07 38

when I do:
Unleash::Client.new.is_enabled?('nogah_test', @unleash_context)

it returns true, but when I just add a random companyId to the context or even change properties to be an empty hash, I still get true. It ignores the properties hash for some reason.
It only checks to see if the feature is enabled or disabled.

We know that a similar implementation works for Node.js, but here it doesn't.
Could it be that it just doesn't work in Ruby?

How come??? Thanks.

Features url does not appear to be correct

Had some issues getting this going in a rails app until I realised that I did not have the URL correct.
In order for it to work for me I have to set the config.url to http://localhost:4242/api/client However this causes the metrics gathering to break due a request being made to http://localhost:4242/api/client/client/metrics

Should https://github.com/Unleash/unleash-client-ruby/blob/master/lib/unleash/configuration.rb#L56 include the client prefix like the other URLS?

When client is disabled, but metrics are not, metrics are referenced but never initialized.

Describe the bug

When disabling the client but not disabling metrics, the metrics attribute/container is never initialized. Yet, attempting to call enabled? on the client results in undefined method ... for nil:NilClass as it attempts to access metrics.

Steps to reproduce the bug

Unleash::Client.new(
  disable_client: true,
  url: 'https://example.com',
  bootstrap_config: Unleash::Bootstrap::Configuration.new(
    data: '{
      "version": 1,
      "features": [
        {
          "enabled": true,
          "name": "featureX"
        }
      ]
    }'
  )
).enabled?('featureX', nil)

Expected behavior

No runtime errors, and correct state of the feature.

Logs, error output, etc.

[2023-05-01T18:45:55.551253+00:00          Unleash WARN ] : Unleash::Client is disabled! Will only return default (or bootstrapped if available) results!
/shared/unleash/lib/unleash/feature_toggle.rb:30:in `is_enabled?': undefined method `increment' for nil:NilClass (NoMethodError)

      Unleash.toggle_metrics.increment(name, choice) unless Unleash.configuration.disable_metrics
                            ^^^^^^^^^^
        from /shared/unleash/lib/unleash/client.rb:50:in `is_enabled?'
        from unleash_client.rb:77:in `<main>'

Screenshots

No response

Additional context

No response

Unleash version

Ruby SDK 4.4.2

Subscription type

Enterprise

Hosting type

Hosted by Unleash

SDK information (language and version)

Ruby SDK 4.4.2

Provide a #to_h method for Unleash::Context

Describe the feature request

The instance of context should be able to be converted to a hash.
The ruby naming convention is a #to_h method to do so.

Background

Sometimes, one wants to re-use the context set. e.g. to report it to Sentry or any other exception tracker.

Solution suggestions

Add a to_h method to Unleash::Context

fix: metrics only sent if not empty

The Ruby SDK sent metrics even if it is empty. Instead it can hold on to the local metrics longer if there are no metrics to report (e.g. up to 10min).

context_value in Constraint#matches_constraint? throws FrozenError when fronzen-string-literal magic comment is true in the calling context

Describe the bug

When frozen-string-literal magic comment is used in a file that calls the Unleash#is_enabled? method, FrozenError exception is raised if the constraint has case-insensitive enabled.

Steps to reproduce the bug

No response

Expected behavior

No response

Logs, error output, etc.

No response

Screenshots

No response

Additional context

No response

Unleash version

v5.3.0

Subscription type

Open source

Hosting type

Self-hosted

SDK information (language and version)

Ruby v4.5.0

Feature flags are not updated using passenger

The client doesn't update unleash feature flags information after the application boots using Phusion Passenger as the aplication server. I have tried with WEBrick and Puma, both work!

The README mentions an specific configuration to work on Puma, is there such configuration for Passenger?

Environment:

  • Ruby (2.6.6)
  • Ruby on Rails (5.2.5)
  • Linux (Ubuntu 20.04.2 LTS)
  • Passenger: Standalone

Gems

  • passenger (6.0.7)
  • unleash (3.2.2)

Files

# config/initializers/unleash.rb
Unleash.configure do |config|
  config.url              = 'https://gitlab.com/api/v4/feature_flags/unleash/[SECRET]'
  config.instance_id      = '[SECRET]'
  config.app_name         = Rails.application.class.parent.to_s
  config.refresh_interval = 5
  config.logger           = Rails.logger
  config.environment      = Rails.env
end

UNLEASH = Unleash::Client.new
# Passenger.json
{
  "port": "3000",
  "max_pool_size":"2",
  "min_instances": "2",
  "engine": "nginx",
  "disable_anonymous_telemetry": true
}

Implement test-runner for client-specification.

So we I finally had the time to defining a set of client specifications, which defines what the expected result should be for a given set of feature toggles and a given Unleash context. The idea is that they shall ensure correct behaviour of clients across languages.

The specifications lives inside: https://github.com/Unleash/client-specification

The node and java client already implements support for automatically running the test cases as part of the CI validation.

Would be awesome if this client also could leverage the specifications. This will defenetly be used for v4, which will add new capabilities to the Unleash protocol.

Default environment variables for connection values

Describe the feature request

We're just starting to use Unleash and realized that all of our apps need the url, app_name and custom_http_headers set in the code. What we would like to do is set at least DEFAULT_UNLEASH_URL and maybe CUSTOM_HTTP_HEADERS at the platform level that the apps inherit env vars from. This means we don't have to go to each app and provide the url and headers per-app. The app_name would be the only per-app variable, which makes sense because its the only thing that would change between apps. If the URL were to change we could update it in one place and all apps would pick that up upon restart. We can always overwrite the default env vars per-app if needed for testing or modifying headers.

If this change is implemented the below code blocks would be functionally the same:

No parameters uses the default values specified by DEFAULT_UNLEASH_URL, DEFAULT_UNLEASH_APP_NAME, DEFAULT_UNLEASH_CUSTOM_HEADERS

require 'unleash'

@unleash = Unleash::Client.new()

Pulling in values from non-default env vars (or just hardcoding values here)

require 'unleash'

@unleash = Unleash::Client.new(
  url: ENV['UNLEASH_URL'],
  app_name: ENV['UNLEASH_APP_NAME'],
  custom_http_headers: ENV['UNLEASH_CUSTOM_HEADERS']
)

Background

Each of our apps has its own configuration but also inherits some environment variables provided by the platform. The platform variables usually are for things that are the same across all apps. Since our apps all use the same Unleash api server, the Unleash URL is the same for every app. It would be ideal to only have to specify this in one place instead of in every app. This also prevents the scenario where a developer forgot to add or update the Unleash api server URL and that one app failed to connect.

Solution suggestions

I would suggest creating at least 3 new env vars: DEFAULT_UNLEASH_URL, DEFAULT_UNLEASH_APP_NAME, and DEFAULT_UNLEASH_CUSTOM_HTTP_HEADERS

If the corresponding parameter is not provided in the call to Unleash::Client.new() then check for the correct DEFAULT_ variable and use that if present.

While the app_name is not something shared by all apps it would be good to have a DEFAULT_ for it so that the naming scheme for these variables is consistent. Potentially there could be a DEFAULT_ variable for every parameter that could be given to Client.new()

feat: add support for "project" query

The Unleash API client endpoint support query for specific project, to avoid having the SDK download all feature toggles like this:

http https://app.unleash-hosted.com/demo/api/client/features?project=solar 
Authorization:56907a2fa53c1d16101d509a10b78e36190b0f918d9f122d

Which will only return feature flags for projectId=solar.

This should be implemented by adding a new configuration to the unleash SDK to specify the "projectId" and if this option is specified it should be added to the feature-toggle query as a query param when fetching feature toggles from the Unleash API.

Inconsistent default result behavior with other Unleash clients

In Unleash's Ruby client, when feature toggle found disabled, enabled? method returns default result argument.

I checked the implementation in Go and Node, both just returns false instead of default result.

While digging further into this issue, I found that in those language's clients, they actually use default value (in form of function callback) for the case that toggle's not found.

Unlike in Ruby's implementation, the default value would be return when toggle not found and toggle not enabled.

Exception logging error

Hi, I noticed that I am getting an exception when the scheduler is trying to log an error:

/Users/gingermusketeer/.rubies/ruby-2.5.1/lib/ruby/2.5.0/logger.rb:544:in `error': wrong number of arguments (given 2, expected 0..1) (ArgumentError)
11:25:08 web.1     | 	from /Users/gingermusketeer/.gem/ruby/2.5.1/gems/unleash-0.1.2/lib/unleash/scheduled_executor.rb:27:in `rescue in block (2 levels) in run'
11:25:08 web.1     | 	from /Users/gingermusketeer/.gem/ruby/2.5.1/gems/unleash-0.1.2/lib/unleash/scheduled_executor.rb:22:in `block (2 levels) in run'
11:25:08 web.1     | 	from /Users/gingermusketeer/.gem/ruby/2.5.1/gems/unleash-0.1.2/lib/unleash/scheduled_executor.rb:17:in `loop'
11:25:08 web.1     | 	from /Users/gingermusketeer/.gem/ruby/2.5.1/gems/unleash-0.1.2/lib/unleash/scheduled_executor.rb:17:in

Is there an issue with my setup or is this part of the gem? It seems to be caused by there being two arguments to the log error call here:

Unleash.logger.error "thread #{name} throwing exception (#{self.retry_count} of #{self.max_exceptions})", e

I was able to reproduce this problem by running Unleash.logger.error 'one', 'two' from the rails console.

Variants in bootstrap file not respected when client is disabled.

Describe the bug

When the client is disabled, attempting to call client.get_variant always returns the default value, even if the toggle has been defined in the bootstrap config. Seems bootstrapped variant toggles should be loaded and available, just like their standard toggle brethen.

Steps to reproduce the bug

client = Unleash::Client.new(
  disable_client: true,
  url: 'https://example.com',
  bootstrap_config: Unleash::Bootstrap::Configuration.new(
    data: '{
      "version": 1,
      "features": [
        {
          "enabled": true,
          "name": "featureVariantX",
          "strategies": [{ "name": "default" }],
          "variants": [
            {
              "name": "default-value",
              "payload": {
                "type": "string",
                "value": "info"
              },
              "weight": 100,
              "weightType": "variable"
            }
          ]
        }
      ]
    }'
  )
)
pp client.get_variant('featureVariantX', nil)
[2023-04-27T22:35:22.506886+00:00          Unleash WARN ] : Unleash::Client is disabled! Will only return default (or bootstrapped if available) results!
#<Unleash::Variant:0x00007fd37763c930 @enabled=false, @name="disabled", @payload=nil>

Expected behavior

Toggle should be enabled and the payload should not be nil.

[2023-04-27T23:03:03.752292+00:00          Unleash WARN ] : Unleash::Client is disabled! Will only return default (or bootstrapped if available) results!
#<Unleash::Variant:0x00007f0622b23ea8 @enabled=true, @name="featureVariantX", @payload={:type=>"string", :value=>"info"}>

Logs, error output, etc.

No response

Screenshots

No response

Additional context

No response

Unleash version

Ruby SDK 4.4.2

Subscription type

Enterprise

Hosting type

Hosted by Unleash

SDK information (language and version)

Ruby SDK 4.4.2

Extend tests for bootstrapping priority

Right now the order of bootstrapping options is codified in source code but not through tests/specs. We should lay down some tests to ensure that the order of bootstrapping selector is defined.

Currently the order of priority is:

  • Data object is highest priority
  • Then the closure
  • Then the file bootstrapper
  • Then the url bootstrapper

This should be fixed in place with a test so that it isn't accidentally changed in future

Constraint and Strategy Order of Operations May be Wrong

Describe the bug

It seems that the Ruby SDK doesn't evaluate strategies and constraints in the same way as the other SDKs. While the other SDKs check constraints first, Ruby seems to check them second. This can potentially lead to different evaluation results when using this SDK.

It seems this is the line of code responsible for the check:

strategy_enabled?(s, context) && strategy_constraint_matches?(s, context)

On line 65 of feature_toggle.rb

Steps to reproduce the bug

Don't have steps to reproduce this yet, I'll update this ticket when we have a specification test that surfaces the issue

Expected behavior

This should evaluate strategies and constraints in the same way as the other SDKs - constraints first

Logs, error output, etc.

No response

Screenshots

No response

Additional context

No response

Unleash version

N/A

Subscription type

No response

Hosting type

No response

SDK information (language and version)

Affects latest version, v4.3 but probably a whole version range previously

Setting logger to Rails.logger unexpectedly changes log_level

I've had a heck of a time tracking down an issue with no logs being written in my rails app.

I have setup the gem with the recommended initialization approach for rails apps but there is some unexpected behavior that seems to be changing the rails logging level to warn and therefore preventing request logs from being written.

Despite the default rails logging level being debug setting the Unleash logger to Rails.logger somehow changes the level to warn.

Setup

  • Rails 5.2.3
  • Ruby 2.6.3 on alpine
  • Unleash 0.1.5
  • config.log_level is NOT set in application.rb or development.rb, therefore defaulting to debug

My Initializer when following the recommened Unleash config

Unleash.configure do |config|
  config.url      = Rails.application.credentials.unleash_url
  config.instance_id = Rails.application.credentials.unleash_instance_id
  config.app_name = Rails.env
  config.logger   = Rails.logger
end

Starting a rails console and running Rails.logger.level returns 2.

Initializer that doesn't change log level

Unleash.configure do |config|
  config.url      = Rails.application.credentials.unleash_url
  config.instance_id = Rails.application.credentials.unleash_instance_id
  config.app_name = Rails.env
  # config.logger   = Rails.logger
end

Starting a rails console and running Rails.logger.level returns 0.

My stackoverflow issue

Let me know if you need any other information to assist in the diagnosing this issue!

Strategy variants

Describe the feature request

Unleash has a big shift from the feature environment variants to strategy variants to allow more flexible variants based on constraints and segments.
The strategy variants take precedence over the feature environment variants.

Background

What we do today:
Screenshot 2023-08-14 at 10 04 37
What we want to add on top of existing variants:
Screenshot 2023-08-14 at 10 04 49

There's a video showing new strategy variants in action: Unleash/unleash#1550 (comment)

Solution suggestions

We already have a specification for strategy variants: https://github.com/Unleash/client-specification/blob/main/specifications/16-strategy-variants.json

We already did the migration for other SDKs:

The gist of the solution is to first check if the activation strategy has a variant/multiple variants attached. If so, select one of them. If none of the strategies have variants we fall back to the feature environment variants as before (for backwards compatibility). So in the new world each strategy resolves not just to enabled/disabled but when it's enabled it has some variant data attached. It makes the order of strategies matter.

feat: add support static context fields

In order to prepare the SDKS for strategy constraints we need to introduce two new fields in the Unleash Context

  • environment (default value is "default")
  • appName (already a required config parameter)

Both these static context fields should be configured at initialisation of the unleash SDK and be set as default values of the Unleash Context provided to the strategy implementations. The user is allowed to override these values in the context object at runtime (this is required to be backward compatible).

This support should be possible to add without breaking any contract and should be released as a minor version (3.3.x).

Puma initialization issue

I have setup the gem with the recommended initialization approach for rails apps but if there is an issue during startup It puts puma into a loop until it is able to connect to the unleash server.

This often has a result of requiring me to restart my computer as puma seems to create processes until this is successful.

Would it make sense for the initial connection to result in a warning instead?

Also is using the on_worker_boot required if using puma? The reason I ask is that it means that Rails.configuration.unleash is not present in the rails console. Having this available is handy for debugging.

fix: default intervals

We should normalize the intervals with other SDKs.

  • refresh_interval should default to 10s
  • metrics_interval should default to 30s.

Strategy list

I see that there are a few strategies that are showing up in the unleash server related to this client library:
screen shot 2018-07-05 at 4 19 20 pm

And from the Unleash::STRATEGIES constant we have:

{:default=>#<Unleash::Strategy::Default:0x00007fe37367f1e8>,
 :gradualRolloutRandom=>#<Unleash::Strategy::GradualRolloutRandom:0x00007fe37367f058>,
 :unknown=>#<Unleash::Strategy::Unknown:0x00007fe37367ef40>,
 :gradualRolloutUserId=>#<Unleash::Strategy::GradualRolloutUserId:0x00007fe37367edb0>,
 :applicationHostname=>#<Unleash::Strategy::ApplicationHostname:0x00007fe37367ec20 @hostname="">,
 :userWithId=>#<Unleash::Strategy::UserWithId:0x00007fe37367ea68>,
 :remoteAddress=>#<Unleash::Strategy::RemoteAddress:0x00007fe37367e8d8>,
 :base=>#<Unleash::Strategy::Base:0x00007fe37367e7c0>,
 :gradualRolloutSessionId=>#<Unleash::Strategy::GradualRolloutSessionId:0x00007fe37367e630>,
 :notImplemented=>#<Unleash::Strategy::NotImplemented: Unleash::Strategy::NotImplemented>}

Would it make sense to filter out the base class, unknown and notimplemented?

Sidekiq/Console usage in a Rails Application with puma setup

I've set up Unleash's Ruby client on a Rails application that is served by a Puma server, and for such I'm creating the client inside the on_worker_boot callback and stopping on before_fork - as described in the README.

It's has been working flawlessly, but since the client is only started as part of the Puma worker lifecycle, unleash is not available when spinning up a Rails console or inside Sidekiq workers.

Any ideas on how I can proceed with this and let Unleash available in others contexts? I tried to do some initialization magic by creating an initializer file that somehow checks if the current process is a Rails console or Sidekiq instance and have had no luck so far.

Basic authentication URL connection

Hey all, we have the server under a basic authentication and when setting the URL in the initializer like so "http://user:psw@the_url/api" it's not connecting we did a test with plain curl and all worked well and also our NodeJS server works properly with the same URL.
Is there a special thing we need to do to handle this issue?

feat: FlexibleRollout should support "custom" stickiness.

The Flexible Rollout strategy supports multiple options for stickiness, used to define how we guarantee consistency for a gradual rollout.

Today it support the following options:

  • default (Unleash chooses the first value present on the context in defined order userId, sessionId, random.)
  • userId
  • sessionId
  • random

We have now extended the protocol to support any field in the unleash context. In addition It can be any of the predefined context field, in addition to any custom properties the user has defined.

This means that the "stickiness" parameter in the strategy configuration can now be any field on the context, and the SDK should use the specified context field to calculate the "hash" which determine whether the activation strategy should evaluate to true or false.

How it looks in the UI:
image

Edge case:

  • If the specified context field is not specific the activation strategy should evaluate to false.

To guide the implementation we have:

  • added new client-specifications, see pr-11
  • implemented PoC in Node SDK see pr-201

Implement Global Segments

Describe the feature request

Just a feature that needs to be implemented - Global Segments. Unleash v4.13 supports enhanced responses for global segments, it would be great if this SDK can make use of this.

Background

Segments are effectively a way for Unleash users to define a list of constraints in such a way that makes them reusable across toggles without manually copying the constraint across to another toggle. Segments have two modes of operation, from the SDK's perspective, the inline mode will have no impact, segments will be remapped on the server side into constraints on the toggle information, no changes need to be . The second mode, global segments, requires that the SDK both opt in and handle the response differently. The handling should effectively result in unpacking the segments referenced in the feature strategies into a set of constraints. The changes required are described below.

Solution suggestions

Control Header

The SDK needs to pass up a Unleash-Client-Spec header with a semver value greater or equal to 4.2.0 (i.e. be greater or equal to the version of the unleash client spec tests where global segments are described) when hitting the get toggles endpoint on the Unleash server. This will enable the Unleash server to respond with the enhanced format

Example of the difference between enhanced and standard format:

Standard Format (default)

{
   "version": 2,
   "features": [  
       {
           "strategies": [
               {
                   "name": "flexibleRollout",
                   "constraints": [
                       {
                           "values": [
                               "31"
                           ],
                           "inverted": false,
                           "operator": "IN",
                           "contextName": "appName",
                           "caseInsensitive": false
                       }
                   ],          
                   "parameters": {
                       "groupId": "Test1",
                       "rollout": "100",
                       "stickiness": "default"
                   }
               }
           ],
           "name": "Test1"
       },
       {
           "strategies": [
               {
                   "name": "flexibleRollout",
                   "constraints": [
                       {
                           "values": [
                               "31"
                           ],
                           "inverted": false,
                           "operator": "IN",
                           "contextName": "appName",
                           "caseInsensitive": false
                       }
                   ],          
                   "parameters": {
                       "groupId": "Test2",
                       "rollout": "100",
                       "stickiness": "default"
                   }
               }
           ],
           "name": "Test2"
       }    
   ],
   "query": {
       "environment": "default"
   }
}

Enhanced Format (requires opt in)

{
   "version": 2,
   "features": [   
       {
           "strategies": [
               {
                   "name": "flexibleRollout",
                   "constraints": [],
                   "parameters": {
                       "groupId": "Test1",
                       "rollout": "100",
                       "stickiness": "default"
                   },
                   "segments": [
                       1
                   ]
               }
           ],
           "name": "Test1"
       },
       {
           "strategies": [
               {
                   "name": "flexibleRollout",
                   "constraints": [],
                   "parameters": {
                       "groupId": "Test2",
                       "rollout": "100",
                       "stickiness": "default"
                   },
                   "segments": [
                       1
                   ]
               }
           ],
           "name": "Test2"
       }     
   ],
   "query": {
       "environment": "default"
   },
   "segments": [
       {
           "id": 1,           
           "constraints": [
               {
                   "values": [
                       "31"
                   ],
                   "inverted": false,
                   "operator": "IN",
                   "contextName": "appName",
                   "caseInsensitive": false
               }
           ]           
       }
   ]
}

The relevant changes between the two formats are that in the enhanced format the segments are defined once as a global list and referenced within the strategy on the toggle by its ID. What's important to note is that the two above packets should be
handled identically, they reference the same toggle state.

Considerations

  • Global segments are intended to handle large datasets, how large has not been formally specified yet but expectations are around 1 000 to 10 000 segments across 1 000 toggles. As a result, time and space complexity of the implementations needs to be considered.
  • In the case of global segments, if the mapping from segment id to segment is incomplete i.e. a segment id is referenced in a toggle strategy that doesn’t map back to the global segments list, then the toggle should be evaluated to false. This is enforced through one of the client specification tests in v4.2.0 of the client spec
  • A reference implementation is provided in node JS: Unleash/unleash-client-node#329 (note that this doesn't include the header, that can be seen here: Unleash/unleash-client-node#335)

Default result always override result when it set as true

I am testing the combinations with default result and seems like it does not work as expected when default value is true. It will always override combined result as true regardless the combined result of toggle is enabled and strategy result.

https://github.com/Unleash/unleash-client-ruby/blob/master/lib/unleash/feature_toggle.rb#L74..L81

when strategy result already computed as false and toggle is enabled, it may respect that result instead.

feat: Backoff based on http statuses

Describe the feature request

In order to reduce load on servers, as well as save our users some bandwidth, I'd like for this SDK to react to http status codes, and not just keep requesting at the same frequency if the server is telling it to chill out.

Background

Part of a cross-SDK initiative to make all our SDKs respect http statuses in order to save ourselves and our users for bandwidth/cpu usage that adds no value to either the server or the client.

Solution suggestions

Unleash/unleash-client-node#537 follows the correct pattern. Use 404, 429 or 50x statuses to reduce polling frequency. On 401 or 403 log and don't keep polling - your user probably needs to update their key before there's any point in continuing to hit the server.

Unable to save backup file. Exception thrown Errno::ENOENT

Describe the bug

Hi there, I face some issues when I use puma in clustered mode (following the documentation in readme)
But I would list here a bit more issues:

Steps to reproduce the bug

system: MacOS BigSur 11.4

setup unleash according to the readme for puma (clustered mode) https://github.com/Unleash/unleash-client-ruby/blob/c745ed4569b5e83498d22e0fbf618e8eb6ce2002/README.md#with-preload_app

  • initializer & puma.rb hook.
    bundle exec puma -w 3 --preload

Expected behavior

No errors should be thrown.

Logs, error output, etc.

...
[2022-06-07T12:06:38.866649+02:00          Unleash ERROR] : Unable to save backup file. Exception thrown Errno::ENOENT:'No such file or directory @ rb_file_s_rename - (/var/folders/pf/_39szp0x68v6s0vrr0jfz9l80000gn/T/unlea
sh-oh-backend-repo.json.tmp, /var/folders/pf/_39szp0x68v6s0vrr0jfz9l80000gn/T/unleash-oh-backend-repo.json)'                                                                                                                  
[2022-06-07T12:06:38.867553+02:00          Unleash ERROR] : stacktrace: ["/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unleash-4.2.1/lib/unleash/toggle_fetcher.rb:76:in `rename'", "/Users/viktorsych/.rb
env/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unleash-4.2.1/lib/unleash/toggle_fetcher.rb:76:in `block in save!'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unleash-4.2.1/lib/unleash/toggle_fetcher.rb
:72:in `synchronize'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unleash-4.2.1/lib/unleash/toggle_fetcher.rb:72:in `save!'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unleash
-4.2.1/lib/unleash/toggle_fetcher.rb:63:in `fetch'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unleash-4.2.1/lib/unleash/toggle_fetcher.rb:22:in `initialize'", "/Users/viktorsych/.rbenv/versions/3.0
.2/lib/ruby/gems/3.0.0/gems/unleash-4.2.1/lib/unleash/client.rb:21:in `new'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/unleash-4.2.1/lib/unleash/client.rb:21:in `initialize'", "/Users/viktorsych/Co
de/oh/overhaul-backend/lib/unleash.rb:19:in `new'", "/Users/viktorsych/Code/oh/overhaul-backend/lib/unleash.rb:19:in `preload'", "config/puma.rb:56:in `block in _load_from'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ru
by/gems/3.0.0/gems/puma-5.6.4/lib/puma/configuration.rb:297:in `block in run_hooks'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/lib/puma/configuration.rb:295:in `each'", "/Users/viktorsyc
h/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/lib/puma/configuration.rb:295:in `run_hooks'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/lib/puma/cluster/worker.rb:55:in `run'
", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/lib/puma/cluster.rb:204:in `worker'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/lib/puma/cluster.rb:97:in `
block in spawn_worker'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/activesupport-6.1.5/lib/active_support/fork_tracker.rb:10:in `block in fork'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/ge
ms/3.0.0/gems/activesupport-6.1.5/lib/active_support/fork_tracker.rb:10:in `block in fork'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/activesupport-6.1.5/lib/active_support/fork_tracker.rb:8:in `fo
rk'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/activesupport-6.1.5/lib/active_support/fork_tracker.rb:8:in `fork'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/activesupport-6
.1.5/lib/active_support/fork_tracker.rb:27:in `fork'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/activesupport-6.1.5/lib/active_support/fork_tracker.rb:8:in `fork'", "/Users/viktorsych/.rbenv/versio
ns/3.0.2/lib/ruby/gems/3.0.0/gems/activesupport-6.1.5/lib/active_support/fork_tracker.rb:27:in `fork'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/lib/puma/cluster.rb:97:in `spawn_worker'"
, "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/lib/puma/cluster.rb:79:in `block in spawn_workers'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/lib/puma/clus
ter.rb:72:in `times'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/lib/puma/cluster.rb:72:in `spawn_workers'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/l
ib/puma/cluster.rb:415:in `run'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/lib/puma/launcher.rb:182:in `run'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.
4/lib/puma/cli.rb:81:in `run'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/puma-5.6.4/bin/puma:10:in `<top (required)>'", "/Users/viktorsych/.rbenv/versions/3.0.2/bin/puma:23:in `load'", "/Users/vikt
orsych/.rbenv/versions/3.0.2/bin/puma:23:in `<top (required)>'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/lib/bundler/cli/exec.rb:58:in `load'", "/Users/viktorsych/.rbenv/versions/3.
0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/lib/bundler/cli/exec.rb:58:in `kernel_load'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/lib/bundler/cli/exec.rb:23:in `run'", "/Users/vikto
rsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/lib/bundler/cli.rb:478:in `exec'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/lib/bundler/vendor/thor/lib/thor/comma
nd.rb:27:in `run'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/rub
y/gems/3.0.0/gems/bundler-2.2.32/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/lib/bundler/cli.rb:31:in `dispatch'", "/Users/viktor
sych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/lib/bundler/cli.
rb:25:in `start'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/exe/bundle:49:in `block in <top (required)>'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2
.2.32/lib/bundler/friendly_errors.rb:103:in `with_friendly_errors'", "/Users/viktorsych/.rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/bundler-2.2.32/exe/bundle:37:in `<top (required)>'", "/Users/viktorsych/.rbenv/versions
/3.0.2/bin/bundle:23:in `load'", "/Users/viktorsych/.rbenv/versions/3.0.2/bin/bundle:23:in `<main>'"]

Screenshots

No response

Additional context

No response

Unleash version

4.2.1

Subscription type

No response

Hosting type

No response

SDK information (language and version)

ruby, RoR, Sidekiq, Puma

Support for Unleash V2

Hello @rarruda,

I have been trying to implement the client to my rails monolith (that is part of Moteefe). One of your enterprise clients.
And I have stumbled upon e big error that does not let me implement it, the request_version is 2 and the only one supported in the gem is 1.

This thing does not let me do anything with Unleash.

Can you please tell me what to do or add support for version 2.

Thanks,
Rares and the @moteefe_team

Review/redesign/refactor ScheduledExecutor

Implementation of bootstrapping has lead to some subtle changes to the ScheduledExecutor class. There's some discussion around how this needs to work and what the code needs to look like. Ideally, we need to define the behaviour we'd like to see from this class before we change code.

This is especially important to decide so that we can finalise how async intialisation works (which we partially support at the moment) and will be important if we decide we need to implement the "block until ready" that some of the other SDKs support. Both of these would be surprising, breaking changes from a consumer perspective so deciding on how ScheduledExecutor needs to work is important

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.