200 Rails 3 Plugins, Part 2: Writing an Engine - Looped Strange

Looped Strange

Ramblings of Programmer who likes Code & Math

Rails 3 Plugins, Part 2: Writing an Engine

200

Note: This was originally posted on my previous blog. It is archived here for sentimental, educational (to me) and, in the unlikely case, actual value

Note: This article was written when Rails 3 was still in the early beta stages. It has not been updated since and it is likely that some information is inaccurate or incomplete. I 200 think, the general information may still pertinent, but please read with that in mind.

Last time, I gave a big-picture overview of the Rails 3 plugin API. This time, we will dive in and write a Rails engine with a simple authorization example I wrote.

You may be wondering why I am not talking about writing a Rails::Railtie first. There is a great article you can read here that expl 200 ains upgrading existing plugins and the Railtie in general.

I will also not be addressing testing your engine because I am a die hard rspec/cucumber fan and support in Rails 3 is in between alpha and beta right now. I feel nasty not writing them and have been using ActiveSupport::TestCase in some work but not in this example. Once support is better I will follow up and write about testing your engine with rspec and cucumber.

So, Let’s Get Started!

You will need to setup a cou 200 ple things before diving in: a directory where your gem’s code-base will live and a playground to play with your engine in. You will also need to be able to install the plugin as a gem.

The name of our example is “authr3” so lets create a directory with the same name to keep our engine’s code in (you can really name it as you please, it won’t matter). The engine directory will also need ‘lib’, ‘app’, and ‘config’ sub-directories. We w 200 ill also name our library ‘authr’ not ‘authr3’ just to show how this is handled in a Gemfile. At the same time lets create a rails app to plug our engine into.

>> mkdir -p authr3/lib/authr
>> mkdir authr3/app
>> mkdir authr3/config
>> touch authr3/lib/authr.rb 
>> rails /path/to/your/app

Once we have our directory structure lets go ahead and go thru the necessary steps to setup our gem although we won’t install it 200 for a bit. I will use Jeweler which makes it simple to build, install and publish gems using Rake. If you haven’t used Jeweler before you can install it with:

gem install jeweler

With Jeweler you do everything in your Rakefile, which if your like me you were probably doing anyways. Add the following to your Rakefile:

begin
  require "jeweler"
  Jeweler::Ta
200
sks.new do |gem|
    gem.name = "authr3"
    gem.summary = "Auth Engine for Rails 3"
    gem.files = Dir["{lib}/**/*", "{app}/**/*", "{config}/**/*"]
    # other fields that would normally go in your gemspec
    # like authors, email and has_rdoc can also be included here

  end
rescue
  puts "Jeweler or one of its dependencies is not installed."
end

Save your Rakefile and shuffle it a side for a bit. Your sanbox is setup. If you want to test things simply run

>> rake
200
 install

from the engine’s root directory to install it as a gem. Run

>> gem uninstall authr3

to uninstall the gem. Thanks to khelben’s comment below you will probably not have to do this again while playing with your engine (Note: Originally, I said you will have to perform this process between changes to your engine’s codebase).

Lastly, you will need to add the gem to your playground’s Gemfile. It is possible with bu 200 ndler to also define a Gemfile within your engine. Since I am no authority on bundler as it is pretty new to me as well I will not be discussing it here. authr3 only has one dependency, the Ruby BCrypt Library, and in this example we will just put it in the app’s Gemfile above our gem. So add the following two lines to your playground’s Gemfile (this article will not use bcrypt-ruby in any example so unless you are following along with my codebase, you can skip it):

gem "bcrypt-r
200
uby", :require => "bcrypt"
gem "authr3", :require => "authr", :path => "/path/to/authr3"

The :require option specifies the name used to ‘require’ the library. If you look above we created the file authr.rb in our lib directory. The first argument passed is the name of the gem itself set with ‘gem.name’ in the gemspec. Finally, again thanks to khelben’s comment below, the path option specifies where the codebase lives locally and will load it from ther 72 e instead. The :path option makes it so the engine does not have to be reinstalled between changes for them to be 200 reflected.

Now that we have all the basics set up, let’s get our hands dirty with our engine.

The Engine Class

The first real piece of code we will write is our engine class. Our class must be a subclass of Rails::Engine, a railtie with extended abilities like shared and self-contained configuration. Below is the most basic class definition we can write for our authentication engine.

#lib/authr/engine.rb
module Authr
 class Engine < Rails::Engine
    engine
128
_name :authr
  end
end

I am not sure if the engine name is required but it is good practice to put it in there. I have yet to see an example in an article or in a library that exlucded it.

We need to add a few more lines to our engine.rb file before it is operational ho 200 wever. Rails plugins must follow a simple rule: REQUIRE WHAT YOU OVERRIDE. If you extend ActionController or ActiveRecord, require it. You should always require Rails and your own gem. My example repository also requires ‘action_controller’ because the full codebase extends it. Since, I won’t be talking about that piece here I have left it out. Our engine.rb file should look like:

