Slide 1

Slide 1 text

Unleash the Secrets of the Standard Library with SimpleDelegator, Forwardable, and more Monday, November 11, 13

Slide 2

Slide 2 text

Jim Gay @saturnflyer Monday, November 11, 13

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Monday, November 11, 13

Slide 5

Slide 5 text

Monday, November 11, 13

Slide 6

Slide 6 text

require ‘delegate’ Monday, November 11, 13

Slide 7

Slide 7 text

user Monday, November 11, 13

Slide 8

Slide 8 text

born user Monday, November 11, 13

Slide 9

Slide 9 text

born user #

Slide 10

Slide 10 text

born user 1989 Monday, November 11, 13

Slide 11

Slide 11 text

user 1989 decorator born Monday, November 11, 13

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

born user decorator born year # 1989 Monday, November 11, 13

Slide 14

Slide 14 text

Delegator SimpleDelegator DelegateClass() Monday, November 11, 13

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

require 'delegate' class UserDecorator < SimpleDelegator def born super.year end end decorated_user == user #=> true decorated_user == decorated_user #=> true Monday, November 11, 13

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

UserDecorator #=> __getobj__, __setobj__ ProfileDecorator #=> __getobj__, __setobj__ ShoppingCartDecorator #=> __getobj__, __setobj__ Monday, November 11, 13

Slide 38

Slide 38 text

UserDecorator #=> user, user= ProfileDecorator #=> profile, profile= ShoppingCartDecorator #=> shopping_cart, shopping_cart= Monday, November 11, 13

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

UserDecorator #=> user, user= ProfileDecorator #=> profile, profile= ShoppingCartDecorator #=> shopping_cart, shopping_cart= Monday, November 11, 13

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

user.inspect #=> "#" user.to_s #=> "#" Monday, November 11, 13

Slide 51

Slide 51 text

user.inspect #=> "#" user.to_s #=> "#" decorated_user.inspect #=> "#" decorated_user.to_s #=> "#" Monday, November 11, 13

Slide 52

Slide 52 text

user.inspect #=> "#" user.to_s #=> "#" decorated_user.inspect #=> "#" decorated_user.to_s #=> "#" user.class #=> User decorated_user.class #=> UserDecorator Monday, November 11, 13

Slide 53

Slide 53 text

decorated_user.class #=> UserDecorator <%= form_for(decorated_user) do |f| %> Monday, November 11, 13

Slide 54

Slide 54 text

# 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

Slide 55

Slide 55 text

# 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

Slide 56

Slide 56 text

decorated_user.class #=> UserDecorator <%= form_for(decorated_user) do |f| %> Monday, November 11, 13

Slide 57

Slide 57 text

decorated_user.class #=> UserDecorator <%= form_for(decorated_user) do |f| %> Monday, November 11, 13

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

def DelegateClass(superclass) klass = Class.new(Delegator) # lots of code... return klass end Monday, November 11, 13

Slide 60

Slide 60 text

class UserDecorator < Decorator end UserDecorator.ancestors #=> [UserDecorator, Decorator, SimpleDelegator, Delegator, #, BasicObject] class Decorator < SimpleDelegator end Monday, November 11, 13

Slide 61

Slide 61 text

class UserDecorator < DelegateClass(User) alias_method :user, :__getobj__ alias_method :user=, :__setobj__ end UserDecorator.ancestors #=> [UserDecorator, #, Delegator, #, BasicObject] Monday, November 11, 13

Slide 62

Slide 62 text

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, #, Delegator, #, BasicObject] Monday, November 11, 13

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

require ‘forwardable’ Monday, November 11, 13

Slide 65

Slide 65 text

user addr. address Monday, November 11, 13

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

user city Miami Monday, November 11, 13

Slide 68

Slide 68 text

user city address city Miami Monday, November 11, 13

Slide 69

Slide 69 text

Forwardable SingleForwardable Monday, November 11, 13

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

# 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

Slide 77

Slide 77 text

# 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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

# 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

Slide 84

Slide 84 text

# 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

Slide 85

Slide 85 text

def oops! fail rescue IveMadeAHugeMistake => e Airbrake.notify(e) end Monday, November 11, 13

Slide 86

Slide 86 text

def oops! fail rescue IveMadeAHugeMistake => e Catcher.save_for_later(e) # Build your own Facade end Monday, November 11, 13

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

object decorator SimpleDelegator object related Forwardable run time behavior load time behavior Monday, November 11, 13

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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