Posts Tagged rails engines

Lock down a Rails 3 app with a single password using lock

This tutorial shows you more about the power and simplicity of Rails 3 Engines. When I release a new feature for one of my  production Rails apps (or an entirely new app) that needs to be beta tested by a select group of people, it is usually necessary to password protect part or all of the application.  A quick and dirty way to do this is to use basic http authentication with Nginx. Previously, I would choose this method because it is fairly easy to set up and it allowed me to create a single username and password for an entire group of testers, without having to modify the user authentication system that I use inside my Rails apps. If you are using nginx, then you can add something like this to the nginx.conf file:
It is also necessary to create the htpasswd file and add the user name and password hash.  The password hash can be generated using Ruby’s String crypt method.  When users visit the protected URL, the basic http auth browser popup is displayed and if they enter the correct login information, they gain access to the protected URL.  While this setup is fairly simple, I prefer staying within Rails to achieve this and having more control over the login page, etc.  HTTP Basic Authentication is a bit too basic for me. I decided to create a simple Rails 3 Engine that would allow me to easily lock down controller/actions and customize the login and error pages.  I call this one lock. Note that this will not protect your public assets, only the Rails actions… so you may want to lock down your server using http basic if you need public assets protected as well.

Here is how you use it in your own application:

1. Install the gem

#Gemfile
gem “lock”
cowboycoded$  bundle install

2. Generate the password file

cowboycoded$  rails g lock:create_password_file yourpasswordhere

3. Add the lock configuration to application_controller.rb

4. Unlock the app

visit this URL and enter the password you created with the generator

http://www.yoursitehere.com/lock/login

5. Override the views:

Login Page: /app/views/lock/login.html.erb #NOTE: POST to /lock/unlock and use a field named “password”
Refused Access Page: /app/views/lock/refused.html.erb
Unlock Confirmation Page: /app/views/lock/unlock.html.erb

By default, these views will render inside your default layout. To create a custom layout for these files, just add /app/views/layouts/lock.html.erb The layout must contain a yield.

TECH DETAILS ABOUT THE GEM

(if you are interested in how the gem was created)

The design around this was simple. Use a class method within the application_controller that accepts controllers & actions as parameters. The class method creates a before_filter and that takes these controllers & actions and compares them against the requested URL. If the URL matches the controller/action and the user has not unlocked the app, then access to the page is denied. Using Rails 3 engines makes it easy to add views and a LockController to handle the login, failed login and unlock confirmation pages. Here are the gory details about how this gem was created:

A Note about TESTING

I am using RSpec and Cucumber to test this engine.  Engines are a different beast than other gems, because they are so closely tied to Rails.  As a result of this, I prefer to create a new Rails 3 app inside my gem called “test_app” for any testing.  I have seen a few things out there about creating a dummy app, that I am guessing is a stripped down version of a real rails app, but I am not sure what the advantage is over a real Rails app for testing.  The footprint of a blank Rails app is not that large, so I doubt it is a concern about gem file size (which should not matter, since the test files are not packaged with the gem).  I still need to read up and give it a fair comparison, but for now I dont see a problem with using a real Rails app to test engines.

First, I bootstrap the “test_app” with rspec and cucumber.  Then I include the local path to the gem in the Gemfile:

#Gemfile

gem “lock”, :path=>”/my_plugins/lock”

Providing the path to the local gem, will tell Bundler to load the files in that directory as opposed to installed the packaged gem. This is necessary, in order to avoid building your gem after every code change. If I remember correctly, you will need to generate a gemspec in the root of the gem before you bundle install with the “path” option.

Start with a gem skelaton and make it an engine

The skelaton can be as simple as a “lib” directory and a file inside the lib directory named “lock.rb”. To make it an engine only one file is needed. I always call it “engine.rb” and place it within a subdirectory of lib that matches the name of my engine. So here is my directory structure so far:

/lock/lib/lock.rb
/lock/lib/lock/engine.rb

When the gem is loaded, it will first execute lock.rb, so the engine.rb file must be required in lock.rb:

The engine.rb file needs to subclass Rails::Engine, require rails and lock itself, in order to make it an engine that Rails can load:

