{"id":13880024,"url":"https://github.com/zendesk/arturo","last_synced_at":"2025-04-09T12:08:44.553Z","repository":{"id":417989,"uuid":"964021","full_name":"zendesk/arturo","owner":"zendesk","description":"Feature Sliders for Rails","archived":false,"fork":false,"pushed_at":"2024-04-12T14:15:22.000Z","size":563,"stargazers_count":186,"open_issues_count":5,"forks_count":24,"subscribers_count":276,"default_branch":"master","last_synced_at":"2024-04-14T08:51:54.530Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://github.com/zendesk/arturo","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zendesk.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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}},"created_at":"2010-10-05T16:25:14.000Z","updated_at":"2024-07-12T12:00:13.492Z","dependencies_parsed_at":"2022-08-09T03:02:00.268Z","dependency_job_id":"ee6968a2-f1fc-4635-9b04-28af5cfdc3f9","html_url":"https://github.com/zendesk/arturo","commit_stats":{"total_commits":292,"total_committers":26,"mean_commits":11.23076923076923,"dds":0.4246575342465754,"last_synced_commit":"c93046ce933a872afd32d569fcd294e50c8fbd90"},"previous_names":["jamesarosen/arturo"],"tags_count":57,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zendesk%2Farturo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zendesk%2Farturo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zendesk%2Farturo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zendesk%2Farturo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zendesk","download_url":"https://codeload.github.com/zendesk/arturo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248036067,"owners_count":21037092,"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","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":"2024-08-06T08:02:43.999Z","updated_at":"2025-04-09T12:08:44.528Z","avatar_url":"https://github.com/zendesk.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"## What\n\nArturo provides feature sliders for Rails. It lets you turn features on and off\njust like\n[feature flippers](https://code.flickr.net/2009/12/02/flipping-out/),\nbut offers more fine-grained control. It supports deploying features only for\na given percentage of your users and whitelisting and blacklisting users based\non any criteria you can express in Ruby.\n\nThe selection is deterministic. So if a user has a feature on Monday, the\nuser will still have it on Tuesday (unless you *decrease* the feature's\ndeployment percentage or change its white- or blacklist settings).\n\n### A quick example\n\nTrish, a developer is working on a new feature: a live feed of recent postings\nin the user's city that shows up in the user's sidebar. First, she uses Arturo's\nview helpers to control who sees the sidebar widget:\n\n```ERB\n\u003c%# in app/views/layout/_sidebar.html.erb: %\u003e\n  \u003c% if_feature_enabled(:live_postings) do %\u003e\n  \u003cdiv class='widget'\u003e\n    \u003ch3\u003eRecent Postings\u003c/h3\u003e\n    \u003col id='live_postings'\u003e\n    \u003c/ol\u003e\n  \u003c/div\u003e\n\u003c% end %\u003e\n```\n\nThen Trish writes some Javascript that will poll the server for recent\npostings and put them in the sidebar widget:\n\n```js\n// in public/javascript/live_postings.js:\n$(function() {\n  var livePostingsList = $('#live_postings');\n  if (livePostingsList.length \u003e 0) {\n    var updatePostingsList = function() {\n      livePostingsList.load('/listings/recent');\n      setTimeout(updatePostingsList, 30);\n    }\n    updatePostingsList();\n  }\n});\n```\n\nTrish uses Arturo's Controller filters to control who has access to\nthe feature:\n\n```Ruby\n# in app/controllers/postings_controller:\nclass PostingsController \u003c ApplicationController\n  require_feature :live_postings, only: :recent\n  # ...\nend\n```\n\nTrish then deploys this code to production. Nobody will see the feature yet,\nsince it's not on for anyone. (In fact, the feature doesn't yet exist\nin the database, which is the same as being deployed to 0% of users.) A week\nlater, when the company is ready to start deploying the feature to a few\npeople, the product manager, Vijay, signs in to their site and navigates\nto `/features`, adds a new feature called \"live_postings\" and sets its\ndeployment percentage to 3%. After a few days, the operations team decides\nthat the increase in traffic is not going to overwhelm their servers, and\nVijay can bump the deployment percentage up to 50%. A few more days go by\nand they clean up the last few bugs they found with the \"live_postings\"\nfeature and deploy it to all users.\n\n## Installation\n\n```Ruby\ngem 'arturo'\n```\n\n## Configuration\n\n### In Rails\n\n#### Run the generators:\n\n```\nrails g arturo:migration\nrails g arturo:initializer\nrails g arturo:routes\nrails g arturo:assets\nrails g arturo:feature_model\n```\n\n#### Run the migration:\n\n```\nrake db:migrate\n```\n\n#### Edit the generated migration as necessary\n\n#### Edit the configuration\n\n#### Edit the Feature model\n\nBy default, the generated model `Arturo::Feature` inherits from `ActiveRecord::Base`. However, if you’re using multiple databases your models should inherit from an abstract class that specifies a database connection, not directly from `ActiveRecord::Base`. Update the generated model in `app/models/arturo/feature.rb` to make it use a correct database.\n\n##### Initializer\n\nOpen up the newly-generated `config/initializers/arturo_initializer.rb`.\nThere are configuration options for the following:\n\n * logging capabilities (see [logging](#logging))\n * the method that determines whether a user has permission to manage features\n   (see [admin permissions](#adminpermissions))\n * the method that returns the object that has features\n   (e.g. User, Person, or Account; see\n   [feature recipients](#featurerecipients))\n * whitelists and blacklists for features\n   (see [white- and blacklisting](#wblisting))\n\n##### CSS\n\nOpen up the newly-generated `public/stylesheets/arturo_customizations.css`.\nYou can add any overrides you like to the feature configuration page styles\nhere. **Do not** edit `public/stylesheets/arturo.css` as that file may be\noverwritten in future updates to Arturo.\n\n### In other frameworks\n\nArturo is a Rails engine. I want to promote reuse on other frameworks by\nextracting key pieces into mixins, though this isn't done yet. Open an\n[issue](http://github.com/zendesk/arturo/issues) and I'll be happy to\nwork with you on support for your favorite framework.\n\n## Deep-Dive\n\n### \u003cspan id='logging'\u003eLogging\u003c/span\u003e\n\nYou can provide a logger in order to inspect Arturo usage.\nA potential implementation for Rails would be:\n\n```Ruby\nArturo.logger = Rails.logger\n```\n\n### \u003cspan id='adminpermissions'\u003eAdmin Permissions\u003c/span\u003e\n\n`Arturo::FeatureManagement#may_manage_features?` is a method that is run in\nthe context of a Controller or View instance. It should return `true` if\nand only if the current user may manage permissions. The default implementation\nis as follows:\n\n```Ruby\ncurrent_user.present? \u0026\u0026 current_user.admin?\n```\n\nYou can change the implementation in\n`config/initializers/arturo_initializer.rb`. A reasonable implementation\nmight be\n\n```Ruby\nArturo.permit_management do\n  signed_in? \u0026\u0026 current_user.can?(:manage_features)\nend\n```\n\n### \u003cspan id='featurerecipients'\u003eFeature Recipients\u003c/span\u003e\n\nClients of Arturo may want to deploy new features on a per-user, per-project,\nper-account, or other basis. For example, it is likely Twitter deployed\n\"#newtwitter\" on a per-user basis. Conversely, Facebook -- at least in its\nearly days -- may have deployed features on a per-university basis. It wouldn't\nmake much sense to deploy a feature to one user of a Basecamp project but not\nto others, so 37Signals would probably want a per-project or per-account basis.\n\n`Arturo::FeatureAvailability#feature_recipient` is intended to support these\nmany use cases. It is a method that returns the current \"thing\" (a user, account,\nproject, university, ...) that is a member of the category that is the basis for\ndeploying new features. It should return an `Object` that responds to `#id`.\n\nThe default implementation simply returns `current_user`. Like\n`Arturo::FeatureManagement#may_manage_features?`, this method can be configured\nin `config/initializers/arturo_initializer.rb`. If you want to deploy features\non a per-account basis, a reasonable implementation might be\n\n```Ruby\nArturo.feature_recipient do\n  current_account\nend\n```\n\nor\n\n```Ruby\nArturo.feature_recipient do\n  current_user.account\nend\n```\n\nIf the block returns `nil`, the feature will be disabled.\n\n### \u003cspan id='wblisting'\u003eWhitelists \u0026 Blacklists\u003c/span\u003e\n\nWhitelists and blacklists allow you to control exactly which users or accounts\nwill have a feature. For example, if all premium users should have the\n`:awesome` feature, place the following in\n`config/initializers/arturo_initializer.rb`:\n\n```Ruby\nArturo::Feature.whitelist(:awesome) do |user|\n  user.account.premium?\nend\n```\n\nIf, on the other hand, no users on the free plan should have the\n`:awesome` feature, place the following in\n`config/initializers/arturo_initializer.rb`:\n\n```Ruby\nArturo::Feature.blacklist(:awesome) do |user|\n  user.account.free?\nend\n```\n\nIf you want to whitelist or blacklist large groups of features at once, you\ncan move the feature argument into the block:\n\n```Ruby\nArturo::Feature.whitelist do |feature, user|\n  user.account.has?(feature.to_sym)\nend\n```\n\n### Feature Conditionals\n\nAll that configuration is just a waste of time if Arturo didn't modify the\nbehavior of your application based on feature availability. There are a few\nways to do so.\n\n#### Controller Filters\n\nIf an action should only be available to those with a feature enabled,\nuse a before filter. The following will raise a 403 Forbidden error for\nevery action within `BookHoldsController` that is invoked by a user who\ndoes not have the `:hold_book` feature.\n\n```Ruby\nclass BookHoldsController \u003c ApplicationController\n  require_feature :hold_book\nend\n```\n\n`require_feature` accepts as a second argument a `Hash` that it passes on\nto `before_action`, so you can use `:only` and `:except` to specify exactly\nwhich actions are filtered.\n\nIf you want to customize the page that is rendered on 403 Forbidden\nresponses, put the view in\n`RAILS_ROOT/app/views/arturo/features/forbidden.html.erb`. Rails will\ncheck there before falling back on Arturo's forbidden page.\n\n#### Conditional Evaluation\n\nBoth controllers and views have access to the `if_feature_enabled?` and\n`feature_enabled?` methods. The former is used like so:\n\n```ERB\n\u003c% if_feature_enabled?(:reserve_table) %\u003e\n  \u003c%= link_to 'Reserve a table', new_restaurant_reservation_path(:restaurant_id =\u003e @restaurant) %\u003e\n\u003c% end %\u003e\n```\n\nThe latter can be used like so:\n\n```Ruby\ndef widgets_for_sidebar\n  widgets = []\n  widgets \u003c\u003c twitter_widget if feature_enabled?(:twitter_integration)\n  ...\n  widgets\nend\n```\n\n#### Rack Middleware\n\n```Ruby\nrequire 'arturo'\nuse Arturo::Middleware, feature: :my_feature\n```\n\n#### Outside a Controller\n\nIf you want to check availability outside of a controller or view (really\noutside of something that has `Arturo::FeatureAvailability` mixed in), you\ncan ask either\n\n```Ruby\nArturo.feature_enabled_for?(:foo, recipient)\n```\n\nor the slightly fancier\n\n```Ruby\nArturo.foo_enabled_for?(recipient)\n```\n\nBoth check whether the `foo` feature exists and is enabled for `recipient`.\n\n#### Caching\n\n**Note**: Arturo has support for caching `Feature` lookups, but doesn't yet\nintegrate with Rails's caching. This means you should be very careful when\ncaching actions or pages that involve feature detection as you will get\nstrange behavior when a user who has access to a feature requests a page\njust after one who does not (and vice versa).\n\nTo enable caching `Feature` lookups, mix `Arturo::FeatureCaching` into\n`Arturo::Feature` and set the `cache_ttl`. This is best done in an\ninitializer:\n\n```Ruby\nArturo::Feature.extend(Arturo::FeatureCaching)\nArturo::Feature.cache_ttl = 10.minutes\n```\n\nYou can also warm the cache on startup:\n\n```Ruby\nArturo::Feature.warm_cache!\n```\n\nThis will pre-fetch all `Feature`s and put them in the cache.\n\nTo use the current cache state when you can't fetch updates from origin:\n\n```Ruby\nArturo::Feature.extend_cache_on_failure = true\n```\n\nThe following is the **intended** support for integration with view caching:\n\nBoth the `require_feature` before filter and the `if_feature_enabled` block\nevaluation automatically append a string based on the feature's\n`last_modified` timestamp to cache keys that Rails generates. Thus, you don't\nhave to worry about expiring caches when you increase a feature's deployment\npercentage. See `Arturo::CacheSupport` for more information.\n\n## The Name\n\nArturo gets its name from\n[Professor Maximillian Arturo](http://en.wikipedia.org/wiki/Maximillian_Arturo)\non [Sliders](http://en.wikipedia.org/wiki/Sliders).\n\n## Quality Metrics\n\n[![Build Status](https://github.com/zendesk/arturo/workflows/CI/badge.svg)](https://github.com/zendesk/arturo/actions?query=workflow%3ACI)\n\n[![Code Quality](https://codeclimate.com/github/zendesk/arturo.png)](https://codeclimate.com/github/zendesk/arturo)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzendesk%2Farturo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzendesk%2Farturo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzendesk%2Farturo/lists"}