Unleash the Secrets of the Standard Library

09477c358c5897d44121a248326e16d7?s=47 Jim Gay
November 09, 2013

Unleash the Secrets of the Standard Library

With Ruby you have simple tools at your fingertips that make building powerful tools easy. Ruby's standard library comes with some helpful libraries like 'delegate' and 'forwardable' that will help make your code easier to maintain.

We'll dive into how SimpleDelegator is different from method_missing and how Forwardable differs still. Learn the basis of the ideas behind these and combine the features to build your own powerful patterns. See how these compare to libraries like ActiveSupport::Delegation and look at how popular presenter libraries like Draper and DisplayCase work.

The understanding of these tools and ideas are used in http://clean-ruby.com

09477c358c5897d44121a248326e16d7?s=128

Jim Gay

November 09, 2013
Tweet

Transcript

  1. Unleash the Secrets of the Standard Library with SimpleDelegator, Forwardable,

    and more Monday, November 11, 13
  2. Jim Gay @saturnflyer Monday, November 11, 13

  3. standard library vs. standard library Monday, November 11, 13

  4. Monday, November 11, 13

  5. Monday, November 11, 13

  6. require ‘delegate’ Monday, November 11, 13

  7. user Monday, November 11, 13

  8. born user Monday, November 11, 13

  9. born user #<Date: 1989-09-10 ((2447780j,0s,0n) Monday, November 11, 13

  10. born user 1989 Monday, November 11, 13

  11. user 1989 decorator born Monday, November 11, 13

  12. born user 1989 decorator born #<Date> Monday, November 11, 13

  13. born user decorator born year #<Date> 1989 Monday, November 11,

    13
  14. Delegator SimpleDelegator DelegateClass() Monday, November 11, 13

  15. class D;def initialize(o);@o=o;end;def method_missing(m, *r, &b);@o.send(m,*r,&b);end;end Monday, November 11, 13

  16. class D def initialize(o) @o = o end def method_missing(m,

    *r, &b) @o(m, *r, &b) end end Monday, November 11, 13
  17. class Delegator def initialize(object) @object = object end def method_missing(method,

    *args, &block) @object.send(method, *args, &block) end end Monday, November 11, 13
  18. class Delegator def initialize(object) @object = object end def method_missing(method,

    *args, &block) @object.send(method, *args, &block) end end Monday, November 11, 13
  19. class Delegator def initialize(object) @object = object end def method_missing(method,

    *args, &block) @object.send(method, *args, &block) end end Monday, November 11, 13
  20. class User def born Date.new(1989, 9, 10) end end user

    = User.new Monday, November 11, 13
  21. class User def born Date.new(1989, 9, 10) end def birth_year

    born.year end end user = User.new user.birth_year #=> 1989 Monday, November 11, 13
  22. class User def born Date.new(1989, 9, 10) end end class

    UserDecorator < Delegator def born super.year end end user = User.new decorated_user = UserDecorator.new(user) decorated_user.born #=> 1989 Monday, November 11, 13
  23. user = User.new decorated_user = UserDecorator.new(user) decorated_user.born == user.born.year #=>

    true Monday, November 11, 13
  24. user = User.new decorated_user = UserDecorator.new(user) decorated_user.born == user.born.year #=>

    true decorated_user == user #=> ???? Monday, November 11, 13
  25. user = User.new decorated_user = UserDecorator.new(user) decorated_user.born == user.born.year #=>

    true decorated_user == user #=> false Monday, November 11, 13
  26. class UserDecorator < Delegator def born super.year end def ==(other)

    @object == other end end decorated_user == user #=> true Monday, November 11, 13
  27. class UserDecorator < Delegator def born super.year end def ==(other)

    @object == other end end decorated_user == user #=> true decorated_user == decorated_user #=> ???? Monday, November 11, 13
  28. class UserDecorator < Delegator def born super.year end def ==(other)

    @object == other end end decorated_user == user #=> true decorated_user == decorated_user #=> false Monday, November 11, 13
  29. class UserDecorator < Delegator def born super.year end def ==(other)

    return true if other.equal?(self) @object == other end end decorated_user == user #=> true decorated_user == decorated_user #=> true Monday, November 11, 13
  30. require 'delegate' class UserDecorator < SimpleDelegator def born super.year end

    end decorated_user == user #=> true decorated_user == decorated_user #=> true Monday, November 11, 13
  31. class SimpleDelegator < Delegator end class Delegator < BasicObject def

    initialize(obj) __setobj__(obj) end def method_missing(m, *args, &block) self.__getobj__.send(m, *args, &block) end def __getobj__ raise NotImplementedError, "need to define `__getobj__'" end def __setobj__(obj) raise NotImplementedError, "need to define `__setobj__'" end end Monday, November 11, 13
  32. class SimpleDelegator < Delegator def __getobj__ @delegate_sd_obj end def __setobj__(obj)

    raise ArgumentError, "cannot delegate to self" if self.equal?(obj) @delegate_sd_obj = obj end end Monday, November 11, 13
  33. class SimpleDelegator < Delegator def __getobj__ @delegate_sd_obj end def __setobj__(obj)

    raise ArgumentError, "cannot delegate to self" if self.equal?(obj) @delegate_sd_obj = obj end end Monday, November 11, 13
  34. require 'delegate' class UserDec < Delegator def __setobj__(obj) @obj =

    obj end def __getobj__ @obj end end require 'delegate' class UserDec < SimpleDelegator end Monday, November 11, 13
  35. require 'delegate' class UserDec < Delegator def __setobj__(obj) @obj =

    obj end def __getobj__ @obj end end require 'delegate' class UserDec < SimpleDelegator end SystemStackError: stack level too deep Monday, November 11, 13
  36. require 'delegate' class UserDec < Delegator def __setobj__(obj) @obj =

    obj end def __getobj__ @obj end end require 'delegate' class UserDec < SimpleDelegator end problem solved Monday, November 11, 13
  37. UserDecorator #=> __getobj__, __setobj__ ProfileDecorator #=> __getobj__, __setobj__ ShoppingCartDecorator #=>

    __getobj__, __setobj__ Monday, November 11, 13
  38. UserDecorator #=> user, user= ProfileDecorator #=> profile, profile= ShoppingCartDecorator #=>

    shopping_cart, shopping_cart= Monday, November 11, 13
  39. class UserDecorator < Decorator alias_method :user, :__getobj__ alias_method :user=, :__setobj__

    end Monday, November 11, 13
  40. class UserDecorator < Decorator alias_method :user, :__getobj__ alias_method :user=, :__setobj__

    end class ProfileDecorator < Decorator alias_method :profile, :__getobj__ alias_method :profile=, :__setobj__ end Monday, November 11, 13
  41. class UserDecorator < Decorator alias_method :user, :__getobj__ alias_method :user=, :__setobj__

    end class ProfileDecorator < Decorator alias_method :profile, :__getobj__ alias_method :profile=, :__setobj__ end class ShoppingCartDecorator < Decorator alias_method :shopping_cart, :__getobj__ alias_method :shopping_cart=, :__setobj__ end Monday, November 11, 13
  42. class Decorator < SimpleDelegator def self.inherited(klass) # get the part

    of the class name # we want like "ShoppingCart" base_name = klass.name.split('::').last.sub('Decorator','') # downcase and underscore # (without activesupport) like "shopping_cart" object_name = base_name.gsub(/([a-z][A-Z])/){ $1.scan(/./).join('_') }.downcase klass.send(:alias_method, object_name, :__getobj__) klass.send(:alias_method, "#{object_name}=", :__setobj__) end end Monday, November 11, 13
  43. class Decorator < SimpleDelegator def self.inherited(klass) # get the part

    of the class name # we want like "ShoppingCart" base_name = klass.name.split('::').last.sub('Decorator','') # downcase and underscore # (without activesupport) like "shopping_cart" object_name = base_name.gsub(/([a-z][A-Z])/){ $1.scan(/./).join('_') }.downcase klass.send(:alias_method, object_name, :__getobj__) klass.send(:alias_method, "#{object_name}=", :__setobj__) end end Monday, November 11, 13
  44. class Decorator < SimpleDelegator def self.inherited(klass) # get the part

    of the class name # we want like "ShoppingCart" base_name = klass.name.split('::').last.sub('Decorator','') # downcase and underscore # (without activesupport) like "shopping_cart" object_name = base_name.gsub(/([a-z][A-Z])/){ $1.scan(/./).join('_') }.downcase klass.send(:alias_method, object_name, :__getobj__) klass.send(:alias_method, "#{object_name}=", :__setobj__) end end Monday, November 11, 13
  45. class Decorator < SimpleDelegator def self.inherited(klass) # get the part

    of the class name # we want like "ShoppingCart" base_name = klass.name.split('::').last.sub('Decorator','') # downcase and underscore # (without activesupport) like "shopping_cart" object_name = base_name.gsub(/([a-z][A-Z])/){ $1.scan(/./).join('_') }.downcase klass.send(:alias_method, object_name, :__getobj__) klass.send(:alias_method, "#{object_name}=", :__setobj__) end end Monday, November 11, 13
  46. class Decorator < SimpleDelegator def self.inherited(klass) # get the part

    of the class name # we want like "ShoppingCart" base_name = klass.name.split('::').last.sub('Decorator','') # downcase and underscore # (without activesupport) like "shopping_cart" object_name = base_name.gsub(/([a-z][A-Z])/){ $1.scan(/./).join('_') }.downcase klass.send(:alias_method, object_name, :__getobj__) klass.send(:alias_method, "#{object_name}=", :__setobj__) end end Monday, November 11, 13
  47. UserDecorator #=> user, user= ProfileDecorator #=> profile, profile= ShoppingCartDecorator #=>

    shopping_cart, shopping_cart= Monday, November 11, 13
  48. class Delegator < BasicObject kernel = ::Kernel.dup kernel.class_eval do [:to_s,:inspect,:=~,:!~,:===,:<=>,:eql?,:hash].each

    do |m| undef_method m end end include kernel #... end Monday, November 11, 13
  49. class Delegator < BasicObject kernel = ::Kernel.dup kernel.class_eval do [:to_s,:inspect,:=~,:!~,:===,:<=>,:eql?,:hash].each

    do |m| undef_method m end end include kernel #... end BasicObject.ancestors #=> [BasicObject] Object.ancestors # => [Object, Kernel, BasicObject] Monday, November 11, 13
  50. user.inspect #=> "#<User:0x007f98d11ba3c0>" user.to_s #=> "#<User:0x007f98d11ba3c0>" Monday, November 11, 13

  51. user.inspect #=> "#<User:0x007f98d11ba3c0>" user.to_s #=> "#<User:0x007f98d11ba3c0>" decorated_user.inspect #=> "#<User:0x007f98d11ba3c0>" decorated_user.to_s

    #=> "#<User:0x007f98d11ba3c0>" Monday, November 11, 13
  52. user.inspect #=> "#<User:0x007f98d11ba3c0>" user.to_s #=> "#<User:0x007f98d11ba3c0>" decorated_user.inspect #=> "#<User:0x007f98d11ba3c0>" decorated_user.to_s

    #=> "#<User:0x007f98d11ba3c0>" user.class #=> User decorated_user.class #=> UserDecorator Monday, November 11, 13
  53. decorated_user.class #=> UserDecorator <%= form_for(decorated_user) do |f| %> <form action="/user_decorators/1">

    Monday, November 11, 13
  54. # https://github.com/objects-on-rails/display-case module DisplayCase class Exhibit < SimpleDelegator alias_method :__class__,

    :class def class __getobj__.class end end end Monday, November 11, 13
  55. # https://github.com/drapergem/draper module Draper class Decorator def self.object_class @object_class ||=

    inferred_object_class end def self.inferred_object_class name = object_class_name name.constantize rescue NameError => error raise if name && !error.missing_name?(name) raise Draper::UninferrableSourceError.new(self) end # ActiveModel compatibility singleton_class.delegate :model_name, to: :object_class end end Monday, November 11, 13
  56. decorated_user.class #=> UserDecorator <%= form_for(decorated_user) do |f| %> <form action="/user_decorators/1">

    Monday, November 11, 13
  57. decorated_user.class #=> UserDecorator <%= form_for(decorated_user) do |f| %> <form action="/users/1">

    Monday, November 11, 13
  58. def DelegateClass(superclass) klass = Class.new(Delegator) methods = superclass.instance_methods methods -=

    ::Delegator.public_api methods -= [:to_s,:inspect,:=~,:!~,:===] klass.module_eval do def __getobj__ # :nodoc: @delegate_dc_obj end def __setobj__(obj) # :nodoc: raise ArgumentError, "cannot delegate to self" if self.equal?(o @delegate_dc_obj = obj end methods.each do |method| define_method(method, Delegator.delegating_block(method)) end end klass.define_singleton_method :public_instance_methods do |all=true super(all) - superclass.protected_instance_methods end klass.define_singleton_method :protected_instance_methods do |all=t super(all) | superclass.protected_instance_methods end return klass end Monday, November 11, 13
  59. def DelegateClass(superclass) klass = Class.new(Delegator) # lots of code... return

    klass end Monday, November 11, 13
  60. class UserDecorator < Decorator end UserDecorator.ancestors #=> [UserDecorator, Decorator, SimpleDelegator,

    Delegator, #<Module:0x007faba2142738>, BasicObject] class Decorator < SimpleDelegator end Monday, November 11, 13
  61. class UserDecorator < DelegateClass(User) alias_method :user, :__getobj__ alias_method :user=, :__setobj__

    end UserDecorator.ancestors #=> [UserDecorator, #<Class:0x007faba20a38b8>, Delegator, #<Module:0x007faba2142738>, BasicObject] Monday, November 11, 13
  62. UserDecorator = Class.new(DelegateClass(user.class)) do base_name = user.class.name.split('::').last object_name = base_name.gsub(/([a-z][A-Z])/){

    $1.scan(/./).join('_') }.downcase self.send(:alias_method, object_name, :__getobj__) self.send(:alias_method, "#{object_name}=", :__setobj__) end UserDecorator.ancestors #=> [UserDecorator, #<Class:0x007faba20a38b8>, Delegator, #<Module:0x007faba2142738>, BasicObject] Monday, November 11, 13
  63. gem install 'decoratificationizer' def DecoratificationClass(obj) Class.new(DelegateClass(obj.class)) do base_name = obj.class.name.split('::').last

    object_name = base_name.gsub(/([a-z][A-Z])/){ $1.scan(/./).join('_') }.downcase self.send(:alias_method, object_name, :__getobj__) self.send(:alias_method, "#{object_name}=", :__setobj__) end end klass = DecoratificationClass(user) Monday, November 11, 13
  64. require ‘forwardable’ Monday, November 11, 13

  65. user addr. address Monday, November 11, 13

  66. user addr. address city Miami Monday, November 11, 13

  67. user city Miami Monday, November 11, 13

  68. user city address city Miami Monday, November 11, 13

  69. Forwardable SingleForwardable Monday, November 11, 13

  70. module Fwd;def fwd(a,m,n=m);self.class_eval"def #{n} (*r,&b);#{a}.__send__(:#{m},*r,&b);end";end;end Monday, November 11, 13

  71. module Fwd def fwd(a,m,n=m) self.class_eval " def #{n}(*r,&b) #{a}.__send__(:#{m},*r,&b) end

    " end end Monday, November 11, 13
  72. module Forward def forward(to, method_name, alternative=method_name) self.class_eval " def #{alternative}(*args,

    &block) #{to}.__send__(:#{method_name},*args, &block) end " end end Monday, November 11, 13
  73. class Address attr_accessor :number, :street, :city, :province, :postal_code end class

    Person attr_accessor :name, :address end home = Address.new.tap{|address| address.number = "123" address.street = "Main Street" address.city = "Miami" address.province = "FL" address.postal_code = "12345" } jim = Person.new jim.name = "Jim" jim.address = home Monday, November 11, 13
  74. # Which city? jim.address.city #=> "Miami" class Person def city

    address.city end end jim.city #=> "Miami" Monday, November 11, 13
  75. # Which city? jim.address.city #=> "Miami" class Person extend Forward

    forward :address, :city end jim.city #=> "Miami" Monday, November 11, 13
  76. # Which city? jim.address.city #=> "Miami" class Person extend Forward

    forward :address, :city, :home_city end jim.city #=> NoMethodError jim.home_city #=> "Miami" Monday, November 11, 13
  77. # Which city? jim.address.city #=> "Miami" class Person extend Forward

    forward :address, :city, :home_city forward :address, :number, :home_number forward :address, :postal_code, :home_postal_code end Monday, November 11, 13
  78. require 'forwardable' class Person extend Forwardable def_delegator :address, :city, :home_city

    def_delegator :address, :number, :home_number def_delegator :address, :postal_code, :home_postal_code end Monday, November 11, 13
  79. require 'forwardable' class Person extend Forwardable def_delegator :address, :city def_delegator

    :address, :number def_delegator :address, :postal_code end Monday, November 11, 13
  80. require 'forwardable' class Person extend Forwardable def_delegators :address, :city, :number,

    :postal_code end Monday, November 11, 13
  81. require 'forwardable' class Person extend Forwardable delegate [:city, :number, :postal_code]

    => :address end Monday, November 11, 13
  82. require 'forwardable' class Person extend Forwardable delegate [:city, :number, :postal_code]

    => :@address end Monday, November 11, 13
  83. # With ActiveSupport class Person delegate :city, :number, :postal_code, :to

    => :address, :allow_nil => true, :prefix => true end person.address_city #=> "Miami" Monday, November 11, 13
  84. # With ActiveSupport class Person delegate :city, :number, :postal_code, :to

    => :address, :allow_nil => true, :prefix => 'home_' end person.home_city #=> "Miami" Monday, November 11, 13
  85. def oops! fail rescue IveMadeAHugeMistake => e Airbrake.notify(e) end Monday,

    November 11, 13
  86. def oops! fail rescue IveMadeAHugeMistake => e Catcher.save_for_later(e) # Build

    your own Facade end Monday, November 11, 13
  87. require 'forwardable' module Catcher extend SingleForwardable def_delegator :AppSignal, :send_exception, :save_for_later

    # def_delegator :Airbrake, :notify, :save_for_later end Catcher.save_for_later(exception) Monday, November 11, 13
  88. object decorator SimpleDelegator object related Forwardable run time behavior load

    time behavior Monday, November 11, 13
  89. Extra credit at github.com... saturnflyer/casting myobie/rep stevenharman/dumb_delegator elight/modest_presenter Monday, November

    11, 13
  90. clean-ruby.com RUBYCONF 20% off @saturnflyer Thanks! Monday, November 11, 13