Giter VIP home page Giter VIP logo

ruby-sdk's Introduction

Optimizely Ruby SDK

Build Status Coverage Status Apache 2.0

This repository houses the Ruby SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy).

Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at Optimizely.com, or see the developer documentation.

Optimizely Rollouts is free feature flags for development teams. You can easily roll out and roll back features in any application without code deploys, mitigating risk for every feature on your roadmap.

Get Started

Refer to the Ruby SDK's developer documentation for detailed instructions on getting started with using the SDK.

Requirements

  • Ruby 3.0+

Install the SDK

The SDK is available through RubyGems. To install:

gem install optimizely-sdk

Feature Management Access

To access the Feature Management configuration in the Optimizely dashboard, please contact your Optimizely customer success manager.

Use the Ruby SDK

Initialization

You can initialize the Optimizely instance in two ways: directly with a datafile, or by using a factory class, OptimizelyFactory, which provides methods to create an Optimizely instance with the default configuration.

Initialization with datafile

Initialize Optimizely with a datafile. This datafile will be used as ProjectConfig throughout the life of the Optimizely instance.

optimizely_instance = Optimizely::Project.new(datafile: datafile)

Initialization by OptimizelyFactory

  1. Initialize Optimizely by providing an sdk_key and an optional datafile. This will initialize an HTTPConfigManager that makes an HTTP GET request to the URL (formed using your provided sdk_key and the default datafile CDN url template) to asynchronously download the project datafile at regular intervals and update ProjectConfig when a new datafile is received.

    optimizely_instance = Optimizely::OptimizelyFactory.default_instance('put_your_sdk_key_here', datafile)

When the datafile is given then it will be used initially before any update.

  1. Initialize Optimizely by providing a Config Manager that implements a config method. You can customize our HTTPConfigManager as needed.

    custom_config_manager = CustomConfigManager.new
    optimizely_instance = Optimizely::OptimizelyFactory.default_instance_with_config_manager(custom_config_manager)
  2. Initialize Optimizely with required sdk_key and other optional arguments.

     optimizely_instance = Optimizely::OptimizelyFactory.custom_instance(
        sdk_key,
        datafile,
        event_dispatcher,
        logger,
        error_handler,
        skip_json_validation,
        user_profile_service,
        config_manager,
        notification_center,
        event_processor
    )

Note: The SDK spawns multiple threads when initialized. These threads have infinite loops that are used for fetching the datafile, as well as batching and dispatching events in the background. When using in a web server that spawn multiple child processes, you need to initialize the SDK after those child processes or workers have been spawned.

HTTP Config Manager

The HTTPConfigManager asynchronously polls for datafiles from a specified URL at regular intervals by making HTTP requests.

 http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
        sdk_key: nil,
        url: nil,
        datafile: nil,
        url_template: nil,
        auto_update: nil,
        polling_interval: nil,
        start_by_default: nil,
        blocking_timeout: nil,
        logger: nil,
        error_handler: nil,
        skip_json_validation: false,
        notification_center: notification_center,
        datafile_access_token: nil,
        proxy_config: nil
      )

Note: You must provide either the sdk_key or URL. If you provide both, the URL takes precedence.

sdk_key The sdk_key is used to compose the outbound HTTP request to the default datafile location on the Optimizely CDN.

datafile You can provide an initial datafile to bootstrap the DataFileProjectConfig so that it can be used immediately. The initial datafile also serves as a fallback datafile if HTTP connection cannot be established. The initial datafile will be discarded after the first successful datafile poll.

polling_interval The polling interval is used to specify a fixed delay between consecutive HTTP requests for the datafile. Valid duration is greater than 0 and less than 2592000 seconds. Default is 5 minutes.

url_template A string with placeholder {sdk_key} can be provided so that this template along with the provided sdk_key is used to form the target URL.

start_by_default Boolean flag used to start the AsyncScheduler for datafile polling if set to true.

blocking_timeout The blocking timeout period is used to specify a maximum time to wait for initial bootstrapping. Valid blocking timeout period is between 1 and 2592000 seconds. Default is 15 seconds.

