Chapter 19. Extending Rails with Plugins

Once again, when we come to the creation of things by people, the form this unfolding takes, always, is step by step to please yourself. We cannot perform the unfolding process without knowing how to please ourselves.

—Christopher Alexander

I doubt that many of us would still be programmers if we had to solve exactly the same problems repeatedly, day after day. Instead, we are always looking for ways to reapply existing solutions to the problems we encounter. Your code represents the abstract solution to a problem, and so you are often striving to either reuse this abstraction (albeit in slightly different contexts), or refine your solution so that it can be reused. Through reuse, you can save time, money, and effort, and give yourself the opportunity to focus on the interesting and novel aspects of the particular problem you’re currently trying to solve. After all, it’s coming up with interesting and novel solutions to problems that makes us really succeessful, not continually reinventing the wheel.

Even though the standard Ruby on Rails APIs are very useful, sooner or later you’ll find yourself wishing for a particular feature not in Rails core or wishing that a bit of standard Rails behavior were different. That’s where plugins come into play, and this book has already described many useful ones that you will use on a day-to-day basis.

This chapter covers the basic topics of extending Rails with plugins. We’ll also supply you with basic information about writing your own Rails plugins.

19.1 The Plugin System

Rails 1.0 introduced a plugin system that lets developers easily add new functionality into the framework. An official mechanism makes it feasible to extract novel, useful features you’ve come up with in your individual applications and share those extracted solutions with other developers, as a single self-contained unit that is easy to maintain.

Plugins aren’t only useful for sharing new features: Plugins are used to test alterations to the Rails framework itself. Almost any significant new piece of functionality or patch can be implemented as a plugin and road-tested easily by a number of developers before it is considered for inclusion in the core framework. Whether you find a bug in Rails and figure out how to fix it or you come up with a significant feature enhancement, you will want to put your code in a plugin for easy distribution and testing.

Of course, changing significant core behavior of the framework demands a solid understanding of how Rails works internally and is beyond the scope of this book. However, some of the techniques demonstrated will help you understand the way that Rails itself is implemented.

19.1.1 Plugins as RubyGems

All popular Rails plugins are published as RubyGems. To install, you just have to add them to your Gemfile and run rake bundle. David Heinemeier Hansson recommends that authors with popular, version-released plugins, and especially ones with dependencies, distribute their plugins as gems.

Interestingly, as of Rails 3, all the major component frameworks of Rails are essentially plugins themselves.

19.1.2 The Plugin Script

The rails plugin install command shouldn’t be necessary very often anymore since popular plugins are now usually distributed as gems. Nonetheless, for legacy reasons we cover the plugin command here.

rails plugin install plugin_url

Note that it should be run from the root directory of the application you are developing and the URL must point to either a Git or Subversion repository.

image

Checking the vendor/plugins, you can see that a directory named will_paginate has appeared. The plugin install command deletes the .git directory, so that you can add your new plugin easily to your own source control.

You also get an easy way to remove plugins, by name.

rails plugin remove plugin_name

Quite appropriately, this command performs the opposite of install: It removes the plugin from vendor/plugins:

$ rails plugin remove will_paginate

A quick inspection of your vendor/plugins directory shows that the will_paginate folder has indeed been removed completely. You can manually delete the plugin’s directory, but running the remove command will also run the plugin’s uninstall.rb script, if it has one.

19.2 Writing Your Own Plugins

At some point in your Rails career, you might find that you want to share common code among similar projects that you’re involved with. Or if you’ve come up with something particularly innovative, you might wonder if it would make sense to share it with the rest of the world.

Rails makes it easy to become a plugin author. It even includes a plugin generator script that sets up the basic directory structure and files that you need to get started:

image

The generator gives you the entire set of possible plugin directories and starter files, even including a /tasks folder for your plugin’s custom rake tasks. The install.rb and uninstall.rb are optional one-time setup and teardown scripts that can do anything you want them to do. You don’t have to use everything that’s created by the plugin generator.

The two defining aspects of a plugin are the presence of the init.rb file and of a directory in the plugin called lib. If neither of these exists, Rails will not recognize that subdirectory of vendor/plugins as a plugin. In fact, many popular plugins consist only of an init.rb script and some files in lib.

