Introducing Warp Drive for Rails

At work recently we had a need to build a large Rails application that we then wanted to, for lack of a better word, subclass. Unfortunately there is no real good way of doing that. Rails Engines and templates have way too may limitations. We wanted to bundle up the entire Rails app (models, controllers, views, routes, migrations, configurations, libs, assets, etc… everything!), but there was no good way of doing that. Well, now there is, it’s called the Warp Drive.

I’ve decided to just include my README file below to explain what it is, since it’s a bit lengthy, and I don’t feel like retyping.

This is still in it’s early stages, so use with care, but it does seem to be working for us on a daily basis. Let me know what you think!

What is Warp Drive?

Warp Drive is what Rails Engines wish they could be, and more! They kick Rails templates in the ass, and they beat keeping an ever evolving base Rails app up to date.

What are Rails Engines?

Rails Engines allow you to package up some of a Rails app (controllers, models, views, routes, libs) and put them in a plugin that can be included into a new Rails app, thereby giving it the functionality you had in the engine. That sounds good, but what about the downsides of using an engine? Well, you can’t override or extend any of the functionality from the engine in your main application. You can hack at the plugin, but now you’ve made it difficult to update. So what do you do if you want to add or alter a method to a controller or model? What do you do if you want to change the look and feel of a view? You have to copy everything into your main application. Boo!

Rails Engines also don’t allow you to package up migrations, assets, plugins, initializers, etc… All the fun stuff that you’ve come to know and love about a Rails application.

Enter the Warp Drive!

So what is a Warp Drive? Great question. To put it simply a Warp Drive is a standard, full featured, Rails application that you can easily bundle up into a Ruby Gem, and include into another Rails app. That second Rails app now has all the power of the first Rails. That is all there is to it.

Creating a Warp Drive.

Let’s assume we have an application that implements AuthLogic for handling user registration/authentication. We have controllers, views, models, plugins, initializers, configurations, migrations, tasks, etc… it’s a full featured fully functional Rails application, we call it authenticator.

We want to turn our authenticator application into a Warp Drive. We can do it in three simple steps, the first two steps you only need to do the first time, to set everything up.

  1. $ gem install warp_drive
  2. $ warpify
    That will add a little bit of code to your Rakefile. That code simply requires the Warp Drive gem, and gives you hooks to configure the gem of your Warp Drive application.
  3. $ rake warp_drive:compile (rake warp_drive:install)This will either compile your gem for your (warp_drive:compile) or compile and install your gem (warp_drive:install)

That’s it! You should now have your Rails application bundled up and/or installed as a RubyGem!

Using a Warp Drive.

With your fancy new Warp Drive, authenticator, built and installed how do you use it in that new application your building? Again, it’s stupid easy, and it only takes one step, that only needs to be run once:

    $ install_warp_drive authenticator

That will put a few lines of code in your Rakefile, so you have access to all the Rakefile tasks in your Warp Drive, and a line in your config/environment.rb so that it will load your Warp Drive when you launch your application.

That’s it! You’re done. Now you can run rake db:migrate to run the migrations from both your Warp Drive and your new application. Enjoy!

Overriding, Extending, and Other Such Fun Things

Overriding and Extending

You’ve been enjoying your new Warp Drive back application for a little while now, but you decide you really need to change an action in your controller, how do you go about that? Simple, just like you would any normal alteration to a Ruby class.

Example:
Here is what the action looks like in our Warp Drive UsersController:


  def new
    @user = User.new
  end

In our new application we can just open up the UsersController like this:


  class UsersController < ApplicationController

    def new_with_default_name
      new_without_default_name
      @user.login = 'default_name'
    end

    alias_method_chain :new, :default_name

  end

Viola! The same works for any thing else in the system, models, libs, etc… In our example we used alias_method_chain to retain the original method, but we could have completely rewritten the method as well.

You can also plop in a new view and it will override the view that was in your Warp Drive. The sky is really the limit.

Assets

You can easily bundle assets from your public directory in your Warp Drive. Just make sure they are in folders called warp_drive. Those folders will then be symlinked to your new project’s public directory when the application starts up.

Keep Those Rake Tasks Private!

We all them, Rake tasks we have created to help us do all sorts of things, and we usually don’t want them to ship. Well, Warp Drive has you covered there. Just place your tasks in folders called private and Bob’s your uncle they won’t be available in the compiled gem.


  lib/
    tasks/
      foo.rake
      private/
        bar.rake

In this example foo.rake will be available to clients of your Warp Drive, but bar.rake will not be.

Copyright (c) 2009 Mark Bates

Tags: , , , ,

