Archive for the ‘ruby idioms’ Category

Ruby idioms : Avoid check class membership

Avoid use class, is_a? and kind_of? to check class membership. Replace these statement with a message to the object that respond to the message. The code below is an anti-pattern.

if mortgage.class==FixRateMortgage
   mortgage.change_rate_not_allowed
else
   mortgage.rate=rate
end

The code below is more idiomatic in Ruby.

class Mortgage
  attr_accessor :rate
end 

class FixedRateMortgage < Mortgage
  def initialize()
    @rate=5
  end
  def rate=(rate)
    change_rate_not_allowed
  end

  def change_rate_not_allowed
     puts "The rate in a fixed rate mortage can not be change"
  end
end

Sending the method rate= to either kind of classes will result in the appropriate behaviour.

Ruby idioms : Add a method to a concrete instance.

Sometimes can be useful to add a method in a specific instance of a class. We can do that with the next code :

 class Person
 end 

 pedro = Person.new
 peter = Person.new

 # inject a method in the instances
 def pedro.hello_world; puts "Hola Mundo"; end

 def peter.hello_world; puts "Hello World"; end 
 
 pedro.hello_world #=> Hola Mundo
 peter.hello_world #=> Hello World

A more useful example, an Array that represent a list of employees that must be sortable by any of the employee attributes : (via Neal Ford)

  employees  = []
  def employees.sort_by_attribute(sym)
    sort {|x, y| x.send(sym) <=> y.send(sym)}
  end

Default values and Hash parameters

I had talked before about it. We can use a hash to pass parameter to our method, and que can merge with another hash that contain the defaults values. Why to do that :

  • Set Default Values in Methods : Avoid unexpected behaviour.
  • Simple Hash as Default Method Parameter : This form allows you to add functionality to methods without breaking existing calls
def add_book(p_arg)

  p_arg = {:title=>"no title", 
           :author=>"anonymous",
           :language=>"esperanto"}.merge! p_arg

end
add_book :title=>"El Qujote",
         :author=>"Miguel De Cervantes",
         :language=>"spanish"

=> {:language=>"spanish", :title=>"El Qujote", :author=>"Miguel De Cervantes"}
add_book :title=>"El Qujote",
         :author=>"Miguel De Cervantes"

=> {:language=>"esperanto", :title=>"El Qujote", :author=>"Miguel De Cervantes"}
add_book :title=>"El Qujote",
         :author=>"Miguel De Cervantes"
         :foo=>:bar

=> {:language=>"esperanto", :foo=>"bar" ,:title=>"El Qujote", :author=>"Miguel De Cervantes"}

Multiples assignmets in a for loop clause.

1
2
3
4
5
 for hello, world in [ [:Hola, :Mundo],
                       [:Hallo, :Welt], 
                       [:Foo, :Bar]    ]
   puts "#{hello} #{world}"
 end

Variable/optional parameter methods, symbols and ruby

First if you want only for optional parameter in a method, just see the next two examples :

 

 

def foo(bar=:hello)
  puts bar
end

foo     #=> hello
foo bye #=> bye

The second example :

 

 

def add_book(title=>"no title")
  arg={:, :author=>"anonymous", :language=>"esperanto"}
  arg.merge! p_argirb(main):020:1>
end

add_book :title=>"El Qujote", :author=>"Miguel De Cervantes", :language=>"spanish"
=> {:language=>"spanish", :title=>"El Qujote", :author=>"Miguel De Cervantes"}

add_book :title=>"El Qujote", :author=>"Miguel De Cervantes"
=> {:language=>"esperanto", :title=>"El Qujote", :author=>"Miguel De Cervantes"}

add_book :title=>"El Qujote", :author=>"Miguel De Cervantes" :foo=>:Bar
=> {:language=>"esperanto", :title=>"El Qujote", :author=>"Miguel De Cervantes"}

Sometimes is usefull to have an optional/variable parameters method, . In a recent domain specific languages (DSL) design i have to implement a object that have some optional parameters.

For example we want to add a book to our database, But we don’t want to write every single attribute, is the attribute is not present then is initialize with a default value. The properties are

