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.
 Chapter 1

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


  • CamelCaseIsForClassNames

  • snake_case_is_everywhere_else*

  • UPPERCASE_IS_FOR_CONSTANTS**



  • *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 < 100
    enddef words @content.split end
      *although this might look more readable:
    def find_document(title, author)
    end

    Parentheses Are Optional but Are Occasionally Required

    When are parentheses required?
    • method chaining
    a.b(c).d(e).f thing.should_receive(:some_method).once.with
    • 1 line method definitions
    def try(*args) puts args; end
    try 1,2
    => 1
    2 
    def try *args puts args; end
    try 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!'; end 
    
    
    class 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 effect  list.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
    => nil
    puts [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 code doc.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_only
    
    document.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:Object

      for 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' 
    else   puts "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


  • for Regexp
  • /AB/i === 'ab' => true  

  • is not cummutative
  • 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
    @first_name = '' unless @first_name #or@first_name ||= '' 
    #which is not the same as "@first_name = @first_name || ''"
       *Don’t try to use ||= to initialize things to booleans - use memoist gem instead

    • 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' } 

    • 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 of echo_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 string given_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


  • there can be only one instance of any given symbol
  • 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 
  • symbols are immutable - can't uppercase it etc..
  • *this makes them ideal for hash keys

    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!
    "str".class => String
    "str".class.class => Class1.class => Fixnum #
    nil.class => NilClass
    false.class => FalseClass -3.class => Fixnum/regex/.class => Regexp:symbol.class => Symbol
    Classes are:
    • 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 

    * won't work -> bar in do_something is local, use self


    • 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  
      end
    private :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
    end 
    rn = 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