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

Unleash the Secrets of the Standard Library

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

Jim Gay

November 09, 2013
Tweet

More Decks by Jim Gay

Other Decks in Technology

Transcript

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

    *r, &b) @o(m, *r, &b) end end Monday, November 11, 13
  2. 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
  3. 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
  4. 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
  5. class User def born Date.new(1989, 9, 10) end end user

    = User.new Monday, November 11, 13
  6. 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
  7. 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
  8. class UserDecorator < Delegator def born super.year end def ==(other)

    @object == other end end decorated_user == user #=> true Monday, November 11, 13
  9. 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
  10. 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
  11. 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
  12. require 'delegate' class UserDecorator < SimpleDelegator def born super.year end

    end decorated_user == user #=> true decorated_user == decorated_user #=> true Monday, November 11, 13
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. # 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
  29. 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
  30. class UserDecorator < Decorator end UserDecorator.ancestors #=> [UserDecorator, Decorator, SimpleDelegator,

    Delegator, #<Module:0x007faba2142738>, BasicObject] class Decorator < SimpleDelegator end Monday, November 11, 13
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. # Which city? jim.address.city #=> "Miami" class Person def city

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

    forward :address, :city end jim.city #=> "Miami" Monday, November 11, 13
  38. # 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
  39. # 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
  40. 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
  41. require 'forwardable' class Person extend Forwardable def_delegator :address, :city def_delegator

    :address, :number def_delegator :address, :postal_code end Monday, November 11, 13
  42. # 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
  43. # 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
  44. 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