Ruby Quicktips Logo

Ruby Quicktips

Random Ruby and Rails tips.
This blog is dedicated to deliver short, interesting and practical tidbits of the Ruby language and Ruby on Rails framework. Read more...

Your submissions are more than welcome!
Aug 18 ’12

Quick Hash to a URL Query trick

Got a hash of values you want to convert into a url query string? Use the to_query method:

"http://www.example.com?" + { language: "ruby", status: "awesome" }.to_query
# => "http://www.example.com?language=ruby&status=awesome" 

Want to do it in reverse? Use CGI.parse:

require 'cgi' # Only needed for IRB, Rails already has this loaded
CGI::parse "language=ruby&status=awesome"
# => {"language"=>["ruby"], "status"=>["awesome"]} 

Both methods support nested values.

This tip was submitted by Victor Solis.

9 notes 0 comments

Jul 22 ’12

Testing CSV file uploads

Let’s say you have a form like this for uploading a CSV file:

<%= form_tag csv_import_path, :multipart => true do %>
  <%= file_field_tag :file, :accept => "text/csv" %>
  <%= submit_tag "Upload" %>
<% end %>

And your controller action looks like this:

require 'csv'

def csv_import    
  file_data = params[:file].read
  csv_rows  = CSV.parse(file_data)

  csv_rows.each do |row|
    # do something with each row
  end
    
  respond_to do |format|
    format.html { redirect_to your_path, :notice => "Successfully imported the CSV file." }
  end
end

How would you test this functionality?

You can’t use fixture_file_upload and stick a sample file in test/fixtures/files/ (as often suggested - e.g. here on SO). That’s because CSV and YAML files will be imported as fixtures into the test database as soon as you run tests. E.g., this would not work:

def test_file_upload
  post :csv_import, :file => fixture_file_upload('files/new_users.csv','text/csv')
end

But what you can do is to use the Tempfile and Rack::Test::UploadFile classes and manually create a CSV file and supply this to the post (or put) method:

def test_should_successfully_import_csv
  csv_rows = <<-eos
Name1,name1@example.com
Name2,name2@example.com
Name3,name3@example.com
eos

  file = Tempfile.new('new_users.csv')
  file.write(csv_rows)
  file.rewind

  assert_difference "User.count", 3 do
    post :csv_import, :file => Rack::Test::UploadedFile.new(file, 'text/csv')
  end

  assert_redirected_to your_path
  assert_equal "Successfully imported the CSV file.", flash[:notice]
end

4 notes 0 comments

May 3 ’12

Using ActiveRecord to query for times within a range

You can pass a range to query for records within that range:

hackerinspiration:

Just discovered this, something I wish I knew a LONG time ago.

Album.where(:created_at => 2.days.ago..Time.now)

Which will generate the following SQL query (depending on the database):

SELECT "albums".* FROM "albums" WHERE ("albums"."created_at" BETWEEN '2012-04-28 11:10:22.780712' AND '2012-04-30 11:10:22.780907')

14 notes 0 comments (via hackerinspiration)

May 1 ’12

Try out your helpers in the Rails console

Ever wanted to try out any of Rails’ view helper methods in the console? Just include ActionView::Helpers after starting the Rails console:

include ActionView::Helpers
# => Object
text_field(:post, :title)
# => "<input id=\"post_title\" name=\"post[title]\" size=\"30\" type=\"text\" />"

2 notes 0 comments

Apr 19 ’12

Creating regular expressions with r{} literals

Creating a regular expression using the r{} literals can for example be helpful when matching URLs, because you don’t have to escape slashes:

url = "http://example.com/"

# /../ literals:
url.match /http:\/\/example\.com\//
# => #<MatchData "http://example.com/">

