Extending Rails: Understanding and Building Plugins Clinton R. Nixon
Welcome! Welcoming robin by Ian-S (http://flickr.com/photos/ian-s/2301022466/)
What are we going to talk about? The short ‣ How plugins work with Ruby on Rails ‣ How to find and install plugins The long ‣ Types of plugins ‣ How to build plugins
Ruby on Rails and plugins History of plugins: ‣ Introduced with Rails 1.0 as a way to extract functionality ‣ Made it easy to distribute functionality ‣ In Rails 2.0, some core features were pulled into plugins and some plugins pulled into core ‣ In Rails 2.1, gem dependencies introduced
Gem dependencies Fulfills same role as a plugin ‣ Major disadvantage of plugins: no dependencies ‣ Your app can now depend on a gem ‣ That depends on other gems Same techniques apply as a standard plugin ‣ Differences will be pointed out
Where do you fi nd plugins? Unfortunately, no simple answer ‣ Rails wiki ‣ Giant list of plugins ‣ Used by script/plugin discover ‣ Lots of out of date information
Where do you fi nd plugins? My recommendations ‣ Agile Web Development ‣ Core Rails Plugins ‣ Technoweenie (Rick Olson)
How do you install plugins? Install from a URL: ‣ script/plugin install http://example.com/ plugins/make_foo ‣ script/plugin install svn://code.mondu.org/ svn/atom_fu/trunk ‣ script/plugin install git://github.com/bnl/ acts_as_replicator.git
How do you install plugins? Install by name: ‣ script/plugin source http://example.com/ plugins/ ‣ script/plugin install make_foo
Plugin installation sources See list of sources: ‣ script/plugin sources Remove source: ‣ script/plugin unsource http://example.com/ plugins/
Autodiscovering plugin sources Scrape Rails wiki for sources: ‣ script/plugin discover
vendor/plugins trivia Plugins are installed, by default, in vendor/ plugins . However: ‣ “plugins can be nested arbitrarily deep within an unspecified number of intermediary directories” - railties/lib/ rails/plugin/locator.rb ‣ So, vendor/plugins/my_organization/acts_as/ acts_as_long_dir_name/ is fine.
Additional plugin paths More plugin paths can be defined as configuration.plugin_paths in environment.rb ‣ Overwrites default plugin paths
How do you install gem plugins? Installing gem dependencies ‣ Add config.gem 'gemname' to environment.rb ‣ rake gems:install makes sure gems are installed locally ‣ rake gems:unpack puts gems in vendor/gems ‣ Even better: rake gems:unpack:dependencies ‣ Recompilation: rake gems:build
Questions Baby monkey (http://flickr.com/photos/7971389@N03/504227772/)
What types of plugins are there? ‣ acts_as... ‣ ...fu ‣ controller and view helpers ‣ testing helpers ‣ resourceful plugins ‣ piggyback plugins
acts_as... plugins Adds capabilities to ActiveRecord models ‣ acts_as_versioned ‣ acts_as_paranoid ‣ acts_as_state_machine ‣ acts_as_taggable_on
...fu plugins Adds new controller capabilities and back-end processing ‣ attachment_fu ‣ GeoKit ‣ BackgrounDRb ‣ Active Merchant ‣ Exception Notification
Helper plugins Automate frequently repeated or complicated tasks; some crossover with ...fu plugins ‣ will_paginate ‣ Stickies ‣ jRails ‣ ssl_requirement ‣ TinyMCE for Rails ‣ permalink_fu
Testing plugins Adds capabilities to testing in Rails ‣ Shoulda ‣ Factory Girl ‣ Test::Spec on Rails ‣ RSpec on Rails
Resourceful plugins Plugins which contain a mini-app ‣ Savage Beast ‣ Comatose ‣ RESTful Authentication ‣ Bloget ‣ Sandstone
Piggyback plugins Plugins that alter the behavior of other plugins ‣ Also known as “evil twin plugin” ‣ Usually not published ‣ But increasingly found on GitHub
Questions Shinji the Hedgehog by Narisa (http://flickr.com/photos/narisa/508277874/)
What are the parts of a plugin? ‣ README ‣ about.yml ‣ install.rb ‣ uninstall.rb ‣ init.rb ‣ lib/ ‣ Rakefile ‣ tasks/ ‣ generators/ ‣ test/ ‣ anything else you want to add
README and about.yml about.yml: author: Clinton R. Nixon summary: Adds ability to set foreign key constraints. description: "Adds ability to set foreign key constraints in the database through ActiveRecord migrations. Only works currently with MySQL, PostgreSQL, and SQLite." homepage: http://www.extendviget.com/ plugin: git://github.com/vigetlabs/foreign_key_migrations.git license: MIT version: 0.9 rails_version: 2.0+ You can see this information with: script/plugin info PLUGIN.
install.rb & uninstall.rb Run automatically ‣ script/plugin install ‣ script/plugin uninstall Usually contains code to display instructions or move files Often not found puts IO.read(File.join(File.dirname(__FILE__), 'README'))
init.rb Always run at Rails startup Arbitrary Ruby code Usually injects plugin code ActionView::Helpers::AssetTagHelper::JAVASCRIPT_DEFAULT_SOURCES = \ ['jquery','jquery-ui','jrails'] ActionView::Helpers::AssetTagHelper::reset_javascript_include_default require 'jrails'
lib/ Arbitrary Ruby code to be loaded ‣ models ‣ controllers ‣ modules Added to require path Because of Rails’ autoloading, all properly named files here will be available without require statements
Rake fi le and tasks/ Rakefile contains tasks internal to the plugin ‣ Only executed from plugin directory tasks/ contains tasks external to the plugin ‣ Available throughout the Rails environment ‣ in .rake files
generators/ Contains new generators that can be run in Rails ‣ script/generate and script/destroy Both generator definitions and generator assets Used to automate creation of models, controllers, views, migrations, and tests
test/ Tests for plugin ‣ rake test:plugins ‣ rake test:plugins PLUGIN=plugin_name ‣ plugin Rakefile test task
Anything else you want to add License files Further instructions Changelog Contribution guidelines Gemspecs Todo lists Asset files (JavaScript, images)
Questions Baby Hippo by phalinn (http://flickr.com/photos/phalinn)
How do you create a plugin? script/generate plugin create vendor/plugins/test_plugin/lib create vendor/plugins/test_plugin/tasks create vendor/plugins/test_plugin/test create vendor/plugins/test_plugin/README create vendor/plugins/test_plugin/MIT‐LICENSE create vendor/plugins/test_plugin/Rakefile create vendor/plugins/test_plugin/init.rb create vendor/plugins/test_plugin/install.rb create vendor/plugins/test_plugin/uninstall.rb create vendor/plugins/test_plugin/lib/test_plugin.rb create vendor/plugins/test_plugin/tasks/test_plugin_tasks.rake create vendor/plugins/test_plugin/test/test_plugin_test.rb
Plugins and metaprogramming Modules ‣ include ‣ extend alias_method and alias_method_chain
Using modules Allows you to namespace your code ‣ Common idiom: YourName::YourPlugin::ModuleName include YourModule ‣ adds methods to class instances extend YourModule ‣ adds methods to class
Common module inclusion idiom module YourName::YourPlugin::YourModule def self.included(base) base.extend ClassMethods end def foo ... end module ClassMethods def bar ... end end end User.send(:include, YourName::YourPlugin::YourModule) >> user = User.new >> user.foo >> User.bar
Aliasing methods Hook onto any method using this technique def awesome_find ... old_find(params) end alias_method :old_find, :find alias_method :find, :awesome_find Kind of messy and unsustainable
alias_method_chain def find_with_awesome(params) ... find_without_awesome(params) end alias_method_chain :find, :awesome # Equivalent to: # alias_method :find_without_awesome, :find # alias_method :find, :find_with_awesome
Multiple aliasing class Finder def find puts "found" end def find_with_awesome puts "AWESOME" find_without_awesome end def find_with_humility find_without_humility puts "nothing, really" end alias_method :find_without_awesome, :find alias_method :find, :find_with_awesome alias_method :find_without_humility, :find alias_method :find, :find_with_humility end >>> Finder.new.find AWESOME found nothing, really
Multiple aliasing class Finder def find puts "found" end def find_with_awesome puts "AWESOME" find_without_awesome end def find_with_humility find_without_humility puts "nothing, really" end alias_method_chain :find, :awesome alias_method_chain :find, :humility end >>> Finder.new.find AWESOME found nothing, really
Plugin initialization order ‣ Framework is initializated (This typo was too great to leave out) ‣ Environment is loaded ‣ Gem dependencies are loaded ‣ Plugins are loaded ‣ config/initializers/*.rb (application initializers) loaded ‣ after_initialize callback executed ‣ Routes and observers loaded
Recommend
More recommend