Have you ever wanted to test out a new feature 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.
- Rails 3.2+
- Ruby 1.9.2+
First add Chili to your app's Gemfile:
gem 'chili'
and run bundle
.
Chili extensions are like mini apps that are created inside your main app's vendor directory using using the "chili" generator.
As an example, assuming you want to add a new extension named "social" that exposes a new feature in the form of a like-button to a subset of users, first within your main app run:
$ rails g chili:extension social
This will:
- Create the directory
vendor/chili/social_extension
containing the basic structure for the extension - Add a reference to the extension to the main app gemfile
Since the extension is mounted as a gem you'll have to restart the app.
Use the active_if block to control whether new the extension is active for each user. The context of the active_if block is the application controller so you can use any methods available to that.
# lib/social_extension.rb
module SocialExtension
extend Chili::Activatable
active_if { logged_in? && current_user.admin? } # Extension is only visible to logged in admin users
end
Chili uses Deface to dynamically modify existing view templates (see Deface docs for details)
Add overrides to the app/overides
directory mirroring the path of the view you want to modify.
For example, assuming the main app has the partial app/views/posts/_post.html.erb
:
<% # app/overrides/posts/_post/like_button.html.erb.deface (folder should mirror main app view path) %>
<!-- insert_bottom 'tr' -->
<td><%= link_to 'Like!', social_extension.likes_path(like: {post_id: post}), method: :post %></td>
Run rails g scaffold Like
from within the extension's directory. The new resource will be namespaced to SocialExtension::Like
and automounted as an isolated engine in the main app at /chili/social_extension/likes
,
but will only be accessible when active_if is true.
Migrations are handled the same way as engines. Use the following commands after you've added a new migration to your extension:
$ rake social_extension:migrations:install
$ rake db:migrate
Create a model with the same name as the one you want to modify by running: rails g model User --migration=false
inside your extension's directory
and edit it to inherit from the original:
# app/models/social_extension/user.rb
module SocialExtension
class User < ::User
has_many :likes
end
end
Access in your overrides/extension views through the namespaced model:
<%= SocialExtension::User.first.likes %>
<%= current_user.becomes(SocialExtension::User).likes %>
Files added to the extension's app/assets/social_extension/javascripts|stylesheets
directory are automatically injected into the layout using a pre-generated override:
<% # app/overrides/layouts/application/assets.html.erb.deface %>
<!-- insert_bottom 'head' -->
<%= stylesheet_link_tag 'social_extension/application' %>
<%= javascript_include_tag 'social_extension/application' %>
If you don't need any css/js in your extension, you can remove this file.
- Chili will not be able to automount if you use a catch-all route in your main app (ie
match '*a', to: 'errors#routing'
), you will have to remove the catch-all or manually add the engine to the main app's routes file. - Just like normal engines, Chili requires you to prepend path helpers with
main_app
(iemain_app.root_path
etc) in view templates that are shared with the main app (such as the main app's application layout file).