After a much awaited opportunity to write a self contained plugin for rails application I ultimately got this chance with my new rails project where a new subsystem had to build for an already existing intranet application. The first thing I tried was to google around for some introductory articles or blog-posts about writing plugins using rails-engines. The ones I found were either outdated, or slightly confusing with vague description. So, I took this as a golden opportunity and decided to go ahead with this blog-post. At the time of writing of this blog v2.0 of engines plugin is available, revamped for using with Rails-2.0. I can't use it simply because the Rails application with which my plugin would ultimately integrate is live and running on v1.2.3.
Assuming that the required rails (1.2.x) application is there, we begin with the installation of engines plugin first.
1. Install engines plugin
railsroot$ ruby script/plugin install http://svn.rails-engines.org/plugins/enginesThat's all with the installation part. With engines 1.2 no extra configuration is required in config/environment.rb. The only thing which is recommended but not required is to keep the loading order of engines plugin first before all the other plugins. This is possible starting with rails 1.2 using
config.plugins = ['engines', '*']option in either config/environment.rb, or config/environments/*.
2. Create new plugin
railsroot$ ruby script/plugin generate mypluginThis will generate the following folder structure within root of rails application.
vendor/ | plugins/ | myplugin/ |- lib/ | |- myplugin.rb | |- tasks/ | |- myplugin_tasks.rake | |- test/ | |- myplugin_test.rb | |- README |- Rakefile |- init.rb |- install.rb |- uninstall.rb
3. Create app, app/controllers, app/models, app/helpers, app/views, db, db/migrate, and assets folders within the root of newly created plugin.
railsroot$ mkdir vendor/plugins/myplugin/app railsroot$ mkdir vendor/plugins/myplugin/app/controllers railsroot$ mkdir vendor/plugins/myplugin/app/models railsroot$ mkdir vendor/plugins/myplugin/app/helpers railsroot$ mkdir vendor/plugins/myplugin/app/views railsroot$ mkdir vendor/plugins/myplugin/db railsroot$ mkdir vendor/plugins/myplugin/db/migrate railsroot$ mkdir vendor/plugins/myplugin/assetsAll the above created folders follow the folder structure of rails. `assets` folder corresponds to the pubic folder of the rails. It acts as a repository for all the plugin resources like images, stylesheets, javascripts etc. When rails starts then engines plugin replicates the content of this folder into public/plugin_assets/myplugin folder. All the resources in plugin's assets folder can be referred anywhere (generally views) using the extended Rails' helpers enhanced by the engines plugin to accept a :plugin option, indicating the plugin containing the desired resource.
# Include stylesheet from plugin <%= stylesheet_link_tag 'mystyles', :plugin => 'myplugin', :media => 'screen' %> # Include plugin javascript <%= javascript_include_tag 'myfuncs', :plugin => 'myplugin' %> # Incorporate plugin images <%= image_tag 'logo.jpg', :plugin => 'myplugin' %> <%= image_path 'header.jpg', :plugin => 'myplugin' %>
4. Migrations
Plugin migrations are required to generate tables for plugin models that can be shared with rest of the rails application. The creation of plugin migrations is similar to that of with rails. The only thing which needs to know is how to execute them. Unlike normal rails migrations, plugin migrations are not meant to be executed directly from within plugin. They are to be first migrated into the main migration stream using
railsroot$ ruby script/generate plugin_migrationin order to accurately reflect the state of appplication's database. Suppose according to the schema_info table in database the rails application is at version 35 and myplugin plugin contains a single migration 001_create_testplugin_table.rb in vendor/plugins/myplugin/db/migrate folder. Executing plugin_migration script
railsroot$ ruby script/generate plugin_migration exists db/migrate create db/migrate/036_create_testplugin_table_to_version_1.rbwill take rails applications to version 36 by adding a new migration 036_create_testplugin_table_to_version_1.rb to main migration stream i.e. migration sequence in railsroot/db/migrate folder. The content of this new migration would be something like this:
class CreateTestpluginTableToVersion1 < ActiveRecord::Migration def self.up Engines.plugins[:myplugin].migrate(1) end def self.down Engines.plugins[:myplugin].migrate(0) end endWhen the application is migrated up using rake db:migrate, then plugin will be migrated to its latest version (1 here). This can be verified by the new plugin tables in database. If at any time later it is required to rollback the application back to version 35 then it can be simple done by using
rake db:migrate VERSION=35which will drop all the tables from myplugin migration (if specified in self.down method). 5. Generate all the controllers, models, views, and helpers for the plugin, exactly as we do in Rails, but this time only in context of the plugin.
vendors/ | plugins/ | myplugin/ | app/ |- controllers/ # Put all your plugin controllers here |- models/ # plugin models |- helpers/ # plugin helpers |- views/ # plugin views
6. Add routes for plugin
railsroot$ touch vendor/plugins/myplugin/routes.rbAdd all the required routes for the plugin.
connect '/login', :controller => 'myplugin/account', :action => 'login' # add a named route logout '/logout', :controller => 'myplugin/account', :action => 'logout' # some restful stuff resources :things do |t| t.resources :other_things endDon't prepend map (map.resource, or map.connect) with any of the routes. Also don't copy paste the application's routes.rb for plugin as the invocation of draw() method in it erases all the previous routes before adding the new ones. I had to struggle really hard to understand this minor stuff in lack of proper documentation. Add plugins' routes within routes of main application.
ApplicationController::Routing::Routes.draw do |map| map.connect '/app_stuff', :controller => 'application_thing' # etc... # This line includes the routes from the given plugin at this point, giving you # control over the priority of your application routes map.from_plugin :myplugin map.connect ':controller/:action/:id' endBe careful while doing this as the placement of this statement will directly affect the priority (Rails interpretation) of routes in the application.
No comments:
Post a Comment