Good notes posted to Ruby

RSS feed
August 20, 2009
3 thanks

Symbol#to_proc

@tadman - or simply defining:

 class Symbol
   def to_proc
     proc { |obj, *args| obj.send(self, *args) }
   end
 end
July 8, 2009 - (<= v1_8_7_72)
4 thanks

Using block version in Ruby < 1.8.7

The block usage was added in 1.8.7, so to get the same functionality in an earlier version of Ruby, you need to utilize the find method.

Here is a quick example:

  match = list.find { |l| l.owner == myself }
  match_index = list.index(match)

If you do some gymnastics, you can have it on one line without extra variables:

  match_index = list.index(list.find { |l| l.owner == myself })
May 13, 2009
3 thanks

Equivalent to Array#reject!

This method is functionally identical to Array#reject!

May 2, 2009
5 thanks

Create a Hash from two Arrays

Here is my favorite idiom for creating a Hash from an Array of keys and an Array of values:

  keys = [:a, :b]
  values = [1,2]
  h = Hash[*keys.zip(values).flatten]      # => {:b=>2, :a=>1}
April 23, 2009
4 thanks

Handy shorthand for array manipulation

You may write something like this:

  >> ['a', 'b', 'c'].collect{|letter| letter.capitalize}
  => ["A", "B", "C"]

But it looks so much nicer this way:

  >> ['a', 'b', 'c'].collect(&:capitalize)
  => ["A", "B", "C"]
April 21, 2009
3 thanks

To throw an exception, use Kernel#raise

Other languages use the term throw for raising exceptions, but Ruby has a specific raise call for that.

April 16, 2009
4 thanks

Parameters for Hash#inject

When running inject on a Hash, the hash is first converted to an array before being passed through.

The typical Enumerable#inject approach would be to simply capture the value:

  array.inject(...) do |c, v|
  end

In the case of a Hash, v is actually a key/value pair Array. That is the key is v.first and the value is v.last, however using the pair this way is awkward and can lead to confusion.

Better to simply expand the parameters in the block definition:

  hash.inject(...) do |c, (k, v)|
  end

Where c is the traditional carry variable and k/v represent key and value respectively.

March 27, 2009
4 thanks

Hour with/without preceding zero

One gotcha is the difference between the hour in 12 hour time with and without a preceding zero. In some fonts they look the same.

With preceding zero (capital I)

  Time.now.strftime("%I:%M") # => 05:21

Without preceding zero (lowercase L)

  Time.now.strftime("%l:%M") # => 5:21
March 5, 2009
6 thanks

String#match will match single token only

  >> s = "{{person}} ate {{thing}}"
  => "{{person}} ate {{thing}}"
  >> r = /\{\{(.*?)\}\}/
  => {{}}
  >> s.match(r).captures
  => ["person"]

Using String#scan pulls out all tokens you were searching for:

  >> s.scan(r).flatten
  => ["person", "thing"]
March 3, 2009
5 thanks

File class documentation

Most of the File class documentation is located in IO class docs. What you see here is what ‘ftools’ gives you.

February 16, 2009
4 thanks

Usage example

Some examples:

 # Remove even numbers
 (1..30).reject { |n| n % 2 == 0 }
 # => [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]

 # Remove years dividable with 4 (this is *not* the full leap years rule)
 (1950..2000).reject { |y| y % 4 != 0 }
 # => [1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000]

 # Remove users with karma below arithmetic mean
 total = users.inject(0) { |total, user| total += user.karma }
 mean = total / users.size
 good_users = users.reject { |u| u.karma < mean }
February 12, 2009
4 thanks

Binary files

Another real important flag is b when dealing with binary files. For example to download an mp3 from the internet you need to pass the b flag or the data will be screwed up:

  # Downloads a binary file from the internet
  require 'open-uri'
  url = "http://fubar/song.mp3"
  open(url, 'rb') do |mp3|
    File.open("local.mp3", 'wb') do |file|
      file.write(mp3.read)
    end
  end

