layout: true
RubyConf
2016-11-10
@CraigBuchek
class: title, middle, center
???
- Feel free to ask questions during!
class: affiliations
???
- I have a company called BoochTek
- We do:
- Web Development
- Rails rescue projects
- Agile player/coaching
- DevOps
- We do:
- I do a lot of work through Binary Noggin
- We have developers available starting in January
- I participate in a podcast called This Agile Life
- Please subscribe
- I mentor teaching underprivileged kids
- I was told there must be at least 1 cat picture
- So there's a picture of my cat
class: what_is_a_hook
- A method:
- Called implicitly by Ruby
- We never call ourselves
???
- Definition I'm using in this talk:
- A method that's called implicitly by Ruby
- And that we never call ourselves
- Because it's implicit:
- Can be surprising
- Can be difficult to troubleshoot
- "Spooky action at a distance"
- A term from physics
- In Ruby docs, you'll see them referenced as:
- "Hook"
- "Callback"
- But that has more meanings
- "Ruby calls"
- I'm covering only hooks in Ruby itself
class X
def initialize
puts "Initializing an instance of X"
end
end
x = X.new
Initializing an instance of X
???
- Who here has used
initialize
?- Pretty much everyone, probably
- Notice that we never explicitly called
initialize
- But it got called anyway
- This is by far the most commonly used hook in Ruby
x = X.new
class Class
def new(*args, &block)
object = allocate # Allocate space in memory to hold the object
object.initialize(*args, &block) if object.respond_to?(:initialize)
return object
end
end
X
is an object of classClass
- So calling
X.new
callsClass#new
- So calling
???
- So what's really happening when we call
X.new
? X
is an object of classClass
- So calling
X.new
callsClass#new
- Seems weird, but how every method call works
- Find the class of what's before the dot
- Call the instance method from that class
- So calling
- Because the call to
initialize
is hidden, it's a hook - NOTE: This code is actually implemented in C
- It also calls
initialize
via__send__
- So it will work if
initialize
is private
- So it will work if
- It also calls
- NOTE: Understanding that classes are instances of the
Class
class is tricky- If you need to, take some time to work through thinking about it
method_missing
respond_to_missing?
???
- Meta-programming is where you're most likely to use hooks
class X
def method_missing(method_name, *args, &block)
puts "Method called on an X: #{method_name}, args: #{args}"
end
end
X.new.some_method(1, "a", :b)
X.new.another_method
Method called on an X: some_method, args: [1, "a", :b]
Method called on an X: another_method, args: []
???
- How many of you have used
method_missing
?- Probably a large percentage
- We're allowing the calling of a method that we never defined
- Pretty commonly used for meta-programming
- ActiveRecord uses
method_missing
in several places - Biggest downside --- you can't search your code for the method
- The method name is passed as a
Symbol
- We use
*args
to get all the arguments as anArray
- All arguments except a block (if one is passed in)
- The
*
is called "splat"
- NOTE: The block isn't being used in this example
- I just wanted to show the full method signature
class X
def method_missing(method_name, *args, &block)
puts "Method called on an X: #{method_name}, args: #{args}" \
if respond_to_missing?(method)
end
def respond_to_missing?(method_name, include_private=false)
method_name =~ /^good_/
end
end
X.new.respond_to?(:good_method)
X.new.respond_to?(:bad_method)
true
false
???
- Called when
respond_to?
is called, and the method is not defined- Before Ruby 1.9.2, we used to just override
respond_to?
- Before Ruby 1.9.2, we used to just override
- Called when calling
method
- Example at http://stackoverflow.com/a/13793573
- Note that it takes a 2nd argument
- Whether to include private methods
- Always define
respond_to_missing?
when overridingmethod_missing
- See a nice explanation at http://blog.marc-andre.ca/2010/11/15/methodmissing-politely/
- Yes, this is on
Object
, whilemethod_missing
is onBasicObject
- Because
BasicObject
doesn't haverespond_to?
- Because
- Avoid this one
- Seems enticing, but leads to long-term problems
- Use
require
instead- Or maybe
autoload
- Or maybe
???
- Rails uses this to auto-load classes
- Remember, class names are constants
- In general, it's not worth it
- Just manually require the file containing the class/constant you need
- If you really want something like this, try registering with
autoload
insteadautoload(:MyModule, "/usr/local/lib/modules/my_module.rb")
included
extended
prepended
inherited
module A
def self.included(other_module)
puts "#{self} included in #{other_module}"
end
end
class B
include A
end
A included in B
???
- Note that it's
def self.included
- Because it's a method on the module itself
- Note that we don't need to new up a B
- The
include
(and theincluded
hook) runs as soon as we call it - TODO: Maybe in a next slide show how that works with normal code
- The
- Called when this module is included in another module or class
- Called with the class or module that is including us
- NOTE: There's a similar hook called
append_features
- It adds constants, methods, and module variables of this module to module passed in
- I know of no reason to use
append_features
overincluded
- Unless you want to do something nefarious and NOT add constants/methods/variables
module A
def self.extended(other_module)
puts "#{self} extended by #{other_module}"
end
end
module B
extend A
end
A extended by B
???
- Just like
included
- Note that it's
def self.extended
module A
def self.prepended(other_module)
puts "#{self} prepended in #{other_module}"
end
end
class B
prepend A
end
A prepended in B
???
- Ruby added
prepend
in 2.0- Basically the same as
include
but adds the module to the beginning of the inheritance chain - Used when
alias_method_chain
used to be used - See https://hashrocket.com/blog/posts/module-prepend-a-super-story for a good example
- Basically the same as
- Just like
included
- Note that it's
def self.prepended
- Because it's a method on the module itself
class A
def self.inherited(other_class)
puts "#{self} subclassed by #{other_class}"
end
end
class B < A
end
A subclassed in B
???
- Note that it's
def self.inherited
- Because it's a method on the class itself
Module#method_added
Module#method_removed
Module#method_undefined
???
- These are on
Module
- Because regular instance methods are defined in modules and classes
- You will probably find few good reasons to call
remove_method
orundef_method
- See https://ruby-doc.org/core-2.3.0/Module.html#method-i-undef_method for docs
- You can also call
undef method_name
instead ofundef_method :method_name
- Removing a method will allow any superclass methods to still be called
- Only example I can think of is if you dynamically added it
- Undefining a method will set the method to return a
NoMethodError
- Only example I can think of is if you're trying to look like an older Ruby
BasicObject#singleton_method_added
BasicObject#singleton_method_removed
BasicObject#singleton_method_undefined
???
- But you can also add methods to individual objects
- These are called singleton methods
- This is how you define a class method --- it's a singleton method on the class itself
- These are the equivalent for singleton methods
class: strict_conversion
to_str
to_int
to_ary
to_hash
to_proc
to_enum
???
- These are used to say something "is like" what it's converting to
- Not just "can be shown as"
- https://zverok.github.io/blog/2016-01-18-implicit-vs-expicit.html
- Most of these are used when trying to compare using
==
- If types are not the same, will try to convert the right side to the same type as the left
to_str
- used when concatenating strings- A UserName class might be a good place to define this
- Because you want to be able to use it anywhere you use a String
- A UserName class might be a good place to define this
to_ary
- assigning to multiple left-hand variables (or block arguments)to_int
- used when performing bit operations (|
,&
,^
)to_hash
- used to convertmethod_name(**kwargs)
to_proc
- called when trying to convert to a block using the&
operatorto_enum
returns an Enumerator, not an Enumerable
class: strict_conversion_2
-
to_str
-
to_int
-
to_ary
-
to_hash
-
to_proc
-
to_enum
-
to_s
-
to_i
-
to_a
-
to_h
-
&
???
- Here are the explicit equivalents
- You almost always want to define the explicit versions
- Think carefully before defining the implicit variants
- Important note:
to_s
is used in interpolation- So
to_s
could be considered implicit in that case- Even though it seems implicit to me
- So
- Important note:
*x
callsto_a(x)
(implicitly)- Once again, called an "explicit" conversion
- I think this changed to
to_ary
in Ruby 2.0
- Find a common type for math with mixed types
class Quaternion
def +(other)
case other
when Quaternion
Quaternion.new(self.i + other.i)
else
Quaternion.new(self.i + other)
end
end
end
q1 = Quaternion.new(1)
puts 2 + q1
coerce.rb:18:in `+': Quaternion can't be coerced into Fixnum (TypeError)
???
coerce
helps find a common type when doing math on mixed typesNumeric
, its subclasses, and similar things likeMatrix
andVector
- Binary operators
- Looks at
self
(the left-hand operand) and the other operand- Allows you to "cast" the operands into something compatible
- See http://stackoverflow.com/a/2799899
- Add
coerce
to fix the problem:
class Quaternion
def coerce(other)
puts "Quaternion#coerce called"
[Quaternion.new(other), self]
end
def to_s
"Quaternion(#{i})"
end
end
q1 = Quaternion.new(1)
puts 2 + q1
Quaternion#coerce called
Quaternion(3)
???
- Note that
coerce
returns anArray
, withself
as the 2nd element
- Coercion transforms binary operators like this:
q1 = Quaternion.new(1)
n = 2
[q2, q1] = q1.coerce(n)
q1 + q2
???
- This only happens after trying without coercion
- Sets a block or proc to run on program exit
- If
$!
is set, program is exiting due to an exception
at_exit do
if $!
puts "We crashed! #{$!.inspect}"
else
puts "fin"
end
end
???
at_exit
sets a proc to be run on program exit- If called multiple times, they're run in reverse order of registration
- Can check if
$!
is set to see if programming has crashed due to an exception - NOTE:
$!
contains the last exception that was raised- We can convert it to a string or inspect it
- If we
require "english"
, it's equivalent to $ERROR_INFO
class: thanks
???
- Thank YOU for coming
- Thanks to STL Ruby members for feedback on my first version
-
Please talk to me in the hallway!
-
Twitter: @CraigBuchek
-
GitHub: booch
-
Email: [email protected]
???
- Please talk to me in the hallway!
- I'm introverted
- I live people
- I like talking to interesting people about interesting things
- I like talking about interesting things
- Credits:
- Clip art from Clipart.co