From ed84c0d2e6bd48fd7e617ed68c9c46549f547cc5 Mon Sep 17 00:00:00 2001 From: Michael Beemer Date: Thu, 8 Aug 2024 13:32:33 -0400 Subject: [PATCH] add ruby to ecosystem (#625) Signed-off-by: Michael Beemer --- docs/reference/technologies/server/ruby.mdx | 281 ++++++++++++++++++++ docusaurus.config.ts | 2 +- src/datasets/index.ts | 2 + src/datasets/providers/launchdarkly.ts | 6 + src/datasets/sdks/ecosystem.ts | 2 + src/datasets/sdks/index.ts | 3 +- src/datasets/sdks/ruby.ts | 13 + src/datasets/sdks/sdk-compatibility.json | 68 ++++- src/datasets/types.ts | 12 +- static/img/ruby-no-fill.svg | 5 + 10 files changed, 383 insertions(+), 11 deletions(-) create mode 100644 docs/reference/technologies/server/ruby.mdx create mode 100644 src/datasets/sdks/ruby.ts create mode 100644 static/img/ruby-no-fill.svg diff --git a/docs/reference/technologies/server/ruby.mdx b/docs/reference/technologies/server/ruby.mdx new file mode 100644 index 000000000..e5930a9fd --- /dev/null +++ b/docs/reference/technologies/server/ruby.mdx @@ -0,0 +1,281 @@ +--- +title: OpenFeature Ruby SDK +slug: ruby +sidebar_label: Ruby +--- + + + +

+ + Specification + + + + + Release + + + +
+ + CII Best Practices + +

+ +## Quick start + +### Requirements + +| Supported Ruby Version | OS | +| ------------ | --------------------- | +| Ruby 3.1.4 | Windows, MacOS, Linux | +| Ruby 3.2.3 | Windows, MacOS, Linux | +| Ruby 3.3.0 | Windows, MacOS, Linux | + +### Install + +Install the gem and add to the application's Gemfile by executing: + +```sh +bundle add openfeature-sdk +``` + +If bundler is not being used to manage dependencies, install the gem by executing: + +```sh +gem install openfeature-sdk +``` + +### Usage + +```ruby +require 'open_feature/sdk' +require 'json' # For JSON.dump + +# API Initialization and configuration + +OpenFeature::SDK.configure do |config| + # your provider of choice, which will be used as the default provider + config.set_provider(OpenFeature::SDK::Provider::InMemoryProvider.new( + { + "flag1" => true, + "flag2" => 1 + } + )) +end + +# Create a client +client = OpenFeature::SDK.build_client + +# fetching boolean value feature flag +bool_value = client.fetch_boolean_value(flag_key: 'boolean_flag', default_value: false) + +# a details method is also available for more information about the flag evaluation +# see `ResolutionDetails` for more info +bool_details = client.fetch_boolean_details(flag_key: 'boolean_flag', default_value: false) == + +# fetching string value feature flag +string_value = client.fetch_string_value(flag_key: 'string_flag', default_value: false) + +# fetching number value feature flag +float_value = client.fetch_number_value(flag_key: 'number_value', default_value: 1.0) +integer_value = client.fetch_number_value(flag_key: 'number_value', default_value: 1) + +# get an object value +object = client.fetch_object_value(flag_key: 'object_value', default_value: JSON.dump({ name: 'object'})) +``` + +## Features + +| Status | Features | Description | +| ------ | --------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. | +| ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](/docs/reference/concepts/evaluation-context). | +| ⚠️ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. | +| ❌ | [Logging](#logging) | Integrate with popular logging packages. | +| ✅ | [Domains](#domains) | Logically bind clients with providers. | +| ❌ | [Eventing](#eventing) | React to state changes in the provider or flag management system. | +| ⚠️ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. | +| ❌ | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread) | +| ⚠️ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. | + +Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌ + +### Providers + +[Providers](/docs/reference/concepts/provider) are an abstraction between a flag management system and the OpenFeature SDK. +Look [here](/ecosystem?instant_search%5BrefinementList%5D%5Btype%5D%5B0%5D=Provider&instant_search%5BrefinementList%5D%5Btechnology%5D%5B0%5D=Ruby) for a complete list of available providers. +If the provider you're looking for hasn't been created yet, see the [develop a provider](#develop-a-provider) section to learn how to build it yourself. + +Once you've added a provider as a dependency, it can be registered with OpenFeature like this: + +```ruby +OpenFeature::SDK.configure do |config| + # your provider of choice, which will be used as the default provider + config.set_provider(OpenFeature::SDK::Provider::InMemoryProvider.new( + { + "v2_enabled" => true, + } + )) +end +``` + +In some situations, it may be beneficial to register multiple providers in the same application. +This is possible using [domains](#domains), which is covered in more detail below. + +### Targeting + +Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location. +In OpenFeature, we refer to this as [targeting](/specification/glossary#targeting). +If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](/docs/reference/concepts/evaluation-context). + +```ruby +OpenFeature::SDK.configure do |config| + # you can set a global evaluation context here + config.evaluation_context = OpenFeature::SDK::EvaluationContext.new("host" => "myhost.com") +end + +# Evaluation context can be set on a client as well +client_with_context = OpenFeature::SDK.build_client( + evaluation_context: OpenFeature::SDK::EvaluationContext.new("controller_name" => "admin") +) + +# Invocation evaluation context can also be passed in during flag evaluation. +# During flag evaluation, invocation context takes precedence over client context +# which takes precedence over API (aka global) context. +bool_value = client.fetch_boolean_value( + flag_key: 'boolean_flag', + default_value: false, + evaluation_context: OpenFeature::SDK::EvaluationContext.new("is_friday" => true) +) +``` + +### Hooks + +Coming Soon! [Issue available](https://github.com/open-feature/ruby-sdk/issues/52) to be worked on. + + + +### Logging + +Coming Soon! [Issue available](https://github.com/open-feature/ruby-sdk/issues/148) to work on. + +### Domains + +Clients can be assigned to a domain. A domain is a logical identifier which can be used to associate clients with a particular provider. +If a domain has no associated provider, the default provider is used. + +```ruby +OpenFeature::SDK.configure do |config| + config.set_provider(OpenFeature::SDK::Provider::NoOpProvider.new, domain: "legacy_flags") +end + +# Create a client for a different domain, this will use the provider assigned to that domain +legacy_flag_client = OpenFeature::SDK.build_client(domain: "legacy_flags") +``` + +### Eventing + +Coming Soon! [Issue available](https://github.com/open-feature/ruby-sdk/issues/51) to be worked on. + + + +### Shutdown + +Coming Soon! [Issue available](https://github.com/open-feature/ruby-sdk/issues/149) to be worked on. + + + +### Transaction Context Propagation + +Coming Soon! [Issue available](https://github.com/open-feature/ruby-sdk/issues/150) to be worked on. + + + +## Extending + +### Develop a provider + +To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. +This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/ruby-sdk-contrib) available under the OpenFeature organization. +You’ll then need to write the provider by implementing the `Provider` duck. + +```ruby +class MyProvider + def init + # Perform any initialization steps with flag management system here + # Return value is ignored + # **Note** The OpenFeature spec defines a lifecycle method called `initialize` to be called when a new provider is set. + # To avoid conflicting with the Ruby `initialize` method, this method should be named `init` when creating a provider. + end + + def shutdown + # Perform any shutdown/reclamation steps with flag management system here + # Return value is ignored + end + + def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil) + # Retrieve a boolean value from provider source + end + + def fetch_string_value(flag_key:, default_value:, evaluation_context: nil) + # Retrieve a string value from provider source + end + + def fetch_number_value(flag_key:, default_value:, evaluation_context: nil) + # Retrieve a numeric value from provider source + end + + def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil) + # Retrieve a integer value from provider source + end + + def fetch_float_value(flag_key:, default_value:, evaluation_context: nil) + # Retrieve a float value from provider source + end + + def fetch_object_value(flag_key:, default_value:, evaluation_context: nil) + # Retrieve a hash value from provider source + end +end +``` + +> Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=provider&projects=&template=document-provider.yaml&title=%5BProvider%5D%3A+) so we can add it to the docs! + +### Develop a hook + +Coming Soon! [Issue available](https://github.com/open-feature/ruby-sdk/issues/52) to be worked on. + + + diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 189c4728d..46b95087b 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -162,7 +162,7 @@ const themeConfig: ThemeCommonConfig & AlgoliaThemeConfig = { }, prism: { theme: themes.oceanicNext, - additionalLanguages: ['java', 'csharp', 'powershell', 'php', 'kotlin'], + additionalLanguages: ['java', 'csharp', 'powershell', 'php', 'kotlin', 'ruby'], magicComments: [ { className: 'theme-code-block-highlighted-line', diff --git a/src/datasets/index.ts b/src/datasets/index.ts index e3327be8b..4d960bc80 100644 --- a/src/datasets/index.ts +++ b/src/datasets/index.ts @@ -20,6 +20,8 @@ export const TECHNOLOGY_COLOR_MAP: Record = { Kotlin: 'bg-purple-50 text-purple-600 ring-purple-500/10', Python: 'bg-blue-50 text-blue-600 ring-blue-500/10', Swift: 'bg-orange-50 text-orange-600 ring-orange-500/10', + Rust: 'bg-pink-50 text-pink-600 ring-pink-500/10', + Ruby: 'bg-red-50 text-red-600 ring-red-500/10', }; export const TYPE_COLOR_MAP: Record = { diff --git a/src/datasets/providers/launchdarkly.ts b/src/datasets/providers/launchdarkly.ts index 5a74baa27..b72b155e6 100644 --- a/src/datasets/providers/launchdarkly.ts +++ b/src/datasets/providers/launchdarkly.ts @@ -47,5 +47,11 @@ export const LaunchDarkly: Provider = { href: 'https://github.com/launchdarkly/openfeature-python-server', category: ['Server'], }, + { + technology: 'Ruby', + vendorOfficial: true, + href: 'https://github.com/launchdarkly/openfeature-ruby-server', + category: ['Server'], + }, ], }; diff --git a/src/datasets/sdks/ecosystem.ts b/src/datasets/sdks/ecosystem.ts index 12a66e130..0ef5eec6e 100644 --- a/src/datasets/sdks/ecosystem.ts +++ b/src/datasets/sdks/ecosystem.ts @@ -12,6 +12,7 @@ import AndroidSvg from '@site/static/img/android-no-fill.svg'; import PythonSvg from '@site/static/img/python-no-fill.svg'; import PhpSvg from '@site/static/img/php-no-fill.svg'; import IosSvg from '@site/static/img/ios-no-fill.svg'; +import RubySvg from '@site/static/img/ruby-no-fill.svg'; const LogoMap: Record = { 'c-sharp-no-fill.svg': CSharpSvg, @@ -25,6 +26,7 @@ const LogoMap: Record = { 'python-no-fill.svg': PythonSvg, 'php-no-fill.svg': PhpSvg, 'ios-no-fill.svg': IosSvg, + 'ruby-no-fill.svg': RubySvg, }; export const ECOSYSTEM_SDKS: EcosystemElement[] = SDKS.map((sdk) => { diff --git a/src/datasets/sdks/index.ts b/src/datasets/sdks/index.ts index a6757b588..5f8f9f035 100644 --- a/src/datasets/sdks/index.ts +++ b/src/datasets/sdks/index.ts @@ -10,8 +10,9 @@ import { PHP } from './php'; import { Swift } from './swift'; import { React } from './react'; import { Nestjs } from './nestjs'; +import { Ruby } from './ruby'; -export const SDKS = [Java, Nodejs, Nestjs, Dotnet, Go, Python, PHP, Web, React, Kotlin, Swift]; +export const SDKS = [Java, Nodejs, Nestjs, Dotnet, Go, Python, PHP, Web, React, Kotlin, Swift, Ruby]; export type SDK = { /** diff --git a/src/datasets/sdks/ruby.ts b/src/datasets/sdks/ruby.ts new file mode 100644 index 000000000..0db2219d6 --- /dev/null +++ b/src/datasets/sdks/ruby.ts @@ -0,0 +1,13 @@ +import { SDK } from '.'; + +export const Ruby: SDK = { + name: 'Ruby', + filename: 'ruby', + slug: 'ruby', + category: 'Server', + repo: 'ruby-sdk', + branch: 'main', + logoKey: 'ruby-no-fill.svg', + technology: 'Ruby', + href: '/docs/reference/technologies/server/ruby', +}; diff --git a/src/datasets/sdks/sdk-compatibility.json b/src/datasets/sdks/sdk-compatibility.json index 90a83645a..d8ca1313f 100644 --- a/src/datasets/sdks/sdk-compatibility.json +++ b/src/datasets/sdks/sdk-compatibility.json @@ -4,8 +4,8 @@ "path": "/docs/reference/technologies/server/java", "category": "Server", "release": { - "href": "https://github.com/open-feature/java-sdk/releases/tag/v1.8.0", - "version": "1.8.0", + "href": "https://github.com/open-feature/java-sdk/releases/tag/v1.9.0", + "version": "1.9.0", "stable": true }, "spec": { @@ -212,8 +212,8 @@ "path": "/docs/reference/technologies/server/python", "category": "Server", "release": { - "href": "https://github.com/open-feature/python-sdk/releases/tag/v0.7.0", - "version": "0.7.0", + "href": "https://github.com/open-feature/python-sdk/releases/tag/v0.7.1", + "version": "0.7.1", "stable": false }, "spec": { @@ -264,8 +264,8 @@ "path": "/docs/reference/technologies/server/php", "category": "Server", "release": { - "href": "https://github.com/open-feature/php-sdk/releases/tag/2.0.7", - "version": "2.0.7", + "href": "https://github.com/open-feature/php-sdk/releases/tag/2.0.8", + "version": "2.0.8", "stable": true }, "spec": { @@ -412,8 +412,8 @@ "path": "/docs/reference/technologies/client/swift", "category": "Client", "release": { - "href": "https://github.com/open-feature/swift-sdk/releases/tag/0.1.0", - "version": "0.1.0", + "href": "https://github.com/open-feature/swift-sdk/releases/tag/0.2.0", + "version": "0.2.0", "stable": false }, "spec": { @@ -454,5 +454,57 @@ "path": "/docs/reference/technologies/client/swift#extending" } } + }, + { + "name": "Ruby", + "path": "/docs/reference/technologies/server/ruby", + "category": "Server", + "release": { + "href": "https://github.com/open-feature/ruby-sdk/releases/tag/v0.4.0", + "version": "0.4.0", + "stable": false + }, + "spec": { + "href": "https://github.com/open-feature/spec/releases/tag/v0.8.0", + "version": "0.8.0" + }, + "features": { + "Providers": { + "status": "✅", + "path": "/docs/reference/technologies/server/ruby#providers" + }, + "Targeting": { + "status": "✅", + "path": "/docs/reference/technologies/server/ruby#targeting" + }, + "Hooks": { + "status": "⚠️", + "path": "/docs/reference/technologies/server/ruby#hooks" + }, + "Logging": { + "status": "❌", + "path": "/docs/reference/technologies/server/ruby#logging" + }, + "Domains": { + "status": "✅", + "path": "/docs/reference/technologies/server/ruby#domains" + }, + "Eventing": { + "status": "❌", + "path": "/docs/reference/technologies/server/ruby#eventing" + }, + "Shutdown": { + "status": "⚠️", + "path": "/docs/reference/technologies/server/ruby#shutdown" + }, + "Transaction Context Propagation": { + "status": "❌", + "path": "/docs/reference/technologies/server/ruby#transaction-context-propagation" + }, + "Extending": { + "status": "⚠️", + "path": "/docs/reference/technologies/server/ruby#extending" + } + } } ] \ No newline at end of file diff --git a/src/datasets/types.ts b/src/datasets/types.ts index de7489b8f..ec627ac43 100644 --- a/src/datasets/types.ts +++ b/src/datasets/types.ts @@ -12,7 +12,17 @@ export type EcosystemElement = { category: Category[]; }; -export type Technology = 'JavaScript' | 'Java' | 'Go' | 'PHP' | '.NET' | 'Kotlin' | 'Python' | 'Swift' | 'Rust'; +export type Technology = + | 'JavaScript' + | 'Java' + | 'Go' + | 'PHP' + | '.NET' + | 'Kotlin' + | 'Python' + | 'Swift' + | 'Rust' + | 'Ruby'; export type Category = 'Server' | 'Client'; export type Type = 'Hook' | 'Provider' | 'SDK'; diff --git a/static/img/ruby-no-fill.svg b/static/img/ruby-no-fill.svg new file mode 100644 index 000000000..1cae9f6c2 --- /dev/null +++ b/static/img/ruby-no-fill.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file