Had never thought about this being a language feature:
Methods are invoked by sending a message to an object. The message contains the method’s name, along with any parameters the method may need. 33
But it’s pretty nice:
num = Math.abs(num) # java. boo. num = num.abs # ruby. yay.
Didn’t know you could use while
in one-lines
square = square*square while square < 1000
An excellent, succinct explanation of how yield
works:
You can think of yield as being something like a method call that invokes the block associated with the call to the method containing the yield. 44
What does this mean?
You may be better off thinking of the block and the method as coroutines, which transfer control back and forth between themselves. 44 (footnote)
Didn’t know about ARGF:
...the variable ARGF is a special kind of I/O object that acts like all the contents of all the files whose names are passed on the command line (or standard input if you don’t pass any filenames). 46
I always thought of a virtual attribute as an attribute which had no corresponding database column. Wrong. It’s simply an writer/reader which doesn’t rely directly on an instance variable (i.e. attribute). For example:
def price_in_cents=(cents) @price = cents / 100.0 end
That’s a powerful abstraction that I had taken for granted (see Uniform Access Principle).
Also, interesting is the blurry distinction between methods and attributes (55). Following the Uniform Access Principle there’s really no important, practical distinction between methods and attributes.
Didn’t know about this little beauty:
require_relative 'book_in_stock'
Never thought of restricted methods this way. Always thought of them simply as internal methods (i.e. methods intended to be called only by other methods).
A good rule of thumb is never to expose methods that could leave an object in an invalid state.
Didn’t know that first
and last
could take arguments:
array = [ 1, 2, 3, 4, 5, 6, 7 ] array.first(4) # => [1, 2, 3, 4] array.last(4) # => [4, 5, 6, 7]
Didn’t know that you could explicitly define local variables by putting them after a semicolon in the block’s parameter list (73):
square = "some shape" sum = 0 [1, 2, 3, 4].each do |value; square| square = value * value sum += square end puts sum # => 30 puts square # => "some shape"
If inject is called with no parameter, it uses the first element of the collection as the initial value and starts the iteration with the second value. Meaning these two examples are logically the same (76):
[1,3,5,7].inject(0) {|sum, element| sum+element} # => 16 [1,3,5,7].inject {|sum, element| sum+element} # => 16
Too magical? You can also pass symbol to specify the method that you’d like called on each successive object in the array (76):
[1,3,5,7].inject(:+) # => 16 [1,3,5,7].inject(:*) # => 105
You can easily grant objects enumerable functionality:
Most of the internal iterator methods—the ones that normally yield successive values to a block—will also return an Enumerator object if called without a block (76).
This means, for example:
[1, "red", "orange"].each # => create an Enumerator using an internal iterator
And now you can chain enumerable methods on to the returned object:
"cat".each_char.each_with_index { |item, index| result << [item, index] }
Ruby 1.9 has an alternative syntax for lambdas:
lambda { |params| puts "params" } # => 1.8 ->params { puts "params" } # => 1.9
As of Ruby 1.9, blocks have the same parameter list capabilities as methods (i.e. you can pass them splat args, default values, etc):
proc1 = lambda do |a, *b, &block| puts "a = #{a.inspect}" puts "b = #{b.inspect}" block.call end
Note that this is block also has closure. This means you can use lambdas to achieve lexical scoping, like in JavaScript. For example, say you had a build_invoice
method.
class AccountsPayable def initialize(number) @number = number end def build_invoice puts "Invoice #{@number}" puts "Amount #{calculate_amount}" fetch_line_items.each do |item| puts "Purchased #{item[:quantity]} #{item[:description]} for #{item[:price]}" end end private def calculate_amount # ...complicated logic for taxes, surcharges, etc. 19.48 end def fetch_line_items # ...lots of code to fetch line items [ { description: "Apples", quantity: 7, price: 1.00 }, { description: "Bananas", quantity: 12, price: 2.00 } ] end end
Following the standard idiom, build_invoice
is composed of several private methods – each responsible for a single, specific task. This is smart especially when the private methods are reused by other public methods (e.g. build_receipt
method might also want to know about line items). By composing build_invoice
of private methods, we’ve added a small about of indirection (i.e. we now must scan through three methods rather than one). But we’ve reduced complexity and presumably removed some duplication. Overall, a good trade.
But what about when these private methods aren’t reused? Or more importantly, what if you’re class has grown large and you have scores of private methods – each existing only to compose other methods. Tracking down the logic for a single method becomes increasingly tedious.
Assuming the private methods are not reused, we can use closure to compose a method without needing to create single-use private methods. For example, build_invoice
could be refactored as:
class AccountsPayable def initialize(number) @number = number end def build_invoice calculate_amount = lambda { # ...complicated logic for taxes, surcharges, etc. 19.48 }.call fetch_line_items = lambda { # ...lots of code to fetch line items [ { description: "Apples", quantity: 7, price: 1.00 }, { description: "Bananas", quantity: 12, price: 2.00 } ] }.call puts "Invoice #{@number}" puts "Amount #{calculate_amount}" fetch_line_items.each do |item| puts "Purchased #{item[:quantity]} #{item[:description]} for #{item[:price]}" end end end
Interesting. Authors don’t seem too hot on inheritance:
The use of the serve method shows a common idiom when using subclassing. A parent class assumes that it will be subclassed and calls a method that it expects its children to implement. This allows the parent to take on the brunt of the processing but to invoke what are effectively hook methods in subclasses to add application-level functionality. As we’ll see at the end of this chapter, just because this idiom is common doesn’t make it good design. (91)
Didn’t know ::
was called the scope resolution operator. (93)
An academic distinction but interesting:
Ruby include does not simply copy the module’s instance methods into the class. Instead, it makes a reference from the class to the included module. If multiple classes include that module, they’ll all point to the same thing. (94)
How method lookups work in Ruby:
Ruby looks first in the immediate class of an object, then in the mixins included into that class, and then in superclasses and their mixins. If a class has multiple modules mixed in, the last one included is searched first. (98)
So when should we use inheritance vs mixins? Of course it depends, making these pages are worth regularly re-reading. The basic synopsis, however:
The real world is built using composition, not strict hierarchies...And that’s where we need to move away from inheritance in our designs...Try to reserve inheritance for the times where it is justified. And try to explore all the cool ways that mixins let you write decoupled, flexible code. (99)gi
Didn’t know that you can use underscore as delimiters to make large numbers easier to read:
123_456_789 # => same as 123456789
Didn’t know there’s built-in support for rational and imaginary numbers:
Rational(3, 4) * Rational(2, 3) # => (1/2) Rational("3/4") * Rational("2/3") # => (1/2) Complex(1, 2) * Complex(3, 4) # => (-5+10i) Complex("1+2i") * Complex("3+4i") # => (-5+10i)
What if you add two different types of numbers?
If the two numbers are different classes, the result will have the class of the more general one. If you mix integers and floats, the result will be a float; if you mix floats and complex numbers, the result will be complex.
Didn’t know you could use ranges as conditions:
while line = gets puts line if line =~ /start/ .. line =~ /end/ end
Didn’t know that you can test to see whether a pattern does not match a string using !~
(113):
File.foreach("testfile").with_index do |line, index| puts "#{index}: #{line}" if line !~ /on/ end
Never thought to sub!
and gsub!
as conditions:
Unlike sub and gsub, sub! and gsub! return the string only if the pattern was matched. If no match for the pattern is found in the string, they return nil instead. This means it can make sense (depending on your need) to use the ! forms in conditions. (114)
Different ways to create regular expressions:
/mm\/dd/ # => /mm\/dd/ Regexp.new("mm/dd") # => /mm\/dd/ %r{mm/dd} # => /mm\/dd/
I did know about extend mode
but probably don’t use it enough:
Complex regular expressions can be difficult to read. The x option allows you to insert spaces and newlines in the pattern to make it more readable. You can also use # to introduce comments. (115)
Whoa! Didn’t know you could use pre_match
and post_match
to avoid using ugly pearl-like globals (116):
def show_regexp(string, pattern) match = pattern.match(string) "#{match.pre_match}->#{match[0]}<-#{match.post_match}" end show_regexp('very interesting', /t/) # => very in->t<-eresting
You can create the intersection of character classes using &&
(118):
# matchs all lowercase ASCII letters that aren’t vowels "now is the time".gsub(/[a-z&&[^aeiou]]/, '*') # => "*o* i* **e *i*e"
Don’t forget that you can use part of the current match later in match using the \1
notation. For example, this matches duplicated substrings (120):
show_regexp('Mississippi', /(\w+)\1/) # => M->ississ<-ippi
Didn’t know that both sub
and gsub
can take a block:
"quick brown fox".gsub(/[aeiou]/) { |vowel| vowel.upcase } # => "qUIck brOwn fOx"
This is awesome too: “You can also give sub and gsub a hash as the replacement parameter, in which case they will look up matched groups and use the corresponding values as replacement text” (122):
replacement = { "cat" => "feline", "dog" => "canine" } replacement.default = "unknown" "cat and dog".gsub(/\w+/, replacement) # => "feline unknown canine"
You can also use backslash sequences as the second argument of sub
and gsub
:
"fred:smith".sub(/(\w+):(\w+)/, '\2, \1') # => smith, fred
Awesome. How to add comments within regular expressions:
The sequence (?# comment) inserts a comment into the pattern. The content is ignored during pattern matching. As we’ll see, commenting complex regular expressions can be as helpful as commenting complex code. (123)
Didn’t know about zero-width positive lookahead
or zero-width positive lookbehind
matchers. Pretty cool. (124)
Also, fairly advanced discussion of backtracing
. (125)
Note: Moved pretty quickly through the advanced section would be good re-read in a few months to help everything sink in better.
Didn’t know a default argument could reference previous arguments (132):
def surround(word, pad_width=word.length/2) "[" * pad_width + word + "]" * pad_width end
If you’re using a splat operator only to pass arguments on to super
, you can omit the argument name. So these two snippets are the same:
class Child < Parent def do_something(*not_used) # our processing super end end class Child < Parent def do_something(*) # our processing super end end