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:
<tr>
<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 %>
</tr>
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!
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:
lib/chili/social_feature
containing the basic structure for the feature
group :chili do
gem 'social_feature', path: 'lib/chili/social_feature'
end
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.
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:
<tr>
<td><%= post.title %></td>
</tr>
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:
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? }
end
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
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.
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
end
end
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 {
font-weight:normal;
font-style:italic;
color:#888;
}
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!