19.2.1 The init.rb Hook

If you pop open the boilerplate init.rb file that Rails generated for you, you’ll read a simple instruction.

# insert hook code here

Hook code means code that hooks into the Rails initialization routines. To see a quick example of hook code in action, just go ahead and generate a plugin in one of your projects and add the following line to its init.rb:

puts "Current Rails version: #{Rails::VERSION::STRING}"

Congratulations, you’ve written your first simple plugin. Run the Rails console and see:

$ rails console
Current Rails version: 3.0.pre
Loading development environment (Rails 3.0.pre)
>>

Code that’s added to init.rb is run at startup. (That’s any sort of Rails commands, including server, console, and runner.) Most plugins have their require statements in init.rb.

Initialization Variables

A few special variables are available to your code in init.rb:

name The name of your plugin ('my_plugin' in our simple example).

path The directory in which the plugin exists, which is useful in case you need to read or write nonstandard files in your plugin’s directory.

config The configuration object created in environment.rb. (See Chapter 1, “Rails Environments and Configuration,” as well as the online API docs for Rails::Configuration to learn more about what’s available via config.)

Our simple example is just that, simple. Most of the time you want a plugin to provide new functionality to the rest of your application or modify the Rails libraries in more interesting ways than printing out a version number on startup.

19.2.2 The lib Directory

The lib directory of your plugin is added to Ruby’s load path before init.rb is run. That means that you can require your code without needing to jump through hoops specifying the load path:

require File.dirname(__FILE__) + '/lib/my_plugin' # unnecessary

Assuming your lib directory contains my_plugin.rb, your init.rb needs to read:

require 'my_plugin'

Simple. You can bundle any class or Ruby code in a plugin’s lib folder and then load it in init.rb (or allow other developers to optionally load it an initializer) using Ruby’s require statement. This is the simplest way to share Ruby code among multiple Rails applications.

It’s typical for plugins to alter or enhance the behavior or existing Ruby classes. As a simple example, Listing 19.1 is the source of a plugin that gives ActiveRecord classes a square brackets operator for finding by id.

Listing 19.1. Adding [] to Active Record Classes

image

In addition to opening existing classes to add or modify behavior, there are at least three other ways used by plugins to extend Rails functionality:

• Mixins, which describes inclusion of modules into existing classes

• Dynamic extension through Ruby’s callbacks and hooks such as method_missing, const_missing, and included

• Dynamic extension using runtime evaluation with methods such as eval, class_eval, and instance_eval

19.2.3 Extending Rails Classes

The way that we reopen the ActiveRecord::Base class in Listing 19.1 and simply add a method to it is simple, but most plugins follow a pattern used internally in Rails and split their methods into two modules, one each for class and instance methods. We’ll go ahead and add a useful to_param instance method to all our ActiveRecord objects too.1

Let’s rework my_plugin so that it follows that style. First, after requiring 'my_plugin' in init.rb, we’ll send an include message to the ActiveRecord class itself:

ActiveRecord::Base.send(:include, MyPlugin)

There’s also another way of accomplishing the same result, which you might encounter when browsing through the source code of popular plugins:2

ActiveRecord::Base.class_eval do
  include MyPlugin
end

Now we need to write a MyPlugin module to house the class and instance variables with which we will extend ActiveRecord::Base. See Listing 19.2.

Listing 19.2. Extensions to ActiveRecord::Base

image

You can use similar techniques to extend controllers and views.3 For instance, if you want to add custom helper methods available in all your view templates, you can extend Action View like this:

ActionView::Base.send(:include, MyPlugin::MySpecialHelper)

Now that we’ve covered the fundamentals of writing Rails plugins (init.rb and the contents of the lib directory), we can take a look at the other files that are created by the plugin generator script.

19.2.4 The README and MIT-LICENSE File

The first thing that developers do when they encounter a new plugin is to take a look in the README file. It’s tempting to ignore this file, but at the very least, you should add a simple description of what the plugin does, for future reference. The README file is also read and processed by Ruby’s RDoc tool, when you generate documentation for your plugin using the doc:: Rake tasks. It’s worth learning some fundamentals of RDoc formatting if you want the information that you put in the README file to look polished and inviting later.

