Plugins James Adam Rails
$ script/plugin install
lib
init.rb
tasks install.rb uninstall.rb test
Plugins developing
Rails plugins can add anything
Rails plugins can change anything
Rails plugins can do anything
Methods Adding with Ruby, and Rails
Modules module MineNotYours def copyright "(c) me" end end
Adding methods to instances... class SomeClass include MineNotYours end c = SomeClass.new c.copyright # => "(c) me"
... and so to Models class SomeModel < AR::Base include MineNotYours end m = SomeModel.find(:first) m.copyright # => "(c) me"
Adding methods to all models # Re-opening the target class # directly class ActiveRecord::Base include MineNotYours end
Adding methods to all models # Send the ‘include’ message to # our target class AR::Base.send(:include, MineNotYours)
Behaviour Adding as a part of Class Definitions
Plain Ol’ Ruby Objects class ConferenceDelegate attr_accessor :name end c = ConferenceDelegate.new c.name = "Joe Blogs" c.name # => "Joe Blogs"
Plain Ol’ Ruby Objects class ConferenceDelegate :name end c = ConferenceDelegate.new c.name = "Joe Blogs" c.name # => "Joe Blogs" attr_accessor
“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"]
Ruby Hacker; Know Thy-self class ConferenceDelegate attr_accessor :name end # self == ConferenceDelegate
Ruby Hacker; Know Thy-self class ConferenceDelegate def self.do_something “OK” end do_something end # => “OK”
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”
Another module module Personable def has_person_attributes attr_accessor :first_name attr_accessor :last_name end end
Adding “class” methods class RubyGuru extend Personable end has_person_attributes g = RubyGuru.new g.first_name = “Dave” g.last_name = “Thomas”
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
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)
Our own ActiveRecord behaviour class Celebrity < AR::Base acts_as_glamourous(50) end
Class and instance behaviour module GlamourPlugin def acts_as_glamourous(size) validates_presence_of :rich_boyfriend # etc... end end
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
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
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
RailsPatching Rails
RailsPatching Rails HACKING
changing the behaviour of existing classes
Changing existing behaviour # replacing implementation # via inheritance class Thing < Object def object_id @my_custom_value end end
Inheritance is not always possible ActiveRecord::Base ActionController::Base ActionView::Base Dependencies ActionMailer::Base Routing DispatchingAssociations ... and more...
changing the implementation of existing methods
Ruby classes are always open class ActiveRecord::Base def count execute("SELECT COUNT(*) FROM #{table_name}") / 2 end end
Aliasing methods class ActiveRecord::Base end alias_method :__count, :count def count __count / 2 end
Method chains with Rails class FunkyBass def play_it "Funky" end end bass = FunkyBass.new bass.play_it # => "Funky"
Method chains - new behaviour module Soul def play_it_with_soul "Smooth & " + play_it_without_soul end end
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:
Adding the new functionality class FunkyBass include Soul alias_method_chain :play_it, :soul end bass.play_it # => "Smooth & Funky"
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
Patching Rails’ Dependencies class Dependencies def require_or_load(file_name) # load the file from the normal # places, i.e. $LOAD_PATH end end
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
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)

Extending Rails with Plugins (2007)

  • 1.
  • 2.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
    Adding methods toinstances... class SomeClass include MineNotYours end c = SomeClass.new c.copyright # => "(c) me"
  • 20.
    ... and soto Models class SomeModel < AR::Base include MineNotYours end m = SomeModel.find(:first) m.copyright # => "(c) me"
  • 21.
    Adding methods toall models # Re-opening the target class # directly class ActiveRecord::Base include MineNotYours end
  • 22.
    Adding methods toall models # Send the ‘include’ message to # our target class AR::Base.send(:include, MineNotYours)
  • 24.
    Behaviour Adding as a partof Class Definitions
  • 25.
    Plain Ol’ RubyObjects class ConferenceDelegate attr_accessor :name end c = ConferenceDelegate.new c.name = "Joe Blogs" c.name # => "Joe Blogs"
  • 26.
    Plain Ol’ RubyObjects class ConferenceDelegate :name end c = ConferenceDelegate.new c.name = "Joe Blogs" c.name # => "Joe Blogs" attr_accessor
  • 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"]
  • 28.
    Ruby Hacker; KnowThy-self class ConferenceDelegate attr_accessor :name end # self == ConferenceDelegate
  • 29.
    Ruby Hacker; KnowThy-self class ConferenceDelegate def self.do_something “OK” end do_something end # => “OK”
  • 30.
    Ruby Hacker; KnowThy-self class ConferenceDelegate def self.has_name attr_accessor :name end has_name end t = ConferenceDelegate.new t.name = “Joe Blogs”
  • 31.
    Another module module Personable defhas_person_attributes attr_accessor :first_name attr_accessor :last_name end end
  • 32.
    Adding “class” methods classRubyGuru extend Personable end has_person_attributes g = RubyGuru.new g.first_name = “Dave” g.last_name = “Thomas”
  • 33.
    Specifying Behaviour inRails 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
  • 34.
    Bundling behaviour module ModelValidation defacts_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)
  • 35.
    Our own ActiveRecordbehaviour class Celebrity < AR::Base acts_as_glamourous(50) end
  • 36.
    Class and instancebehaviour module GlamourPlugin def acts_as_glamourous(size) validates_presence_of :rich_boyfriend # etc... end end
  • 37.
    Class and instancebehaviour 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
  • 38.
    Class and instancebehaviour 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
  • 39.
    Our plugin inaction 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
  • 40.
  • 41.
  • 42.
    changing the behaviour ofexisting classes
  • 43.
    Changing existing behaviour #replacing implementation # via inheritance class Thing < Object def object_id @my_custom_value end end
  • 44.
    Inheritance is not alwayspossible ActiveRecord::Base ActionController::Base ActionView::Base Dependencies ActionMailer::Base Routing DispatchingAssociations ... and more...
  • 45.
  • 46.
    Ruby classes arealways open class ActiveRecord::Base def count execute("SELECT COUNT(*) FROM #{table_name}") / 2 end end
  • 47.
    Aliasing methods class ActiveRecord::Base end alias_method:__count, :count def count __count / 2 end
  • 48.
    Method chains withRails class FunkyBass def play_it "Funky" end end bass = FunkyBass.new bass.play_it # => "Funky"
  • 49.
    Method chains -new behaviour module Soul def play_it_with_soul "Smooth & " + play_it_without_soul end end
  • 50.
    How alias_method_chain works classFunkyBass 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:
  • 51.
    Adding the newfunctionality class FunkyBass include Soul alias_method_chain :play_it, :soul end bass.play_it # => "Smooth & Funky"
  • 52.
    Method chains inaction class ActiveRecord::Base def count_with_fixes return count_without_fixes + 1 end alias_method_chain :count, :fixes end MyModel.count # calls new method
  • 53.
    Patching Rails’ Dependencies classDependencies def require_or_load(file_name) # load the file from the normal # places, i.e. $LOAD_PATH end end
  • 54.
    New plugin-loading behaviour moduleLoadingFromPlugins # 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
  • 55.
    Injecting the newbehaviour 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)