Monday, February 02, 2009

Engines in Rails 2.3

Some support for ‘engine’ plugins has been merged into the Rails core codebase in Rails 2.3. In this guide, I’ll try to explain what works, what has changed, and what is currently missing compared to the original engines plugin.

What works

The core of the engines plugin has always been sharing controllers, models and views seamlessly from within a plugin. I’m glad to say that this is now fully supported by Rails itself. The same app/controllers and app/models folders will be added to the $LOAD_PATH in the appropriate order.

Secondly, loading views also works in the same way you’d expect. This means that you can override views and partials in your application, just like with the original engines plugin.

What has changed

Rails 2.3 also brings support for loading routes from plugins, but the technique has slightly changed. With Rails 2.3, create your routing file in your plugin as config/routes.rb (the engines plugin did not require the config directory).

The other change is that they should be formatted as a normal routing file. For example, with the file app/controllers/awesome_controller.rb in your plugin, the config/routes.rb could be

ActionController::Routing::Routes.draw do |map|
  map.blah '/awesome', :controller => 'awesome'
end

The main difference here is that you need to include the ActionController::Routing::Routes.draw line.

What is missing

Sadly, there are a few features of the engines plugin that have not been ported to Rails 2.3 yet. Those are:

Migrations from plugins

It’s not possibly to run migrations from plugins with Rails 2.3. There are a number of reasons for this, but principally the problem comes from needing to be able to determine which migrations from which plugins have already run.

If you’re using timestamped migrations there is no problem, since the likelihood of a timestamp collision is quite low. In this case, I would recommend writing a simple task or script to copy migrations from your plugin into the main db/migrate directory. It’s important that the migrations are anchored in some way to the main migration directory, since some folks use migrations to recreate their database rather than schema.rb, and so the set of migrations should accurately reflect the actual state of the application at any point in time.

If you are not using timestamped migrations, this is a bigger problem. Imagine you are developing your application, and have several migrations:

/my_app/db/migrate/001_create_users.rb
/my_app/db/migrate/002_add_widgets.rb
/my_app/db/migrate/003_reticulate_splines.rb

You then install a plugin with some migrations:

/my_app/vendor/plugins/my_plugin/db/migrate/001_install_the_awesome.rb

We now have two migrations with the version ‘1’ - one from the app (which has probably already run), and one pending from the plugin. There’s no way to tell, at the moment, that the plugin migration needs to be run.

I have some ideas for how to fix this, and so hopefully we’ll see some progress soon.

Public Assets in plugins

The engines plugin will mirror files from public or assets directories into the main application public directory, so that those files can be served directly to browsers. The plugin also augmented some of the view helpers so they could reference these files.

Rails 2.3 won’t automatically do this for you, but it shouldn’t be too arduous to write a few lines of code in init.rb to copy files into the public directory. However, without the helper additions of the engines plugin, your files won’t be automatically ‘namespaced’ according to plugin, so if two plugins try to copy a file called styles.css, one will overwrite the other. The solution? Try copying your files to a unique filename or subdirectory to avoid these clashes.

Code mixing

Rails 2.3 doesn’t attempt to do any of the ‘code mixing’ that the engines plugin does. For those of you not in the know, this is the functionality that lets your application override a single method in a controller or helper, without having to copy the whole file from the plugin into your application.

I cannot say for sure, but I doubt this feature will ever make it into Rails itself, and to be honest, I think that’s probably a good thing. While the code mixing mechanism is quite neat, there are other ways of overriding the implementation of methods which are more typically Rubyish, and involve less magic. I’ll try and post some examples here soon.

There you go

I hope that’s outlined the engines support in Rails 2.3 in a reasonably clear way. I hope that we’ll be able to merge some of the simpler missing features into the core (migrations and assets, notable) soon. Maybe you’re the one to write that patch? PDI :)