add_book :title=>"El quijote", :author=> "Miguel de Cervantes"
add_book :title=>"El quijote", :author=> "Miguel de Cervantes", :tag=>"novel"
add_book :title=>"El quijote"
add_book :title=>"hogehoge", :language=>"Japanese" # => raise ArgumentError

Asking in the ruby list, Hidetoshi NAGAI, teach me how to use symbols, variable parameter methods (varargs), to create this clean example. The main idea is use a hash for parameter value preinitializate and check is the parameter is in the list. BTW, he also override the method_missing method to emulate all the getters and setters for the object.

 

 

class Book
  def initialize(properties = {})
      @props = {
          'title' => '',
          'author' => '',
          'tag' => nil,
          'price' => nil,
          'ISBN' => nil,
      }

      properties.each{|k, v|
          k = k.to_s
          raise ArgumentError, "unknown property: #{k}" unless @props.key?(k)
          @props[k] = v
      }
  end

  def method_missing(id, *args)
      prop = id.id2name
      case args.length
      when 1

          if prop[-1] == ?= # it will be a setter
              self[prop[0..-2]] = args[0]
              args[0]
          else # Else it will be "self.property(val)".
                                  # Then call "self[] = val"
              self[prop] = args[0]
              self
          end
      when 0 # the getter
          self[prop]
      else #continue the search
          super(id, *args)
      end
  end

  def [](prop)
      prop = prop.to_s
      raise ArgumentError, "unknown property: #{prop}" unless @props.key?(prop)
      @props[prop]
  end

  def []=(prop, val)
      prop = prop.to_s
      raise ArgumentError, "unknown property: #{prop}" unless @props.key?(prop)
      @props[prop] = val
  end
end

def add_book(props)
  book = Book.new(props)
  puts "case1: title : #{book.title} -- Author : #{book.author}"
  puts "case2: title : #{book[:title]} -- Author : #{book[:author]}"
  puts "case3: title : #{book['title']} -- Author : #{book['author']}"
  book
end

In a next post i explain ho to to improve the optinal parameter using meta-programming.

Ruby idioms : The splat operator.

The split mode :

   pet1, pet2, pet3 = *["duck","dog","cat"]

The collect mode :

  *zoo = pet1, pet2, pet3

The splat operator can be used in a case statement :

BOARD_MEMBERS = ['Jan', 'Julie', 'Archie', 'Stewick']
HISTORIANS = ['Braith', 'Dewey', 'Eduardo']case name
 when *BOARD_MEMBERS
  "You're on the board!  A congratulations is in order."
 when *HISTORIANS
  "You are busy chronicling every deft play." 
 when *HISTORIANS|BOARD_MEMBERS
  "We welcome you all to the First International
   Symposium of Board Members and Historians Alike."
 end

We can also use the splat operator to create a Hash object form a array of pairs.

 

 

a = [[:planes, 21], [:cars, 36]]
h = Hash[*a]  # => { :planes=>21, :cars=>36}

The ||= and |= operators

We initialize a variable with a value with the operator |=, but the value is only assigned if the variable has not been initialized yet.

  # Forma mas o menos normal
  if not breakfast
     breakfast = :bacon
  end

  # Esta forma es mas concisa y mas parecida al lenguaje humano
  breakfast = :bacon unless breakfast

  #usando ||=
  breakfast ||= :bacon

With the ||= we can add an object to an array, but if the object is already present the array, the operation dosnt introduce repetition in the array.

    animals  = ['dog','cat']

    # usando un condicion
    animlas += ['dog'] unless animals.include? ('dog')

    # using |=
    >> animal = ['dog','cat']
    => ["dog", "cat"]

    >> animal |= ['cat']
    => ["dog", "cat"]

    >> animal |= ['cat']
    => ["dog", "cat"]

    >> animal += ['cat']
    => ["dog", "cat", "cat"]

Ruby idioms : case

How to use the case statement :

case val
  when 5: puts 'integer'
  when 'foo': puts 'string'
  when :a, :b, :c: puts 'list'
  when *[:d, :e, :f]: puts 'splatted list'
  when /d{2,4}/: puts 'regex'
  else puts 'the rest'
end
Follow

Get every new post delivered to your Inbox.