Note that the original presentation relied heavily on animations, which are omitted on Speaker Deck. Be sure to look up the video on http://confreaks.tv/ once they post it!
object. If you've stubbed methods for a test, you've used singleton methods. class WebSpider def get(address, path) Net::HTTP.get_response(address, path).body end end spider = WebSpider.new puts spider.get("google.com", "/search") In your test: Slow, fragile method
you've stubbed methods for a test, you've used singleton methods. class WebSpider def get(address, path) Net::HTTP.get_response(address, path).body end end spider = WebSpider.new def spider.get(address, path) "<html>...</html>" end puts spider.get("google.com", "/search") In your test: So define a faster singleton method instead! Singleton methods
Ruby creates a "singleton class" that is specific to that object. class MyClass end ! instance = MyClass.new MyClass singleton Singleton class New instance
#<Class:#<MyClass:0x007f84e1830ac8>> Access the singleton class of "instance" Singleton methods You can access any object's singleton class via the singleton_class method. MyClass singleton
#<Class:#<MyClass:0x007f84e1830ac8>> Singleton methods Why does Ruby do this? Consistency. The same logic that lets you call methods on an object's class also lets you call methods on its singleton class. MyClass singleton
they're added to its singleton class. The singleton class makes the methods available exclusively to that object. class MyClass end ! instance = MyClass.new ! def instance.my_method puts "Singleton method" end MyClass singleton Define a singleton method my_method Lives on singleton class
Ruby dispatches a message to that object, looking for that particular method. class MyClass end ! instance = MyClass.new ! def instance.my_method puts "Singleton method" end ! instance.my_method my_method Calling an instance method
with a matching name, it invokes it. class MyClass end ! instance = MyClass.new ! def instance.my_method puts "Singleton method" end ! instance.my_method MyClass singleton my_method Singleton method Output
looks on the singleton class first, but there's no method there! Where can the method be found? MyClass singleton my_method class MyClass def my_method puts "Instance method" end end ! instance = MyClass.new class MyClass def my_method puts "Instance method" end end ! instance = MyClass.new ! instance.my_method
instance = MyClass.new ! p instance.singleton_class.ancestors ancestors is your cheat sheet You can access this list of classes via the ancestors class method. [#<Class:#<MyClass:0x007fb9b0984ba0>>, MyClass, Object, Kernel, BasicObject] Call the ancestors method on the singleton class.
the singleton class, it's directed to the next class on the chain: MyClass. MyClass singleton my_method class MyClass def my_method puts "Instance method" end end ! instance = MyClass.new ! instance.my_method [#<Class:#<MyClass:0x007fb9b0984ba0>>, MyClass, Object, Kernel, BasicObject] Look here next!
the singleton class, it's directed to the next class in the chain: MyClass. MyClass singleton my_method class MyClass def my_method puts "Instance method" end end ! instance = MyClass.new ! instance.my_method Instance method
superclass of MyClass. MyClass singleton my_method class MySuperclass def my_method puts "Instance method" end end ! class MyClass < MySuperclass end ! instance = MyClass.new ! MySuperclass Method defined in this class... ...which this class is a subclass of. Create an instance of the subclass.
class MyClass < MySuperclass end ! instance = MyClass.new ! p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007faa719e51b0>>, MyClass, MySuperclass, Object, Kernel, BasicObject] Singleton class Instance's class Superclass If we call ancestors on the singleton class again, we can see all the places Ruby will look for my_method.
more than one place in the lookup chain? MyClass singleton my_method class MySuperclass def my_method puts "MySuperclass" end end ! class MyClass < MySuperclass def my_method puts "MyClass" end end ! instance = MyClass.new ! MySuperclass Method defined in this class... ...and its subclass my_method
as a superclass method, it "overrides" the superclass method. MyClass singleton my_method class MySuperclass def my_method puts "MySuperclass" end end ! class MyClass < MySuperclass def my_method puts "MyClass" end end ! instance = MyClass.new ! MySuperclass Overrides the method from the superclass my_method
name matching the one it's looking for, and stops. MyClass singleton my_method class MySuperclass def my_method puts "MySuperclass" end end ! class MyClass < MySuperclass def my_method puts "MyClass" end end ! instance = MyClass.new ! instance.my_method MySuperclass my_method MyClass Doesn't get invoked!
Ruby to resume its search, on the next class in the chain. MyClass singleton my_method class MySuperclass def my_method puts "MySuperclass" end end ! class MyClass < MySuperclass def my_method puts "MyClass" super end end ! instance = MyClass.new ! instance.my_method MySuperclass my_method MyClass Just use the "super" keyword! MySuperclass
named method_missing, and the search starts over. class MyClass end ! instance = MyClass.new ! instance.nonexistent(1, 2) singleton MyClass Kernel MySuperclass Object BasicObject method_missing
raises an exception. (That's probably what you're used to seeing.) class MyClass end ! instance = MyClass.new ! instance.nonexistent(1, 2) undefined method `nonexistent' for #<MyClass: 0x007f975a135698> (NoMethodError) singleton MyClass Kernel MySuperclass Object BasicObject method_missing
classes. method_missing class MySuperclass def method_missing(name, arg1, arg2) puts "method_missing" p name, arg1, arg2 end end ! class MyClass < MySuperclass end ! instance = MyClass.new singleton MyClass MySuperclass Object etc... Override method_missing to print the name of the called method, plus 2 arguments.
you call... method_missing class MySuperclass def method_missing(name, arg1, arg2) puts "method_missing" p name, arg1, arg2 end end ! class MyClass < MySuperclass end ! instance = MyClass.new ! instance.nonexistent(1, 2) singleton MyClass MySuperclass Object etc...
class itself, without needing to create an instance of it first. Class methods class MyClass ! def self.class_method puts "class_method" end ! end ! MyClass.class_method class_method def self.method_name instead of just def method_name
class MyClass ! def MyClass.class_method puts "class_method" end ! end ! MyClass.class_method class_method You can use the class constant instead of self...
class MyClass end ! def MyClass.class_method puts "class_method" end ! MyClass.class_method class_method Or you can define it outside of the class altogether...
end ! MyClass.class_method class_method ! instance = Object.new ! def instance.my_method puts "singleton method" end ! instance.my_method In Ruby, a class method is just a singleton method on the class! singleton method
end ! MyClass.class_method class_method When you call a class method, Ruby finds it on the singleton class, and invokes it. Class singleton class_method
! end ! MyClass.class_method Within the body of a class, Ruby sets self to point to that class. Equivalent to def MyClass.class_method A singleton method on the class!
and call them, without surrounding them in a class or module. Methods defined in main def my_method puts "my_method" end ! my_method Not within any class my_method
any instance method, of any class. Methods defined in main def my_method puts "my_method" end ! class MyClass def call_my_method my_method end end Can be called from anywhere! MyClass my_method Object Not within any class
any instance method, of any class. Methods defined in main def my_method puts "my_method" end ! class MyClass def call_my_method my_method end end ! instance = MyClass.new instance.call_my_method my_method Can be called from anywhere! MyClass singleton my_method Object Not within any class
class at the top level! Methods defined in main def my_method puts "my_method" end ! puts self puts self.class main Object At the top level, self is set to an instance of Object!
my_method my_method self is the implicit receiver of this method call, and it's an instance of Object! Ruby just sets self to an instance of the Object class at the top level!
a superclass, by "mixing" the module into a class. MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule end my_method MyModule Mix in MyModule
of the class. MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule end ! p MyClass.ancestors my_method MyModule [MyClass, MyModule, Object, Kernel, BasicObject] Class Module
it was a class! MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule end ! p MyClass.ancestors my_method MyModule [MyClass, MyModule, Object, Kernel, BasicObject] Class Module
a mixin as with a superclass. MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule end ! instance = MyClass.new ! instance.my_method my_method MyModule singleton Create an instance Call the method MyModule
classes, all the same rules apply. Like overriding... MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule def my_method puts "MyClass" end end ! instance = MyClass.new ! instance.my_method my_method MyModule singleton Override the mixin method MyClass my_method Doesn't get invoked!
classes, all the same rules apply. Like overriding... and super. MyClass module MyModule def my_method puts "MyModule" end end ! class MyClass include MyModule def my_method puts "MyClass" super end end ! instance = MyClass.new ! instance.my_method my_method MyModule singleton Call the overridden module method MyClass my_method MyModule
a module with a method from a class. But not vice-versa. Not if you use include. MyClass module MyModule def my_method puts "Good thing I'm here!" end end ! class MyClass include MyModule def my_method fail "Not implemented" end end ! instance = MyClass.new ! instance.my_method my_method MyModule singleton ...But include adds module after class in lookup chain in `my_method': Not implemented (RuntimeError) my_method Method from class overrides method from module! We want this to override method from class...
the module appears after the class in the lookup chain. module MyModule def my_method puts "Good thing I'm here!" end end ! class MyClass include MyModule def my_method fail "Not implemented" end end ! p MyClass.ancestors [MyClass, MyModule, Object, Kernel, BasicObject] Class Module Call "ancestors"
in the module instead, it will be added to the lookup chain before the class. module MyModule def my_method puts "Good thing I'm here!" end end ! class MyClass prepend MyModule def my_method fail "Not implemented" end end ! p MyClass.ancestors [MyModule, MyClass, Object, Kernel, BasicObject] Module Class Call "ancestors" prepend adds module before class in lookup chain
class method. MyClass module MyModule def my_method puts "Good thing I'm here!" end end ! class MyClass prepend MyModule def my_method fail "Not implemented" end end ! instance = MyClass.new ! instance.my_method singleton prepend adds module before class in lookup chain Good thing I'm here! my_method Method from module overrides method from class! my_method MyModule
class MovieTitle def title puts "the matrix".capitalize end end ! MovieTitle.new.title The matrix But capitalize capitalizes only the first word You have a bunch of strings you want in Title Case
method. (You "monkey-patch" it.) Refinements class String def capitalize words = split(" ") words.map! do |w| w[0].upcase + w[1..-1] end words.join(" ") end end Change String#capitalize to convert strings to Title Case
do |w| w[0].upcase + w[1..-1] end words.join(" ") end end class MovieTitle def title puts "the matrix".capitalize end end ! MovieTitle.new.title The Matrix This works great for your MovieTitle class! class Sentence def sentence puts "That's wrong.".capitalize end end ! Sentence.new.sentence That's Wrong. ...But not so great for your Sentence class.
|w| w[0].upcase + w[1..-1] end words.join(" ") end end Just take your monkey patch from the String class... Situations like this are why refinements were created.
") words.map! do |w| w[0].upcase + w[1..-1] end words.join(" ") end end end ...And convert it to a refinement! (Sorry, no time to cover refinements syntax here. Just Google "ruby refinements".)
") words.map! do |w| w[0].upcase + w[1..-1] end words.join(" ") end end end A refinement is a module that prepends an existing class, but only within lexical scopes (files, classes or modules) that explicitly activate it.
") words.map! do |w| w[0].upcase + w[1..-1] end words.join(" ") end end end class MovieTitle using MyCapitalize def title puts "the matrix".capitalize end end ! MovieTitle.new.title The Matrix String#capitalize is overridden, but only within MovieTitle. class Sentence def sentence puts "That's better.".capitalize end end ! Sentence.new.sentence That's better. Now that your refinement is defined... You can activate it within MovieTitle. Outside of MovieTitle, we still get the original capitalize!
do def capitalize words = split(" ") words.map! do |w| w[0].upcase + w[1..-1] end words.join(" ") end end end ! class MovieTitle using MyCapitalize end ! class Sentence end capitalize The matrix String capitalize MovieTitle Sentence Now, the version of capitalize you get depends on your starting point! A scope without refinements ...A scope where the refinement has been activated
include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end ! ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...]
< MySuperclass include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end using MyRefinement ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...]
< MySuperclass include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end using MyRefinement ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...] my_method my_method my_method my_method my_method my_method
< MySuperclass include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end using MyRefinement ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...] my_method instance.my_method
< MySuperclass include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end using MyRefinement ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...] my_method my_method instance.my_method
< MySuperclass include MyModule prepend PrependedModule end ! module MyRefinement refine MyClass do end end using MyRefinement ! instance = MyClass.new p instance.singleton_class.ancestors [#<Class:#<MyClass:0x007fabec1049e8>>, PrependedModule, MyClass, MyModule, MySuperclass, ...] my_method my_method instance.my_method What questions do you have?
Rubyist (2nd ed.) David A. Black Manning, 2014 Ruby Under a Microscope Pat Shaughnessy No Starch Press, 2014 RubyTapas - Refinements http://rubytapas.com/episodes/250-Refinements Ruby internals. Hardcore. This episode is free!