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
Comments (2)