Arturo provides feature sliders for Rails. It lets you turn features on and off just like feature flippers, but offers more fine-grained control. It supports deploying features only for a given percent* of your users and whitelisting and blacklisting users based on any criteria you can express in Ruby.
* The selection isn't random. It's not even pseudo-random. It's completely deterministic. This assures that if a user has a feature on Monday, the user will still have it on Tuesday (unless, of course, you *decrease* the feature's deployment percentage or change its white- or blacklist settings).
A quick example
Trish, a developer is working on a new feature: a live feed of recent postings in the user's city that shows up in the user's sidebar. First, she uses Arturo's view helpers to control who sees the sidebar widget:
<%# in app/views/layout/_sidebar.html.erb: %> <% if_feature_enabled(:live_postings) do %> <div class='widget'> <h3>Recent Postings</h3> <ol id='live_postings'> </ol> </div> <% end %>
Trish uses Arturo's Controller filters to control who has access to the feature:
# in app/controllers/postings_controller: class PostingsController < ApplicationController require_feature :live_postings, :only => :recent # ... end
Trish then deploys this code to production. Nobody will see the feature yet,
since it's not on for anyone. (In fact, the feature doesn't yet exist
in the database, which is the same as being deployed to 0% of users.) A week
later, when the company is ready to start deploying the feature to a few
people, the product manager, Vijay, signs in to their site and navigates
/features, adds a new feature called "live_postings" and sets its
deployment percentage to 3%. After a few days, the operations team decides
that the increase in traffic is not going to overwhelm their servers, and
Vijay can bump the deployment percentage up to 50%. A few more days go by
and they clean up the last few bugs they found with the "live_postings"
feature and deploy it to all users.
In Rails 3 or 4, with Bundler
gem 'arturo', '~> 1.0'
In Rails 3 or 4, without Bundler
$ gem install arturo --version="~> 1.0"
In Rails 2.3
Rails 2.3 is no longer supported and has been archived on the
Run the generators:
$ rails g arturo:migration $ rails g arturo:initializer $ rails g arturo:routes $ rails g arturo:assets
Run the migration:
$ rake db:migrate
Edit the generated migration as necessary
Edit the configuration
Open up the newly-generated
There are configuration options for the following:
- the method that determines whether a user has permission to manage features (see admin permissions)
- the method that returns the object that has features (e.g. User, Person, or Account; see feature recipients)
- whitelists and blacklists for features (see white- and blacklisting)
Open up the newly-generated
You can add any overrides you like to the feature configuration page styles
here. Do not edit
public/stylehseets/arturo.css as that file may be
overwritten in future updates to Arturo.
In other frameworks
Arturo is a Rails engine. I want to promote reuse on other frameworks by extracting key pieces into mixins, though this isn't done yet. Open an issue and I'll be happy to work with you on support for your favorite framework.
Arturo::FeatureManagement#may_manage_features? is a method that is run in
the context of a Controller or View instance. It should return
and only if the current user may manage permissions. The default implementation
is as follows:
current_user.present? && current_user.admin?
You can change the implementation in
config/initializers/arturo_initializer.rb. A reasonable implementation
Arturo.permit_management do signed_in? && current_user.can?(:manage_features) end
Clients of Arturo may want to deploy new features on a per-user, per-project, per-account, or other basis. For example, it is likely Twitter deployed "#newtwitter" on a per-user basis. Conversely, Facebook -- at least in its early days -- may have deployed features on a per-university basis. It wouldn't make much sense to deploy a feature to one user of a Basecamp project but not to others, so 37Signals would probably want a per-project or per-account basis.
Arturo::FeatureAvailability#feature_recipient is intended to support these
many use cases. It is a method that returns the current "thing" (a user, account,
project, university, ...) that is a member of the category that is the basis for
deploying new features. It should return an
Object that responds to
The default implementation simply returns
Arturo::FeatureManagement#may_manage_features?, this method can be configured
config/initializers/arturo_initializer.rb. If you want to deploy features
on a per-account basis, a reasonable implementation might be
Arturo.feature_recipient do current_account end
Arturo.feature_recipient do current_user.account end
If the block returns
nil, the feature will be disabled.
Whitelists & Blacklists
Whitelists and blacklists allow you to control exactly which users or accounts
will have a feature. For example, if all premium users should have the
:awesome feature, place the following in
Arturo::Feature.whitelist(:awesome) do |user| user.account.premium? end
If, on the other hand, no users on the free plan should have the
:awesome feature, place the following in
Arturo::Feature.blacklist(:awesome) do |user| user.account.free? end
If you want to whitelist or blacklist large groups of features at once, you can move the feature argument into the block:
Arturo::Feature.whitelist do |feature, user| user.account.has?(feature.to_sym) end
All that configuration is just a waste of time if Arturo didn't modify the behavior of your application based on feature availability. There are a few ways to do so.
If an action should only be available to those with a feature enabled,
use a before filter. The following will raise a 403 Forbidden error for
every action within
BookHoldsController that is invoked by a user who
does not have the
class BookHoldsController < ApplicationController require_feature :hold_book end
require_feature accepts as a second argument a
Hash that it passes on
before_filter, so you can use
:except to specify exactly
which actions are filtered.
If you want to customize the page that is rendered on 403 Forbidden
responses, put the view in
RAILS_ROOT/app/views/arturo/features/forbidden.html.erb. Rails will
check there before falling back on Arturo's forbidden page.
Both controllers and views have access to the
feature_enabled? methods. The former is used like so:
<% if_feature_enabled?(:reserve_table) %> <%= link_to 'Reserve a table', new_restaurant_reservation_path(:restaurant_id => @restaurant) %> <% end %>
The latter can be used like so:
def widgets_for_sidebar widgets =  widgets << twitter_widget if feature_enabled?(:twitter_integration) ... widgets end
require 'arturo' use Arturo::Middleware, :feature => :my_feature
Outside a Controller
If you want to check availability outside of a controller or view (really
outside of something that has
Arturo::FeatureAvailability mixed in), you
can ask either
or the slightly fancier
Both check whether the
foo feature exists and is enabled for
Note: Arturo has support for caching
Feature lookups, but doesn't yet
integrate with Rails's caching. This means you should be very careful when
caching actions or pages that involve feature detection as you will get
strange behavior when a user who has access to a feature requests a page
just after one who does not (and vice versa).
To enable caching
Feature lookups, mix
Arturo::Feature and set the
cache_ttl. This is best done in an
Arturo::Feature.extend(Arturo::FeatureCaching) Arturo::Feature.cache_ttl = 10.minutes
You can also warm the cache on startup:
This will pre-fetch all
Features and put them in the cache.
The following is the intended support for integration with view caching:
require_feature before filter and the
evaluation automatically append a string based on the feature's
last_modified timestamp to cache keys that Rails generates. Thus, you don't
have to worry about expiring caches when you increase a feature's deployment
Arturo::CacheSupport for more information.