Rails is open-sourced under the extremely liberal and open MIT license, as are most of the popular plugins available. In his keynote address to Railsconf 2007, David announced that the plugin generator will auto-generate an MIT license for the file, to help to solve the problem of plugins being distributed without an open-source license. Of course, you can still change the license to whatever you want, but the MIT license is definitely considered the Rails way.

19.2.5 The install.rb and uninstall.rb Files

This pair of files is placed in the root of the plugin directory along with init.rb and README. Just as the init.rb file can be used to perform a set of actions each time the server starts, these files can be used to ensure that prerequisites of your plugin are in place when the plugin is installed using the rails plugin install command and that your plugin cleans up after itself when it is uninstalled using rails plugin remove.

Installation

For example, you might develop a plugin that generates intermediate data stored as temporary files in an application. For this plugin to work, it might require a temporary directory to exist before the data can be generated by the plugin—the perfect opportunity to use install.rb. See Listing 19.3.

Listing 19.3. Creating a temporary directory during plugin installation

image

By adding these lines to your plugin’s install.rb file, the directory tmp/ my_plugin_data will be created in any Rails application in which the plugin is installed. This fire-once action can be used for any number of purposes, including but not limited to the following:

• Copying asset files (HTML, CSS, and so on) into the public directory

• Checking for the existence of dependencies (for example, RMagick)

• Installing other requisite plugins

• Displaying documentation (see Listing 19.4)

Listing 19.4. Outputting documentation

image

Removal

As mentioned, the rails plugin remove command checks for the presence of a file called uninstall.rb when removing a plugin. If this file is present, it will be evaluated just prior to the plugin files actually being deleted. Typically, this is useful for reversing any actions performed when the plugin was installed. This can be handy for removing any directories or specific data files that your plugin might have created when installed, or while the application was running.

Commonsense Reminder

What might not be so obvious about this scheme is that it isn’t foolproof. Users of plugins often skip the installation routines without meaning to do so. Because plugins are distributed via source control, it is trivial to add a plugin to your project with a simple checkout:

$ git checkout git://github.com/mislav/will_paginate.git
vendor/plugins/will_paginate

Or perhaps a more common scenario is to add a plugin to your project by copying it over from another Rails project using the filesystem. (I know I’ve done just that many times.) Same situation applies to plugin removal—a developer that doesn’t know any better might uninstall a plugin from his project simply by deleting its folder from the vendor/plugins directory, in which case the uninstall.rb script would never run.

If as a plugin writer you are concerned about making sure that your install and/or uninstall scripts are actually executed, it’s probably worthwile to stress the point in your announcements to the community and within the plugin documentation itself, such as the README file.

19.2.6 Custom Rake Tasks

It is often useful to include Rake tasks in plugins. For example, if your plugin stores files in a temporary directory (such as /tmp), you can include a helpful task for clearing out those temporary files without having to dig around in the plugin code to find out where the files are stored. Rake tasks such as this should be defined in a .rake file in your plugin’s tasks folder (see Listing 19.5).

Listing 19.5. A plugin’s cleanup rake task

image

Rake tasks added via plugins are listed alongside their standard Rails brothers and sister when you run rake -T to list all the tasks in a project. (In the following snippet, I limited Rake’s output by passing a string argument to use for matching task names):

$ rake -T my_plugin
rake my_plugin:cleanup  # Clear out the temporary files

19.2.7 The Plugin’s Rakefile

Generated plugins get their own little Rakefile, which can be used from within the plugin’s directory to run its tests and generate its RDoc documentation (see Listing 19.6).

Listing 19.6. A generated plugin rakefile

image

While we’re on the subject, I’ll also mention that Rails has its own default rake tasks related to plugins, and they’re fairly self-explanatory:

image

Before closing this section, let’s make the distinction between a plugin’s Rakefile and any .rake files in the tasks folder clear:

• Use Rakefile for tasks that operate on the plugin’s source files, such as special testing or documentation. These must be run from the plugin’s directory.