datafile_access_token An access token sent in an authorization header with the request to fetch private datafiles.

You may also provide your own logger, error handler, or notification center.

Advanced configuration

The following properties can be set to override the default configurations for HTTPConfigManager.

PropertyName Default Value Description
update_interval 5 minutes Fixed delay between fetches for the datafile
sdk_key nil Optimizely project SDK key
url nil URL override location used to specify custom HTTP source for the Optimizely datafile
url_template 'https://cdn.optimizely.com/datafiles/{sdk_key}.json' Parameterized datafile URL by SDK key
datafile nil Initial datafile, typically sourced from a local cached source
auto_update true Boolean flag to specify if callback needs to execute infinitely or only once
start_by_default true Boolean flag to specify if datafile polling should start right away as soon as HTTPConfigManager initializes
blocking_timeout 15 seconds Maximum time in seconds to block the config call until config has been initialized

A notification signal will be triggered whenever a new datafile is fetched and Project Config is updated. To subscribe to these notifications, use the

notification_center.add_notification_listener(Optimizely::NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE], @callback)

BatchEventProcessor

BatchEventProcessor is a batched implementation of the EventProcessor

  • Events passed to the BatchEventProcessor are immediately added to a Queue.

  • The BatchEventProcessor maintains a single consumer thread that pulls events off of the Queue and buffers them for either a configured batch size or for a maximum duration before the resulting LogEvent is sent to the NotificationCenter.

Use BatchEventProcessor

event_processor = Optimizely::BatchEventProcessor.new(
    event_queue: SizedQueue.new(10),
    event_dispatcher: event_dispatcher,
    batch_size: 10,
    flush_interval: 30000,
    logger: logger,
    notification_center: notification_center
)

Advanced configuration

The following properties can be used to customize the BatchEventProcessor configuration.

Property Name Default Value Description
event_queue 1000 SizedQueue.new(100) or Queue.new. Queues individual events to be batched and dispatched by the executor. Default value is 1000.
event_dispatcher nil Used to dispatch event payload to Optimizely. By default EventDispatcher.new will be set.
batch_size 10 The maximum number of events to batch before dispatching. Once this number is reached, all queued events are flushed and sent to Optimizely.
flush_interval 30000 ms Maximum time to wait before batching and dispatching events. In milliseconds.
notification_center nil Notification center instance to be used to trigger any notifications.

Close Optimizely

If you enable event batching, make sure that you call the close method, optimizely.close(), prior to exiting. This ensures that queued events are flushed as soon as possible to avoid any data loss.

Note: Because the Optimizely client maintains a buffer of queued events, we recommend that you call close() on the Optimizely instance before shutting down your application or whenever dereferencing the instance.

Method Description
close() Stops all timers and flushes the event queue. This method will also stop any timers that are happening for the datafile manager.

For Further details see the Optimizely Feature Experimentation documentation to learn how to set up your first Ruby project and use the SDK.

SDK Development

Building the SDK

To build a local copy of the gem which will be output to /pkg:

rake build

Unit Tests

Running all tests

You can run all unit tests with:

rake spec

Contributing

Please see CONTRIBUTING.

Credits

This software incorporates code from the following open source projects:

Httparty https://github.com/jnunemaker/httparty Copyright © 2008 John Nunemaker License (MIT): https://github.com/jnunemaker/httparty/blob/master/MIT-LICENSE

JSON Schema Validator https://github.com/ruby-json-schema/json-schema Copyright © 2010-2011, Lookingglass Cyber Solutions License (MIT): https://github.com/ruby-json-schema/json-schema/blob/master/LICENSE.md

Murmurhash3 https://github.com/funny-falcon/murmurhash3-ruby Copyright © 2012 Sokolov Yura 'funny-falcon' License (MIT): https://github.com/funny-falcon/murmurhash3-ruby/blob/master/LICENSE

Additional Code

This software may be used with additional code that is separately downloaded by you. These components are subject to their own license terms, which you should review carefully.

