Ruby is quite unique in many ways, and can be a bit overwhelming when you first start tinkering with it. This post is my first in a series on getting people new to the language up to speed. This particular post focuses on language constructs that I found myself looking up repeatedly when I first started coding in Ruby.
Different types of parameters and variables
Global variables: Variables with $
This one is easy, any variable that starts with $ is global. That’s all there is to it. Of course if you find yourself introducing global variables it’s probably a code smell. Ruby has a good number of predefined global variables, very similar to Perl.
$! |
latest error message |
$@ |
location of error |
$_ |
string last read by gets |
$. |
line number last read by interpreter |
$& |
string last matched by regexp |
$~ |
the last regexp match, as an array of subexpressions |
$n |
the nth subexpression in the last match (same as $~[n]) |
$= |
case-insensitivity flag |
$/ |
input record separator |
$\ |
output record separator |
$0 |
the name of the ruby script file |
$* |
the command line arguments |
$$ |
interpreter’s process ID |
$? |
exit status of last executed child process |
This table was taken from ruby-doc.org. These variables are a clear indication of Perl’s influence on Ruby. But it should be noted that use of many of these variables — for example $_ — is now frowned upon. The interpreter will emit a warning if you do, and it is possible they will be removed in the future.
Symbols: Variables with :
Symbols are quite unique to Ruby. They are sort of like enums in C-style languages, but can also act like interned strings. The main advantage to symbols is the interpreter will only ever instantiate one instance for a given symbol.
puts :hello.object_id # 24065 puts :whats_up.object_id # 24081 puts :hello.to_s # "hello" puts "hello".to_sym.object_id # 24065 puts "hello".to_sym == :hello # true :"this is valid too" foo = "bar" :"#{foo}" # so is this
Ruby’s dynamic nature makes it easy for the interpreter to convert symbols to strings and vice versa. So in many cases — especially in Rails — you can use a string or a symbol interchangeably. But since a symbol is only ever instantiated once, they make better keys in hashes than strings do:
a = { "greeting" => "hello" } b = { "greeting" => "what's up?" } # false. they are two different strings # using more memory than you probably intended puts a.keys.first.object_id == b.keys.first.object_id c = { :greeting => "hello" } d = { :greeting => "what's up?" } # true. same symbol is reused puts c.keys.first.object_id == d.keys.first.object_id
Symbols can take some getting used to, but they are a welcome aspect of the language.
Variable parameter methods: Parameters with *
If a parameter has a * prepended to it, it acts just like the params keyword in C# or … in C.
def print_args(*args) # all the parameters got collected # into an array named args args.each { |arg| puts arg } end
This method can be called with any number of arguments, and they will be gathered into an array named args within the body of the method.
print_args("hello", "there", "ruby") # output: # hello # there # ruby
However, unlike C# or C, the opposite is also true (which I think is rather cool). An array can be prepended with * to expand it into parameters of a method call
def add_three(a, b, c) a + b + c end args = [4, 8, 2] # equivalent to add_three(4, 8, 2) add_three(*args) # output is 14 # only expand some parameters args2 = [6,7] add_three(10, *args2) # output is 23 # the array has to expand to exactly # the correct number of parameters args3 = [4,5,6,7] # ArgumentError, wrong number of arguments (4 for 3) add_three(*args3)
Dealing with blocks: Parameters with &
A block in Ruby is an anonymous body of code, much like anonymous methods in Javascript, C# or any functional language. It’s rare to program in Ruby without using them. As a method author, you use the yield keyword to execute a block passed into your method:
def give_me_a_block # use block_given? to check if # you actually were passed one if block_given? puts 'Going to execute the block' yield puts 'Done executing the block' else puts "Hey, give me a block" end end
That’s all well and good but what you if you need to keep that block around? Perhaps store it and call it later? Or ask the block about itself? If you explicitly accept the block in a parameter prepended with & (which indicates this parameter contains a block) Ruby will convert it into a Proc object for you. From there you can refer to the block as needed and execute it with the call method. There are advantages to promoting the block to a full fledged Proc object, but the draw back is it’s slower than just implicitly calling the block with yield.
class CallBlockLater def initialize(name, &later) @name = name @later = later end def invoke # the block passed into the constructor # is now finally getting called @later.call(@name) end end cb = CallBlockLater.new("henry") { |name| puts "Hi #{name}"} cb.invoke # "Hi henry"
require, include, extend, load, wha?
These methods seem to all do very similar things at first and can be confusing. Here’s the lowdown.
require
require is simple, it merely says you are adding code written elsewhere for use in your program. You can require a path on the filesystem or a simple name. In the latter case, the global variable $LOAD_PATH (aliased as $:) is all the places Ruby will look for the file you are attempting to require. require automatically appends valid extensions in its search for the resource, such as .rb, .so, .dll, etc. So leave them off if you are not specifying an absolute path. Once require locates the ruby resource you have specified, it will then load it, but only once per session.
require '/home/matt/ruby/lib/foo_bar.rb' require 'net/http' # now I can use objects/methods # defined in any of the above http = Net::HTTP.new('www.teamfortress.com', 80) page = http.get('/') # ...
load
load is similar to require, but has some subtle differences. load also looks in $: to try and find the ruby resource you have specified (or finds the resource directly if you provide it an absolute path), and will then load and run the program if it is found. Unlike require, load will execute the program every time it is called. load makes no attempt at discovering the extension for you, so you must provide it. If you have “require ‘foo’” then the load equivalent would be “load ‘foo.rb’” (or whatever the extension is for foo.)
include
include has two different uses. When used inside of a class, it adds the contents of a module to the class. This is known as a “mixin”.
module SuperPowers def x_ray_vision(object) puts "I can see through #{object}" end end class Hero include SuperPowers def initialize(name) @name = name end end h = Hero.new("Super Man") h.x_ray_vision("brick wall") # output: I can see through brick wall
include is also used much like using in C# or import in Java. It’s common for people to define their logic within a Module. If you have required that person’s script into yours, you must use the fully qualified names of their objects, as defined by the Module definitions, such as Foo::Bar::SomeClass. Where SomeClass is a class inside the Module Bar, which is inside the Module Foo. If you add ‘include Foo::Bar’ in your file, you can then refer to SomeClass directly.
Even though this looks like two uses for include, it’s actually the same thing. include just includes the contents of a module into a space. In the latter case you are including the contents of the module into the global space.
extend
Ruby is very dynamic and altering the definition of an object on the fly is easy to do. extend is one means of accomplishing that; it will append all of the methods found in the specified module to the object you apply it to.
jill = Person.new("Jill") # now jill has X-Ray vision # but no other Person does jill.extend(SuperPowers) jill.x_ray_vision("vault")
Since classes are objects just like everything else in Ruby, you can do this to them too.
class Dog extend SuperPowers end # this is equivalent to Dog.extend(SuperPowers)
Wait, don’t all dogs now have super powers? Isn’t this the same as include? Not quite. This example is actually a rather poor one, but shows what I’ve seen is a common misconception. Here I have extended the class itself with super powers. So I can call Dog.x_ray_vision. Not very useful in this particular case is it? Well, there are certainly valid times when you want to extend a class with additional methods, and extend is the way to do it.