Extending Rails: Understanding and Building Plugins Clinton R. - - PowerPoint PPT Presentation

extending rails understanding and building plugins
SMART_READER_LITE
LIVE PREVIEW

Extending Rails: Understanding and Building Plugins Clinton R. - - PowerPoint PPT Presentation

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


slide-1
SLIDE 1

Extending Rails: Understanding and Building Plugins

Clinton R. Nixon

slide-2
SLIDE 2

Welcome!

Welcoming robin by Ian-S (http://flickr.com/photos/ian-s/2301022466/)

slide-3
SLIDE 3

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
slide-4
SLIDE 4

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
slide-5
SLIDE 5

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
slide-6
SLIDE 6

Where do you find plugins?

Unfortunately, no simple answer

  • Rails wiki
  • Giant list of plugins
  • Used by script/plugin
discover
  • Lots of out of date information
slide-7
SLIDE 7

Where do you find plugins?

My recommendations

  • Agile Web Development
  • Core Rails Plugins
  • Technoweenie (Rick Olson)
slide-8
SLIDE 8

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

slide-9
SLIDE 9

How do you install plugins?

Install by name:

  • script/plugin
source
http://example.com/

plugins/

  • script/plugin
install
make_foo
slide-10
SLIDE 10

Plugin installation sources

See list of sources:

  • script/plugin
sources

Remove source:

  • script/plugin
unsource
http://example.com/

plugins/

slide-11
SLIDE 11

Autodiscovering plugin sources

Scrape Rails wiki for sources:

  • script/plugin
discover
slide-12
SLIDE 12

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.

slide-13
SLIDE 13

Additional plugin paths

More plugin paths can be defined as configuration.plugin_paths
in
 environment.rb

  • Overwrites default plugin paths
slide-14
SLIDE 14

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
slide-15
SLIDE 15

Questions

Baby monkey (http://flickr.com/photos/7971389@N03/504227772/)

slide-16
SLIDE 16

What types of plugins are there?

  • acts_as...
  • ...fu
  • controller and view helpers
  • testing helpers
  • resourceful plugins
  • piggyback plugins
slide-17
SLIDE 17

acts_as... plugins

Adds capabilities to ActiveRecord models

  • acts_as_versioned
  • acts_as_paranoid
  • acts_as_state_machine
  • acts_as_taggable_on
slide-18
SLIDE 18

...fu plugins

Adds new controller capabilities and back-end processing

  • attachment_fu
  • GeoKit
  • BackgrounDRb
  • Active Merchant
  • Exception Notification
slide-19
SLIDE 19

Helper plugins

Automate frequently repeated or complicated tasks; some crossover with ...fu plugins

  • will_paginate
  • Stickies
  • jRails
  • ssl_requirement
  • TinyMCE for Rails
  • permalink_fu
slide-20
SLIDE 20

Testing plugins

Adds capabilities to testing in Rails

  • Shoulda
  • Factory Girl
  • Test::Spec on Rails
  • RSpec on Rails
slide-21
SLIDE 21

Resourceful plugins

Plugins which contain a mini-app

  • Savage Beast
  • Comatose
  • RESTful Authentication
  • Bloget
  • Sandstone
slide-22
SLIDE 22

Piggyback plugins

Plugins that alter the behavior of other plugins

  • Also known as “evil twin plugin”
  • Usually not published
  • But increasingly found
  • n GitHub
slide-23
SLIDE 23

Questions

Shinji the Hedgehog by Narisa (http://flickr.com/photos/narisa/508277874/)

slide-24
SLIDE 24

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
slide-25
SLIDE 25

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.

slide-26
SLIDE 26

install.rb & uninstall.rb

Run automatically

  • script/plugin
install
  • script/plugin
uninstall

Usually contains code to display instructions

  • r move files

Often not found

puts IO.read(File.join(File.dirname(__FILE__), 'README'))

slide-27
SLIDE 27

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'

slide-28
SLIDE 28

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

slide-29
SLIDE 29

Rakefile 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
slide-30
SLIDE 30

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

slide-31
SLIDE 31

test/

Tests for plugin

  • rake
test:plugins
  • rake
test:plugins
PLUGIN=plugin_name
  • plugin Rakefile test task
slide-32
SLIDE 32

Anything else you want to add

License files Further instructions Changelog Contribution guidelines Gemspecs Todo lists Asset files (JavaScript, images)

slide-33
SLIDE 33

Questions

Baby Hippo by phalinn (http://flickr.com/photos/phalinn)

slide-34
SLIDE 34

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

slide-35
SLIDE 35

Plugins and metaprogramming

Modules

  • include
  • extend

alias_method and alias_method_chain

slide-36
SLIDE 36

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
slide-37
SLIDE 37

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

slide-38
SLIDE 38

Aliasing methods

Hook onto any method using this technique

def awesome_find ...

  • ld_find(params)

end alias_method :old_find, :find alias_method :find, :awesome_find

Kind of messy and unsustainable

slide-39
SLIDE 39

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

slide-40
SLIDE 40

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

slide-41
SLIDE 41

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

slide-42
SLIDE 42

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
slide-43
SLIDE 43

Plugin best practices

  • Namespace your code
  • Enhance, not override
  • Leave choices to plugin users
  • Do as little as possible
  • Don’t do anything unexpected
slide-44
SLIDE 44

Questions

Chloe’s Baby #2 by mdprovost (http://flickr.com/photos/anderani/2617606614/)

slide-45
SLIDE 45

How do you setup a plugin?

install.rb - on installation

  • nly works when script/plugin
install used

init.rb - on application load

slide-46
SLIDE 46

init.rb

Run every time the Rails environment is loaded

  • script/server
  • script/console
  • script/runner
slide-47
SLIDE 47

init.rb

For a small plugin, may be all you need Should kick off most of your metaprogramming Sometimes used to copy assets

  • Don’t do this unless absolutely necessary

Should only do things you need to do every time your plugin is loaded

slide-48
SLIDE 48

init.rb binding

def evaluate_init_rb(initializer) if has_init_file? silence_warnings do # Allow plugins to reference the current configuration object config = initializer.configuration eval(IO.read(init_path), binding, init_path) end end end

slide-49
SLIDE 49

Gem plugins and init.rb

init.rb found under rails/ directory in a gem plugin lib/ still added to load path

slide-50
SLIDE 50

How do you work with models?

New models dropped in lib/ will automatically get picked up and be accessible - but are not reloaded in development. Downside: user cannot easily extend model.

slide-51
SLIDE 51

Model behavior in lib/

A solution:

  • Put a module in lib/ to be included in a model class in

app/models/

We can get assistance from generators if we require a class for our plugin to work.

  • Example: acts_as_taggable_on vs. Bloget
slide-52
SLIDE 52

Single table inheritance as a solution

You can drop a model in lib/ intended to be inherited from.

  • DB table will need a type column
  • STI has its own downsides
slide-53
SLIDE 53

Adding behavior to multiple models

To affect all models automatically, include code in ActiveRecord::Base.

  • Not very common usage
slide-54
SLIDE 54

Granting ability to add behavior

AKA acts_as... Include module in ActiveRecord::Base, but

  • nly add one method to trigger behavior

module ActiveRecord::Acts::Versioned def self.included(base) base.extend ClassMethods end module ClassMethods def acts_as_versioned(options = {}, &extension) ... end end end ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned

slide-55
SLIDE 55

acts_as_paranoid walkthrough

Baby croc by marcelgermain (http://flickr.com/photos/marcelgermain/1472148914/)

slide-56
SLIDE 56

How do you work with controllers?

Like models, you can drop a controller in lib/ and it will work. Again, this has the drawback that the user cannot easily edit it. Wrapping behavior in a module and including it is smart. Inheritance also works well.

slide-57
SLIDE 57

Classes involved with controllers

ActionController::Base and ActionView::Base are the two classes to change.

ActionController::Base.send(:include, Stickies::ControllerActions) ActionController::Base.send(:include, Stickies::AccessHelpers) ActionView::Base.send(:include, Stickies::AccessHelpers) ActionView::Base.send(:include, Stickies::RenderHelpers)

slide-58
SLIDE 58

A better way to handle helpers

Since Rails 0.8.5, there’s been a better way to add helpers to controllers and views.

ActionController::Base.send(:include, Stickies::AccessHelpers) ActionController::Base.helper(Stickies::AccessHelpers) ActionController::Base.helper(Stickies::RenderHelpers)

slide-59
SLIDE 59

Adding behavior to controllers

Included modules are the most common way to add controller behavior.

slide-60
SLIDE 60

resource_this walkthrough

You can use a setup method to add parameters to your behavior, allowing for complex controller manipulation.

slide-61
SLIDE 61

How do you work with views?

Working with views is much like working with controllers. Use ActionController::Base.helper to add new helpers.

slide-62
SLIDE 62

Changing template root

class FooController < ActionController::Base self.template_root = \ File.join(File.dirname(__FILE__), '..', 'views') end

One problem with this: the controller will expect all views - including templates and partials - to be found under this directory.

slide-63
SLIDE 63

Questions

Baby tiger (http://flickr.com/photos/modu_li/1788817738/)

slide-64
SLIDE 64

How do you work with generators?

Generators can let you make models, controllers,

  • r anything else to stick directly into the user’s

Rails app. Put assets and generation script in generators/.

Thanks to Brian Landau

slide-65
SLIDE 65

Structure of generators/ directory

  • generators/
  • plugin_name/
  • plugin_name_generator.rb
  • templates/
  • a_model.rb
  • a_controller.rb
  • some_views/
  • view_file.html.erb
  • USAGE
slide-66
SLIDE 66

Two types of generators

Rails::Generator::Base

  • No required argument
  • More basic

Rails::Generator::NamedBase

  • First argument is a class name
  • Extra attributes available in generation
  • Use when you're creating a specific named object
slide-67
SLIDE 67

Generator class structure

Should inherit from Base or NamedBase Must define a manifest method Can have a banner method Can have an add_options! method

slide-68
SLIDE 68

NamedBase attributes

name Blog::Comment
or
blog/comment class_nesting class_nesting_depth class_path file_path class_name plural_name singular_name table_name Blog 1 ['blog'] blog/comment Blog::Comment comments comment blog_comments

slide-69
SLIDE 69

Manifest directives

  • class_collisions
  • directory
  • file
  • template
  • migration_template
  • route_resources
  • readme
  • dependency
slide-70
SLIDE 70

USAGE

Description: 

The
comatose
generator
creates
a
migration
for
the
comatose
model. 

The
generator
takes
a
migration
name
as
its
argument.
The 

migration
name
may
be
given
in
CamelCase
or
under_score.
 

'add_comatose_support'
is
the
default. 

The
generator
creates
a
migration
class
in
db/migrate
prefixed
by
 

its
number
in
the
queue. Example: 

./script/generate
comatose
add_comatose_support 

With
4
existing
migrations,
this
will
create
an
Comatose
migration
 

in
the
file
db/migrate/005_add_comatose_support.rb

slide-71
SLIDE 71

Generator templates

Use ERB to customize

  • For ERB generating ERB: <%%=
@post.name
%>
  • Generator methods available
  • Unlike Rails’ views, generator instance variable not available
slide-72
SLIDE 72

Programmatically running generators

Rails::Generator::Scripts::Generate.new.run( ['authenticated', 'user', 'sessions'])

slide-73
SLIDE 73

How do you add new tasks?

No different from Rails

  • Place Rake files in tasks/ named whatever.rake

The plugin Rakefile is only for Rake tasks run in the plugin directory.

  • rake
test
  • rake
rdoc
  • rake
rerdoc
  • rake
clobber_rdoc
slide-74
SLIDE 74

How do you deal with other plugins?

Simple trick: all plugins in vendor/plugins are loaded in alphabetical order.

slide-75
SLIDE 75

How do you test a plugin?

One way: require that your plugin is in a Rails app

  • Easy
  • Ugly
  • Cannot specify Rails version
  • May interfere with app classes
  • Requires your plugin’s setup to be run
slide-76
SLIDE 76

Running plugin tests

From plugin dir: rake
test

  • Does not automatically load Rails environment
  • To load Rails env:
  • require File.join(File.dirname(__FILE__),

'../../../../test/test_helper')

slide-77
SLIDE 77

Running plugin tests

From Rails app dir

  • rake
test:plugins
  • rake
test:plugins
PLUGIN=plugin_name

Running from Rails app dir does not run plugin’s Rakefile, so dependencies in there are not executed.

slide-78
SLIDE 78

Standalone testing of plugins

Helper plugins may not need a Rails app For other plugins, you can mock out a Rails app

  • Generators may need a full directory
  • Model plugins can get by with a test database
  • Controller plugins may need very little
  • But mocking out routing can be very hard
slide-79
SLIDE 79

How do you package your plugin?

A public Subversion or Git repository lets users install with script/plugin. To make a gem plugin, try Mr Bones.

PROJ.name = 'friend-feed' PROJ.authors = 'Clinton R. Nixon' PROJ.email = 'crnixon@gmail.com' PROJ.url = 'friend-feed.rubyforge.org' PROJ.dependencies = ['json'] PROJ.version = FriendFeed::VERSION

slide-80
SLIDE 80

Bloget walkthrough

Baby tapir (http://flickr.com/photos/su-lin/2087767962/)

slide-81
SLIDE 81

Foreign Key Migrations walkthrough

Baby duck (http://flickr.com/photos/dizzygirl/437988363/)

slide-82
SLIDE 82

RESTful Authentication walkthrough

Uganda Mbeya (http://flickr.com/photos/youngrobv/2347565498/)

slide-83
SLIDE 83

Resources

  • Peepcode’s Plugin Patterns by Andrew Stewart
  • Addison-Wesley’s Shortcut Rails Plugins by James Adam
  • Rick Olson: techno-weenie.net - tons of plugins and plugin

building blog posts

  • This talk: http://crnixon.org/talks/rails-plugins