Don’t say you haven’t been warned. :)

February 12, 2009
3 thanks

Other regular-expression modifiers

Likewise you can set Regexp::IGNORECASE directly on the regexp with the literal syntax:

  /first/i
  # This will match "first", "First" and even "fiRSt"

Even more modifiers

  • o — Perform #{} interpolations only once, the first time the regexp literal is evaluated.
  • x — Ignores whitespace and allows comments in * regular expressions
  • u, e, s, n — Interpret the regexp as Unicode (UTF-8), EUC, SJIS, or ASCII. If none of these modifiers is specified, the regular expression is assumed to use the source encoding.

Literal to the rescue

Like string literals delimited with %Q, Ruby allows you to begin your regular expressions with %r followed by a delimiter of your choice.

This is useful when the pattern you are describing contains a lot of forward slash characters that you don’t want to escape:

  %Q(http://)
  # This will match "http://"
February 12, 2009
3 thanks

Literal syntax

As you propably know you can create an Array either with the constructor or the literal syntax:

  Array.new == []
  # => true

But there is also another nice and concise literal syntax for creating Arrays of Strings:

  ["one", "two", "three"] == %w[one two three]
  # => true

You can use any kind of parenthesis you like after the %w, either (), [] or {}. I prefer the square brackets because it looks more like an array.

February 12, 2009
3 thanks

Useful scenario

This can be quite useful, for example when writing a command line script which takes a number of options.

Example

Let’s say you want to make a script that can make the basic CRUD operations. So want to be able to call it like this from the command line:

  > my_script create
  > my_script delete

The following script allows you to use any abbreviated command as long as it is unambiguous.

  # my_script.rb
  require 'abbrev'

  command = ARGV.first
  actions = %w[create read update delete]
  mappings = Abbrev::abbrev(actions)
  puts mappings[command]

That means you can call it like this:

  > my_script cr
  > my_script d

And it will print:

  create
  delete
February 4, 2009
3 thanks

Multiline regexps

A shortcut for multiline regular expressions is

  /First line.*Other line/m

(notice the trailing /m)

For example:

  text = <<-END
    Hello world!
    This is a test.
  END

  text.match(/world.*test/m).nil?  #=> false
  text.match(/world.*test/).nil?   #=> true
January 14, 2009
6 thanks

File open permissions

Usage: File.open path, flags, [permissions]

Flags (bitmasks)

Access:

File::RDONLY:Read-only
File::WRONLY:Write-only
File::RDWR:Read and write

If the file exists:

File::TRUNC:Truncate
File::APPEND:Append
File::EXCL:Fail

If the file doesn’t exist:

File::CREAT:Create

Flags (strings)

r:File::RDONLY
r+:File::RDWR
w:File::WRONLY|File::TRUNC|File::CREAT
a:File::WRONLY|File::APPEND|File::CREAT

Examples

 File.open path, File::RDONLY
 File.open path, 'w'
 File.open path, File::WRONLY|File::TRUNC|File::CREAT
 File.open path, File::WRONLY|File::TRUNC|File::CREAT, '0666'
December 6, 2008
3 thanks

Array expansion in blocks

The syntax can be improved as changing the second parameter of the block (values) and using an array of two variables instead, which will be used by Ruby as the key and value of "array".

  array = [['A', 'a'], ['B', 'b'], ['C', 'c']]

  hash = array.inject({}) do |memo, (key, value)|
    memo[key] = value
    memo
  end

  hash
  # => {'A' => 'a', 'B' => 'b', 'C' => 'c'}
December 2, 2008
4 thanks

From the official docs

  enum.inject(initial) {| memo, obj | block } => obj
  enum.inject {| memo, obj | block } => obj

Combines the elements of enum by applying the block to an accumulator value (memo) and each element in turn. At each step, memo is set to the value returned by the block. The first form lets you supply an initial value for memo. The second form uses the first element of the collection as a the initial value (and skips that element while iterating).

   # Sum some numbers
   (5..10).inject {|sum, n| sum + n }              #=> 45
   # Multiply some numbers
   (5..10).inject(1) {|product, n| product * n }   #=> 151200

   # find the longest word
   longest = %w{ cat sheep bear }.inject do |memo,word|
      memo.length > word.length ? memo : word
   end
   longest                                         #=> "sheep"

   # find the length of the longest word
   longest = %w{ cat sheep bear }.inject(0) do |memo,word|
      memo >= word.length ? memo : word.length
   end
   longest                                         #=> 5

http://www.ruby-doc.org/core/classes/Enumerable.html

November 19, 2008
7 thanks

Formatting options

Readable strftime

%a - The abbreviated weekday name (``Sun’‘)

%A - The full weekday name (``Sunday’‘)

%b - The abbreviated month name (``Jan’‘)

%B - The full month name (``January’‘)

%c - The preferred local date and time representation

%d - Day of the month (01..31)

%H - Hour of the day, 24-hour clock (00..23)

%I - Hour of the day, 12-hour clock (01..12)

%j - Day of the year (001..366)

%m - Month of the year (01..12)

%M - Minute of the hour (00..59)

%p - Meridian indicator (``AM’‘ or ``PM’‘)

%S - Second of the minute (00..60)

%U - Week number of the current year, starting with the first Sunday as the first day of the first week (00..53)

%W - Week number of the current year, starting with the first Monday as the first day of the first week (00..53)

%w - Day of the week (Sunday is 0, 0..6)

%x - Preferred representation for the date alone, no time

%X - Preferred representation for the time alone, no date

%y - Year without a century (00..99)

%Y - Year with century

%Z - Time zone name %% - Literal ``%’’ character t = Time.now t.strftime("Printed on %m/%d/%Y") #=> "Printed on 04/09/2003" t.strftime("at %I:%M%p") #=> "at 08:56AM"

November 18, 2008
5 thanks

Pop for last, Shift for first

If you want to pop the first element instead of the last one, use shift .

October 9, 2008
5 thanks

Works with URLs too!

You can use it for web urls as well:

  path, file = File.split('/uploads/art/2869-speaking-of-pic.jpg')

  p path # => "/uploads/art"

  p file # => "2869-speaking-of-pic.jpg"

And you can also use join, to merge url back from the components:

  path = File.join(["/uploads/art", "2869-speaking-of-pic.jpg"])

  p path # => "/uploads/art/2869-speaking-of-pic.jpg"

Using #join and #split for operations on files and path parts of the URLs is generally better than simply joining/splitting strings by ’/’ symbol. Mostly because of normalization:

  File.split('//tmp///someimage.jpg') # => ["/tmp", "someimage.jpg"]

  '//tmp///someimage.jpg'.split('/') # => ["", "", "tmp", "", "", "someimage.jpg"]

Same thing happens with join.

September 12, 2008
23 thanks

Readable strftime

%a - The abbreviated weekday name (``Sun’‘)

%A - The full weekday name (``Sunday’‘)

%b - The abbreviated month name (``Jan’‘)

%B - The full month name (``January’‘)

%c - The preferred local date and time representation

%d - Day of the month (01..31) %H - Hour of the day, 24-hour clock (00..23)

%I - Hour of the day, 12-hour clock (01..12)

%j - Day of the year (001..366)

%m - Month of the year (01..12) %M - Minute of the hour (00..59)

%p - Meridian indicator (``AM’‘ or ``PM’‘)

%S - Second of the minute (00..60)

%U - Week number of the current year, starting with the first Sunday as the first day of the first week (00..53)

%W - Week number of the current year, starting with the first Monday as the first day of the first week (00..53)

%w - Day of the week (Sunday is 0, 0..6)

%x - Preferred representation for the date alone, no time

%X - Preferred representation for the time alone, no date

%y - Year without a century (00..99) %Y - Year with century

%Z - Time zone name %% - Literal ``%’’ character t = Time.now t.strftime("Printed on %m/%d/%Y") #=> "Printed on 04/09/2003" t.strftime("at %I:%M%p") #=> "at 08:56AM"

August 23, 2008
3 thanks

Needs requiring 'enumerator' to work

This method needs that you

  require 'enumerator'

for this method to be available.

August 17, 2008
5 thanks

Re: Convert an Array of Arrays to a Hash using inject

If you’re sure you have a two-level array (no other arrays inside the pairs) and exactly two items in each pair, then it’s faster and shorter to use this:

  array = [['A', 'a'], ['B', 'b'], ['C', 'c']]
  hash = Hash[*array.flatten]

For more than two-level deep arrays this will give the wrong result or even an error (for some inputs).

  array = [['A', 'a'], ['B', 'b'], ['C', ['a', 'b', 'c']]]
  hash = Hash[*array.flatten]
  # => {"A"=>"a", "B"=>"b", "C"=>"a", "b"=>"c"}

But if you’re running Ruby 1.8.7 or greater you can pass an argument to Array#flatten and have it flatten only one level deep:

  # on Ruby 1.8.7+
  hash = Hash[*array.flatten(1)]
  # => {"A"=>"a", "B"=>"b", "C"=>["a", "b", "c"]}
August 17, 2008
4 thanks

Regexes with groups and split

When you use a Regex with capture groups, all capture groups are included in the results (interleaved with the "real" results) but they do not count for the limit argument.

Examples:

  "abc.,cde.,efg.,ghi".split(/.(,)/)
  => ["abc", ",", "cde", ",", "efg", ",", "ghi"]
  "abc.,cde.,efg.,ghi".split(/(.)(,)/)
  => ["abc", ".", ",", "cde", ".", ",", "efg", ".", ",", "ghi"]
  "abc.,cde.,efg.,ghi".split(/(.(,))/)
  => ["abc", ".,", ",", "cde", ".,", ",", "efg", ".,", ",", "ghi"]
  "abc.,cde.,efg.,ghi".split(/(.(,))/, 2)
  => ["abc", ".,", ",", "cde.,efg.,ghi"]
  "abc.,cde.,efg.,ghi".split(/(.(,))/, 3)
  => ["abc", ".,", ",", "cde", ".,", ",", "efg.,ghi"]
August 15, 2008
4 thanks

Cheking if a number is prime?

It’s a class for generating an enumerator for prime numbers and traversing over them.

It’s really slow and will be replaced in ruby 1.9 with a faster one.

Note: if you just want to test whether a number is prime or not, you can use this piece of code:

  class Fixnum
    def prime?
      ('1' * self) !~ /^1?$|^(11+?)\1+$/
    end
  end

  10.prime?
August 15, 2008
6 thanks

Optional Argument for detect/find [Not Documented]

detect/find’s optional argument lets you specify a proc or lambda whose return value will be the result in cases where no object in the collection matches the criteria.

  classic_rock_bands = ["AC/DC", "Black Sabbath","Queen", "Ted Nugent and the Amboy Dukes","Scorpions", "Van Halen"]
  default_band = Proc.new {"ABBA"}
  classic_rock_bands.find(default_band) {|band| band > "Van Halen"}
  => "ABBA"

or

  random_band = lambda do
    fallback_bands = ["Britney Spears", "Christina Aguilera", "Ashlee Simpson"]
    fallback_bands[rand(fallback_bands.size)]
  end
  classic_rock_bands.find(random_band) {|band| band > "Van Halen"}
  => "Britney Spears"
August 15, 2008
3 thanks

Using all? on Empty Arrays and Hashes

When applied to an empty array or hash, with or without a block, all? always returns true. That’s because with an empty collection, there are no values to process and return a false value. so, watch out, if your array or hash is empty for any reason you will get a true which might not be what you expect it to be.