ChiliThe spicy feature toggle framework for Rails

Fork me on GitHub
Test out prototypes in production, with subsets of users, without touching your main code base.

Have you ever wanted to test out new features on only a subset of users? Did that implementation end up being lots of if/else statements embedded in the main code? If so, Chili can help.

Chili is built on top of Rails Engines and Deface and allows you to conditionally add new/modify existing views, while leaving the main code untouched.


The easiest way to explain how Chili works is through an example. Lets say we have a simple site were people can submit posts about their travel experiences:

It is hard to tell which posts are interesting so we decide to test out a new social feature where users can "like" posts and see which are popular.

We want to first test out this feature on a set of beta testers before deciding whether to release it to everyone.
One way of doing it would be something like this:


  <td><%= post.title %></td>
  <% if logged_in? && current_user.beta_tester? %>
    <td><%= link_to 'Like!', likes_path(like: { post_id: post }), method: 'post' %></td>
  <% end %>

However, adding conditionals in the code like this can quickly become unmaintainable. And what about the likes_controller? How do we make sure that only beta testers can access this?

Chili makes it easier to do this in a clean and unobtrusive fashion!

Creating a new chili feature

First add Chili to your main app's gemfile and run bundle:

gem 'chili'

Then generate a new feature named "social":

$ rails g chili:feature social

This will:

  1. Create the directory lib/chili/social_feature containing the basic structure for the feature
  2. Add a reference to the main app gemfile:

group :chili do
  gem 'social_feature', path: 'lib/chili/social_feature'

Since the feature is mounted as a gem we restart the app:

The message at the top is injected into the layout by the feature and shows us that it is working.

Modifying view templates in main app

Chili uses Deface overrides to dynamically modify existing view templates (see Deface docs for details).
The message shown is an example of how this works.

For now, let's remove the example override and add our own. We will start out by adding a 'Like' link next to each post. Our app's posts/_post partial looks like this:


  <td><%= post.title %></td>

We can modify this at runtime by adding an override to the feature, using a generator:

$ rails g social_feature deface:override posts/_post like_actions

This will create a dummy override for the posts/_post partial that we can modify:


<!-- insert_bottom 'tr' -->
<td><%= link_to 'Like!', '#' %></td>

Deface's "magical comment" <!-- insert_bottom 'tr' --> looks for tr tags in the partial and appends the html written under the comment. Refreshing the page shows the result:

Define who can see the feature

By default a newly generated feature will be active for everyone. Let's change this so that only beta testers can see the links by editing the active_if block in lib/social_feature.rb


module SocialFeature
  extend Chili::Base
  active_if { logged_in? && current_user.beta_tester? }

Now the links will be hidden unless a user with a beta_tester boolean set to true logs in.

The context of the active_if block is the application controller so you can use any methods available to that.
You could also use something like Rollout within active_if for more granular control

Adding new resources

The links don't actually do anything yet. Let's add the controller and model code needed to make this work.
You can use all the typical Rails generators for Chili features by prepending the generator with the name of the feature:

$ rails g social_feature scaffold Like post:references
$ rake social_feature:db:migrate

The new resource will be automatically mounted as an isolated engine in the main app at /chili/social_feature/likes. Only when active_if is true is this URL available, otherwise it will return a 404!

We can now modify the override and add the full path to the links:


<!-- insert_bottom 'tr' -->
<td><%= link_to 'Like!', social_feature.likes_path(like: { post_id: post }), method: 'post' %></td>

Note that since the new resource is added as an isolated engine in the main app you will have to prefix paths with the name of the feature.

Modifying existing models

We now have a working like button, only available to beta users, but it would be nice if we could also see how many likes each post has.

We will need to go through the Post model but we don't want to add the has_many association directly to the main code.

Instead we can extend the Post model inside the feature. Run:

$ rails g social_feature model Post --migration=false

Edit the generated file to make it inherit from the original Post model and add the association:


module SocialFeature
  class Post < ::Post
    has_many :likes

This will now allow you to access the has_many association by going through the namespaced model:


<!-- insert_bottom 'tr' -->
<td><%= link_to 'Like!', social_feature.likes_path(like: { post_id: post }), method: 'post' %></td>
<td><%= pluralize post.becomes(SocialFeature::Post).likes.size, 'like' %></td>

Now each post shows how many likes it has received:

You can also add new methods to models in this way. Writing becomes each time may get cumbersome so in that case using tap can clean things up:


<!-- insert_bottom 'tr' -->
<% post.becomes(SocialFeature::Post).tap do |post| %>
  <td><%= link_to 'Like!', social_feature.likes_path(like: { post_id: post }), method: 'post' %></td>
  <td><%= pluralize post.likes.size, 'like' %></td>
  <td class='remark'><%= post.well_liked? ? 'This post is well liked!' : 'This post is boring...' %></td>
<% end %>


Finally let's add some styling to the feature:


.remark {

Files added to the feature's app/assets/social_feature/javascripts|stylesheets directory are automatically injected into the layout using a pre-generated override:


<!-- insert_bottom 'head' -->
<%= stylesheet_link_tag 'social_feature/application' %>
<%= javascript_include_tag 'social_feature/application' %>

This gives us the final result, only visible to logged in beta users:

Please give Chili a try and let me know of any bugs/feature requests on GitHub!