Now the gem can function as an engine. So what does that really mean? Well for one, you use the same directory structure as you would in a normal Rails app. And you can add initializers to engine.rb to interact with rails when it is loading at boot.

Add the Rails-like directory structure

Take a look at my Rails-like directory structure:

The helper & model directories are not necessary for this gem, but I went ahead and added them in case I need them later.

Add the views & controllers

As I mentioned earlier I have 3 views that I use for the login form, unlock confirmation, and refusal page… there is not much to these views. Rails Engines also allow you to add routes, so I placed mine in /lock/config/routes.rb. Most of the code (and its not really much) is in the controllers. I have a lock_controller that handles the login. As you can see, it just checks to see if the form password is equal to the generated password. I placed the “password_match?” method in /lib/lock.rb, to reduce the logic in the controller.

I wrote a few blogs in March about how Rails prevents you from reopening engine classes. Basically, if you have the file in the parent Rails app, then the one in the engine is ignored. For this reason, I was not able to create a file named “application_contoller.rb” and simply add my methods to it. In order to make this work, I have to use a different file name (lock_application_controller.rb) and include/extend the methods into action_controller using an initializer. Here is what the module looks like:

When you create a class method and put it in action_controller, then application_controller can call this method and make it look like a nice DSL (as you can see in step #3 of the gem installation tutorial). So this class method just adds a before_filter and an instance method is created that matches what the before_filter is calling. lock_filter checks to see if it is a locked action and if the user has already unlocked the app. It redirects them to the refusal URL if they have not unlocked and the action is on the lock list. Note that you may prefer to use ActiveSupport::Concern when coding a module that includes instance methods and extends class methods. I am not using it here, but many find the syntax a bit sweeter, so if you dont know about it then google it.

Use an initializer to include the instance methods and extend the class methods in action_controller

There is a pretty cool feature in Rails 3, which allows you to lazy load code in initializers. Check out Simone Carletti’s tutorial on Lazy Load Hooks for details on how this works. In short, when action_controller is loaded, our code can be placed in that class. So I want to include/extend my methods in action_controller. Here is what the engine.rb file looks like after I add this code:

I find that the engine.rb is the best place to put code that dynamically modifies classes at boot. It gives you a central location where you can place all of these calls, and IMO makes engines easier to read. You know exactly what modifications are being made to Rails, just by looking at the engine file.

Creating the password generator

The last piece of the puzzle is to create a Rails generator to spit out our hashed password file. When you make your gem an engine, Rails automatically picks up your generator if you put it in the default path that is expected from an engine. Here is what the directory structure looks like:

So Rails is expecting this path for the generator class (expressing variables in uppercase):

/GEM_NAME/lib/generators/GEM_NAME/GENERATOR_NAME/GENERATOR_NAME_generator.rb

USAGE will allow rails to ouput some documentation for it, and the templates directory (which I don’t use in lock) can house any template files needed by the generator. The generator class is pretty basic:

Subclassing “Rails::Generators::Base” gives you the functionality you need to make this a generator. Any methods contained within this class will be executed. Notice that the argument class method is necessary to pull in the command line arg for the password. source_root will tell the generator where your templates are stored. This code generates a salt and a password hash from the password & salt combination and writes it to a file using the create_file method. You can read more about Rails generators HERE.

So there you have it. Pretty basic, but IMO useful. I hope you find this gem useful and also hope you learned something about Rails Engines. If you have any problems with this gem, leave me a comment or open a github issue. As always, suggestions are welcome, as well as criticism!

Tags: , ,

Working with ApplicationController in a Rails 3 Engine

UPDATE 02-13-2011: It appears that approach #2 does have a downside after all. Given my current implementation, dev will not reload the class b/c config.cache_classes is set to false. I might still try this approach using config.to_prepare.. I will update the post when I have a chance to experiment more.

Rails 3 Engines use a standard Rails app directory setup by default, so logically, you might think that you can just drop in /app/controllers/application_controller.rb, and reopen the class. This is not the case. Rails will ignore any files that have the same name as a file in the parent application. Seeing as how every Rails app has “/app/controllers/application_controller.rb”, the engine has no chance of loading this file. Poor little engine. But if your engine has a strong will… I think I can, I think I can.. then there are other ways to get your methods inside the parent app’s ApplicationController. Let’s take a look at some of the choices you have.

Option 1 – ActiveSupport.on_load

Lets start by looking at one of the most popular Rails plugins -> Devise. At the time of this writing, Devise has a helpers module that defines a class method called “define_helpers”. A mapping (user or admin) is passed to the method and several instance methods are defined using class_eval. Then these instance methods are included in the ApplicationController using the “helper_method” function. Check out how this is acheived:

ActiveSupport.on_load(:action_controller) do
  helper_method "current_#{mapping}", "#{mapping}_signed_in?", "#{mapping}_session"
end

on_load.. reminds me of something you would see in a javascript library. So when ActiveSupport loads the ActionController, the helper methods are inserted. You could also use a different approach within this block. Let’s say you have defined a module with submodules for ClassMethods and InstanceMethods. You would just extend/include them in the block. The most common approach for Rails 3 engines is using an initializer in the Engine class.

# You can put this in /myengine/app/models/ and it will load it automatically (or put it in your engine lib directory and require it)
module MyModule
  module ClassMethods
    def some_awesome_class_method
    end
  end
  module InstanceMethods
    def some_awesome_instance_method
    end
  end
end
#/myengine/lib/myengine/engine.rb
require "myengine"
require "rails"

module MyEngine
  class Engine < Rails::Engine
    initializer 'myengine.app_controller' do |app|
      ActiveSupport.on_load(:action_controller) do
        extend MyModule::ClassMethods
        include MyModule::InstanceMethods
      end
    end
  end
end

There are plenty of variations of the extend/include stuff that use ActiveSupport::Concern, “self.included”, “self.extended”, but they all achieve basically the same thing… adding instance and class methods.

Option 2 – Reopen the class

In the intro of this blog post, I said that Rails will not allow you to load classes from an engine with the same name as the parent app. When it comes to the ApplicationController, I am not sure if it was the intent of the Rails Core to deter you using the same name/format as the application_controller in a regular rails app… there may be use cases that I am not aware of where this is harmful or lacks functionality. It makes sense that other Engine classes with the same name/path should not be loaded since you would want the parent app to override them if it has the same controllers. This could result in clashes and unexpected consequences. But, I see the ApplicationController as a bit of a anomaly in this situation. Unless your parent app has tons of methods in the ApplicationController and the engine ApplicationController is using common method names, I don’t see this as a problem. Use fairly unique method names or prefix the method name with your plugin name if you are worried about it. There is a simple workaround to allow you to use the same format for ApplicationController that you are accustomed to seeing a standard Rails app… just prefix the file name with the plugin name for the ApplicationController. For example: /app/controllers/myplugin_application_controller.rb. Then you can add code to this file, just as I would with a Rails app ApplicationController. Check it out:

#myplugin_application_controller.rb
class ApplicationController < ActionController::Base
  before_filter :this_filter_is_the_best_thing_since_sliced_bread

  def this_filter_is_the_best_thing_since_sliced_bread
  end

  def really_cool_instance_method
  end

  def self.really_cool_class_method
  end
end

I think technically you are not reopening this class, since it gets loaded first.. so lets say we are defining the ApplicationController and the parent application is reopening the class. I did some quick tests and the Engine ApplicationController loads before the parent ApplicationController. I also wrote a spec to make sure all filters were intact using this method:

# get a list of the filter names from ApplicationController
filters = ApplicationController._process_action_callbacks.select { |c| c.kind == :before }
filter_names = filters.collect{|filter|filter.filter}
filter_names.include?(:this_filter_is_the_best_thing_since_sliced_bread).should be true #yup
filter_names.include?(:a_filter_defined_in_the_parent_app_controller).should be true #yup

Tags: , , ,

Making the case for Rails 3 Engines

Engines have been around for a while now in some for or another, but with the release of Rails 3, they became an integral part of the framework, and IMO they make it easy for any developer to create a plugin. Rails 3.1 will make Engines even better thanks to the SOC work done by Piotr Sarnacki, but I will be focusing on what is available in Rails 3.0.3 in this post.

Engine? Say what??

In case you are not familiar with Rails 3 Engines, in a nutshell… they are sub-applications that are packaged as gems and can run inside a standard Rails application. They give you most of the functionality of a standard rails app, including the controllers, views, helpers, configuration, etc… Engines lay the groundwork of having “mountable” apps in Rails.

So, why should I care?

I think this is useful on 2 fronts. The first being fully functional sub-applications such as a blogging engine. You could drop the engine into your Gemfile and you would immediately have the functionality of a full-featured blog that is running in the same process as your parent app. The second use is of more interest to me, although I have seen little discussion about it. I guess you could call it building blocks. These engines would be smaller than something like a blog. For example, lets say you want to add comments to your application. Right now I think most people would probably look at something like this: https://github.com/jackdempsey/acts_as_commentable. Pretty cool. It gives you a migration, a model and the polymorphic association needed to make a model “commentable”. But you are left with implementing the views & controller yourself. Although it is not terribly difficult to implement this, it still takes some time to do it. Let’s look at how an Engine could improve this plugin. You could basically do everything with 4 lines of code and a few commands.

#Gemfile
gem 'comment_engine'

# command shell - add the migration and run it
rails g comment_engine
rake db:migrate

#model
class Article < ActiveRecord::Base
is_commentable
end

#view
<%=comment_form(@article) %>
<%=comment_list(@article) %>

So you add the engine to your Gemfile, you make your model commentable and you use 2 helpers in your view. This will give you a fully functional comment form in your view and list of comments associated with that model. So how does the Engine make this so easy? You can use controllers, routes, partials, and helpers. So the plugin would include the following:

1. comment.rb – your comment model
2. generator – migration template for Comment
3. Commentable module – AR::Base will extend this module. Uses polymorphic associations to allow models to be “commentable”
4. Helper module – included in the parent app’s application_helper. This provides some shortcuts like comment_form and comment_list. Basically these methods take the model and generate a form or list based on that commentable model.
5. comments_controller.rb – has a create method that creates the comment and redirects to :back.
6. routes.rb – adds resource routes for comments.

While these are the necessities, there are plenty of other things that you could add to an engine like this, for example: attaching the authenticated user to the comment.

This makes development a breeze, as long as you have good defaults in the Engine. One of the main things that “building block” engine authors should focus on is the view portion of this. This is a fairly new concept when it comes to Rails plugins and I have not seen it implemented in many plugins. The plugin needs to be easy to style or give the developer the choice to completely override the view with their own. I guess it is also important to allow the parent app developer to override or add functionality to the controllers and models. The tricky part about that is re-opening the classes. Rails blocks the engine classes from loading if they are the same file name/path as something in the parent app. This is a concept that I am still exploring. Ideally, you would want to allow the developer to drop in a class with the same path that would reopen the class from the engine. Although this could be dangerous if the app developer is not aware of the class in the Engine, IMO it would be very useful if you use it wisely.

How I have been using Engines

I started working with Engines using the Rails 3 betas, and I am still working on figuring out the best practices. Although I am still experimenting quite a bit, Engines have become a major part of my workflow. IMO, the Engine plugin is the most important addition to Rail 3, since they make your code extremely modular and can increase your productivity by quite alot. I am really looking forward to seeing what kind of “building blocks” are created using this plugin architecture. Here is what I have focused on so far for my own environment:

1. authentication_engine – basic and simple authentication that includes signup, login and roles.  Views and controllers included.
2. social_engine - includes comments, ratings, reviews, votes, reputation, and helpers for things like facebook like and tweetmeme. Most if this is impelemented in the same manner as I described for the comments engine at the beginning of this post
3. ui_engine – includes good defaults for the base layout, a configuration for the layout, css, javascript, and plenty of partials that you can override in the parent app. Based on HTML5 boilerplate. My intention with this engine is to get rid of all the BS work you have to do for your front end. I know layout and view stuff differs alot per application, but I find myself doing alot of the same things for each app when it comes to UI.

I am working on extracting alot of the code into an open source plugin for #2 and #3. I should have the social_engine on github in the next few weeks. The UI engine definitely needs alot of work, in order to be useful to developers other than myself.

Conclusion

I love Rails and Rails 3 is leaps and bounds above the previous versions in terms of modularity and extensibility. I like the fact that it has a solid base architecture that plugins can sit on top of. Although there are plenty of productivity gains by using Rails alone, I think the plugins that sit on top of Rails give you the greatest productivity gains. I am looking forward to seeing the Engine ecosystem that is sure to develop in the next few years.   I think it will remove many of the mundane development tasks that developers have to worry about currently, allowing us to focus more on stitching together the building blocks that we need for our applications to function… not on the details of creating these building blocks.

Thoughts?  Leave me a comment

Tags: ,

Developing is_able or acts_as plugins for Rails 3 – Part 1

Note: This is an introduction explaining the concept of “acts_as”. This will not go into detail about creating the plugin.. only the basic description of the functionality of the plugin. Part 2 will go into detail about creating the structure outside of  this and making it a real plugin.


You have seen them everywhere in the Rails world. Acts as this, Acts as that, Is able…. Most of these plugins have the same goal: Allow any ActiveRecord subclass to “have many” of another specific AR subclass. For example, acts_as_taggable_on allows a post, article, product, or any other AR model to have many tags. Hence the name “taggable”. These plugins take care of the associations for you, by adding only one extra database table to your schema. You don’t have to alter the “able” model’s table structure to make it work. “Able” plugins do this by using polymorphism. It is actually quite simple to create something that is “able” using Rails 3. In this tutorial, I will create the code directly in the Rails app, instead of using a gem (so as the introductory note states… this is not a real plugin yet). Check it out:

Requirement:

Create a plugin that allows any ActiveRecord model to have many reviews.  Reviews will have a rating, a text review, pros, cons, and a user ID

Implementation:

1. Create the review Model

Easy enough to create using Rails generators:

rails g model review rating:integer review_text:text pros:string cons:string user_id:integer reviewable_type

2. Define the polymorphic associations in the model and migration

You will need to add this line to your migration in order to generate the 2 extra columns for polymorphism (reviewable_type,reviewable_id)

t.references :reviewable, :polymorphic => true

This will spit out our migration and create the Review model.

Next we will add the polymorphic association to the Review class that was just generated:

class Review < ActiveRecord::Base
   belongs_to :reviewable, :polymorphic=>true
end

Basically, this tells Rails that you want to the Review to belong to “reviewable” and “reviewable” can be any other type of model.  Behind the scenes, Rails just looks at the “reviews” table for “reviewable” using the “reviewable_type” and “reviewable_id”.  This may be a little confusing if you are not familiar with polymorphic associations in Rails.  Lets take a look at how the records actually show up in the table and it may make a little more sense:

id | reviewable_type  | reviewable_id | review_text |
1  | "Product" | 99 | "my review of product #99" |
2  | "Article" | 25 | "my review of article #25" |

As you can see, each row represents a single review, but each review belongs to a different model instance. Review #1 belongs to Product #99, and review #2 belongs to Article #25. By creating the 2 “reviewable” columns, you are able to associate a review with more than one model class.

3. Run the migration

rake db:migrate

4. Create a module that ActiveRecord::Base will extend

This allows you to keep things DRY and simple in each model that is “reviewable” (has_many :reviews). You can drop this into your /app/models directory.

module Reviewable
  def is_reviewable
    has_many :reviews, :as=>:reviewable, :dependent=>:destroy
    include InstanceMethods
  end
  module InstanceMethods
    def reviewable?
      true
    end
  end
end
ActiveRecord::Base.extend Reviewable

First, lets look at the last line in the code above. When this Ruby file is evaluated it will make ActiveRecord::Base extend Reviewable. Basically when you extend a module, the class methods are added to the class that is extending the module. So ActiveRecord::Base now has a class method named “is_reviewable”. Since all your models extend ActiveRecord::Base, they will also respond to this class method.

Next take a look inside the class method. The “is_reviewable” method calls the “has_many” class method and also adds an instance method to the ActiveRecord::Base subclass instance. Lets take a look at a real example that would use the “is_reviewable” class method.

class Product < ActiveRecord::Base
   is_reviewable
end

This used to look like some kind of magic to me when I was a Rails novice. Its just a nice DSL, but behind the scenes this line is calling a class method. Since this line is not in a “def”, the class method will be executed immediately when the class is loaded by Rails. in other words… self.is_reviewable. The class is loaded, the method is executed and has_many (also a class method) is executed and the methods defined in the InstanceMethods module are included in the Product class.

5. Try it out

For simplicity I will not include the tests here, but please note that it is important to create tests using Rspec (or whatever framework you prefer) for all of this code.  These console lines could easily be translated into a test spec.

Lets see this in action in the Rails console:

#check to see if the class method is there for AR Base
ActiveRecord::Base.methods.include?(:is_reviewable) => true

#check to see if class method is there for Product (has to be b/c it subclasses AR Base)
Product.methods.include?(:is_reviewable)
=> true

#check to see if the instance method is there
Product.instance_methods.include?(:reviewable?)
=> true
product = Product.new
=> #

product.reviewable?
=> true

product.reviews
=> [] #nothing here yet.. we havent created any

product.reviews.create(review_text: "test")
=>  #this results in error.. you can create a review until the product has been saved

product.save
product.reviews.create(review_text: "test")
=> #<Review id: 1, reviewable_type: "Product", reviewable_id: 101 ....>

product.reviews.size
=> 1

Conclusion

It looks a bit complicated and magical when you first encounter these plugins, but in reality its as simple as adding a class method to AR. Please read this blog by Yehuda Katz which shows how some people make the extends/includes thing alot more complicated than it should be. I was using the “overkill” way before I read his post. It totally makes sense and removes some of the confusion you may have when looking at the source of some of the “able” plugins.

Tags: , , , ,

Gem dev tutorial: Adding Rails 3 Engines to Jeweler

If you are a Ruby or Rails developer, chances are you will run into a situation where you want to modify a gem that is on github.  In this tutorial I will show you a real-world example of how I do this, using Jeweler as an example.

I like Jeweler.. I like it alot.. but I always end up creating a gem with it and then tweaking a bunch of files to turn it into a Rails 3 Engine. So I am attempting to integrate Rails 3 engines into Jeweler by adding a command line argument that will tell jeweler to create the engine directories and add the engine file templates.

Fork The Repo

First, I need to fork the repo so I can add my changes. I just login to my github account and go to the jeweler page and click the fork button. Now I have a repo under my account. Then I clone the repo on my dev box. In order to pull changes from the original repo when anything changes, I will need to add a remote upstream:

#clone my forked repo
git clone git@github.com:johnmcaliley/jeweler.git
#add upstream that points to original repo
git remote add upstream git://github.com/technicalpickles/jeweler.git

Test Driven Development

I see that this project uses Cucumber for many of its tests. This time I decide to be a good boy and do some TDD from the get-go. The first thing I want to do is create a cuc feature and write 2 scenarios. The first is a scenario without the –rails3_engine argument. The second is with the argument. I used some of the steps from the cucumber.feature in order to get up and running.

Feature: generating rails3 engine directories and files
In order to use rails 3 engines with jeweler generators
A user should be able to
generate a project setup and specify the rails3_engine option

Scenario: sans rails3 engine setup
Given a working directory
And I have configured git sanely
And I do not want to create a rails 3 engine
When I generate a project named ‘the-perfect-gem’ that is ‘zomg, so good’
Then a file named ‘the-perfect-gem/app’ is not created
And a file named ‘the-perfect-gem/app/controllers’ is not created
And a file named ‘the-perfect-gem/app/helpers’ is not created
And a file named ‘the-perfect-gem/app/models’ is not created
And a file named ‘the-perfect-gem/app/views’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ is not created
And a file named ‘the-perfect-gem/lib/generators’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties/tasks.rake’ is not created

Scenario: Rails 3 engine setup
Given a working directory
And I have configured git sanely
And I want to create a rails 3 engine
When I generate a project named ‘the-perfect-gem’ that is ‘zomg, so good’
Then a file named ‘the-perfect-gem/app’ is created
And a file named ‘the-perfect-gem/app/controllers’ is created
And a file named ‘the-perfect-gem/app/helpers’ is created
And a file named ‘the-perfect-gem/app/models’ is created
And a file named ‘the-perfect-gem/app/views’ is created
And a file named ‘the-perfect-gem/lib/the-perfect-gem’ is created

And a file named ‘the-perfect-gem/lib/generators’ is not created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties’ is created
And a file named ‘the-perfect-gem/lib/the-perfect-gem/railties/tasks.rake’ is created
And ‘the-perfect-gem/lib/the-perfect-gem/railties/tasks.rake’ is blank

And a file named ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ is created
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ requires ‘the-perfect-gem’
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ requires ‘rails’
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ has a module based on the class name
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ subclasses rails engine
And ‘the-perfect-gem/lib/the-perfect-gem/engine.rb’ has placeholders for initializers and rake task inclusion

These scenarios have some steps that are not defined, so obviously they will fail. I need to add the missing step definitions.

##features/step_definitions/generator_steps.rb

Given /^I do not want to create a rails3 engine$/ do
  @rails3_engine = false
end

Given /^I want to create a rails3 engine$/ do
  @rails3_engine = true
end

# one line added here also:
When /^I generate a (.*)project named '((?:\w|-|_)+)' that is '([^']*)' and described as '([^']*)'$
#....
  @use_cucumber ? '--cucumber' : nil,
  @rails3_engine ? '--rails3_engine' : nil, # I added this
#....
end

Then /^'(.*)' has a module based on the class name$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match %Q{module ThePerfectGem}, content
end

Then /^'(.*)' is blank$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match "", content
end

Then /^'(.*)' subclasses rails engine$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match "class Engine < Rails::Engine", content
end

Then /^'(.*)' has placeholders for initializers and rake task inclusion$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match "#rake_tasks do", content
  assert_match '#load "the-perfect-gem/railties/tasks.rake"', content
  assert_match "#initializer 'the-perfect-gem.helper' do |app|", content
  assert_match "#ActionView::Base.send :include, ThePerfectGemHelper", content
end

Then /^'(.*)' requires the engine$/ do |file|
  content = File.read(File.join(@working_dir, @name, file))

  assert_match 'PATH = File.dirname(__FILE__) + "/the-perfect-gem"', content
  assert_match 'require "#{PATH}/engine.rb"', content
end

Dig Into The Existing Code

Next I need to figure out how this thing works and add my code.

The gem contains a binary file under /bin that includes the jeweler libraries and then fires off a generator that accepts arguments

require 'jeweler/generator'
exit Jeweler::Generator::Application.run!(*ARGV)

The run class method is in lib/generator/application.rb. It does some checks on the options and then creates an instance of Jeweler::Generator and calls its run method

generator = Jeweler::Generator.new(options)
generator.run

Make changes to generator.rb

Taking a closer look at the initialize method, I see that I will need to add something for the “rails3_engine” argument that I pass to the jeweler binary. I will add something similar to the cucumber option

self.should_use_cucumber    = options[:use_cucumber]
self.should_create_rails3_engine = options[:rails3_engine] # added this code

I also need to add an attribute accessor so the object can set this instance variable:

attr_accessor   # a bunch of existing attr_accessors listed here.. append mine to end of list
,:should_create_rails3_engine

I think that will do for the initialize stuff. Next I look at the run instance method. It has a method called create_files. I will need to add a condition to this method so Jeweler can check for my rails3_engine option and create the appropriate files. Again I look at the cucumber option for inspiration, since it creates directories in the gem and adds files.  Similar to the cucumber condition, the “rails3_engine” option will create some directories and output my engine template files into these directories.

if should_use_cucumber
  mkdir_in_target           features_dir
  output_template_in_target File.join(%w(features default.feature)), File.join('features', feature_filename)

  mkdir_in_target           features_support_dir
  output_template_in_target File.join(features_support_dir, 'env.rb')

  mkdir_in_target           features_steps_dir
  touch_in_target           File.join(features_steps_dir, steps_filename)
end

# here is what I added
if should_create_rails3_engine
  mkdir_in_target           app_dir
  mkdir_in_target           controllers_dir
  mkdir_in_target           models_dir
  mkdir_in_target           helpers_dir
  mkdir_in_target           views_dir
  mkdir_in_target           main_dir
  output_template_in_target File.join('rails3_engine','engine_template.erb'), File.join(main_dir,'engine.rb')
  mkdir_in_target           generators_dir
  mkdir_in_target           railties_dir
  touch_in_target           File.join(railties_dir,'tasks.rake')
  output_template_in_target File.join('rails3_engine','lib_engine_template.erb'), File.join(lib_dir,lib_filename)
end

I also need to add instance variables for the directories that are used in the rails3_engine option condition. Again, I look at what was coded for cucumber:

#check for a dash in the project name and convert to underscore so classify understands it
def project_class
  self.project_name.gsub("-","_").classify
end

def app_dir
  'app'
end

def controllers_dir
  "#{app_dir}/controllers"
end

def models_dir
  "#{app_dir}/models"
end

def views_dir
  "#{app_dir}/views"
end

def helpers_dir
  "#{app_dir}/helpers"
end

def main_dir
  "#{lib_dir}/#{self.project_name}"
end

def generators_dir
  "#{lib_dir}/generators"
end

def railties_dir
  "#{main_dir}/railties"
end

Since I use the pluralize method in “project_class” I need to include active_support inflectors in the Jeweler class

require 'git'
require 'erb'
require 'active_support/inflector' # I added this one
require 'net/http'
require 'uri'

Add code to options.rb

One part that I notice is missing while looking at how cucumber is set up is the code in lib/jeweler/generator/options.rb.

o.on('--cucumber', 'generate cucumber stories in addition to the other tests') do
  self[:use_cucumber] = true
end

# I added this
o.on('--rails3_engine', 'generate rails3 engine directories and files') do
  self[:rails3_engine] = true
end

Add the file templates

generator.rb makes a few calls with methods like this:

output_template_in_target File.join('rails3_engine','engine_template.erb'), File.join(main_dir,'engine.rb')

I created a directory called “lib/jeweler/templates/rails3_engine” that will house my template files. Then I add 2 erb templates that will generate the source code files dynamically in the gem.
Here are the files:

##engine_template.erb
require "<%=require_name%>"
require "rails"

module <%=project_class%>
  class Engine < Rails::Engine
      #rake_tasks do
        #load "<%=require_name%>/railties/tasks.rake"
      #end

      #initializer '<%=require_name%>.helper' do |app|
        #ActionView::Base.send :include, <%=project_class%>Helper
      #end
  end
end
##lib_engine_template.erb
PATH = File.dirname(__FILE__) + "/<%=require_name%>"
require "#{PATH}/engine.rb"

The first file creates the engine.rb which is necessary to make our gem behave as a Rails 3 engine plugin. Using erb allows me to insert text dynamically based on the project name and the require directory which were set in generator.rb instance methods. The second file is just a simple template that requires engine.rb in the project.

Fire off them tests man!

This tutorial does not reflect what I actually did in development.  I run my tests first, look at the failures, write code, refactor, then repeat until everything is green as a cuc.  But to simplify, I am just showing that you need to run tests after you write your code.  Jeweler uses a rake task to execute all the cuc scenarios, so I run this command:

rake features
103 scenarios (103 passed)
652 steps (652 passed)

Now I can see all of my scenarios as well as existing scenarios are all passing. Good to go!

I also updated the unit test, test_options.rb.  But I am getting some errors relating to ‘rr’ -> “NotImplementedError: super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later”.  I don’t think I will be able to run these tests until I get this resolved.  Looks like it is an issue with ‘rr’ and ruby 1.9.2.  I might try downgrading ruby using RVM and running from there.

Test with a live app

After the cucumber tests are passing, I want to look at this in a real Rails 3 app.  So I create a new gem using my jeweler fork. I just execute the jeweler bin from my fork:

>cd jeweler
>bin/jeweler myengine --rails3_engine
create  .gitignore
create  Rakefile
create  Gemfile
create  LICENSE.txt
create  README.rdoc
create  .document
create  lib
create  lib/myengine.rb
create  test
create  test/helper.rb
create  test/test_myengine.rb
create  app
create  app/controllers
create  app/models
create  app/helpers
create  app/views
create  lib/myengine
create  lib/myengine/engine.rb
create  lib/generators
create  lib/myengine/railties
create  lib/myengine/railties/tasks.rake
create  lib/myengine.rb
Jeweler has prepared your gem in myengine

I add a controller and a view to the gem, create a gemspec and install my gem. Then I add the gem to a rails 3 app Gemfile (using :path=>/path/to/my/gem), bundle install, fire up the app server and take a look at the view that I added.

It appears, so I know that the Engine was initialized in the app. Good to go!

Here is the fork if you want to look at the code:
https://github.com/johnmcaliley/jeweler

Tags: , , , , , ,