Ruby basics - Part I
on the basis of 'Eloquent Ruby' by Russ Olsen
Bartłomiej Skwira
Ruby's father - Yukihiro “Matz” Matsumoto
"Ruby inherited the Perl philosophy of having more than one way to do the same thing. I inherited that philosophy from Larry Wall, who is my hero actually. I want to make Ruby users free. I want to give them the freedom to choose."
"Ruby is NOT simple." but... "Tool Complexity is OK
if it makes the Solution Simple"
For me the purpose of life is partly to have joy. Programmers often feel joy when they can concentrate on the creative side of programming, So Ruby is designed to make programmers happy.
Write code that looks like Ruby
The Very Basic Basics
- good code is crystal clear
- good code is concise
-
1 indent == 2 spaces
- no tabs (!)
Go Easy on the Comments
-
"good code speaks for itself, greate code shouts it!"
-
write "how to use it" comments
-
write "how it works" for complicated code
Camels for Classes, Snakes Everywhere Else
*methods, variables, arguments
**usually, you can use camel case also
Parentheses Are Optional but Are Occasionally Required
def find_document title, author
#...
end
find_document 'Frankenstein', 'Shelley' good examples:
puts "hello world" "my lord!".instance_of? Stringif words.size < 100enddef words @content.split end
*although this might look more readable:
def find_document(title, author)
endParentheses Are Optional but Are Occasionally Required
When are parentheses required?
-
method chaining
a.b(c).d(e).fthing.should_receive(:some_method).once.with
-
1 line method definitions
def try(*args) puts args; end try 1,2 => 1 2def try *args puts args; endtry 1, 2, 4 SyntaxError: (irb):15: syntax error, unexpected tIDENTIFIER, expecting ';' or '\n' def try *args puts args; end; ^ (irb):15: syntax error, unexpected keyword_end, expecting end-of-input def try *args puts args; end;
Parentheses Are Optional but Are Occasionally Required
When are parentheses required?
task :rake => pre_rake_task do something end #really means: task(:rake => pre_rake_task){ something }#but task :rake => pre_rake_task { something } #really means: task :rake => (pre_rake_task { something })#solution - brackets task(:rake => pre_rake_task) { something }
Folding Up Those Lines
def method_to_be_overriden; puts 'I override you!'; endclass DocumentException < Exception; end
*don't cram too much into 1 line!
Folding Up Those Code Blocks
10.times { |n| puts "The number is #{n}" }
Rule 1:
single statement => braces
more statements => do..end
Rule 2:
blocks that return values => braces
blocks that are executed for side effects => do..end
# block used only for side effectlist.each do |item| puts item end # Block used to return test value list.find { |item| item > 10 } # Block value used to build new value list.collect { |item| "-r" + item }
Folding Up Those Code Blocks
Gotcha!
puts [1,2,3].map{ |k| k+1 } 2 3 4 => nilputs [1,2,3].map do |k| k+1; end #<Enumerator:0x000000009f9a20> <0x0000010a06d140 style="font-style: normal; font-variant: normal;">=> nil#same as [1,2,3].map() do |k| k+1; end
{} has a higher precedance than do..end
f g { }
#is parsed as f(g { })
f g do end
#is parsed as f(g) do end Moar!
-
true/false methods end with ?
[].empty? => true
-
unexpected or a bit dangerous methods end with !
a = [ 1, 2, [3, [4, 5] ] ]
a.flatten! => [1, 2, 3, 4, 5]
a => [1, 2, 3, 4, 5] - Ruby code is readable, so don't sacrifice readability just to follow conventions, be pragmatic
#bad codedoc.words.each { |word| some_really_really_long_expression( ... with lots of args ... ) }
-
some rules have exceptions
pi = Float('3.14159') #float is a method! Chapter 2
Choose the Right Control
Structure
If, Unless, While, and Until
-
unless
#turnif !@read_only #into unless @read_only#this is executed if "@read_only == false/nil"
-
until
#turn
while ! document.is_printed?
...
end
#into
until document.printed?
...
end If, Unless, While, and Until
-
when no to use unless
#don't use unless with negations and multiple conditions
unless !person.present? && !company.present?
end
#do
if person.present? && company.present?
end #don't use with else
unless person.present?
puts "Not present"
else
puts "Present"
end
#do
if person.present?
puts "Present"
else
puts "Not present"
end Use the Modifier Forms Where Appropriate
@title = new_title unless @read_onlydocument.print_next_page while document.pages_available?
Use each, Not for
fonts = [ 'courier', 'times roman', 'helvetica' ]for font in fonts puts font end fonts.each do |font| puts font end
-
for is an each in disguise
-
each has a scope
numbers = [1,2,3]numbers.each {|i| zmienna = i; } zmienna => NameError: undefined local variable or method `zmienna' for main:Objectfor i in numbers zmienna = i end zmienna => 3
A Case of Programming Logic
case title when 'War And Peace' puts 'Tolstoy' when 'Romeo And Juliet' puts 'Shakespeare' elseputs "Don't know"end
#which can be compacted to
author = case title
when 'War And Peace' then 'Tolstoy'
when 'Romeo And Juliet' then 'Shakespeare'
else "Don't know"
end
- in Ruby everything returns a value, even case (when no return value, nil is returned)
A Case of Programming Logic
Case uses === operator
-
for class Object - the same as == - subclasses override it
- for class Module - true if an object is an instance of specified class or its decendants (uquivalent to object.is_a? Class)
Fixnum === 2 #=> true Fixnum.ancestors => [Fixnum, Integer, Numeric, Comparable, Object, Kernel, BasicObject] Integer === 2 #=> true- for Range
(1...10) === 5 => true
A Case of Programming Logic
/AB/i === 'ab' => true
2 === Fixnum #=> false
A Case of Programming Logic
-
regex
case title when /War And .*/ puts 'Maybe Tolstoy?' when /Romeo And .*/ puts 'Maybe Shakespeare?'end
-
class
case doc when Document puts "It's a document!" when String puts "It's a string!"end
Moar!
-
only nil and false are treated as false, eveything else is true (also 0)
# Broken in a subtle way...
while next_object = get_next_object
# Do something with the object
end
until (next_object = get_next_object).nil?
# Do something with the object
end -
capture value of if
ret = if @not_before && @not_before > time [false, :expired, "not valid before '#@not_before'"] elsif @not_after && @not_after < time [false, :expired, "not valid after '#@not_after'"]end
Moar!
- initialization
*Don’t try to use ||= to initialize things to booleans - use memoist gem instead@first_name = '' unless @first_name#or@first_name ||= ''#which is not the same as "@first_name = @first_name || ''"
-
ternary operator
file = all ? 'specs' : 'latest_specs' Chapter 3
Take Advantage of Ruby’s
Smart Collections
Literal Shortcuts
-
hash initialization
book_info = { first_name: 'Russ', last_name: 'Olsen' }
book_info = { first_name: 'Russ', last_name: 'Olsen' } -
array of string initialization
poem_words = %w{ twinkle little star how I wonder }
=> ["twinkle", "little", "star", "how", "I", "wonder"]
Instant Arrays and Hashes from Method Calls
-
default parameter value
def load_font( name, size = 12 )
# Go font hunting...
end -
any number of parameters -> splat
def echo_all( *args ) args.each { |arg| puts arg } end#so no instead ofecho_all ["he", "ll", "o"]#you can doecho_all "he", "ll", "o"
Instant Arrays and Hashes from Method Calls
- you can have only 1 starred parameter, but anywhere in the list
def some_method(string, *args, hash) -
works other way around
my_array = [1,2,3]
#if some_method takes 3 parameters
some_method( *my_array ) -
hashes
load_font { :name => 'times roman', :size => 12 }Instant Arrays and Hashes from Method Calls
Splat kung-fu
-
array to hash conversion
array = [[key_1, value_1], [key_2, value_2], ... [key_n, value_n]] Hash[*array.flatten] #Hash[key1, value1, ...] = { key1 => value1, ... }- destructuring assignment
first, second, *rest = [1,2,3,4,5] first => 1 second => 2 rest => [3,4,5]
Running Through Your Collection
-
iterating hash
movie = {a: 1, b: 2}movie.each { |key, value| puts "#{key} => #{value}"}a => 1 b => 2 => {:a=>1, :b=>2}
-
find an index
["aa", "a", "Aa"].find_index { |word| word.length == 2 && word =~ /aa/ }=> 0
-
transform all elements -> map
require 'pp'pp ["aa", "a", "Aaa"].map { |word| word.size } =>[2, 1, 3]
Running Through Your Collection
-
inject
words = ["amber", "bit", "gold"]
total = words.inject(0.0){ |result, word| word.size + result}
total => 12.0
Beware the Bang!*
a = [1, 2, 3]
pp a.reverse => [3, 2, 1]
pp a =>[1, 2, 3]
a.reverse!
pp a => [3, 2, 1] -
not all array-changers have ! - push, pop, delete, shift etc..
*good title for a porn movie ;)
Rely on the Order of Your Hashes
- from Ruby 1.9 hashes are ordered!
hey_its_ordered = { first: 'mama', second: 'papa', third: 'baby' } hey_its_ordered.each { |entry| pp entry } [:first, "mama"] [:second, "papa"] [:third, "baby"]- adding a value -> to the end
Moar!
- delete all negative values
array.delete_if {|x| x < 0} - find - find one item that matches the block
%w[ruby is really nice].find { |item| item.length > 5 } => "really" -
group_by - groups elements with the block value as hash
%w[ruby is really nice].group_by { |item| item.length}
=> {4=>["ruby", "nice"], 2=>["is"], 6=>["really"]} - grep - search for members by pattern
%w[ruby is really nice].grep /rea/ => ["really"]
Moar!
grep uses === operator -> fuck yeah!
["lol", Object.new, :symbol, "Matz"].grep String =>
=> ["lol", "Matz"]
Other from Enumerable: all?, any?, collect, detect, each_cons, each_slice, each_with_index, entries, enum_cons, enum_slice, enum_with_index, find_all, include?, max, member?, min, partition, reject, select, sort, sort_by, to_a, to_set, zip
Chapter 4
Take Advantage of Ruby’s
Smart Strings
Coming Up with a String
- double or single (limited interpretation) quotes
a_string_with_a_quote = 'Say it ain\'t so!' double_quoted = "I have a tab: \t and a newline: \n"author = "Ben Bova" title = "Mars" puts "#{title} is written by #{author}"- nesting each other
str = '"Stop", she said, "I cannot deal with the backslashes."
Coming Up with a String
-
backslash hell
str = '"Stop", she said, "I can\'t live without \'s and "s."' arbitrarily quoted string to the rescue!
str = %q{"Stop", she said, "I can't live without 's and "s."} *we can use any other special character: (), {}, <>, [], $, ...
- interpreted version -> uppercase
str = %Q<The time in now #{Time.now}>
Coming Up with a String
- spannig across lines
a_multiline_string = "a multi-line
string"
another_one = %q{another multi-line
string} - no newlines
yet_another = %Q{another multi-line string with \
no newline}
-
here document for long multiline string
heres_one = <<RANDOM_TEXTof my here document. And this is the end. RANDOM _TEXT
Another API to Master
' hello'.lstrip => 'hello' #rstrip #remove 1 unwanted newline at the end "It was a dark and stormy night\n".chomp => "It was a dark and stormy night\n".chomp "hello\n\n\n".chomp => "hello\n\n" #remove last character "hello".chop => "hell" #upcase/downcase 'Hello'.swapcase => 'hELLO'#substitute 1 occurance'It is warm outside'.sub( 'warm', 'cold' ) => "It is cold outside"#substitute allputs 'yes yes'.gsub( 'yes', 'no' ) => no no
Another API to Master
#break into smaller parts'It was a dark and stormy night'.split=> ["It", "was", "a", "dark", "and", "stormy", "night"]'Bill:Shakespeare:Playwright:Globe'.split( ':' ) => ["Bill", "Shakespeare", "Playwright", "Globe"]#with regexps="http://www.website.com/dir1/dir2/file.txt"s.split /:\/\/|\.|\// => ["http", "www", "website", "com", "dir1", "dir2", "file", "txt"]#locate string"It was a dark and stormy night".index( "dark" ) => 9
The String: A Place for Your Lines, Characters, and Bytes
- string - a collection of characters
"lol".each_char {|c| puts c}
l
o
l
-
string - a collection of bytes
"lol".each_byte {|b| puts b}
108
111
108 The String: A Place for Your Lines, Characters, and Bytes
- string - a collection of lines
"some\n funky\n string\n here".each_line { |l| puts l}
some
funky
string
here
*from Ruby 1.9 string.each is gone
Moar!
-
strings are mutable
first_name = 'Karen' given_name = first_name #only 1 stringgiven_name << " Kowalski"first_name => "Karen Kowalski"
-
make unmutable
s = "omg" s.freeze s.frozen? => true s << "won't work" => RuntimeError: can't modify frozen String #unfreeze -> make new object s = "A new string" => "A new string"s.freeze s += "this will also work"=> "A new stringthis will also work"
- last character
first_name[-1]
Chapter 5
Find the Right String with
Regular Expressions
Basics
-
. - any single character except newline
-
\. - to match dot character (period)
-
[aeiou] - set, any single lowercase vowl
- [0-9] - range, single decimal digit
- \d - any digit
- \D - any non-digit
- \w - word character: letter, number, underscore
- \s - any whitespace character (tab, newline, space)
- \S - any non-whitespace character
- | - alternative, ie: AM|PM
- () - to set pattern off, ie: (AM|PM)
The Regular Expression Star
* - asterisk, zero or more of what was before, ie:
-
A* - zero or more A's
-
[aeiou]* - any number of vowls
-
.* - any number of any characters
Regular Expressions in Ruby
- literal syntax
/\d\d:\d\d (AM|PM)/
-
to match regex with string: =~
puts /\d\d:\d\d (AM|PM)/ =~ '10:24 PM' => 0
puts '10:24 PM' =~ /PM/ => 6
/May/ =~ 'Sometime in June' => nil
-
case insensitive:
'PM' =~ /am/i => 0
- string methods searching
@content.gsub!( /\d\d:\d\d (AM|PM)/, '**:** **' )
Beginnings and Endings
-
\A - beginning of string
-
\z - end of string
-
^ - begining of string or any newline within string
-
$ - end of string or any newline
content = 'The Princess And The Monkey
Once upon a time there was a princess...
...and they all lived happily ever after.
The End'
content =~ /^Once upon a time/ => 28 -
match across lines: /aeiou/m
/^Once upon a time.*happily ever after\.$/m Moar!
? - zero or one what was before
+ - one or more what was before
a{3} - excactly 3 of a
a{3, 6} - between 3 and 6
Tester tool http://rubular.com/
Chapter 6
Use Symbols to Stand for
Something
Basics
-
similar to strings: 'dog' vs :dog, but not quite the same
- the lack some of stings' methods
-
used as flags !
Optimized to Stand for Something
a = :all
b = :all
a == b => true
#check if objects are same
a.equal? b => true
s1 = "ruby"
s2 = "ruby"
s1 == s2 => true
s1.equal? s2 => false Moar!
-
conversion
:all.to_s => 'all'
'string'.to_sym => :string -
use strings for data, use symbols to stand for
* watch out for this
person = {}
person[:name] = 'russ'
person[:eyes] = 'misty blue'
# A little later...
puts "Name: #{person['name']} Eyes: #{person['eyes']}" *Rails - HashWithIndifferentAccess
Chapter 7
Treat Everything Like an Object—Because Everything Is
A Quick Review of Classes, Instances, and Methods
Every Ruby object is an instance of some class:
- no primitives!
Classes are:"str".class => String "str".class.class => Class1.class => Fixnum # nil.class => NilClass false.class => FalseClass-3.class => Fixnum/regex/.class => Regexp:symbol.class => Symbol
-
containers for methods
-
factories for making instances
doc = Document.new( 'Ethics', 'Spinoza', 'By that which is...' ) =>A Quick Review of Classes, Instances, and Methods
-
self - instance that called the method
class Document
...
def about_me
puts "I am #{self}"
puts "My title is #{self.title}"
puts "I have #{self.word_count} words"
end
end - calling a method without explicit object reference - defaults to self
A Quick Review of Classes, Instances, and Methods
Gotcha:
class Foo attr_writer :bar def do_something bar = 2 end end
-
every class has a superclass, when no superclass is explicitly defined - superclass defaults to Object
-
method call - find the method in the class, when no method found search in superclass, reapeat until no more superclasses left
The Importance of Being an Object
Some of Object's methods:
"lol".class => String
"lol".instance_of? String => true
some_object.to_s => "#<0x00000001e75cb0>"
eval "puts 'hello'" => hello
"lol".public_methods => [:<=>, :==, :===, :eql?, :hash, :casecmp, :+, :*, :%, :[], :[]=, :insert, :length, :size, :bytesize, ....]
class Document
def initialize
@name = "Borat"
end
end
d = Document.new
d.instance_variables => [:@name] Public, Private, and Protected
- methods are public by default
-
making private
class Document
# Most of the class omitted
private
# Methods are private starting here
def word_count
return words.size
end
end or this way
class Document # Most of the class omitted def word_count return words.size endprivate :word_count end
Public, Private, and Protected
-
private methods are callable from the class that defines them
class Document
# Most of the class omitted...
def word_count
return words.size
end
private :word_count
# This method works because self is the right thing,
# the document instance, when you call it.
def print_word_count
n = word_count
puts "The number of words is #{word_count}"
end
end Public, Private, and Protected
-
private methods are callable from subclasses
# RomanceNovel is a subclass of Document, # which is a subclass of Object class RomanceNovel < Document def number_of_steamy_words word_count / 4 # Works: self is a Document instance! end endrn = RomanceNovel.newrn.word_count =>NoMethodError: private method `word_count' called for <RomanceNovel:0x00000001049088>
-
Any instance of a class can call a protected method on any other instance of the class, so any instance of Document can call word_count on any other instance of Document (including subclasses)
Public, Private, and Protected
-
private and protected aren't widely used: Ruby standard library 20 000 lines of code, 1000 private, 50 protected
-
calling a private method outside of class:
n = doc.send( :word_count )
Moar!
method calls:
-
private, protected
-
attr_accessor, attr_writer, attr_reader
-
require
not everything is a method call:
-
assigning a value
-
if statement
-
while
Fin
* lightning talk my ass ;]
Ruby basics - Part I
By Bartlomiej Skwira
Ruby basics - Part I
- 764