-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathslides.html
760 lines (602 loc) · 16.9 KB
/
slides.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
<!DOCTYPE html>
<html>
<head>
<title>A Look at Hooks</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz);
@import url(https://fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic);
body {
font-family: 'Droid Serif';
}
header, footer {
display: block;
color: #888;
position: absolute;
font-size: 16px;
width: 100%;
/* Make up for padding on enclosing .remark-slide-content DIV. */
margin-left: -5.5em;
}
header {
top: 0;
margin-top: -8px;
}
header .left, footer .left {
float: left;
}
header .right, footer .right {
float: right;
margin-right: 2em;
}
footer {
bottom: 0;
margin-bottom: -10px;
}
h1, h2, h3 {
font-family: 'Yanone Kaffeesatz';
font-weight: normal;
}
.remark-slides-area { background-color: #9B111E; }
.remark-slide-content {
font-size: 26px; line-height: 1.6;
width: 100%;
}
img[src="ruby_preserves.png"] {
height: 40%;
width: 40%;
}
.remark-slide-number { font-size: 16px; }
.remark-code, .remark-inline-code { font-family: monospace; line-height: 1.5; font-size: 16px; }
.remark-inline-code { font-size: 24px; }
.remark-slide .title li {
list-style-type: none;
font-size: 28px;
}
.remark-slide .title ul {
padding-left: 0px;
}
.remark-slide .title img {
height: 40%;
width: 40%;
}
.remark-slide .title h1 {
font-size: 60px;
}
.remark-slide .diagram p {
text-align: center;
}
.remark-slide li p {
margin: 0;
}
a {
text-decoration: none;
border-bottom: 1px #666666 dashed;
color: #202;
}
a:hover {
color: #505;
background-color: #DD0;
}
html .remark-container.remark-presenter-mode .remark-slides-area {
left: -10%;
top: 0;
height: 70%;
width: 70%;
}
.remark-presenter-mode .remark-notes-area {
left: 50%;
z-index: 1;
font-size: 150%;
line-height: 1.4;
}
.remark-notes-area .remark-bottom-area .remark-notes-current-area {
height: 95%;
}
html .remark-container.remark-presenter-mode .remark-preview-area, .remark-presenter-mode .remark-notes-preview-area {
display: none;
}
.affiliations ul {
column-count: 2;
}
.affiliations li {
list-style: none;
}
.affiliations img {
height: 120px;
margin: 10px 0;
vertical-align: middle;
}
.thanks img {
width: 500px;
margin-left: 100px;
}
.title {
background-image: url(captain_hook.gif);
background-size: 300px 300px;
background-position-x: 500px;
}
.title h1, .title h2, .title h3, .title h4, .title h5 {
margin-left: -300px;
}
.what_is_a_hook {
background-image: url(fish_hook.png);
background-size: 300px 300px;
background-position: 500px 200px;
}
.strict_conversion_2 ul {
column-count: 2;
}
</style>
</head>
<body>
<textarea id="source">
layout: true
<header>
<p class="left">RubyConf</p>
<p class="right">2016-11-10</p>
</header>
<footer>
<p class="left">@CraigBuchek</p>
</footer>
---
class: title, middle, center
# A Look at Hooks
### Craig Buchek
???
* Feel free to ask questions during!
---
class: affiliations
# About Me
*  [BoochTek][boochtek]
*  [This Agile Life][tal]
*  St. Louis
*  [Binary Noggin][binary_noggin]
*  [Tech Institute](http://www.handsupunited.org/techimpact/)
*  Ivan
???
* I have a company called BoochTek
* We do:
* Web Development
* Rails rescue projects
* Agile player/coaching
* DevOps
* 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
# 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
---
# BasicObject#initialize
~~~ ruby
class X
def initialize
puts "Initializing an instance of X"
end
end
x = X.new
~~~
~~~ output
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
---
# Class#new: Behind the Scenes
~~~ ruby
x = X.new
~~~
~~~ ruby
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 class `Class`
* So calling `X.new` calls `Class#new`
???
* So what's really happening when we call `X.new`?
* `X` is an object of class `Class`
* So calling `X.new` calls `Class#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
* 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
* 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
---
# Meta-Programming Basics
* `method_missing`
* `respond_to_missing?`
???
* Meta-programming is where you're most likely to use hooks
---
# BasicObject#method_missing
~~~ ruby
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
~~~
~~~ output
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 an `Array`
* 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
---
# Object#respond_to_missing?
~~~ ruby
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)
~~~
~~~ output
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?`
* 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 overriding `method_missing`
* See a nice explanation at http://blog.marc-andre.ca/2010/11/15/methodmissing-politely/
* Yes, this is on `Object`, while `method_missing` is on `BasicObject`
* Because `BasicObject` doesn't have `respond_to?`
---
# Module#const_missing
* Avoid this one
* Seems enticing, but leads to long-term problems
* Use `require` instead
* Or maybe `autoload`
???
* 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` instead
* `autoload(:MyModule, "/usr/local/lib/modules/my_module.rb")`
---
# Inheritance Life-Cycle
* `included`
* `extended`
* `prepended`
* `inherited`
---
# Module#included
~~~ ruby
module A
def self.included(other_module)
puts "#{self} included in #{other_module}"
end
end
class B
include A
end
~~~
~~~ output
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 the `included` hook) runs as soon as we call it
* TODO: Maybe in a next slide show how that works with normal code
* 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` over `included`
* Unless you want to do something nefarious and NOT add constants/methods/variables
---
# Module#extended
~~~ ruby
module A
def self.extended(other_module)
puts "#{self} extended by #{other_module}"
end
end
module B
extend A
end
~~~
~~~ output
A extended by B
~~~
???
* Just like `included`
* Note that it's `def self.extended`
---
# Module#prepended
~~~ ruby
module A
def self.prepended(other_module)
puts "#{self} prepended in #{other_module}"
end
end
class B
prepend A
end
~~~
~~~ output
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
* Just like `included`
* Note that it's `def self.prepended`
* Because it's a method on the module itself
---
# Class#inherited
~~~ ruby
class A
def self.inherited(other_class)
puts "#{self} subclassed by #{other_class}"
end
end
class B < A
end
~~~
~~~ output
A subclassed in B
~~~
???
* Note that it's `def self.inherited`
* Because it's a method on the class itself
---
# Method Life-Cycle
* `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` or `undef_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 of `undef_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
---
# Singleton Method Life-Cycle
* `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
# Implicit (Strict) Conversion Methods
* `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
* `to_ary` - assigning to multiple left-hand variables (or block arguments)
* http://stackoverflow.com/questions/9467395/whats-the-difference-between-to-a-and-to-ary
* `to_int` - used when performing bit operations (`|`, `&`, `^`)
* `to_hash` - used to convert `method_name(**kwargs)`
* `to_proc` - called when trying to convert to a block using the `&` operator
* `to_enum` returns an Enumerator, not an Enumerable
---
class: strict_conversion_2
# Implicit Versus Explicit Conversion
* `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
* Important note: `*x` calls `to_a(x)` (implicitly)
* Once again, called an "explicit" conversion
* I think this changed to `to_ary` in Ruby 2.0
---
# Numeric#coerce
* Find a common type for math with mixed types
~~~ ruby
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
~~~
~~~ output
coerce.rb:18:in `+': Quaternion can't be coerced into Fixnum (TypeError)
~~~
???
* `coerce` helps find a common type when doing math on mixed types
* `Numeric`, its subclasses, and similar things like `Matrix` and `Vector`
* 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
---
# Numeric#coerce
* Add `coerce` to fix the problem:
~~~ ruby
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
~~~
~~~ output
Quaternion#coerce called
Quaternion(3)
~~~
???
* Note that `coerce` returns an `Array`, with `self` as the 2nd element
---
# Numeric#coerce
* Coercion transforms binary operators like this:
~~~ ruby
q1 = Quaternion.new(1)
n = 2
[q2, q1] = q1.coerce(n)
q1 + q2
~~~
???
* This only happens after trying without coercion
---
# Kernel#at_exit
* Sets a block or proc to run on program exit
* If `$!` is set, program is exiting due to an exception
~~~ ruby
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
# Thanks

???
* Thank YOU for coming
* Thanks to STL Ruby members for feedback on my first version
---
# Feedback
* Please talk to me in the hallway!
* Twitter: [@CraigBuchek][twitter]
* GitHub: [booch][github]
* Email: [email protected]
* Slides: https://github.com/booch/presentations/
???
* 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
[twitter]: https://twitter.com/CraigBuchek
[github]: https://github.com/booch
[github-boochtek]: https://github.com/boochtek
[boochtek]: http://boochtek.com
[binary_noggin]: http://binarynoggin.com/
[tal]: http://www.thisagilelife.com
[remark]: http://remarkjs.com/
</textarea>
<!-- <script src="https://gnab.github.io/remark/downloads/remark-latest.min.js"> -->
<script src="remark-latest.min.js">
</script>
<script>
var slideshow = remark.create({highlightLines: true, highlightLanguage: "ruby"});
for (i = 0; slide = slideshow.getSlides[i]; )
{
slide.properties.class = "middle, center";
}
</script>
</body>
</html>