#li
200
b/authr/engine.rb
require "authr"
require "rails"

module Authr
  class Engine < Rails::Engine
    engine_name :authr
  end
end

This is the full definition of our engine. It may seem kind of worthless but there are many things we can do inside our engine class. So many in fact, I will address them in another post.

To get things completely wired up we must require the engine class in our library. You can simply add a require to lib/authr.rb. Many examples, instead, do something l 200 ike this,

 require 'authr/engine' if defined?(Rails)

or

 module Authr
   require 'authr/engine' if defined?(Rails)
 end

You can, also, explicity check that a version of Rails 3 is being used with,

 require 'authr/engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3

Reaping our First Reward

So we have done a whole lot without much to show for it. Let’s do something that is tangibl 200 e from within our playground application. Lets add an Account model to our app and make sure it is defined when running the host application.

In your engine add the following model

 #lib/app/models/account.rb
 class Account < ActiveRecord::Base
 end

Re-install the engine gem and boot up the console in your playground app. If the gem is installed properly and you have added authr to your Gemfile then you should be able to bootup ‘rails console’ and see 200

 > Account
 => Account(Table doesn't exist)

If you see

 > Account
 => NameError: uninitialized constant Account

then you have probably not added the gem to your Gemfile properly. The ‘Table doesn’t exists’ response is of course because we have not created an accounts table. You can use the migration from the authr 200 repository to create the table. If you use the code from my authr repositiory, it can generate the migration for your host app. To stay focused on engine basics, I will leave generators for later in this series.

Making our engine look like a real app

So now we have our app loading classes from our engine. This is because the paths app/ and lib/ along with several others under your engine’s root directory are automatically added to your application’s load path. Jose Valim&# 200 8217;s gist on engines lists each path your engine can modify. These paths are relative to your engine’s root not your application. Paths are not configuration shared between app and engine. I personally leave these alone. I intially tried doing all my work under the lib/ dir. It seems, at least for now if you want to redfine some of the paths your must redefine them all. For example you cannot only use,

 pat
200
hs.app = "lib/app"

to set the app, controllers, views and models paths to all live under the lib/app directory. You must explicitly specify the path for each.

We know our controllers are automatically loaded from app/controllers so lets add one. Let’s add an Authr::AccountsController which has a new and create action. The new action will later have a corresponding view which holds the new Account form.

 #app/controllers/authr/accounts_controller.rb
 module Aut
200
hr
   class AccountsController < ApplicationController

    unloadable

    def create
      @account = Account.new(params[:account])
      if @account.save
         redirect_to '/'
      else
         render :action => :new
      end
    end

    def new
     @account = Account.new
    end
  end
end

There are a few things of note here. Like our engine, I have namespaced the controller as well. This is not necessary outside of your library, and infact I did not namespace the model. I 200 ts a matter of preference and as I look back I think I may namespace the model as well later on. This way your plugin has no chance of colliding with classes in the host application.

The second thing of note is the call to ‘unloadable’ in the AccountsController. This must be included in your engine controllers. Unloadable marks your class for reloading inbetween requests. You can read more about unloadable here and here.

So we have our AccountsController, we just need a view for #new. Since this is a simple task I will let you write your own, or just copy mine. Views use the same naming conventions inside engines as they do applications. This view should live in authr3/app/views/authr/accounts/.

I would like to mentio 200 n, quickly, that I believe it is important to write engine views with only the markup directly relavant to the action. Remember, this view will be included in a layout of a host application and you do not want your markup to be a hassle.

Routing it all together

So to review, we now have an Account model, an AccountsController and a view, new.html.erb, so its time we see something of substance in the browser. To do so we need one final piece, routes.

Its easy to add routes to a host 200 application from an engine. Just like the app/ directory is automatically added to the load path so is the config/ directory in our engine root. We will add our controller using RESTful routes but only for the new and create actions.

 #config/routes.rb
 Rails.application.routes.draw do |map|
   resources :accounts, :controller => 'authr/accounts', :only => [:new, :create]
 end

Reinstall your gem one more time and then you should see the routes appear in your host app 200 lication.

 >> rake routes
 accounts    POST   /accounts(.:format)     {:action=>"create", :controller=>"authr/accounts"}
 new_account GET    /accounts/new(.:format) {:action=>"new", :controller=>"authr/accounts"}

Boot up ‘rails server’ from your playground and point your browser at localhost:3000/accounts/new. You should see whatever you put in new.html.erb.

So there you have it. The start of a simple Rails 3 engine. Of course the code 200 presented here leaves much to be desired. I will leave it up to you to make it functional or you can look at my code.

In the next installment of this series I will continue to discuss engines and there more advanced features like including rake tasks and writing generators and initializers.

0