This article was originally posted on the globaldev blog, they have kindly allowed me to repost it here. If you’re looking for a Ruby job in London you should check out their jobs page.
This is part 4 of a 5-part series on Ruby tips and tricks gleaned from the Global Personals team’s pull requests over the last two years. Part 1 covers blocks and ranges, part 2 deals with destructuring and type conversions, and part 3 talks about exceptions and modules.
The Rails console is really useful for interactive debugging, and the same approach can come in very handy for non-Rails projects. Using irb from the the stdlib it’s surprisingly easy to get something up and running for your own project.
This example assumes you’ve put your code in lib/
and your project has a
sensible entry point that you can require
to load up all the code.
I’ve added an example of setting up a Sequel database connection, you can
replace this with whatever setup code you might need, or just remove it. You
can also remove the require "bundler/setup"
if you’re not using Bundler.
#!/usr/bin/env ruby
require "bundler/setup"
require_relative "lib/my_project"
require "sequel"
require "irb"
def config
return @config if @config
config_path = File.expand_path("../config/application.yml", __FILE__)
@config = YAML.load_file(config_path)
end
DB = Sequel.connect(config[:database])
IRB.start
Save this in the base directory of you project as console
and run
chmod +x console
on the command line to make the file executable. You can now
start up a console session with ./console
in the base directory of your
project.
Any methods, instance variables, or constants you declare before the
IRB.start
will be available during the console session, but it creates a new
scope so local variables aren’t accessible. Anything after will be run once you
quit irb.
When you’re debugging on the console irb shows you the result of each
expression by calling #inspect
on the result. Usually this gives you a fairly
low-level representation helpful in debugging, however in the case of Floats
it’s not quite as useful as it could be. Due to the way Ruby (and indeed most
programming languages) represent floating point numbers they are rarely
exactly the value you specify. An easy way to see the true value is using
sprintf
sprintf("%.50f", 1.115) #=> "1.11499999999999999111821580299874767661094665527344"
Other times you can have the opposite problem, the detailed output from
#inspect
can obscure what it is you’re actually looking for.
require "bigdecimal"
decimals = [BigDecimal("1.0")]
5.times {decimals << decimals.last / 2}
decimals #=> [#<BigDecimal:7fbdb8871928,'0.1E1',9(18)>, #<BigDecimal:7fbdb88717c0,'0.5E0',9(36)>, #<BigDecimal:7fbdb88716a8,'0.25E0',9(36)>, #<BigDecimal:7fbdb8871590,'0.125E0',9(36)>, #<BigDecimal:7fbdb88714a0,'0.625E-1',9(36)>, #<BigDecimal:7fbdb8871360,'0.3125E-1',9(36)>]
In this case you can redefine #inspect
on the class in hand in your test or
console script to return something more intuitive.
class BigDecimal
def inspect
"#{to_s("F")}d"
end
end
decimals #=> [1.0d, 0.5d, 0.25d, 0.125d, 0.0625d, 0.03125d]
For the same reason it can often be helpful to define a custom #inspect
on
your own classes.
Sometimes you’re trying to hunt down a bug and you’re faced with two outputs
nearly identical, but not quite. You don’t want to hunt though the output
yourself, but using diff
seems like a lot of effort when you have to save
each output to a file, especially when you’re constantly changing things and
running the code again to narrow the problem down. Fortunately it turns out
Ruby has a handy diff method hidden away in the stdlib.
require "minitest/unit"
include Minitest::Assertions
a = ["foo", "bar", "baz"]
b = ["food","bar", "baz"]
puts diff(a.join("\n"), b.join("\n"))
outputs
--- expected
+++ actual
@@ -1,3 +1,3 @@
-"foo
+"food
bar
baz"
As a highly dynamic language, one that allows you to change almost anything at any time, Ruby sometimes makes it embarrassingly tricky to work out where a method is actually defined. It turns out Ruby can keep track of all this for you.
require "set"
array = [1,2,3]
m = array.method(:to_set) # get ahold of an object representing the to_set method
m.owner #=> Enumerable
m.source_location #=> ["~/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/set.rb", 635]
This shows us the #to_set
method on an Array instance comes from the
Enumerable module, and is actually defined on line 635 of set.rb
from the
stdlib.
There’s no set way to layout your Ruby project, but there are a few things that are good practice if you want to package your code up as a gem, and make sense for other projects too.
RubyGems works by adding a directory for each gem to the load path (the list to
search for files when you require
). You don’t want this to be the base
directory of your project, as you’d then make all the ancillary files
(Gemfile, tests, build scripts, etc) in your gem available to any users. To
work around this you usually put your code in a directory called lib
, and set
the require_path
attribute in your gemspec to "lib"
.
However now everything in your lib
directory can now potentially be loaded
with a require "name"
. This is fine for your core class or module, but say
you have an api.rb
file, this is a potentially common name and may clash with
other gems or the user’s code. For this reason it’s a good idea to have a
single main .rb file in your lib
directory, and then have a name-spaced
directory within this for the rest of your code.
Any ancillary files can go in your base directory, and most people will have a
test
(or spec
) directory in there too.
This gives us the layout:
my_project/
lib/
my_project/
bar.rb
foo.rb
my_project.rb
test/
my_project_test.rb
console
Gemfile
my_project.gemspec
Rakefile
Usually you’d use one file per class, and name the file for the class,
snake_case rather than CamelCase. Directories under lib
would match your
name-spaces, thus you’d expect lib/my_project/foo.rb
to look something like
the following.
module MyProject
class Foo
# ...
end
end
I’ll often have a couple of extra files that don’t map directly to a class, a
lib/my_project/errors.rb
that contains all the Exception classes for my
project, and sometimes a lib/my_project/constants.rb
that contains constants
being used across the project.
Don’t feel the need to strictly follow the ‘one class per file’ rule, if you have a small helper class only used by one other it’s fine to bundle it in with the other.
When it comes to requiring files within your project you’ll want to use
require_relative
. require
is great for loading up other gems and things
from the stdlib, but as it searches the global variable $:
(or $LOAD_PATH
)
for the thing you have required you can’t determine exactly what it’s going to
do. require_relative
always follows the path starting from the file it’s
called in, meaning that no matter what it will always find the same file. This
is especially useful in your tests, as then you know for sure which bits of code
you are testing.
It’s usually a good idea to require all your classes, or at least the core ones,
from your lib/my_project.rb
file. This way if packaged up as a gem or when
writing tests, scripts, etc there’s a single ‘entry point’ file to require and
you get everything.
Ruby comes packaged with some great documentation, and a handy little tool for
navigating it called ri
. It’s really simple to use, just run ri
at the
command line. Once it’s running you can enter a class name to get an overview of
that class and a list of methods. You can view class methods with
ClassName.method
, and instance methods with ClassName#method
The docs for any gems you have installed will also be available though ri
,
assuming the authors have written any.
Documentation for gems can also be viewed by running gem server
and visiting
http://0.0.0.0:8808.
You can build the documentation for you own code using the rdoc
command, just
supply it with the directory of your code, and any additional files. It will
output to a doc
folder, open doc/index.html
in your web browser to see what
it has generated.
rdoc lib README.rdoc --main README.rdoc
This will give you an outline of your classes and methods, but you can make the documentation much more helpful to others by writing comments using the rdoc format. These comments become the descriptions of classes and methods in the rdoc output.
module Namespace # :nodoc: so we don't have an empty doc page for the namespace
# The Example class is an example of rdoc
#
# Link to a real
# example[https://github.com/globaldev/going_postal/blob/master/lib/going_postal.rb]
#--
# double minus hides docs from here on, we can un-hide with double plus
#++
#
# = Heading
#
# == Sub Heading
#
# Indent example code 2 spaces (3 total from the #)
#
# example = Example.new
# baz = example.foo(bar)
#
class Example
# :call-seq: Example.new(arg) -> example
#
#--
# The :call-seq: directive lets you specify a custom example of how the
# method is called, if you don't provide one the method name and arguments
# are taken from the definition, and no return value is specified.
#++
#
# This is the initialize method, it gets documented as the +new+ class
# method.
#
# Returns a new Example instance.
#
def initialize(arg)
end
# :section: demonstration methods
# :call-seq: example.foo(bar) -> baz
#
# foos the bar, returning baz
#
def foo(bar)
end
# :call-seq:
# example.qux(params) -> array or nil
# example.quux(params) -> array or nil
#
# Available params are
# [foos] an array of Foos
# [bar] a Bar instance
# [baz] a Baz instance (optional)
#
def qux(params)
end
alias quux qux
# :section: block methods
# :call-seq: example.each {|foo| block} -> example
def each
yield foo
self
end
end
end
Head on to Part 5.