Ruby
Syntactic sugar: describes a language feature that makes code easier to read and write, though there are alternative ways to express the same code.
Ruby’s creator, doesn't worry about the efficiency of the language. He optimizes the efficiency of the programmers.
Ruby is an interpreted, object-oriented, dynamically typed language from a family of so-called scripting languages.
Object-oriented means the language supports encapsulation (data and behavior are packaged together), inheritance through classes (object types are organized in a class tree), and polymorphism (objects can take many forms).
use ruby with console (irb):
>> puts 'hello, world'
hello, world
=> nil
>> language = 'Ruby'
=> "Ruby"
>> puts "hello, #{language}"
hello, Ruby
=> nil
>> language = 'my Ruby'
=> "my Ruby"
>> puts "hello, #{language}"
hello, my Ruby
=> nil
two types of strings: One quote around a string means the string should be interpreted literally, and two quotes leads to string evaluation.
Ruby is a pure object-oriented language:
>> 4
=> 4
>> 4.class
=> Fixnum
>> 4 + 4
=> 8
>> 4.methods
=> ["inspect", "%", "<<", "singleton_method_added", "numerator", ...
" * ", "+", "to_i", "methods", ... ]
if, unless:
>> x = 4
=> 4
>> puts 'This appears to be false.' unless x == 4
=> nil
>> puts 'This appears to be true.' if x == 4
This appears to be true.
=> nil
>> if x == 4
>> puts 'This appears to be true.'
>> end
This appears to be true.
=> nil
>> unless x == 4
>> puts 'This appears to be false.'
>> else
?> puts 'This appears to be true.'
>> end
This appears to be true.
=> nil
>> puts 'This appears to be true.' if not true
=> nil
>> puts 'This appears to be true.' if !true
=> nil
while, until:
>> x = x + 1 while x < 10
=> nil
>> x
=> 10
>> x = x - 1 until x == 1
=> nil
>> x
=> 1
>> while x < 10
>> x = x + 1
>> puts x
>> end
everything but nil and false evaluate to true . C and C++ programmers, take note. 0 is true !
irb(main):023:0> 5.class
=> Fixnum
irb(main):024:0> 5.0.class
=> Float
irb(main):025:0> 5 == 5.0
=> true
Ruby is strongly typed, meaning you’ll get an error when types collide. Ruby makes these type checks at run time, not compile time.
The keyword def defines a function but doesn't execute it.
converts to integer, method 'to_i':
>> i = 0
=> 0
>> a = ['100', 100.0]
=> ['100', 100.0]
>> while i < 2
>> puts a[i].to_i
>> i = i + 1
>> end
100
100
Duck Typing: http://en.wikipedia.org/wiki/Duck_typing
Duck typing doesn’t care what the underlying type might be. If it walks like a duck and quacks like a duck, it’s a duck. In this case, the quack method is to_i .
Every function returns something. If you do not specify an explicit return, the function will return the value of the last expression that’s processed before exiting. Like everything else, this function is an object.
>> def tell_the_truth
>> true
>> end
array:
>> animals = ['lions', 'tigers', 'bears']
=> ["lions", "tigers", "bears"]
>> puts animals
lions
tigers
bears
=> nil
>> animals[0]
=> "lions"
>> animals[2]
=> "bears"
>> animals[10]
=> nil
>> animals[-1]
=> "bears"
>> animals[-2]
=> "tigers"
>> animals[0..1]
=> ['lions', 'tigers']
>> (0..1).class
=> Range
>> a[0] = 0
NameError: undefined local variable or method `a' for main:Object
from (irb):23
>> a = []
=> []
>> [1].class
=> Array
>> [1].methods.include?('[]')
=> true
>> # use [1].methods.include?(:[]) on ruby 1.9
>> a[0] = 'zero'
=> "zero"
>> a[1] = 1
=> 1
>> a[2] = ['two', 'things']
=> ["two", "things"]
>> a
=> ["zero", 1, ["two", "things"]]
>> a = [[1, 2, 3], [10, 20, 30], [40, 50, 60]]
=> [[1, 2, 3], [10, 20, 30], [40, 50, 60]]
>> a[0][0]
=> 1
>> a[1][2]
=> 30
>> a = [1]
=> [1]
>> a.push(1)
=> [1, 1]
>> a = [1]
=> [1]
>> a.push(2)
=> [1, 2]
>> a.pop
=> 2
>> a.pop
=> 1
Arrays have an incredibly rich API. You can use an array as a queue, a linked list, a stack, or a set.
hash:
>> numbers = {1 => 'one', 2 => 'two'}
=> {1=>"one", 2=>"two"}
>> numbers[1]
=> "one"
>> numbers[2]
=> "two"
>> stuff = {:array => [1, 2, 3], :string => 'Hi, mom!'}
=> {:array=>[1, 2, 3], :string=>"Hi, mom!"}
>> stuff[:string]
=> "Hi, mom!"
The last hash: A symbol is an identifier preceded with a colon, like :symbol . Symbols are great for naming things or ideas. Although two strings with the same value can be different physical strings, identical symbols are the same physical object. You can tell by getting the unique object identifier of the symbol several times.
Ruby does not support named parameters:
>> def tell_the_truth(options={})
>> if options[:profession] == :lawyer
>> 'it could be believed that this is almost certainly not false.'
>> else
>> true
>> end
>> end
=> nil
>> tell_the_truth
=> true
>> tell_the_truth :profession => :lawyer
=> "it could be believed that this is almost certainly not false."
Notice that you didn’t have to type in the braces. These braces are optional for the last parameter of a function.
Code Blocks:
A code block is a function without a name. You can pass it as a parameter to a function or a method.
>> 3.times {puts 'hiya there, kiddo'}
hiya there, kiddo
hiya there, kiddo
hiya there, kiddo
The code between braces is called a code block. times is a method on
Fixnum that simply does something some number of times, where some-
thing is a code block and number is the value of the Fixnum . You can
specify code blocks with {/} or do/end . The typical Ruby convention is
to use braces when your code block is on one line and use the do/end
form when the code blocks span more than one line. Code blocks can
take one or more parameters
>> animals = ['lions and ', 'tigers and', 'bears', 'oh my']
=> ["lions and ", "tigers and", "bears", "oh my"]
>> animals.each {|a| puts a}
lions and
tigers and
bears
oh my
custom implementation of a method:
>> class Fixnum
>> def my_times
>> i = self
>> while i > 0
>> i = i - 1
>> yield
>> end
>> end
>> end
=> nil
>> 3.my_times {puts 'mangy moose'}
mangy moose
mangy moose
mangy moose
This code opens up an existing class and adds a method. In this case,
the method called my_times loops a set number of times, invoking the
code block with yield . Blocks can also be first-class parameters.
>> def call_block(&block)
>> block.call
>> end
=> nil
>> def pass_block(&block)
>> call_block(&block)
>> end
=> nil
>> pass_block {puts 'Hello, block'}
Hello, block
Class
irb(main):001:0> 4.class
=> Fixnum
irb(main):002:0> 4.class.superclass
=> Integer
irb(main):003:0> 4.class.superclass.class
=> Class
irb(main):004:0> 4.class.superclass.superclass
=> Numeric
irb(main):005:0> 4.class.superclass.superclass.superclass
=> Object
irb(main):006:0> 4.class.superclass.superclass.superclass.superclass
=> BasicObject
irb(main):007:0> 4.class.superclass.superclass.superclass.superclass.superclass
=> nil
Classes start with capital letters and typically use CamelCase to denote capitalization. You must prepend instance variables (one value per object) with @ and class variables (one value per class) with @@ . Instance variables
and method names begin with lowercase letters in the underscore_style . Constants are in ALL_CAPS .
class Tree
attr_accessor :children, :node_name
def initialize(name, children=[])
@children = children
@node_name = name
end
def visit_all(&block)
visit &block
children.each {|c| c.visit_all &block}
end
def visit(&block)
block.call self
end
end
ruby_tree = Tree.new( "Ruby",
[Tree.new("Reia"),
Tree.new("MacRuby")] )
puts "Visiting a node"
ruby_tree.visit {|node| puts node.node_name}
puts
puts "visiting entire tree"
ruby_tree.visit_all {|node| puts node.node_name}
The attr keyword defines an instance variable. Several versions exist. The most common are attr (defining an instance variable and a method of the same name to access it) and attr_accessor , defining an instance variable, an accessor, and a setter.
Module
A module is a collection of functions and constants. When you include a module as part of a class, those behaviors and constants become part of the class.
module ToFile
def filename
"object_#{self.object_id}.txt"
end
def to_f
File.open(filename, 'w') {|f| f.write(to_s)}
end
end
class Person
include ToFile
attr_accessor :name
def initialize(name)
@name = name
end
def to_s
name
end
end
Person.new('matz').to_f
What’s interesting here is that
to_s is used in the module but implemented in the class! The class
has not even been defined yet. The module interacts with the including
class at an intimate level. The module will often depend on several class
methods. With Java, this contract is explicit: the class will implement
a formal interface. With Ruby, this contract is implicit, through duck
typing.
The details of Person are not at all interesting, and that’s the point. The
Person includes the module, and we’re done. The ability to write to a file
has nothing to do with whether a class is actually a Person . We add the
capability to add the contents to a file by mixing in the capability. We
can add new mixins and subclasses to Person , and each subclass will
have the capabilities of all the mixins without having to know about
the mixin’s implementation. When all is said and done, you can use a
simplified single inheritance to define the essence of a class and then
attach additional capabilities with modules. This style of programming,
introduced in Flavors and used in many languages from Smalltalk to
Python, is called a mixin. The vehicle that carries the mixin is not always
called a module, but the premise is clear. Single inheritance plus mixins
allow for a nice packaging of behavior.
Enumerable
A couple of the most critical mixins in Ruby are the enumerable and
comparable mixins. A class wanting to be enumerable must implement
each , and a class wanting to be comparable must implement <=> . Called
the spaceship operator, <=> is a simple comparison that returns -1 if
b is greater, 1 if a is greater, and 0 otherwise. In exchange for imple-
menting these methods, enumerable and comparable provide many con-
venience methods for collections.
>> 'begin' <=> 'end'
=> -1
>> 'same' <=> 'same'
=> 0
>> a = [5, 3, 4, 1]
=> [5, 3, 4, 1]
>> a.sort
=> [1, 3, 4, 5]
>> a.any? {|i| i > 6}
=> false
>> a.any? {|i| i > 4}
=> true
>> a.all? {|i| i > 4}
=> false
>> a.all? {|i| i > 0}
=> true
>> a.collect {|i| i
*
2}
=> [10, 6, 8, 2]
>> a.select {|i| i % 2 == 0 } # even
=> [4]
>> a.select {|i| i % 2 == 1 } # odd
=> [5, 3, 1]
>> a.max
=> 5
>> a.member?(2)
=> false
set-based operations: collect, map, inject
>> a
=> [5, 3, 4, 1]
>> a.inject(0) {|sum, i| sum + i}
=> 13
>> a.inject {|sum, i| sum + i}
=> 13
>> a.inject {|product, i| product
*
i}
=> 60
Metaprogramming
Metaprogramming means writing programs that write programs. The ActiveRecord framework that’s the centerpiece of Rails uses metaprogramming to implement a friendly language for building classes that link to database tables.
class Department < ActiveRecord::Base
has_many :employees
has_one :manager
end
With the kind of freedom that lets you redefine any class or object at any time, you can build some amazingly readable code.
class NilClass
def blank?
true
end
end
class String
def blank?
self.size == 0
end
end
[ "" , "person" , nil].each do |element|
puts element unless element.blank?
end
another example:
class Numeric
def inches
self
end
def feet
self * 12.inches
end
def yards
self * 3.feet
end
def miles
self * 5280.feet
end
def back
self * -1
end
def forward
self
end
end
puts 10.miles.back
puts 2.feet.forward
Ruby calls a special debugging method each time a method is missing in order to print some diagnostic information. This behavior makes the language easier to debug. But sometimes, you can take advantage of this language feature to build some unexpectedly rich behavior. All you need to do is override method_missing.
class Roman
def self.method_missing name, *args
roman = name.to_s
roman.gsub!("IV", "IIII")
roman.gsub!("IX", "VIIII")
roman.gsub!("XL", "XXXX")
roman.gsub!("XC", "LXXXX")
(roman.count("I") +
roman.count("V") * 5 +
roman.count("X") * 10 +
roman.count("L") * 50 +
roman.count("C") * 100)
end
end
puts Roman.X
puts Roman.XC
puts Roman.XII
puts Roman.X
Weakness:
Concurrency and OOP
Object-oriented programming has a critical limitation. The whole prem-
ise of the model depends on wrapping behavior around state, and usu-
ally the state can be changed. This programming strategy leads to
serious problems with concurrency. At best, significant resource con-
tentions are built into the language. At worst, object-oriented systems
are next to impossible to debug and cannot be reliably tested for con-
current environments.