• Use tasks/*.rake for tasks that are part of the development or deployment of the application in which the plugin is installed. These will be shown in the output of rake ,ÄìT, the list of all Rake tasks for this application.

19.2.8 Including Assets with Your Plugin

Sometimes when writing a plugin you will want to have additional resources available to the application, such as javascript or css, but you do not want installation to copy files all over the place. This can be handled in your plugin initializer via a few hooks provided by Action View.

For javascript, you will simply need to register a javascript expansion using ActionView::Helpers::AssetTagHelper. In the following example we add two javascript files from our plugin under the namespace my_plugin. Note that the source files in the plugin must also reside in public/javascripts.

ActionView::Helpers::AssetTagHelper.
  register_javascript_expansion :my_plugin => ["core", "ext"]

This javascript can then be loaded into the application through the standard javascript include tag, and passing the name defined by the plugin.

javascript_include_tag :my_plugin

Stylesheets are handled in a similar manner, with stylesheets needing to reside in your plugin’s public/stylesheets directory.

ActionView::Helpers::AssetTagHelper.
  register_stylesheet_expansion :my_plugin => ["layouts", "forms"]

Once initialized the stylesheets can then be loaded using the stylesheet link tag in the application.

stylesheet_link_tag :my_plugin

19.2.9 Testing Plugins

Last but not least, the development of your plugin should be Test-Driven. Writing tests for plugins is for the most part identical to any testing in Rails or Ruby and for the most part the methods used to test both are the same. However, because plugins cannot often predict the exact environment in which they are run, they require extra precautions to ensure that the test behavior of your plugin code is isolated from the rest of the application.

There is a subtle distinction between running plugin tests using the global test:plugins rake task and via the plugin’s own Rakefile. Although the former can test all installed plugins at the same time, the internal Rakefile can and should be exploited to add any specific tasks your plugin requires to be tested properly.

Techniques used in testing plugins properly include bootstrapping a separate database for testing plugins in complete isolation. This is particularly useful when a plugin augments ActiveRecord with additional functionality, because you need to test the new methods in a controlled environment, minimizing the interaction with other plugins and the application’s own test data.

As you can imagine, testing of plugins is a lengthy topic that is primarily of interest to plugin authors. Unfortunately, I must leave further analysis of the subject out of this book for reasons of practicality and overall length.

19.2.10 Railties

Railties are classes that extend from Rails::Railtie and provide hooks into Rails initialization for add-on libraries. This is extremely useful for gems which want to seemlessly integrate with Rails.

Railties provide hooks for libraries to add the following functionality:

• Creating initializers

• Providing Rake tasks

• Adding generators

• Registering event subscribers (for logging)

To create a Railtie, create a class called Railtie in your project’s namespace that inherits from Rails::Railtie. Make sure you require ‘rails’ and your own gem in the file as well.

image

At this point you are ready to hook in. Methods defined on Rails::Railtie that can be used for configuration are:

initializer(&block) Execute the block on initialization, it yields to the application configuration object.

config Provides access to the global configuration object.

rake_tasks(&block) Loads rake tasks to be used by the application.

generators(&block) Require generators to be used by the application.

log subscriber(name, subscriber) Register a custom log subscriber for your framework.

A more thorough example is

image


Note

Rails Engines are self-contained applications that can be packaged as gems and included in another Rails application. Devise (covered in Chapter 14) is an example of an engine and contains, among other things, its own configuration, routes, models, controllers, views and even generators. The primary author of Devise, Jose Valim, has written one of the best descriptions of creating a Rails Engine at https://gist.github.com/af7e572c2dc973add221#file_2_engine.rdoc


19.3 Conclusion

You have now learned about all the basic aspects of Rails plugins. You learned how to install and remove them. You also learned the fundamentals of writing your own plugins, enough to at least get you started experimenting with them.

To cover everything related to Rails plugins would require its own book and would go beyond the needs of most Rails developers, so we did not cover testing plugins or the more advanced techniques employed by plugin developers. We also did not discuss topics related to the life of a plugin beyond its initial development.

For in-depth learning about extending Rails with plugins, I strongly recommend the Addison-Wesley publication Rails Plugins by James Adam, who is considered the world’s top expert on the subject.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.226.255