A common stumbling block for Rails developers is learning the basics required to write plugins. This is made more complicated by the fact that Ruby is inherently dynamic and offers many techniques for code reuse. Luckily, if you can write Rails applications you can write plugins by simply drawing on a handful of basic patterns.
The purpose of this article is to demystify writing plugins using examples of common patterns used by popular plugins.
Why write plugins?
Writing a plugin will:
- Help make sharing code more efficient, whether it’s between projects or within the same project
- Allow you to publish generic code to the community
- Save time and increase your confidence by testing once and reusing many times
- Share functionality in a robust manner, especially when using namespaces with ActiveRecord
Usage
Management
Rails provides installation scripts through script/plugin install, and a generator for creating new plugins: script/generate plugin. These will work with URLs, saving time when trying out plugins. You can read more about installing and managing plugins at the Rails wiki.
Creation
Rails provides a generator for creating plugins. Invoking the following command:
script/generate plugin acts_as_
generates a set of files in vendor/plugins/acts_as_, with stubs for you to fill in. You’ll notice files are generated for installing, uninstalling and testing, as well as a rake task. Many plugins won’t require a rake task, and installing and uninstalling a plugin can mean whatever you want it to: perhaps your plugin requires database tables (consider a taggable plugin that requires tables to store tags), or maybe you want to check for dependancies during script/plugin install.
Rubyisms
Any of the following tools and techniques provided by Ruby are used by plugins to extend functionality:
- Mixins: including or extending classes using modules
- Opening a class or module definition and adding or overriding methods
- Dynamic extension through callbacks and hooks:
method_missing,Class#inherited,Module#const_missing,Module#included - Dynamic extension through code generation:
eval,class_eval,instance_eval
These techniques fall into two broad categories:
- Using modules and classes to extend existing classes, providing new features
- Using introspection to adapt generic code to specific cases
It’s important to consider exactly what should be extended when writing a plugin. If complex meta-programming to adapt your plugin to the host application is required, care should be taken to ensure concurrency will not produce unexpected results.
Extending ActiveRecord
The most popular technique for extending ActiveRecord classes is by using modules and an acts_as_ class method. Most plugins split themselves into two modules containing class methods and instance methods, making it clear where methods are meant to be used.
Adding new code to ActiveRecord
To extend ActiveRecord::Base with your new acts_as_ plugin, the most common technique is to simply add the following to the plugin’s init.rb file:
ActiveRecord::Base.send(:include, MyPlugin)
You could also use class_eval to achieve the same effect. This example is from acts_as_rateable:
ActiveRecord::Base.class_eval do
include FortiusOne::Acts::Rateable
end
Notice the use of the company name to create a namespace for the author’s plugins.
The next step is to extend ActiveRecord::Base with the module housing the acts_as_ method. Again, from acts_as_rateable:
module FortiusOne #:nodoc:
module Acts #:nodoc:
module Rateable #:nodoc:
def self.included(mod)
mod.extend(ClassMethods)
end
module ClassMethods
def acts_as_rateable(options = {})
end
end
At this point, it would be possible for any ActiveRecord class to use acts_as_rateable in its class definition. Doing so would send any provided options to acts_as_rateable in the plugin’s ClassMethods module.
Managing options
Most plugins use extend and include within their acts_as_ method to furnish the receiving class with class methods and instance methods. This is also where options are dealt with. Options are generally handled by using a hash:
def acts_as_taggable(options = {})
If options are required elsewhere by both class and instance methods, write_inheritable_attribute and class_inheritable_reader can be used to save settings. This edited example is from acts_as_state_machine:
def acts_as_state_machine(opts)
self.extend(ClassMethods)
raise NoInitialState unless opts[:initial]
write_inheritable_attribute :initial_state, opts[:initial]
class_inheritable_reader :initial_state
There’s a reason so many plugins use write_inheritable_attribute and not cattr_ methods. Consider what would happen if initial_state was a class variable: other objects that used the plugin would contain references to the same initial_state. Plugins are generally distinct, reusable chunks of code, so a copy (rather than a reference) of initial_state is required.
Reusing sets of associations and callbacks
acts_as_taggable_on_steroids demonstrates a simple use for plugins:
module ClassMethods
def acts_as_taggable(options = {})
has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag
has_many :tags, :through => :taggings
before_save :save_cached_tag_list
after_save :save_tags
As you can see, the author has called associations and callbacks from within the plugin’s class method. This is possible because the acts_as_taggable method runs from within the receiving class’s definition.
I’ve found myself using the same technique for defining complex but reusable associations (particularly in the case of reusable polymorphic has_many associations.)
For example, I worked on a group-based security system that used callbacks and polymorphic has_many associations. Instead of adding this (albeit simple) code to each class that required group-based security, I wrote a plugin. Compare the following code:
class Contact < ActiveRecord::Base
acts_as_restricted
end
To the original:
class Contact < ActiveRecord::Base
has_many :restrictions, :as => :restricted, :dependent => :destroy, :extend => ...
before_save :auto_groups
after_create :save_groups
...
This code can now be reused between all of my projects, and new classes can be “restricted” merely by adding acts_as_restricted to their class definition. This can help save time—especially in cases where clients are still making up their mind about what should be restricted.
Structure
I prefer to structure my plugins by creating separate files for each module. This can be seen demonstrated in acts_as_solr, which uses a separate file for class methods.
Modules can be used for class methods, instance methods and singleton methods. Although it might seem difficult to distinguish between class and singleton methods, some plugin authors exploit this to clarify their code. Since singleton methods apply to a particular object, rather than a class of objects, you could effectively add new methods to particular instances of a class as required.
Or, in the case of acts_as_rateable, module SingletonMethods is used to distinguish between methods added to ActiveRecord and those added to the receiver. In this case finders are added.
Extending ActionController and ActionView
The same techniques used by ActiveRecord plugins are used to extend ActionController and ActionView. The UploadProgress plugin extends ActionView and ActionController to add its own helpers and controller functionality:
ActionController::Base.send(:include, UploadProgress)
ActionView::Base.send(:include, UploadProgress::UploadProgressHelper)
Controllers can then use the plugin by calling upload_status_for, defined in UploadProgress::ClassMethods.
Best practices
- Eschew liberal usage of
includein favour of well-designed plugins - Structure your plugins carefully to ensure they’re easy to understand and easy to test
- Make sure you use
write_inheritable_attributeandclass_inheritable_readerso you don’t end up referencing settings instead of creating new ones - Write tests as part of the design process, not least because plugins can be slow to write as they’re not reloaded when using mongrel/webbrick
- Ensure generated SQL isn’t open to injection attacks: escape user input1
- Think carefully about the implications of meta-programmed code, and be wary of eval and eval-like methods
Next steps
This article will be continued with examples of how plugins are tested.
References
- acts_as_rateable
- acts_as_state_machine
- acts_as_taggable_on_steroids
- acts_as_solr
- Rails Plugin Directory
- Rails Wiki on plugins
1 I’ve found open source plugins that generate raw SQL without escaping variables.




