How to create a Rails Engine
What is a Rails Engine?
Engine are miniature Rails applications that you embed into your main application. You can share an Engine accross different applications. Since Rails 3.0, every Rails application is nothing more than an Engine, allowing you to share it very easily
Sentinel
In this post, we are going to show you how to write an Engine. Since the Engine will provides authentication functionality so I named it, Sentinel. Sentinel is a simple Rails application that provides user model with sign up and log in. Most of the code here is taken directly from the nifty:authentication code which is part of the nifty-generators gem.
Let's create the basic Engine files and directories, here is my structure
├── Gemfile ├── Gemfile.lock ├── lib │ └── sentinel.rb ├── MIT-LICENSE ├── README.rdoc └── sentinel.gemspec
Open up the gemspec file and add the following
Gem::Specification.new do |s| s.name = "sentinel" s.summary = "Simple authentication Engine." s.description = "Sentinel is an Engine that provides user model with sign up and log in." s.files = Dir["{app,lib,config}/**/*"] + ["MIT-LICENSE", "Gemfile", "README.rdoc"] s.version = "0.0.1" end
for now the sentinel.rb
should look like
module Sentinel end
and the Gemfile
source "http://rubygems.org" gem "rails", "3.0.7"
Start the Engine
Modify lib/sentinel.rb
so it look like
module Sentinel class Engine < Rails::Engine; end end
By this point you already have a working Engine. You can try to use it by adding the following to your application's Gemfile
gem 'sentinel', :path => '/path/to/sentinel'
The Controllers
Let's prepare the controllers. In your Engine root, create directory structure like this
├── app │ ├── controllers │ │ └── sentinel
then in sentinel
put sessions_controller.rb
and users_controller.rb
.
Modify the sessions_controller.rb
, put the SessionsController
class inside module Sentinel
and add unloadable
in SessionsController
.
This must be included in your engine controllers. Unloadable marks your class for reloading inbetween requests.
module Sentinel class SessionsController < ApplicationController unloadable ... end
Then do the same for the UsersController
class.
The Models
We don't modify user.rb
, just put it in app/models
of your Engine root directory.
The Views
Copy layouts
, users
and sessions
directories to app/views/sentinel
so the structure should look like
├── app │ ├── controllers │ │ └── sentinel │ │ ├── sessions_controller.rb │ │ └── users_controller.rb │ ├── models │ │ └── user.rb │ └── views │ └── sentinel │ ├── layouts │ │ └── application.html.erb │ ├── sessions │ │ └── new.html.erb │ └── users │ ├── edit.html.erb │ ├── _form.html.erb │ └── new.html.erb
The Routes
Create file routes.rb
in config
directory and add the following
Rails.application.routes.draw do |map| match 'user/edit' => 'sentinel/users#edit', :as => :edit_current_user match 'signup' => 'sentinel/users#new', :as => :signup match 'logout' => 'sentinel/sessions#destroy', :as => :logout match 'login' => 'sentinel/sessions#new', :as => :login resources :sessions, :controller => 'sentinel/sessions', :only => [:new, :create, :destroy] resources :users, :controller => 'sentinel/users', :only => [:new, :create] end
Database migration
We need to create users
table. Let's create a migration file for that purpose.
The file will be placed in lib/generators/sentinel/templates/migration.rb
with the following content
class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :username t.string :email t.string :password_hash t.string :password_salt t.timestamps end end def self.down drop_table :users end end
Now, let's define the generator by subclassing Rails::Generators::Base in lib/generators/sentinel/sentinel_generator.rb
require 'rails/generators' require 'rails/generators/migration' class SentinelGenerator < Rails::Generators::Base include Rails::Generators::Migration def self.source_root @source_root ||= File.join(File.dirname(__FILE__), 'templates') end def self.next_migration_number(dirname) if ActiveRecord::Base.timestamped_migrations Time.now.utc.strftime("%Y%m%d%H%M%S") else "%.3d" % (current_migration_number(dirname) + 1) end end def create_migration_file migration_template 'migration.rb', 'db/migrate/create_users.rb' end end
Controller authentication
The ControllerAuthentication
module is needed to be included in your application controller which makes several methods available to all controllers and views.
Copy controller_authentication.rb
to lib
directory and modify it by adding put the original ControllerAuthentication
module inside Sentinel
module
module Sentinel module ControllerAuthentication ...Next, modify the
lib/sentinel.rb
so it look like
module Sentinel class Engine < Rails::Engine; end require 'controller_authentication' end
Using the Engine
Let's say you already have a Rails application with a simple CRUD functionality, i.e. product items management. Now you want to add log in, log out and sign up to your application. User needs to be authenticated before she can adding or updating product items. Sentinel will be used for that purpose.
Add sentinel
into your Gemfile
gem 'sentinel', :path => '/path/to/sentinel'
then run bundle install
.
Run the migration generator followed by the database migration from your application
william@walden:~/Public/sentinel-app$ rails generate sentinel create db/migrate/20110513082547_create_users.rb william@walden:~/Public/sentinel-app$ rake db:migrate (in /home/william/Public/sentinel-app) == CreateUsers: migrating ==================================================== -- create_table(:users) -> 0.0014s == CreateUsers: migrated (0.0015s) ===========================================
Next, on the top of your application_controller.rb
add the following
include Sentinel::ControllerAuthentication
I assume that the CRUD functionality is on the products_controller.rb
, so you need to define before_filter
in there
before_filter :login_required
Now, start your application and visit the products path. If everything goes well, you should see the Log in form.
Summary
I showed you how to build a simple Rails Engine. You see that the Engine is just a typical Rails application. You can easily convert a whole application or some functionality into an Engine so you can have more reusable component that you can share accross different application.
The Engine used as example in this post is available at https://gitorious.org/rails-app-examples/sentinel
Here are some resources to learn more about Rails Engine
- http://www.themodestrubyist.com/2010/03/05/rails-3-plugins---part-2---writing-an-engine/
- http://nicksda.apotomo.de/2010/10/testing-your-rails-3-engine-sitting-in-a-gem/
- http://thechangelog.com/post/2336985491/railsadmin-rails-3-engine-to-view-your-data
- http://olympiad.posterous.com/how-to-building-a-rails-3-engine-and-set-up-t
- https://github.com/krschacht/rails3engine_demo
- http://www.arailsdemo.com/posts/43