# %r{} literals:
url.match %r{http://example\.com/}
# => #<MatchData "http://example.com/">

4 notes 0 comments

Apr 17 ’12

Create a hash from an array with Hash[*arr]

The Hash::[] class method can be used in addition to the splat operator to create a hash from an array.

arr = [:a, 1, :b, 2]
Hash[*arr]
# => {:a => 1, :b => 2}

6 notes 0 comments

Apr 12 ’12

Rails’ index_by: the easy way to convert an Array to a Hash

As Aditya Sanghi notes in "Quickly convert an Array to a Hash", a very quick way to convert an Array to a Hash, is to use Rails’ Enumerable#index_by:

Post.all.index_by { |post| post.id }
# => { 1 => #<Post ...>, 2 => #<Post ...>, ... }

Post.all.index_by(&:title)
# => { "My first post" => #<Post ...>, "My second post" => #<Post ...>, ... }

6 notes 0 comments

Apr 11 ’12

Convert a number string in any base to a decimal number

As Brich Thompson notes in the comments on "Convert between number bases easily", you can also convert a number string in any base to a decimal number with the String#to_i method:

"21".to_i     #=> 21 
"21".to_i(8)  #=> 17
"21".to_i(16) #=> 33

4 notes 0 comments

Apr 4 ’12

Two Ruby tricks using method chaining and Procs

[1,2,3].each_with_object(1).map(&:+)
# => [2, 3, 4]

# Same outcome, even shorter
[1, 2, 3].map(&1.method(:+))
# => [2, 3, 4]

In his blog post In Ruby, &method passes you!, Andrew Grimm explains how this all works.

Both snippets via Peter Cooper.

7 notes 0 comments

Mar 29 ’12

Enumerable#each_with_object

Enumerable#each_with_object is quite similar to Enumerable#inject, yet slightly different. From the Ruby 1.9 and Rails 3 API docs:

Iterates the given block for each element with an arbitrary object given, and returns the initially given object.

Iterates over a collection, passing the current element and the memo to the block. Handy for building up hashes or reducing collections down to one object.

evens = (1..10).each_with_object([]) {|i, a| a << i*2 }
#=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

%w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
# => {'foo' => 'FOO', 'bar' => 'BAR'}

As BiHi noted on this tip on Enumerable#inject, its example can be slightly simplified using each_with_object instead of inject:

my_hash = { a: 'foo', b: 'bar' }
# => {:a=>"foo", :b=>"bar"}

my_hash.each_with_object({}) { |(k,v), h| h[k.upcase] = v.upcase }
# => {:A=>"FOO", :B=>"BAR"}

2 notes 0 comments

Mar 27 ’12

Mark Deprecated Code in Ruby

To mark deprecated code in Ruby simply add a comment to the rdoc and call the Kernel#warn method. For example:

class Foo
  # DEPRECATED: Please use useful instead.
  def useless
    warn "[DEPRECATION] `useless` is deprecated.  Please use `useful` instead."
    useful
  end

  def useful
    # ...
  end
end

If you’re using Yard instead of rdoc, your doc comment should look like this:

# @deprecated Please use {#useful} instead

Also, don’t forget to remove the deprecated method in some future release.

Source: Ryan McGeary’s answer on stackoverflow.

9 notes 0 comments

Mar 22 ’12

Using Hash as Recursive Function with Memoization

We can define a new Hash in a smart way by using Hash#new and passing it a block:

h    = Hash.new {|hash,key| hash[key] = hash[key-1] + hash[key-2]}
h[1] = 0
h[2] = 1

puts h[3] #=> 1
puts h[100] #=> 218922995834555169026

Here an example of famous Collatz Conjecture:

h    = Hash.new {|hash,n| hash[n] = 1 + (n.odd? ? hash[3*n+1] : hash[n/2])}
h[1] = 1

puts h[100] #=> 26
puts h[1000] #=> 112

via: thoughbot’s blog

3 notes 0 comments

Mar 20 ’12

Block style comments

Wrap a block of code within =begin and =end to comment it out.

# the comment format you're used to
puts "I am evaluated"

=begin
puts "I"
puts "am"
puts "commented"
puts "out"
=end

This tip was submitted by Tim Linquist.

6 notes 0 comments

Mar 15 ’12

Method chaining with inject

Aim: perform a method chaining based on hash

Required operation:

ErrorLog.event_eq([3, 7]).subdomain_like("default").user_id_eq(100)

Given:

search_opts = { :event_eq => [3, 7], :subdomain_like => "default", :user_id_eq => 100 }

Solution:

search_opts.inject(ErrorLog) { |memo, (k, v)| memo.send(k, v) }

This tip was submitted by sumskyi.

2 notes 0 comments

Mar 13 ’12

Pretend to generate

If you’re not sure what files a Rails Generator would create for you, just add the -p option (or --pretend) for a dry run:

rails generate model Blog -p

3 notes 0 comments