Skip to content

Latest commit

 

History

History
172 lines (117 loc) · 4.4 KB

03_methods.md

File metadata and controls

172 lines (117 loc) · 4.4 KB

Chapter 3 - Tuesday: Methods

Dynamic Methods

Ruby allows you to call and define methods at runtime, which removes repetitive code such as defining getters and setters for numerous attributes.

Calling Methods Dynamically

Normally you do this:

class MyClass
  def my_method(arg)
    arg * 2
  end
end

obj = MyClass.new
obj.my_method(3) # => 6

Alternatively, you can call MyClass#my_method using Object#send:

obj.send(:my_method, 3) # => 6

This is referred to as Dynamic Dispath, where you wait until the last moment to decide which method to call.

Why use symbol (rather than string) for method name? Symbols are immutable, which make them good for naming things.

You can call any method with send, including private methods! If you want to respect receiver's privacy, you can use #public_send. In the wild, most people don't, however.

Defining Methods Dynamically

You can define a method on the spot with Module#define_method. This is referred to as a Dynamic Method.

class MyClass
  define_method :my_method do |arg|
    arg * 3
  end
end

obj = MyClass.new
obj.my_method(3) # => 6

An example using Dynamic Methods along with regular expression to remove duplicate code.

class Computer
  def initilize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
    data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $1 }
  end

  def self.define_component(name)
    define_method(name) do
      # ...
    end
  end
end

method_missing

Method lookup starts with object's class and if it can't find it, it searches up the ancestor chain all the way up to Object and eventually into BasicObject.

class Lawyer; end
nick = Lawyer.new
nick.talk_simple # => NoMethodError: undefined method 'talk_simple'

If it cannot find a method, it calls #method_missing. It is a private instance method of BasicObject, which every object inherits. We can dynamically call it with send.

nick.send(:method_missing, :talk_simple) # => NoMethodError: undefined method 'talk_simple'

Overriding method_missing

Overriding method_missing allows you to call methods that don't exist (Ghost Methods). It's like saying "if you don't understand a message, do this."

class Lawyer
  def method_missing(method, *args)
    puts "You called: #{member}(#{args.join(', ')})"
    puts "You also passed it a block" if block_given?
  end
end

nick = Lawyer.new
nick.talk_simple(a, b) do
  # a block
end
You called: talk_simple(a,b)
You also passed it a block

The Hashie Example

TODO

Dynamic Proxies

Dynamic proxy refers to when you collect methods calls through method_missing and forward them to a wrapped object.

class Computer
  def initialize(computer_id, data_source)
    @id = computer_id
    @data_source = data_source
  end

  def method_missing
    super if !@data_source.respond_to?("get_#{name}_info")

    info = @data_source.send("get_#{name}_info", @id)
    price = @data_source.send("get_#{name}_price", @id)
    result = "#{name.capitalize}: #{info} ($#{price})"
    return "* #{result}" if price >= 100
    result
  end
end

What happens if you call Computer#mouse? Call gets routed to method_missing, which checks if wrapped data source has a get_mouse_info method. If it doesn't, call falls back to BasicObject#method_missing.

If the wrapped data source knows about it, the call gets forwarded.

TODO Add note about respond_to? not recognizing ghost methods.

Blank Slates

A blank slate refers to a "skinny" class with a minimual number of methods that hopefully won't collide with any ghost methods we forward using a dynamic proxy.

BasicObject

BasicObject only has a handful of methods, so is suitable to use as a blank slate.

im  = BasicObject.instance_methods
im # => [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]

By default, Ruby objects inherit from Object (which is itself a subclass of BasicObject). Inheriting from BasicObject is the quicker way to define a Blank Slate in Ruby.

class Computer < BasicObject
  # ...

Removing Methods

TODO

Dynamic Methods vs Ghost Methods

Use Dynamic Methods if you can and Ghost Methods if you have to. Ghost methods are more dangerous and can lead to very tricky bugs.

If you must use Ghost Methods, always make sure to call super.