What
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 %>
Then Trish writes some Javascript that will poll the server for recent postings and put them in the sidebar widget:
// in public/javascript/live_postings.js:
$(function() {
var livePostingsList = $('#live_postings');
if (livePostingsList.length > 0) {
var updatePostingsList = function() {
livePostingsList.load('/listings/recent');
setTimeout(updatePostingsList, 30);
}
updatePostingsList();
}
});
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
to /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.
Installation
In Rails 3, with Bundler
gem 'arturo', '~> 1.0'
In Rails 3, without Bundler
$ gem install arturo --version="~> 1.0"
Note: the following two sections describe the intended use of Arturo with Rails 2. Arturo does not yet have Rails 2 support.
In Rails 2, with Bundler
gem 'arturo', :git => 'git://github.com/jamesarosen/arturo.git',
:tag => 'rails_2_3'
In Rails 2, without Bundler
Put the rails_2_3
branch of git://github.com/jamesarosen/arturo.git
into
your vendor/plugins/
directory. You can use Git submodules or a simple
checkout.
Configuration
In Rails
Run the migrations:
$ rails g arturo:migration
$ rails g arturo:initializer
$ rails g arturo:route
$ rails g arturo:assets
Edit the configuration
Initializer
Open up the newly-generated config/initializers/arturo_initializer.rb
.
There are configuration options for the following:
- the block that determines whether a user has permission to manage features (see admin permissions)
- the block that yields the object that has features (a User, Person, or Account, see feature recipients)
- whitelists and blacklists for features (see white- and blacklisting)
CSS
Open up the newly-generated public/stylehseets/arturo_customizations.css
.
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.
Deep-Dive
Admin Permissions
Arturo.permit_management
is a block that is run in the context of
a Controller instance. It should return true
iff the current user
can manage permissions. Configure the block in
config/initializers/arturo_initializer.rb
. A reasonable implementation
might be
Arturo.permit_management do
current_user.admin?
end
or
Arturo.permit_management do
signed_in? && signed_in_person.can?(:manage_features)
end
Feature Recipients
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.feature_recipient
is intended to support these many use cases. It is a
block 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. Like Arturo.permit_management
, it is configured in
config/initializers/arturo_initializer.rb
. It should return an Object
that
responds to #id
. If you want to deploy features on a per-user basis, a
reasonable implementation might be
Arturo.thing_that_has_features do
current_user
end
or
Arturo.thing_that_has_features do
signed_in_person
end
If, on the other hand, you have accounts that have many users and you want to deploy features on a per-account basis, a reasonable implementation might be
Arturo.thing_that_has_features do
current_account
end
or
Arturo.thing_that_has_features 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
config/initializers/arturo_initializer.rb
:
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
config/initializers/arturo_initializer.rb
:
Arturo::Feature.blacklist(:awesome) do |user|
user.account.free?
end
Feature Conditionals
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.
Controller Filters
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 :hold_book
feature.
class BookHoldsController < ApplicationController
require_feature! :hold_book
end
require_feature!
accepts as a second argument a Hash
that it passes on
to before_filter
, so you can use :only
and :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.
Conditional Evaluation
Both controllers and views have access to the if_feature_enabled
and
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
= []
<< if feature_enabled?(:twitter_integration)
...
end
Caching
Note: Arturo does not yet have caching support. Be very careful when caching actions or pages that involve feature detection as you will get strange behavior when a use who has access to a feature requests a page just after one who does not (and vice versa). The following is the intended support for caching.
Both the require_feature!
before filter and the if_feature_enabled
block
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
percentage. See Arturo::CacheSupport
for more information.
The Name
Arturo gets its name from Professor Maximillian Arturo on Sliders.