Bundler https://github.com/bundler/bundler Copyright © 2008-2018 Andre Arko, Engine Yard, et al License (MIT): https://github.com/bundler/bundler/blob/master/LICENSE.md

Coveralls https://github.com/lemurheavy/coveralls-ruby Copyright © 2012 Wil Gieseler License (MIT): https://github.com/lemurheavy/coveralls-ruby/blob/master/LICENSE

Rake https://github.com/ruby/rake Copyright © 2004-2017 Jim Weirich License (MIT): https://github.com/ruby/rake/blob/master/MIT-LICENSE

RSpec https://github.com/rspec/rspec Copyright © 2009 Chad Humphries, David Chelimsky Copyright © 2006 David Chelimsky, The RSpec Development Team Copyright © 2005 Steven Baker License (MIT): https://github.com/rspec/rspec/blob/master/LICENSE.md

RuboCop https://github.com/rubocop-hq/rubocop Copyright © 2012-19 Bozhidar Batsov License (MIT): https://github.com/rubocop-hq/rubocop/blob/master/LICENSE.txt

WebMock https://github.com/bblimke/webmock Copyright © 2009-2010 Bartosz Blimke License (MIT): https://github.com/bblimke/webmock/blob/master/LICENSE

Other Optimizely SDKs

ruby-sdk's People

Stargazers

 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  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  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

ruby-sdk's Issues

[ENHANCEMENT] Add missing information to gemspec for Dependabot to pull it in

Description

We recently got a Dependabot pull request filed to update to the 5.0 version of this SDK. The pull request had a few issues:

image

A) There was no release notes or changelog in the pull request
B) The link back to the library just goes to https://www.optimizely.com/, making it hard to find the codebase and the above information

I think this Gem is missing some fields that would enable Dependabot to pull in this information automatically in its .gemspec file:

