00:00:14.540
I think we're going to get started. I'm going to talk about hooks in Ruby today. My name is Craig Buchek, and I have my own consulting firm called Booch Tech. I do a lot of web development, Rails rescue projects, agile player coaching, and some DevOps. I also work with a company called Binary Noggin, and we're going to have a few developers free in January.
00:00:20.740
I do a podcast called This Agile Life, where we talk about agile web development. We emphasize discussing people who work here, not resources. I mentor for a tech institute for underprivileged kids from St. Louis. And apparently, you have to have a picture of your cat, so that's my cat Ivan.
00:01:02.240
So, what is a hook? The definition I'm going to use in this talk is a method that is called implicitly by Ruby, and that we never call ourselves because it's implicit. This can be surprising and difficult to troubleshoot. I like to think of it as 'spooky action at a distance.'.
00:01:19.670
In the Ruby docs, you'll see these hooks referenced in a few different ways. Sometimes they will be called hooks, and sometimes they might be called callbacks. I didn’t want to use the term callback because it has distinct meanings that differ from what we're discussing today. A lot of times, you’ll see the phrase 'Ruby calls this,' which is an indicator that it’s a hook. There are hooks and callbacks in Rails and other libraries, but I'm just going to cover the ones in Ruby today.
00:01:52.540
Who here has used 'initialize'? Probably everyone! Notice that we never explicitly call 'initialize' ourselves, but it gets called anyway. That’s the hook. This is obviously the most commonly used hook in Ruby, but it does define that 'initialize' is found on BasicObject. So, if you are looking up documentation, that’s where you should check.
00:02:14.900
What's happening behind the scenes when we call X.new? Here, X is a class, and when we call X.new, it calls 'new' on the Class class. This seems weird, but it’s how every method call works. You have to figure out that this class is a class of classes.
00:02:22.579
First, we allocate some space in memory to hold the object, and then we take that object and run 'initialize' on it. We should return the object. Meta-programming is where you're most likely to use hooks; the big ones are 'method_missing' and 'respond_to_missing.' How many of you have used 'method_missing'? It seems like a little more than half.
00:02:45.770
'Method_missing' allows calling a method that we never defined. This is probably the first tool you'd reach for when doing meta-programming. Active Record uses 'method_missing' in various places. The biggest downside of 'method_missing' is that you can't search your code for the method you're looking for because it’s not defined there; it’s dynamically found.
00:03:05.440
A couple of things to note about 'method_missing': the method name is passed in as a symbol, and we use 'splat args' as a second argument to 'method_missing' to gather all the arguments into a single array. Splat collects the arguments, and you can also pass a block. I didn’t use the block in this example, but I want to show that that's the signature you should always use for 'method_missing.'
00:03:35.060
'Whenever you use 'method_missing,' you should also use 'respond_to_missing.' Note that while 'method_missing' is on BasicObject, 'respond_to_missing' is on Object. If you’re not subclassing from Object and you’re only subclassing from BasicObject, you don't need to follow that rule. Otherwise, definitely follow that rule.
00:03:59.150
What does 'respond_to_missing' do? If you ask an object if it has a method and that method is not listed in that class or on that object, it’s going to look at 'respond_to_missing.' Before Ruby 9.1.2, we had to override 'respond_to_missing' this way. This way is a lot cleaner.
00:04:20.279
It's also called when you invoke the 'method' method. Note that 'respond_to_missing' can take a second argument. If you pass true, it will look for private methods as well as public methods.
00:04:39.950
You’ve got 'method_missing,' and you’ve also got 'constant_missing,' which is defined on the module. Generally, avoid this one: it seems pretty enticing, but it leads to long-term problems that I don’t necessarily find worth pursuing. I prefer to just use 'require' or maybe use 'autoload.' I don't know where I would quite draw the line; I know Rails likes to use this for autoloading. Remember that class names are constants, so if you're doing an automatic class loading trying to find classes, 'constant_missing' is what you'd be using.
00:05:09.949
The inheritance lifecycle for methods helps us see when some inheritance is happening. The first is 'included.' If you include a module, this will get called. In this example, we are including Module A inside Class B. When that happens, exactly when that happens, A.included gets called with B passed in. You can see what it would output; in this case, it’s 'self.include' since it is on A itself; there’s no instance of A that's being included.
00:06:00.640
Note that we didn't even need to create an instance of B; we just needed to define the class and include it, and it got called. This is useful if you need to keep track of which classes are including a module, and you can provide some way to use that list in some other context.
00:06:31.560
Similarly, if you were extending a class, that's basically the exact same thing. It’s called 'extended' and accepts Module B. It has to be a module because you can't normally have a class extending a module. In Ruby 2.0, we added 'prepend', which is basically similar to 'include' but adds it to the end of the module chain instead of the beginning.
00:06:49.120
We used to have something for meta-programming called 'alias_method_chain.' It seemed like a good idea when we started using it, but it ran into problems; it was very hard to follow the flow. We realized that module inclusion was a better way to do meta-programming in most cases. But 'prepend' works just like 'include', and again we've got 'self.prepended'.
00:07:32.200
Now, this one is a little bit different because it's related to class inheritance instead of modules. There's not even a keyword for that; it’s just Class B < Class A means Class B inherits from Class A. As soon as that happens, you’ve called the 'inherited' method on A, with B passed in.
00:08:07.990
There are some hooks related to method life cycles: we have 'method_added', 'method_removed', and 'method_undefined'. These aren't used too often because we don't dynamically add and remove methods too often. If you use 'method_remove', it leads to the 'method_removed' hook being called, allowing the superclass method to still be called.
00:08:54.200
If you call 'method_undefined' or just call 'undef' on a method, it actually does the same thing as if you wrote a method that just returns or raises NoMethodError. The only reason I can think of doing that is if you’re trying to mimic an older version of Ruby, or if you’re dynamically adding methods and want to start over.
00:09:33.370
You can also add methods to individual objects, which are called singleton methods. A class method is just a singleton method defined on the class itself. There are equivalent lifecycle methods for singleton, so if you add a method to a class or some other object, you would get the singleton method added.
00:10:11.600
The next set of hooks I want to discuss are the implicit or strict conversion methods. We have a couple here: 'to_str', 'to_integer', 'to_array', 'to_hash', 'to_proc', and 'to_num'. These do have equivalents that are explicit, and we'll talk about those in a second.
00:10:44.820
The implicit ones are used when we want to say something is converting to something else. For instance, 'to_str' would be used for something that not only can be shown as a string but should roughly behave like a string. If you create a Username class, you might want to compare it with a string.
00:11:14.780
So if you’re using '==' and the two things are different on either side, Ruby will call 'to_str' on the right side to try to get it on par with the left side. 'to_str' is also used when concatenating strings with the plus sign (+). 'to_ary' is used when assigning to multiple left-hand variables.
00:11:45.420
For example, if you're doing 'a, b = 1, 2', that would use 'to_array' if it needs to do some conversion. We do have equivalents for explicit conversion functions and methods you're probably more familiar with: 'to_s' for strings, 'to_i' for integers, 'to_a' for arrays, and 'to_h' for hashes.
00:12:05.610
Usually, you'd use your explicit conversions in typical cases. However, there are a few places where you wouldn’t; I want to point out that the ampersand (&) is basically syntax to convert to a proc, which calls 'to_proc'.
00:12:26.210
The exceptions here are if you’re doing string interpolation with hashes and curlies, that will actually call 'to_s'. I consider that implicit, but some developers indicate that this syntax is asking for a conversion, so it uses the explicit conversion operators.
00:12:55.299
Similarly, using splat will convert to an array; it will use 'to_a' implicitly. I think this may have changed in Ruby 2.0, but I didn’t get a chance to check that out.
00:13:09.260
This is a case that people often overlook when thinking about hooks. There are several different ways for number-like objects to interact with each other. Ruby's implementation offers a suitable solution, while another is called double dispatch, which is what Smalltalk uses. This is one area where Ruby is quite different from Smalltalk.
00:13:38.900
In our example, we define a mathematical type; it's nothing like a quaternion. However, if you want to define something similar, you might find that it doesn't work because the line of code is attempting to add 'self' to 'other', but it doesn't know how to add 'other'.
00:14:14.590
Ruby kindly provides a hint, saying it can't coerce to 'Fixnum'. This hint urges us to add the 'coerce' function to this class. By implementing 'coerce', we can ensure that the two types can interact correctly.
00:14:48.660
If we do that, we can successfully add the two objects together. What truly occurs under the surface is when 'q1' is a quaternion and 'n' is just an integer. The 'coerce' function will automatically run and set 'q2' to the first coerced item. 'q1' will return itself because we’re returning 'self' and then proceed to execute the addition operation.
00:15:15.560
This tactic works with any binary operator defined on numeric classes. It allows various types—numbers, matrices, and vectors—to interact appropriately.
00:15:43.620
'At_exit' allows you to set a block or proc to be executed when the program exits. If you call 'at_exit' multiple times, the blocks will be executed in reverse order of their registration. You can also check '$!' to see if the program crashed due to an exception.
00:16:18.890
We implement this in the example here, and indeed you can print out the value of it. It’s just a global variable, and if you require the English library, it behaves similarly to 'error_info' for those who aren't fans of Perl-like operators or globals.
00:16:36.670
So, we can convert that to a string. We're inspecting it just like any other object. Thank you for attending! I've wrapped up a little early, so I am open to questions from the audience if you have any.