Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Extending Rails with Plugins (2007)

Extending Rails with Plugins (2007)

(QCon London, 2007)

"One of the most important and useful aspects of the Rails framework is it's ability to be extended using plugins. Thanks to the flexibility and power of Ruby, almost any aspect of Rails can be altered to suit the needs of a particular application.

"This presentation will help give developers the boost that's often required to get up to speed developing plugins. We'll cover the hooks that Rails' plugin mechanism makes available, and how to put them to best use in practice with practical examples.

"Once we've covered the groundwork, we'll start to look at more advanced programming techniques for sharing code (and other files) between Rails applications. With a few key programming techniques under our belt, we can use plugins to alter and enhance the Rails framework itself."

lazyatom

March 16, 2007
Tweet

More Decks by lazyatom

Other Decks in Programming

Transcript

  1. Plugins
    James Adam
    Rails

    View Slide

  2. $ script/plugin install

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. lib

    View Slide

  11. init.rb

    View Slide

  12. tasks install.rb
    uninstall.rb test

    View Slide

  13. Plugins
    developing

    View Slide

  14. Rails plugins
    can add
    anything

    View Slide

  15. Rails plugins
    can change
    anything

    View Slide

  16. Rails plugins
    can do
    anything

    View Slide

  17. Methods
    Adding
    with Ruby, and Rails

    View Slide

  18. Modules
    module MineNotYours
    def copyright
    "(c) me"
    end
    end

    View Slide

  19. Adding methods to instances...
    class SomeClass
    include MineNotYours
    end
    c = SomeClass.new
    c.copyright # => "(c) me"

    View Slide

  20. ... and so to Models
    class SomeModel < AR::Base
    include MineNotYours
    end
    m = SomeModel.find(:first)
    m.copyright # => "(c) me"

    View Slide

  21. Adding methods to all models
    # Re-opening the target class
    # directly
    class ActiveRecord::Base
    include MineNotYours
    end

    View Slide

  22. Adding methods to all models
    # Send the ‘include’ message to
    # our target class
    AR::Base.send(:include, MineNotYours)

    View Slide

  23. View Slide

  24. Behaviour
    Adding
    as a part of Class Definitions

    View Slide

  25. Plain Ol’ Ruby Objects
    class ConferenceDelegate
    attr_accessor :name
    end
    c = ConferenceDelegate.new
    c.name = "Joe Blogs"
    c.name # => "Joe Blogs"

    View Slide

  26. Plain Ol’ Ruby Objects
    class ConferenceDelegate
    :name
    end
    c = ConferenceDelegate.new
    c.name = "Joe Blogs"
    c.name # => "Joe Blogs"
    attr_accessor

    View Slide

  27. “Class” methods
    irb:> c.class.private_methods
    => ["abort", "alias_method", "at_exit", "attr",
    "attr_accessor", "attr_reader", "attr_writer",
    "binding", "block_given?", "define_method",
    "eval", "exec", "exit", "extended", "fail",
    "fork", "getc", "gets", "global_variables",
    "include", "included", "irb_binding", "lambda",
    "load", "local_variables", "private", "proc",
    "protected", "public", "puts", "raise",
    "remove_class_variable", "remove_const",
    "remove_instance_variable", "remove_method",
    "require", "sleep", "split", "sprintf", "srand",
    "sub", "sub!", "syscall", "system", "test",
    "throw", "undef_method", "untrace_var", "warn"]

    View Slide

  28. Ruby Hacker; Know Thy-self
    class ConferenceDelegate
    attr_accessor :name
    end
    # self == ConferenceDelegate

    View Slide

  29. Ruby Hacker; Know Thy-self
    class ConferenceDelegate
    def self.do_something
    “OK”
    end
    do_something
    end
    # => “OK”

    View Slide

  30. Ruby Hacker; Know Thy-self
    class ConferenceDelegate
    def self.has_name
    attr_accessor :name
    end
    has_name
    end
    t = ConferenceDelegate.new
    t.name = “Joe Blogs”

    View Slide

  31. Another module
    module Personable
    def has_person_attributes
    attr_accessor :first_name
    attr_accessor :last_name
    end
    end

    View Slide

  32. Adding “class” methods
    class RubyGuru
    extend Personable
    end
    has_person_attributes
    g = RubyGuru.new
    g.first_name = “Dave”
    g.last_name = “Thomas”

    View Slide

  33. Specifying Behaviour in Rails
    class SuperModel < ActiveRecord::Base
    validates_presence_of :rich_boyfriend
    validates_size_of :entourage,
    :minimum => 20
    has_many :vices, :through => :boyfriends
    end
    class FootballersWife < ActiveRecord::Base
    validates_presence_of :rich_boyfriend
    validates_size_of :entourage,
    :minimum => 5
    has_many :vices, :through => :boyfriends
    end

    View Slide

  34. Bundling behaviour
    module ModelValidation
    def acts_as_glamourous(size)
    validates_presence_of :rich_boyfriend
    validates_size_of :entourage,
    :minimum => size
    has_many :vices, :through => :boyfriends
    end
    end
    # ... add this method to the target class
    ActiveRecord::Base.send(:extend,
    ModelValidation)

    View Slide

  35. Our own ActiveRecord behaviour
    class Celebrity < AR::Base
    acts_as_glamourous(50)
    end

    View Slide

  36. Class and instance behaviour
    module GlamourPlugin
    def acts_as_glamourous(size)
    validates_presence_of :rich_boyfriend
    # etc...
    end
    end

    View Slide

  37. Class and instance behaviour
    module GlamourPlugin
    def acts_as_glamourous
    validates_presence_of :rich_boyfriend
    # etc...
    end
    module InstanceBehaviour
    def react_to(other_person)
    if other_person.is_a?(Paparazzo)
    other_person.destroy
    end
    end
    end
    end

    View Slide

  38. Class and instance behaviour
    module GlamourPlugin
    def acts_as_glamourous
    validates_presence_of :rich_boyfriend
    # etc...
    include GlamourPlugin::InstanceBehaviour
    end
    module InstanceBehaviour
    def react_to(other_person)
    if other_person.is_a?(Paparazzo)
    other_person.destroy
    end
    end
    end
    end

    View Slide

  39. Our plugin in action
    class Diva < ActiveRecord::Base
    acts_as_glamourous
    end
    dude = Paparazzo.create(:lens => "Huge")
    Paparazzo.count # => 1
    starlet = Diva.new(:name => "Britney",
    :entourage => 873,
    :quirk => "No hair")
    starlet.react_to(dude)
    Paparazzo.count # => 0

    View Slide

  40. Rails
    Patching
    Rails

    View Slide

  41. Rails
    Patching
    Rails
    HACKING

    View Slide

  42. changing the behaviour
    of existing classes

    View Slide

  43. Changing existing behaviour
    # replacing implementation
    # via inheritance
    class Thing < Object
    def object_id
    @my_custom_value
    end
    end

    View Slide

  44. Inheritance is not
    always possible
    ActiveRecord::Base
    ActionController::Base
    ActionView::Base
    Dependencies ActionMailer::Base
    Routing
    DispatchingAssociations
    ... and more...

    View Slide

  45. changing the
    implementation of
    existing methods

    View Slide

  46. Ruby classes are always open
    class ActiveRecord::Base
    def count
    execute("SELECT COUNT(*)
    FROM #{table_name}") / 2
    end
    end

    View Slide

  47. Aliasing methods
    class ActiveRecord::Base
    end
    alias_method :__count, :count
    def count
    __count / 2
    end

    View Slide

  48. Method chains with Rails
    class FunkyBass
    def play_it
    "Funky"
    end
    end
    bass = FunkyBass.new
    bass.play_it # => "Funky"

    View Slide

  49. Method chains - new behaviour
    module Soul
    def play_it_with_soul
    "Smooth & " +
    play_it_without_soul
    end
    end

    View Slide

  50. How alias_method_chain works
    class FunkyBass
    include Soul
    alias_method_chain :play_it, :soul
    end
    alias_method :play_it_without_soul,
    :play_it
    alias_method :play_it,
    :play_it_with_soul
    # underneath the hood:

    View Slide

  51. Adding the new functionality
    class FunkyBass
    include Soul
    alias_method_chain :play_it,
    :soul
    end
    bass.play_it
    # => "Smooth & Funky"

    View Slide

  52. Method chains in action
    class ActiveRecord::Base
    def count_with_fixes
    return count_without_fixes + 1
    end
    alias_method_chain :count, :fixes
    end
    MyModel.count # calls new method

    View Slide

  53. Patching Rails’ Dependencies
    class Dependencies
    def require_or_load(file_name)
    # load the file from the normal
    # places, i.e. $LOAD_PATH
    end
    end

    View Slide

  54. New plugin-loading behaviour
    module LoadingFromPlugins
    # we want to replace Rails’ default loading
    # behaviour with this
    def require_or_load_with_plugins(file_name)
    if file_exists_in_plugin(file_name)
    load_file_from_plugin(file_name)
    else
    require_or_load_without_plugins(file_name)
    end
    end
    end

    View Slide

  55. Injecting the new behaviour
    module LoadingFromPlugins
    def require_or_load_with_plugins(file_name)
    if file_exists_in_plugins(file_name)
    load_file_from_plugin(file_name)
    else
    require_or_load_without_plugins(file_name)
    end
    end
    end
    def self.included(base)
    base.send(:alias_method_chain,
    :require_or_load, :plugins)
    end
    Dependencies.send(:include, LoadingFromPlugins)

    View Slide