15 Responses to “Introducing Warp Drive for Rails”

  1. grimen Says:

    Looks interesing, will def try it out. One tip though: Avoid “alias_method_chain” when you can rely “super” instead (good ‘ol subclassing applies in the sample above). Meta-programming is very useful – but in some cases only overkill/overhead.

  2. Mark Bates Says:

    @grimen, agreed, but in the above example, I’m not sub-classing the UsersController, but rather re-opening it, so super wouldn’t work.

  3. Ariejan de Vroom Says:

    This sounds really cool! But, can you combine different warp drives?

  4. Mark Bates Says:

    @Ariejan, not yet. That’s coming though. I want to be able to add multiple warp drives to a Rails app.

  5. Micah Geisel Says:

    a comment! … you should add a link to the project; i assume its on github?

    a question? … in your UsersController example above, where is the original users_controller.rb, and how are you making sure rails’ lazy-class-loading method-missing magic finds the original first, and then your override?

    awesome stuff here! props for getting something working.

  6. Mark Bates Says:

    @Micah, here’s the github link, http://github.com/markbates/warp_drive sorry about that. In the example above the original UsersController is in the ‘authenticator’ WarpDrive. I didn’t put in all the steps to build the ‘authenticator’ app, because all you need to do is build a ’standard’ Rails app. As far as the magic is concerned, there is definitely a lot of magic in there, I’m not going to lie to you. :) There is definitely some aliasing and reworking of some private Rails methods to get this to work. It would be nice if they had a public initialization api, that let you hook into all of the initialization steps, but alas, they don’t. Check out the project on GitHub, if you’re curious as to how I did it.

  7. Oliver Says:

    I like the idea! But I wonder how “Warp Drive” compares to “mountable apps” in Rails 3?

  8. Dan PIckett Says:

    Thanks for open sourcing this! I definitely have a few projects where warp drive is a plausible solution.

  9. Twitted by aberant Says:

    [...] This post was Twitted by aberant [...]

  10. Mark Bates Says:

    @Oliver, honestly, I don’t know. Mountable apps are still a bit of ‘vaporware’, so it’s hard to tell. In the meantime, I needed this type of functionality, so I took matters into my own hands. :) Perhaps some of the WarpDrive, or it’s ideas, will make it into 3.0. I’ll be really disappointed if they cripple mountable apps like they did with engines.

  11. grimen Says:

    @Mark Bates: Actually you can, try. This works for me:

    http://github.com/grimen/delayed_job_mailer/blob/master/lib/delayed_job_mailer.rb

    Gotta love Ruby!

  12. Sam Smoot Says:

    You know, this is actually pretty similar to Harbor (our own OSS framework). We’ve had it in production for some time. The goal with it is a bit different than Rails. Burned enough by breaking changes in Rails/Merb we decided to do our own thing. Beyond that we wanted to override individual layouts, actions, views, partials, routes, assets, etc, picking and choosing what we wanted to pull in from gemmed, mounted apps like PortAuthority (http://github.com/wiecklabs/port_authority).

    Some of our apps are mounting over a dozen individual applications to run as one client facing app.

    bc.. run Harbor::Cascade.new(
    ENV['ENVIRONMENT'],
    services,
    Newsroom, PortAuthority, UserManagement, ChannelManagement, SyndicationManagement,
    ReleaseManagement, PhotoManagement, VideoManagement, AudioManagement,
    AssetManagement, PageManagement, BasketManagement, BulletinManagement,
    Cleat
    )

    Then we apply a layout, override a view here and there where necessary and in a few hours we’ve launched a pretty massive new application for a new client.

    Harbor has been 1.0-ish for awhile. We just haven’t announced it since we’re still putting together our blog (http://wiecklabs.com) and all that jazz. Look at the commit history though, it’s a lot more mature than you might imagine for a framework you’ve never heard of before.

    So yeah, the idea is basically that I hate writing the same thing a dozen times over the course of a year. ;-)

    Anyways, lookin’ good Mark. This is more inline with what I was hoping Mack would eventually evolve into, so it’s neat to see you’re still innovating.

    Of course, even neater would be to ditch Rails, but I understand. :-D

  13. Evan Light Says:

    I love the idea; however, after a couple of hours pairing, we had a great deal of trouble to get a warp drive with a controller with a “hello, world” action to work within another Rails app. The lack of specs, lack of documentation beyond the README, and the relative complexity of the code make for an extremely steep learning curve.

    I would strongly recommend more specs and examples.

  14. Mark Bates Says:

    Hey Evan, sorry you had such a problem with it. We’ve been using in it in production on several really complex projects with no problem. There are actually quite a few specs, but they’re not where you think they would be. Because of what it is doing the best way to test it is with two applications. If you look at that test_apps folder in the project you will see two Rails apps, Enterprise and Voyager. You install the Enterprise app as a WarpDrive and then run the specs from the Voyager app. That tests that the everything is doing what it is supposed to be doing. I agree more documentation could be useful, but since it’s really not doing much, there’s not a whole lot more to document. What problems were you running into exactly? I’d love to help resolve them.

  15. Rich Apodaca Says:

    @Mark – great work – thanks! I’ve been able to get Warp Drive to work on a blogging engine I’m creating. Everything seems to work except for Authlogic/OpenID authentication.

    I’ve written a summary of my problem here (with links to example code and complete error messages):

    http://stackoverflow.com/questions/1986851/authlogic-openid-authentication-fails-using-warp-drive

    Any ideas on where this is going wrong?

Leave a Reply