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 full-size slide

  2. $ script/plugin install

    View full-size slide

  3. tasks install.rb
    uninstall.rb test

    View full-size slide

  4. Plugins
    developing

    View full-size slide

  5. Rails plugins
    can add
    anything

    View full-size slide

  6. Rails plugins
    can change
    anything

    View full-size slide

  7. Rails plugins
    can do
    anything

    View full-size slide

  8. Methods
    Adding
    with Ruby, and Rails

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. Behaviour
    Adding
    as a part of Class Definitions

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  17. “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 full-size slide

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

    View full-size slide

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

    View full-size slide

  20. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  23. 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 full-size slide

  24. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  27. 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 full-size slide

  28. 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 full-size slide

  29. 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 full-size slide

  30. Rails
    Patching
    Rails

    View full-size slide

  31. Rails
    Patching
    Rails
    HACKING

    View full-size slide

  32. changing the behaviour
    of existing classes

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  35. changing the
    implementation of
    existing methods

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  40. 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 full-size slide

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

    View full-size slide

  42. 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 full-size slide

  43. 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 full-size slide

  44. 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 full-size slide

  45. 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 full-size slide