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
