{"id":30653784,"url":"https://github.com/featurevisor/featurevisor-ruby","last_synced_at":"2025-10-14T13:08:03.291Z","repository":{"id":311222881,"uuid":"1042930753","full_name":"featurevisor/featurevisor-ruby","owner":"featurevisor","description":"Ruby SDK for Featurevisor","archived":false,"fork":false,"pushed_at":"2025-08-23T17:55:23.000Z","size":81,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-31T08:19:09.207Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://featurevisor.com","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/featurevisor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-22T20:23:45.000Z","updated_at":"2025-08-25T15:32:55.000Z","dependencies_parsed_at":"2025-08-22T23:07:49.148Z","dependency_job_id":null,"html_url":"https://github.com/featurevisor/featurevisor-ruby","commit_stats":null,"previous_names":["featurevisor/featurevisor-ruby"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/featurevisor/featurevisor-ruby","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurevisor%2Ffeaturevisor-ruby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurevisor%2Ffeaturevisor-ruby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurevisor%2Ffeaturevisor-ruby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurevisor%2Ffeaturevisor-ruby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/featurevisor","download_url":"https://codeload.github.com/featurevisor/featurevisor-ruby/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/featurevisor%2Ffeaturevisor-ruby/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279018697,"owners_count":26086604,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-14T02:00:06.444Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-08-31T08:08:03.869Z","updated_at":"2025-10-14T13:08:03.273Z","avatar_url":"https://github.com/featurevisor.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Featurevisor Ruby SDK \u003c!-- omit in toc --\u003e\n\nThis is a port of Featurevisor [JavaScript SDK](https://featurevisor.com/docs/sdks/javascript/) v2.x to Ruby, providing a way to evaluate feature flags, variations, and variables in your Ruby applications.\n\nThis SDK is compatible with [Featurevisor](https://featurevisor.com/) v2.0 projects and above.\n\n## Table of contents \u003c!-- omit in toc --\u003e\n\n- [Installation](#installation)\n- [Initialization](#initialization)\n- [Evaluation types](#evaluation-types)\n- [Context](#context)\n  - [Setting initial context](#setting-initial-context)\n  - [Setting after initialization](#setting-after-initialization)\n  - [Replacing existing context](#replacing-existing-context)\n  - [Manually passing context](#manually-passing-context)\n- [Check if enabled](#check-if-enabled)\n- [Getting variation](#getting-variation)\n- [Getting variables](#getting-variables)\n  - [Type specific methods](#type-specific-methods)\n- [Getting all evaluations](#getting-all-evaluations)\n- [Sticky](#sticky)\n  - [Initialize with sticky](#initialize-with-sticky)\n  - [Set sticky afterwards](#set-sticky-afterwards)\n- [Setting datafile](#setting-datafile)\n  - [Updating datafile](#updating-datafile)\n  - [Interval-based update](#interval-based-update)\n- [Logging](#logging)\n  - [Levels](#levels)\n  - [Customizing levels](#customizing-levels)\n  - [Handler](#handler)\n- [Events](#events)\n  - [`datafile_set`](#datafile_set)\n  - [`context_set`](#context_set)\n  - [`sticky_set`](#sticky_set)\n- [Evaluation details](#evaluation-details)\n- [Hooks](#hooks)\n  - [Defining a hook](#defining-a-hook)\n  - [Registering hooks](#registering-hooks)\n- [Child instance](#child-instance)\n- [Close](#close)\n- [CLI usage](#cli-usage)\n  - [Test](#test)\n  - [Benchmark](#benchmark)\n  - [Assess distribution](#assess-distribution)\n- [Development](#development)\n  - [Setting up](#setting-up)\n  - [Running tests](#running-tests)\n  - [Releasing](#releasing)\n- [License](#license)\n\n\u003c!-- FEATUREVISOR_DOCS_BEGIN --\u003e\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'featurevisor'\n```\n\nAnd then execute:\n\n```bash\n$ bundle install\n```\n\nOr install it yourself as:\n\n```bash\n$ gem install featurevisor\n```\n\n## Initialization\n\nThe SDK can be initialized by passing [datafile](https://featurevisor.com/docs/building-datafiles/) content directly:\n\n```ruby\nrequire 'featurevisor'\nrequire 'net/http'\nrequire 'json'\n\n# Fetch datafile from URL\ndatafile_url = 'https://cdn.yoursite.com/datafile.json'\nresponse = Net::HTTP.get_response(URI(datafile_url))\n\n# Parse JSON with symbolized keys (required)\ndatafile_content = JSON.parse(response.body, symbolize_names: true)\n\n# Create SDK instance\nf = Featurevisor.create_instance(\n  datafile: datafile_content\n)\n```\n\n**Important**: When parsing JSON datafiles, you must use `symbolize_names: true` to ensure proper key handling by the SDK.\n\nAlternatively, you can pass a JSON string directly and the SDK will parse it automatically:\n\n```ruby\n# Option 1: Parse JSON yourself (recommended)\ndatafile_content = JSON.parse(json_string, symbolize_names: true)\nf = Featurevisor.create_instance(datafile: datafile_content)\n\n# Option 2: Pass JSON string directly (automatic parsing)\nf = Featurevisor.create_instance(datafile: json_string)\n```\n\n## Evaluation types\n\nWe can evaluate 3 types of values against a particular [feature](https://featurevisor.com/docs/features/):\n\n- [**Flag**](#check-if-enabled) (`boolean`): whether the feature is enabled or not\n- [**Variation**](#getting-variation) (`string`): the variation of the feature (if any)\n- [**Variables**](#getting-variables): variable values of the feature (if any)\n\nThese evaluations are run against the provided context.\n\n## Context\n\nContexts are [attribute](https://featurevisor.com/docs/attributes/) values that we pass to SDK for evaluating [features](https://featurevisor.com/docs/features/) against.\n\nThink of the conditions that you define in your [segments](https://featurevisor.com/docs/segments/), which are used in your feature's [rules](https://featurevisor.com/docs/features/#rules).\n\nThey are plain hashes:\n\n```ruby\ncontext = {\n  userId: '123',\n  country: 'nl',\n  # ...other attributes\n}\n```\n\nContext can be passed to SDK instance in various different ways, depending on your needs:\n\n### Setting initial context\n\nYou can set context at the time of initialization:\n\n```ruby\nrequire 'featurevisor'\n\nf = Featurevisor.create_instance(\n  context: {\n    deviceId: '123',\n    country: 'nl'\n  }\n)\n```\n\nThis is useful for values that don't change too frequently and available at the time of application startup.\n\n### Setting after initialization\n\nYou can also set more context after the SDK has been initialized:\n\n```ruby\nf.set_context({\n  userId: '234'\n})\n```\n\nThis will merge the new context with the existing one (if already set).\n\n### Replacing existing context\n\nIf you wish to fully replace the existing context, you can pass `true` in second argument:\n\n```ruby\nf.set_context({\n  deviceId: '123',\n  userId: '234',\n  country: 'nl',\n  browser: 'chrome'\n}, true) # replace existing context\n```\n\n### Manually passing context\n\nYou can optionally pass additional context manually for each and every evaluation separately, without needing to set it to the SDK instance affecting all evaluations:\n\n```ruby\ncontext = {\n  userId: '123',\n  country: 'nl'\n}\n\nis_enabled = f.is_enabled('my_feature', context)\nvariation = f.get_variation('my_feature', context)\nvariable_value = f.get_variable('my_feature', 'my_variable', context)\n```\n\nWhen manually passing context, it will merge with existing context set to the SDK instance before evaluating the specific value.\n\nFurther details for each evaluation types are described below.\n\n## Check if enabled\n\nOnce the SDK is initialized, you can check if a feature is enabled or not:\n\n```ruby\nfeature_key = 'my_feature'\n\nis_enabled = f.is_enabled(feature_key)\n\nif is_enabled\n  # do something\nend\n```\n\nYou can also pass additional context per evaluation:\n\n```ruby\nis_enabled = f.is_enabled(feature_key, {\n  # ...additional context\n})\n```\n\n## Getting variation\n\nIf your feature has any [variations](https://featurevisor.com/docs/features/#variations) defined, you can evaluate them as follows:\n\n```ruby\nfeature_key = 'my_feature'\n\nvariation = f.get_variation(feature_key)\n\nif variation == 'treatment'\n  # do something for treatment variation\nelse\n  # handle default/control variation\nend\n```\n\nAdditional context per evaluation can also be passed:\n\n```ruby\nvariation = f.get_variation(feature_key, {\n  # ...additional context\n})\n```\n\n## Getting variables\n\nYour features may also include [variables](https://featurevisor.com/docs/features/#variables), which can be evaluated as follows:\n\n```ruby\nvariable_key = 'bgColor'\n\nbg_color_value = f.get_variable('my_feature', variable_key)\n```\n\nAdditional context per evaluation can also be passed:\n\n```ruby\nbg_color_value = f.get_variable('my_feature', variable_key, {\n  # ...additional context\n})\n```\n\n### Type specific methods\n\nNext to generic `get_variable()` methods, there are also type specific methods available for convenience:\n\n```ruby\nf.get_variable_boolean(feature_key, variable_key, context = {})\nf.get_variable_string(feature_key, variable_key, context = {})\nf.get_variable_integer(feature_key, variable_key, context = {})\nf.get_variable_double(feature_key, variable_key, context = {})\nf.get_variable_array(feature_key, variable_key, context = {})\nf.get_variable_object(feature_key, variable_key, context = {})\nf.get_variable_json(feature_key, variable_key, context = {})\n```\n\n## Getting all evaluations\n\nYou can get evaluations of all features available in the SDK instance:\n\n```ruby\nall_evaluations = f.get_all_evaluations({})\n\nputs all_evaluations\n# {\n#   myFeature: {\n#     enabled: true,\n#     variation: \"control\",\n#     variables: {\n#       myVariableKey: \"myVariableValue\",\n#     },\n#   },\n#\n#   anotherFeature: {\n#     enabled: true,\n#     variation: \"treatment\",\n#   }\n# }\n```\n\nThis is handy especially when you want to pass all evaluations from a backend application to the frontend.\n\n## Sticky\n\nFor the lifecycle of the SDK instance in your application, you can set some features with sticky values, meaning that they will not be evaluated against the fetched [datafile](https://featurevisor.com/docs/building-datafiles/):\n\n### Initialize with sticky\n\n```ruby\nrequire 'featurevisor'\n\nf = Featurevisor.create_instance(\n  sticky: {\n    myFeatureKey: {\n      enabled: true,\n      # optional\n      variation: 'treatment',\n      variables: {\n        myVariableKey: 'myVariableValue'\n      }\n    },\n    anotherFeatureKey: {\n      enabled: false\n    }\n  }\n)\n```\n\nOnce initialized with sticky features, the SDK will look for values there first before evaluating the targeting conditions and going through the bucketing process.\n\n### Set sticky afterwards\n\nYou can also set sticky features after the SDK is initialized:\n\n```ruby\nf.set_sticky({\n  myFeatureKey: {\n    enabled: true,\n    variation: 'treatment',\n    variables: {\n      myVariableKey: 'myVariableValue'\n    }\n  },\n  anotherFeatureKey: {\n    enabled: false\n  }\n}, true) # replace existing sticky features (false by default)\n```\n\n## Setting datafile\n\nYou may also initialize the SDK without passing `datafile`, and set it later on:\n\n```ruby\n# Parse with symbolized keys before setting\ndatafile_content = JSON.parse(json_string, symbolize_names: true)\nf.set_datafile(datafile_content)\n\n# Or pass JSON string directly for automatic parsing\nf.set_datafile(json_string)\n```\n\n**Important**: When calling `set_datafile()`, ensure JSON is parsed with `symbolize_names: true` if you're parsing it yourself.\n\n### Updating datafile\n\nYou can set the datafile as many times as you want in your application, which will result in emitting a [`datafile_set`](#datafile_set) event that you can listen and react to accordingly.\n\nThe triggers for setting the datafile again can be:\n\n- periodic updates based on an interval (like every 5 minutes), or\n- reacting to:\n  - a specific event in your application (like a user action), or\n  - an event served via websocket or server-sent events (SSE)\n\n### Interval-based update\n\nHere's an example of using interval-based update:\n\n```ruby\nrequire 'net/http'\nrequire 'json'\n\ndef update_datafile(f, datafile_url)\n  loop do\n    sleep(5 * 60) # 5 minutes\n    \n    begin\n      response = Net::HTTP.get_response(URI(datafile_url))\n      datafile_content = JSON.parse(response.body)\n      f.set_datafile(datafile_content)\n    rescue =\u003e e\n      # handle error\n      puts \"Failed to update datafile: #{e.message}\"\n    end\n  end\nend\n\n# Start the update thread\nThread.new { update_datafile(f, datafile_url) }\n```\n\n## Logging\n\nBy default, Featurevisor SDKs will print out logs to the console for `info` level and above.\n\n### Levels\n\nThese are all the available log levels:\n\n- `error`\n- `warn`\n- `info`\n- `debug`\n\n### Customizing levels\n\nIf you choose `debug` level to make the logs more verbose, you can set it at the time of SDK initialization.\n\nSetting `debug` level will print out all logs, including `info`, `warn`, and `error` levels.\n\n```ruby\nrequire 'featurevisor'\n\nf = Featurevisor.create_instance(\n  logger: Featurevisor.create_logger(level: 'debug')\n)\n```\n\nAlternatively, you can also set `log_level` directly:\n\n```ruby\nf = Featurevisor.create_instance(\n  log_level: 'debug'\n)\n```\n\nYou can also set log level from SDK instance afterwards:\n\n```ruby\nf.set_log_level('debug')\n```\n\n### Handler\n\nYou can also pass your own log handler, if you do not wish to print the logs to the console:\n\n```ruby\nrequire 'featurevisor'\n\nf = Featurevisor.create_instance(\n  logger: Featurevisor.create_logger(\n    level: 'info',\n    handler: -\u003e(level, message, details) {\n      # do something with the log\n    }\n  )\n)\n```\n\nFurther log levels like `info` and `debug` will help you understand how the feature variations and variables are evaluated in the runtime against given context.\n\n## Events\n\nFeaturevisor SDK implements a simple event emitter that allows you to listen to events that happen in the runtime.\n\nYou can listen to these events that can occur at various stages in your application:\n\n### `datafile_set`\n\n```ruby\nunsubscribe = f.on('datafile_set') do |event|\n  revision = event[:revision]        # new revision\n  previous_revision = event[:previous_revision]\n  revision_changed = event[:revision_changed] # true if revision has changed\n\n  # list of feature keys that have new updates,\n  # and you should re-evaluate them\n  features = event[:features]\n\n  # handle here\nend\n\n# stop listening to the event\nunsubscribe.call\n```\n\nThe `features` array will contain keys of features that have either been:\n\n- added, or\n- updated, or\n- removed\n\ncompared to the previous datafile content that existed in the SDK instance.\n\n### `context_set`\n\n```ruby\nunsubscribe = f.on('context_set') do |event|\n  replaced = event[:replaced] # true if context was replaced\n  context = event[:context]   # the new context\n\n  puts 'Context set'\nend\n```\n\n### `sticky_set`\n\n```ruby\nunsubscribe = f.on('sticky_set') do |event|\n  replaced = event[:replaced] # true if sticky features got replaced\n  features = event[:features] # list of all affected feature keys\n\n  puts 'Sticky features set'\nend\n```\n\n## Evaluation details\n\nBesides logging with debug level enabled, you can also get more details about how the feature variations and variables are evaluated in the runtime against given context:\n\n```ruby\n# flag\nevaluation = f.evaluate_flag(feature_key, context = {})\n\n# variation\nevaluation = f.evaluate_variation(feature_key, context = {})\n\n# variable\nevaluation = f.evaluate_variable(feature_key, variable_key, context = {})\n```\n\nThe returned object will always contain the following properties:\n\n- `feature_key`: the feature key\n- `reason`: the reason how the value was evaluated\n\nAnd optionally these properties depending on whether you are evaluating a feature variation or a variable:\n\n- `bucket_value`: the bucket value between 0 and 100,000\n- `rule_key`: the rule key\n- `error`: the error object\n- `enabled`: if feature itself is enabled or not\n- `variation`: the variation object\n- `variation_value`: the variation value\n- `variable_key`: the variable key\n- `variable_value`: the variable value\n- `variable_schema`: the variable schema\n\n## Hooks\n\nHooks allow you to intercept the evaluation process and customize it further as per your needs.\n\n### Defining a hook\n\nA hook is a simple hash with a unique required `name` and optional functions:\n\n```ruby\nrequire 'featurevisor'\n\nmy_custom_hook = {\n  # only required property\n  name: 'my-custom-hook',\n\n  # rest of the properties below are all optional per hook\n\n  # before evaluation\n  before: -\u003e(options) {\n    # update context before evaluation\n    options[:context] = options[:context].merge({\n      someAdditionalAttribute: 'value'\n    })\n    options\n  },\n\n  # after evaluation\n  after: -\u003e(evaluation, options) {\n    reason = evaluation[:reason]\n    if reason == 'error'\n      # log error\n      return\n    end\n  },\n\n  # configure bucket key\n  bucket_key: -\u003e(options) {\n    # return custom bucket key\n    options[:bucket_key]\n  },\n\n  # configure bucket value (between 0 and 100,000)\n  bucket_value: -\u003e(options) {\n    # return custom bucket value\n    options[:bucket_value]\n  }\n}\n```\n\n### Registering hooks\n\nYou can register hooks at the time of SDK initialization:\n\n```ruby\nrequire 'featurevisor'\n\nf = Featurevisor.create_instance(\n  hooks: [my_custom_hook]\n)\n```\n\nOr after initialization:\n\n```ruby\nf.add_hook(my_custom_hook)\n```\n\n## Child instance\n\nWhen dealing with purely client-side applications, it is understandable that there is only one user involved, like in browser or mobile applications.\n\nBut when using Featurevisor SDK in server-side applications, where a single server instance can handle multiple user requests simultaneously, it is important to isolate the context for each request.\n\nThat's where child instances come in handy:\n\n```ruby\nchild_f = f.spawn({\n  # user or request specific context\n  userId: '123'\n})\n```\n\nNow you can pass the child instance where your individual request is being handled, and you can continue to evaluate features targeting that specific user alone:\n\n```ruby\nis_enabled = child_f.is_enabled('my_feature')\nvariation = child_f.get_variation('my_feature')\nvariable_value = child_f.get_variable('my_feature', 'my_variable')\n```\n\nSimilar to parent SDK, child instances also support several additional methods:\n\n- `set_context`\n- `set_sticky`\n- `is_enabled`\n- `get_variation`\n- `get_variable`\n- `get_variable_boolean`\n- `get_variable_string`\n- `get_variable_integer`\n- `get_variable_double`\n- `get_variable_array`\n- `get_variable_object`\n- `get_variable_json`\n- `get_all_evaluations`\n- `on`\n- `close`\n\n## Close\n\nBoth primary and child instances support a `.close()` method, that removes forgotten event listeners (via `on` method) and cleans up any potential memory leaks.\n\n```ruby\nf.close()\n```\n\n## CLI usage\n\nThis package also provides a CLI tool for running your Featurevisor [project](https://featurevisor.com/docs/projects/)'s test specs and benchmarking against this Ruby SDK.\n\n- Global installation: you can access it as `featurevisor`\n- Local installation: you can access it as `bundle exec featurevisor`\n- From this repository: you can access it as `bin/featurevisor`\n\n### Test\n\nLearn more about testing [here](https://featurevisor.com/docs/testing/).\n\n```bash\n$ bundle exec featurevisor test --projectDirectoryPath=\"/absolute/path/to/your/featurevisor/project\"\n```\n\nAdditional options that are available:\n\n```bash\n$ bundle exec featurevisor test \\\n  --projectDirectoryPath=\"/absolute/path/to/your/featurevisor/project\" \\\n  --quiet|verbose \\\n  --onlyFailures \\\n  --keyPattern=\"myFeatureKey\" \\\n  --assertionPattern=\"#1\"\n```\n\n### Benchmark\n\nLearn more about benchmarking [here](https://featurevisor.com/docs/cmd/#benchmarking).\n\n```bash\n$ bundle exec featurevisor benchmark \\\n  --projectDirectoryPath=\"/absolute/path/to/your/featurevisor/project\" \\\n  --environment=\"production\" \\\n  --feature=\"myFeatureKey\" \\\n  --context='{\"country\": \"nl\"}' \\\n  --n=1000\n```\n\n### Assess distribution\n\nLearn more about assessing distribution [here](https://featurevisor.com/docs/cmd/#assess-distribution).\n\n```bash\n$ bundle exec featurevisor assess-distribution \\\n  --projectDirectoryPath=\"/absolute/path/to/your/featurevisor/project\" \\\n  --environment=production \\\n  --feature=foo \\\n  --variation \\\n  --context='{\"country\": \"nl\"}' \\\n  --populateUuid=userId \\\n  --populateUuid=deviceId \\\n  --n=1000\n```\n\n\u003c!-- FEATUREVISOR_DOCS_END --\u003e\n\n## Development\n\n### Setting up\n\nAfter 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.\n\n### Running tests\n\n```bash\n$ bundle exec rspec\n```\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n### Releasing\n\n- Update version in `lib/featurevisor/version.rb`\n- Run `bundle install`\n- Push commit to `main` branch\n- Wait for CI to complete\n- Tag the release with the version number\n- This will trigger a new release to [RubyGems](https://rubygems.org/gems/featurevisor)\n\n## License\n\nMIT © [Fahad Heylaal](https://fahad19.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeaturevisor%2Ffeaturevisor-ruby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffeaturevisor%2Ffeaturevisor-ruby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeaturevisor%2Ffeaturevisor-ruby/lists"}