homepage: The URL of your GitHub repository (e.g., https://github.com/your-username/your-library).
source_code_uri: The URL of your repository's code (usually the same as homepage).
changelog_uri: The URL of your release notes (e.g., https://github.com/your-username/your-library/blob/master/CHANGELOG.md).

can the maintainers add these so Dependabot can automatically pull in this information please?

This and us not looking closer for the notes caused us to miss the change "BatchEventProcessor is now the default EventProcessor when Optimizely::Project is instantiated (#325)." which caused a memory leak in our job processing queue and took it down for a few hours. Our fault for not reading the Changelog ourselves, but it would be so much more convenient if it was included automatically by Dependabot in future updates.

Thanks for your consideration!

Benefits

We wouldn't miss key updates to the gem in Dependabot pull requests.

Detail

No response

Examples

No response

Risks/Downsides

No response

Reporting a vulnerability

Hello!

I hope you are doing well!

We are a security research team. Our tool automatically detected a vulnerability in this repository. We want to disclose it responsibly. GitHub has a feature called Private vulnerability reporting, which enables security research to privately disclose a vulnerability. Unfortunately, it is not enabled for this repository.

Can you enable it, so that we can report it?

Thanks in advance!

PS: you can read about how to enable private vulnerability reporting here: https://docs.github.com/en/code-security/security-advisories/repository-security-advisories/configuring-private-vulnerability-reporting-for-a-repository

Feature request: make JSON schema available as a file

The datafile schema is stored within the ruby sdk as a Hash constant. As Optimizely is unable to envision the multitude of integrations of the ruby-sdk, nor the many potential toolchains and development environments into which the ruby-sdk will be installed, it would be more versatile for the json schema to be shipped as a standard schema file ( datafile-v2.schema.json). This would enable the schema to be hooked into test suites and other tools that (rightfully) expect the schema to be a regular file.

The ruby sdk could still parse the file into a hash and expose it as a constant. But making the actual file available would enable additional schema toolchain configurations.

Setting up the SDK in a Rails initializer causes a segmentation fault

This happens during the initial page load for me. It does not happen in our docker containers which use Centos.

Mac version: 10.14.6 (Mojave)
Ruby version: 2.5.1
Rails version: 5.2.3
optimizely-sdk version: 3.2.0

Output:

/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/2.5.0/socket.rb:227: [BUG] Segmentation fault at 0x0000000110796a3a
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin18]

-- Crash Report log information --------------------------------------------
   See Crash Report log file under the one of following:
     * ~/Library/Logs/DiagnosticReports
     * /Library/Logs/DiagnosticReports
   for more details.
Don't forget to include the above Crash Report log file in bug reports.

-- Control frame information -----------------------------------------------
c:0013 p:---- s:0125 e:000124 CFUNC  :getaddrinfo
c:0012 p:0034 s:0115 e:000114 METHOD /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/2.5.0/socket.rb:227
c:0011 p:0073 s:0104 e:000103 METHOD /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/2.5.0/socket.rb:631
c:0010 p:0034 s:0091 e:000090 METHOD /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/webpacker-4.0.7/lib/webpacker/dev_server.rb:16
c:0009 p:0026 s:0087 e:000086 METHOD /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/webpacker-4.0.7/lib/webpacker/dev_server_proxy.rb:14
c:0008 p:0014 s:0079 E:001ac8 METHOD /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/rack-proxy-0.6.5/lib/rack/proxy.rb:57
c:0007 p:0020 s:0074 E:001b30 METHOD /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.3/lib/rails/engine.rb:524
c:0006 p:0026 s:0068 E:001b88 METHOD /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-4.1.1/lib/puma/configuration.rb:228
c:0005 p:0262 s:0063 E:001c88 METHOD /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-4.1.1/lib/puma/server.rb:664
c:0004 p:0026 s:0038 E:001d20 METHOD /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-4.1.1/lib/puma/server.rb:467
c:0003 p:0065 s:0026 E:001da8 BLOCK  /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-4.1.1/lib/puma/server.rb:328 [FINISH]
c:0002 p:0125 s:0016 E:001e50 BLOCK  /Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-4.1.1/lib/puma/thread_pool.rb:135 [FINISH]
c:0001 p:---- s:0003 e:000002 (none) [FINISH]

-- Ruby level backtrace information ----------------------------------------
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-4.1.1/lib/puma/thread_pool.rb:135:in `block in spawn_thread'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-4.1.1/lib/puma/server.rb:328:in `block in run'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-4.1.1/lib/puma/server.rb:467:in `process_client'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-4.1.1/lib/puma/server.rb:664:in `handle_request'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/puma-4.1.1/lib/puma/configuration.rb:228:in `call'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.3/lib/rails/engine.rb:524:in `call'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/rack-proxy-0.6.5/lib/rack/proxy.rb:57:in `call'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/webpacker-4.0.7/lib/webpacker/dev_server_proxy.rb:14:in `rewrite_response'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/gems/2.5.0/gems/webpacker-4.0.7/lib/webpacker/dev_server.rb:16:in `running?'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/2.5.0/socket.rb:631:in `tcp'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/2.5.0/socket.rb:227:in `foreach'
/Users/matthew.mcgarvey/.asdf/installs/ruby/2.5.1/lib/ruby/2.5.0/socket.rb:227:in `getaddrinfo'

-- Machine register context ------------------------------------------------
 rax: 0x0000000110796a38 rbx: 0x00007fe81652eb20 rcx: 0x000070000c13b0ac
 rdx: 0x0000000000000000 rdi: 0x00007fe81652eb20 rsi: 0x0000000000000001
 rbp: 0x000070000c138e50 rsp: 0x000070000c138a10  r8: 0x0000000000000067
  r9: 0x0000000000000000 r10: 0x0000000000000003 r11: 0xfffff017f5bf6a18
 r12: 0x00007fe81652eb20 r13: 0x0000000000000000 r14: 0x0000000000000002
 r15: 0x0000000110796a3c rip: 0x00007fff59daa90a rfl: 0x0000000000010202

-- C level backtrace information -------------------------------------------
0   ruby                                0x000000011014a487 rb_vm_bugreport + 135
1   ruby                                0x000000010ffc1ae3 rb_bug_context + 467
2   ruby                                0x00000001100b7e71 sigsegv + 81
3   libsystem_platform.dylib            0x00007fff59d87b5d _sigtramp + 29
4   libsystem_trace.dylib               0x00007fff59daa90a _os_log_preferences_refresh + 76
5   libsystem_trace.dylib               0x00007fff59dab13d os_log_type_enabled + 627
6   libsystem_info.dylib                0x00007fff59cbd709 si_destination_compare_statistics + 1993
7   libsystem_info.dylib                0x00007fff59cbc1a5 si_destination_compare_internal + 661
8   libsystem_info.dylib                0x00007fff59cbbd3f si_destination_compare + 559
9   libsystem_info.dylib                0x00007fff59c9a6df _gai_addr_sort + 111
10  libsystem_c.dylib                   0x00007fff59c44e5b _isort + 193
11  libsystem_c.dylib                   0x00007fff59c44d88 _qsort + 2125
12  libsystem_info.dylib                0x00007fff59c91f2d _gai_sort_list + 781
13  libsystem_info.dylib                0x00007fff59c90885 si_addrinfo + 2021
14  libsystem_info.dylib                0x00007fff59c8ff77 _getaddrinfo_internal + 231
15  libsystem_info.dylib                0x00007fff59c8fe7d getaddrinfo + 61
16  socket.bundle                       0x00000001107f8275 nogvl_getaddrinfo + 181
17  ruby                                0x00000001100f2ce2 rb_thread_call_without_gvl + 82
18  socket.bundle                       0x00000001107f80ce rb_getaddrinfo + 1934
19  socket.bundle                       0x00000001107f8546 rsock_getaddrinfo + 150
20  socket.bundle                       0x00000001107fc616 call_getaddrinfo + 182
21  socket.bundle                       0x00000001107fa920 addrinfo_s_getaddrinfo + 144
22  ruby                                0x000000011013cf77 vm_call_cfunc + 295
23  ruby                                0x000000011012465e vm_exec_core + 13262
24  ruby                                0x0000000110137600 vm_exec + 144
25  ruby                                0x000000011013609a vm_invoke_proc + 362
26  ruby                                0x000000011013e4af vm_call_opt_call + 159
27  ruby                                0x000000011012465e vm_exec_core + 13262
28  ruby                                0x0000000110137600 vm_exec + 144
29  ruby                                0x000000011013609a vm_invoke_proc + 362
30  ruby                                0x00000001100f9a52 thread_start_func_2 + 1762
31  ruby                                0x00000001100f9348 thread_start_func_1 + 184
32  libsystem_pthread.dylib             0x00007fff59d902eb _pthread_body + 126
33  libsystem_pthread.dylib             0x00007fff59d93249 _pthread_start + 66

Tags are named inconsistently

I'm having a hard time following the tags for this gem because the tags are named inconsistently between version. Some have the leading v prefix and some do not, but it's not clear which is intended to be the format long-term.

First 1.2.0 without the prefix, then v1.3.0 with, then 1.4.0 without again, then v1.5.0 and all the 2.x with the prefix again. Then I assumed the style was back to no prefix since the entire 3.x release line didn't use the prefix, but now v3.4.0 has the prefix again!?

$ git tag | sort
1.2.0
1.4.0
3.0.0
3.1.0
3.1.1
3.2.0
3.3.0
3.3.1
3.3.2
v1.3.0
v1.5.0
v2.0.0
v2.0.0.beta
v2.0.0.beta1
v2.0.1
v2.0.2
v2.0.3
v2.1.0
v2.1.1
v3.4.0

Feature request: Notification center to conform to ActiveSupport notifications API

I have a couple concerns with the instrumentation implementation presently in use.

  1. Currently, the Project's notification_center is being type-checked against Optimizely::Notificiation center. This anti-pattern violates one of ruby's core idioms of duck typing. A notification center should only need to conform to the expected API. In particular, as long as the given object responds to send_notifications, then it should be considered a valid notification center.

  2. Secondly, the existing NotificationCenter api does not conform to semi-standard notification APIs in Rails land. A de-facto standard API in Rails land is ActiveSupport::Notifications, of which there are many implementations. Conforming to this API would allow existing instrumentation objects within a codebase to be passed to the Optimizely project to handle the new optimizely events.

  3. Lastly, the existing NotificationCenter only allows registration of Method listeners. This is completely abnormal ruby, as a normal ruby listener could be a block, proc, or lambda. Even better, it should be anything that responds to call, but at the very least, accepting block/proc/lambdas is table-stakes. (addressed by #216 🎉 )

Feature request: DatafileProjectConfig should be round-trippable

Given that the DatafileProjectConfig represents the object form of the datafile JSON structure, it seems reasonable to expect that his object could be re-serialized to JSON. A compelling use-case is for servers to use the ruby-sdk for polling/fetching/storing the datafile and inline the datafile into (cached) page requests. This way the client-side optimizely sdk can avoid another costly HTTP request to fetch data that the server already has.

A rough spike of this feature: (this proof of concept implementation relies on ActiveSupport extensions as_json and deep_transform_keys.)

      def datafile
        as_json.slice *::Optimizely::Helpers::Constants::JSON_SCHEMA_V2["properties"].keys
      end

      def as_json
        project_config_manager.config.as_json.deep_transform_keys { |k| k.camelize(:lower) }
      end

Even without the use-case described above, I would generally expect any library class which represents a serialized data structure to be round-trippable.

Slowness after upgrading to 3.9

Hey there, we recently bumped to 3.9 and noticed latency spike (~double) along a critical code-path. We have json schema validation disabled here for performance reasons.

As a comparison, here's a flamegraph I made where our code uses

Release version 3.8 of the ruby-sdk gem:
Screen Shot 2022-01-24 at 10 50 32 AM

vs. 3.9.
Screen Shot 2022-01-24 at 10 50 43 AM

I see a lot of cycles spent in Optimizely::StaticProfileManager, possibly tied to this change

I was wondering if anyone's noticed something similar/ could give me more context on the change here and what we might be able to do to address the performance issues

Feature request: accept lambdas instead of single-function classes

  • The ErrorHandler only exists to expose a single method: handle_error.
  • The EventProcessor only exists to expose a single method: process.
  • The EventDispatcher only exists to expose a single method: dispatch_event.

Many of these bits of functionality only need to be a single line:

ErrorHandler: Rollbar.log('error', e)
EventProcessor: Thread.new { event_dispatcher.dispatch_event(event) }

These objects would be easily swapped for simple lambdas:

# some details omitted for clarify
dispatcher = Optimizely::EventDispatcher.new
Optimizely::Project.new(
  event_processor: ->(event) { Thread.new { dispatcher.dispatch_event(event) } }
  
  error_handler: ->(error) { Rollbar.log('error', error) }
)

These types of objects are extracted into separate objects such that they can be provided by SDK users. However, each bit of logic is essentially a function/callback. For trivial configuration, providing a simple callable (lambda) is all that should be necessary. (Indeed, this would improve testability as well.)

Feature Request: for all of these single-method classes, accept a Callable in place of a custom object and invoke .call instead of the particular method.

Happy to throw up a PR if the idea is well received.

Implement Factory for Creating Optimizely::Project instances to Avoid Passing in a Large Number of Positional Arguments

The constructor for Optimizely::Project expects 10 positional arguments. This is pretty unwieldy because it's hard for a caller to remember which argument is which. It also requires a bunch of nil values to be passed in if only some of the non-default values are desired.

It would be helpful to introduce a factory that takes in keyword arguments as input and constructs the Project instance. Since there are probably existing callers, we can't remove the constructor with positional arguments.

NoMethodError: undefined method `empty?' when activating an integer id

It looks like #63 introduced an error where the DecisionService#get_variation calls get_forced_variation, which doesn't validate that the user ID is a string before calling empty? at https://github.com/optimizely/ruby-sdk/blob/master/lib/optimizely/project_config.rb#L279

This leads to a NoMethodError when trying to activate an experiment or getting a variation for an integer based user id. #93 fixed some of these validations, but not all of them.

Feature request: Datafile schema validation errors should include details

While attempting to spike out an implementation for #246 I routinely encountered datafile schema validation errors. However, the validation errors did not provide any insight into the schema violation.

The only output provided was: E, [2020-04-30T14:16:25.777688 #23143] ERROR -- : Provided datafile is in an invalid format.

It would be helpful if the validator provided more specific error details beyond "valid / not valid".

The json-schema gem already provides this capability via the fully_validate method.

Feature request: eliminate HTTParty dependency

In a large Rails app, the number of dependencies can grow quite large. In such an app, the risk of unresolveable version conflicts between versions of a single dependency grows ever higher. As a library, it would be very helpful to consumers if the library's own dependencies were kept to a minimum. This would minimize or eliminate any such version conflicts.

Case in point, the HTTParty gem is a dependency that could be removed. From what I can tell, there are only two small use-cases for HTTP in this gem: event dispatcher and the http config manager. Neither object has that much extensive use of HTTP.

I would request that these classes just use net/http from the ruby standard library instead of pulling in a dependency.

NoMethodError: undefined method `get_feature_flag_from_key` for nil:NilClass when calling `get_feature_variable_*`

SDK version 2.0.1

On occasions where fetching a valid datafile fails, any call to a get_feature_variable_* method will raise the exception NoMethodError: undefined method 'get_feature_flag_from_key' for nil:NilClass instead of failing gracefully and logging.

Within Project#get_feature_variable_for_type there is no check for if the project is useable via the is_valid attr_reader before attempting to access the config

ruby-sdk/lib/optimizely.rb

Lines 439 to 449 in c1ca00d

return nil unless Optimizely::Helpers::Validator.inputs_valid?(
{
feature_flag_key: feature_flag_key,
variable_key: variable_key,
variable_type: variable_type,
user_id: user_id
},
@logger, Logger::ERROR
)
feature_flag = @config.get_feature_flag_from_key(feature_flag_key)

All of the other methods seem to return nil or false if the datafile cannot be parsed

ruby-sdk/lib/optimizely.rb

Lines 262 to 266 in c1ca00d

unless @is_valid
logger = SimpleLogger.new
logger.log(Logger::ERROR, InvalidDatafileError.new('is_feature_enabled').message)
return false
end

Event Batching doesn't seem to be enabled by default

We have found that if a feature is a part of experiment, Optimizely sends a blocking API call to send an impression event every time a decision is made. Your doc here: https://docs.developers.optimizely.com/full-stack/docs/event-batching-ruby says that BatchEventProcessor is enabled by OptimizelyFactory.default_instance, however, an instance of ForwardingEventProcessor seem to be actually used.

We're using v3.6.0, upgrading to 4.0.0 shows the same.

Feature request: DatafileProjectConfig should accept Hash

The DatafileProjectConfig takes in a json string to parse as the datafile source. If a consumer already has the datafile representation parsed into memory as a Hash, it's is unnecessary to serialize it, only for DatafileProjectConfig to immediately re parse the string into a Hash.

rake fails after a fresh clone and bundle

$ rake
rake aborted!
LoadError: cannot load such file -- optimizely
/Users/jasonkarns/Projects/optimizely-ruby-sdk/spec/benchmarking/benchmark.rb:18:in `<top (required)>'
/Users/jasonkarns/Projects/optimizely-ruby-sdk/Rakefile:4:in `require_relative'
/Users/jasonkarns/Projects/optimizely-ruby-sdk/Rakefile:4:in `<top (required)>'
(See full trace by running task with --trace)

[feature request] Add ability for HTTP Config Manager to take proxy options

I propose adding the following options to HTTP Config manager:

name default description
proxy_host nil To perform a web request behind a proxy, this is required. This can be a domain or IP address of the proxy server.
proxy_port nil port number that the proxy server uses for client connections.
proxy_username nil username for proxy auth.
proxy_password nil password for proxy auth.

Happy to submit a PR to add this functionality.

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.