NOTE: this is an almost complete rewrite of the earlier article, written for v0.5.0.
Lightrail is a ruby on rails plugin generator intended to make it easier to share controllers, models, helpers, views and migrations across multiple apps without hacking Rails internals.
Basically, lightrail is a scaffold generator that generates the code into a plugin, rather than the main app directory.
Installation
Currently this is a gem, but you have to go through some hoops to install it:
1. Install the gems
sudo gem install hoe newgem git clone git://github.com/zilkey/lightrail.git lightrail cd lightrail rake local_deploy
2. Generate your plugin generator
Once the plugin is installed, you can create a plugin which will store your reusable code.
script/generate lightrail_plugin plugin_name namespace |
3. Install your reusable code with your plugin generator
Once you‘ve generated your plugin, you‘ll see that there is a new generator available. If you ran script/generate lightrail_plugin zilkey/product your generator would be named zilkey_product
script/generate zilkey_product |
When you run your own generator, it:
- Copies the controller, model, helper and views to the appropriate app directory
- Copies all of the migrations in the plugins lib/db/migrate directory to the app‘s db/migrate directory
Example
1 2 3 4 5 6 7 8 9 |
rails lightrail-demo cd lightrail-demo script/generate lightrail_plugin zilkey cms script/generate model page name:string rake db:migrate script/generate lightrail_pluginize_model page #=> creates a base class and an inherited class and sets the table name correctly script/generate zilkey cms # => moves the generated files back to the app directory # make some changes to the generated base class then run script/generate lightrail_clone # => moves all of the changes you made in the _app_ back to the _plugin_ |
How it works
When you create a lightrail plugin like zilkey cms, the plugin has a directory structure like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
|-- MIT-LICENSE |-- README |-- Rakefile |-- generators | `-- your_plugin | |-- USAGE | `-- your_plugin_generator.rb |-- init.rb |-- install.rb |-- lib | |-- create | | |-- app | | | |-- controllers | | | | `-- your_namespace | | | |-- helpers | | | | `-- your_namespace | | | |-- models | | | | `-- your_namespace | | | `-- views_your_namespace | | |-- config | | | `-- initializers | | |-- db | | | |-- migrate | | | | `-- migrate_your_namespace | | |-- lib | | |-- public | | `-- spec | | |-- controllers | | |-- fixtures | | |-- helpers | | |-- models | | `-- views | `-- destroy | |-- app | | |-- controllers | | | `-- your_namespace | | |-- helpers | | | `-- your_namespace | | |-- models | | | `-- your_namespace | | `-- views_your_namespace | |-- config | | `-- initializers | |-- db | | |-- migrate | | | `-- migrate_your_namespace | |-- lib | |-- public | `-- spec | |-- controllers | |-- fixtures | |-- helpers | |-- models | `-- views |-- tasks | `-- your_plugin_tasks.rake `-- uninstall.rb |
When you run script/generate on your plugin, you‘ll notice that your main RAILS_ROOT directory looks like this:
1 2 3 4 5 6 7 8 |
|-- app |-- controllers | `-- your_namespace |-- helpers | `-- your_namespace |-- models | `-- your_namespace `-- views_your_namespace |
Notice that it‘s placed a copy of each of your controller, model, helpers and views and migrations in the main app directory.
So for every controller, helper, model, view, migration and route in your plugin there is a corresponding file in the usual location in your main app. In addition, the migration fits logically into your migration scheme (that is, it‘s renamed to have the correct numbering scheme). The files in your app directory inherit from, or include, the files in the plugin, and the base classes are generated into the app directory for easy editing and cloning.
You can create modules for your controllers and helpers, so that the files look like this:
app/controllers/articles_controller.rb
1 2 3 |
class ArticlesController < ApplicationController include Zilkey::ArticlesController end |
app/helpers/articles_helper.rb
1 2 3 |
module ArticlesHelper include Zilkey::ArticlesHelper end |
app/models/article.rb
1 2 |
class Article < Zilkey::Article end |
This means that you can override specific methods in the controller, helpers and model and those changes extend or replace the plugin‘s functionality. Changes to the base classes/modules can be shared across all apps. Customizations to each individual app remain untouched. Upgrading
Let‘s say you‘ve added a new field to Article and you‘ve updated one of the views. When you reinstall your plugin, and regenerate the code Rails will ask if you want to overwrite the existing files the same way it normally would.
New models, views, controllers, migrations and routes are added (actually, all files in the plugin’s create directory are added – so you can even add dependent plugins to your generator!)
Let‘s say you have an Article model that you want to share across 2 apps. One app has a separate Publisher model that has_many :articles, the other app does not. When you add a field to Article, you want to be able to seamlessly update it across both installations, but you don‘t want to have to rewrite your Publisher customizations each time. With Lightrail, it‘s easy – here‘s how it works:
- Create an Article plugin
- Install it on app 1
- Install it on app 2
- Add the publisher model to app 2 and modify the files in your app folder with the changes
When Rails updates
Rails is constantly coming out with new versions and upgrades. Other plugins and systems like appable_plugins and engines rely on changing rails internals. Lightrail changes no rails internals and instead adds a few new methods to the generator commands. Other than that it is non-obtrusive.
As an example, rails just updated it‘s migration names to be named-based as opposed to numeric. This plugin, without making any changes, works the same on Rails 2.0.2 and edge (rev 9166)
Guides for making code more reusable
- Cut your views into smaller partials – this way you can update some parts of the page without updating others
- Never change migrations – always add new migrations with the changes
Ease of development
When developing plugins, it‘s helpful to be able to make changes to the base classes. As such, all models, controllers, helpers and views are removed from the load_once paths so as you make changes to the plugin, you don‘t have to restart your web server to see changes (in development mode).