Ruby is a nice and expressive language, and full of hidden tricks. It takes time for new programmers to learn them, and Ruby Best Practices speeds this process. Expert programmers can also benefit from the book by learning new tricks and remembering forgotten ones. As a seasoned Ruby newbie, I’ve found the book to be mostly useful.
The book is basically a collection of strategies to solve common problems in Ruby, and it’s full of real world examples. The author, Gregory T. Brown, is an experienced Rubyist who authored libraries like Prawn, a PDF documents generator (it’s very good!). Given his practical experience, the book is filled with real and relevant examples and intelligent and elegant solutions to them.
Besides the quality content, the book is well written, straightforward and funny. Gregory is enthusiastic and he keeps you hooked wanting to read more and more, which is something very interesting in a technical book. And even though the book was released in 2009, the problems and code are still valid.
This book is a great read for the ones who aspire becoming great (or at least good) Rubyists. ;)
Book on Amazon: Ruby Best Practices
My personal notes
Chapter 1: driving code through tests
- Automate test tasks with Rake.
- Complex data testing in viewsby using small text chunks with tools like nokogiri.
- Stubs are used when we want to replace some functionality with canned results to make testing other code easier.
- Mocks are used to create objects that can act in place of an external resource for the purpose of testing. Mock objects are set up with expected responses, which are then verified when the tests are run.
- If your solution seems difficult to test, it may be a sign that your design is not flexible enough to easily be refactored or interacted with.
Chapter 2: designing beautiful APIs
- Execute a block in the scope of an instantiated object
class Server
# other methods same as before
def self.run(port=3333,&block)
server = Server.new(port)
server.instance_eval(&block)
server.run
end
end
Server.run do
handle(/hello/i) { "Hello from server at #{Time.now}" }
handle(/goodbye/i) { "Goodbye from server at #{Time.now}" }
handle(/name is (\w+)/) { |m| "Nice to meet you #{m[1]}!" }
end
- If you create a collection class that you need to traverse, build on top of Enumerable rather than reinventing the wheel.
- If you have shared code that differs only in the middle, create a helper method that yields a block in between the pre/postprocessing code to avoid duplication of effort.
- If you use the &block syntax, you can capture the code block provided to a method inside a variable. You can then store this and use it later, which is very useful for creating dynamic callbacks.
- Using a combination of &block and instance_eval, you can execute blocks within the context of arbitrary objects, which opens up a lot of doors for highly customized interfaces.
- The return value of yield (and block.call) is the same as the return value of the provided block.
- Hack for converting Ruby objects to their boolean values:
!!(obj)
!!(:foo)
!!(123)
Chapter 4: text and file processing
-
When processing a file:
- Identify beggining and end markers of sections with a pattern.
- If sections are nested, maintain a stack that you update before further processing of each line.
- Break up your extraction code into different cases and select the right one based on the section you are in.
- When a line cannot be processed, skip to the next one ASAP, using the ’next’ keyword.
- Maintain state as you normally would, processing whatever data you need.
-
Tempfile: deals with temporary files without hassle.
-
Enum.inject => Combines all elements of enum by applying a binary operation, specified by a block or a symbol that names a method or operator.
(5..10).inject(:+)
(5..10).inject('+')
(5..10).inject { |sum, n| sum + n }
Chapter 5: functional programming
- Lazy code => lambda, Proc => is essentially a chunk of code that gets executed on demand, rather than in place
- Memoizable: caches function results in well defined functions (function that return the same result on the same input). Can be done using hashes.
Appendix
- Ruby worst practices
- Reserve class variables (@@foo) to shared data only
- Beware the use of eval
- Don’t mess with method_missing
Code examples
# logger.rb
require 'logger'
class StandardError
def report
%{#{self.class}: #{message}\n#{backtrace.join("\n")}}
end
end
logger = Logger.new('newb.log')
logger.error 'Foo barrrrrrr!'
logger.info 'Fus rooooh!'
logger.fatal 'Dead!'
logger.warn 'Danger!'
logger.debug 'Here comes the error'
# meta.rb
class MetaProg
def self.go(&block)
m = MetaProg.new
m.instance_eval &block
m.say
end
def yell(sentence)
puts sentence
end
def say
puts 'say foo!'
end
end
MetaProg.go do |m|
m.yell 'Hayoooo!'
m.yell 'Silver!'
end
# module.rb
module Foo
extend self
def bar
puts 'baz'
end
module Baz
extend self
def biz
puts 'bir'
end
end
end
Foo.bar
Foo::Baz.biz
# Rakefile
task default: [:bang]
desc 'Faz algo inútil'
task :bang do
sh 'pwd'
puts `ruby -v`
end
desc 'Imprime o texto shoop da whoop no terminal'
task :shoop do
puts "sh00p da wh00p"
end
namespace :bar do
desc 'Does bar:foo without name clash.'
task :foo do
puts 'task bar:foo'
end
end
# stubs.rb
module Stubber
extend self
def stubs(method, options = {})
singleton(options[:for]).send(:define_method, method) do |*a|
options[:returns]
end
end
def singleton(obj)
class << obj
self
end
end
end
class User
end
user = User.new
puts user.inspect
Stubber.stubs(:logged_in?, for: user, returns: true)
# True
puts user.logged_in?
# NoMethodError
puts User.new.logged_in?