Simple database lock for MySQL

Note: For PostgreSQL you should use advisory locks. For MySQL we still recommend the solution in this card.


If you need to synchronize multiple rails processes, you need some shared resource that can be used as a mutex. One option is to simply use your existing (MySQL) database.

The attached code provides a database-based model level mutex for MySQL. You use it by simply calling

Lock.acquire('string to synchronize on') do
  # non-th...

RSpec < 2.11: ActiveRecord scopes must be loaded before using the "=~" matcher

To test whether two arrays have the same elements regardless of order, you can use the =~ matcher in RSpec < 2.11:

actual_array.should =~ expected_array

If either side is an ActiveRecord scope rather than an array, you should call to_a on it first, since =~ does not play nice with scopes:

actual_scope.to_a.should =~ expected_scope.to_a

If you use RSpec >= 2.11 we recommend using the match_array or contain_exactly matchers instead of =~.
Use the eq matcher only if the order of records matters.

Test that a select field contains an option with Cucumber

This note describes a Cucumber step definition that lets you say:

Then "Mow lawn" should be an option for "Activity"
But "Reply support mail" should not be an option for "Activity"

Note that this step checks whether an option is available, not that it is selected. There is a separate step to test that an option is selected.

Capybara (0.4.1 or higher)

Then /^"([^"]*)" should( not)? be an option for "([^"]*)"(?: within "([^\...

Fix errors when rendering PDF output

If you run specs or your application and get an error like:
ActionController::MissingFile in 'ProductsController#show, should render PDF'
Cannot read file /some/file.pdf

You may be missing the HTMLDOC binary on your system. Install it like this on Debian/Ubuntu:
sudo apt-get install htmldoc

Testing if two date ranges overlap in Ruby or Rails

A check if two date or time ranges A and B overlap needs to cover a lot of cases:

  1. A partially overlaps B
  2. A surrounds B
  3. B surrounds A
  4. A occurs entirely after B
  5. B occurs entirely after A

This means you actually have to check that:

  • neither does A occur entirely after B (meaning A.start > B.end)
  • nor does B occur entirely after A (meaning B.start > A.end)

Flipping this, A and B overlap iff A.start <= B.end && B.start <= A.end

The code below shows how to implement this in Ruby on Rails. The example is a class `Interv...

Rails: When defining scopes with class methods, don't use `self`

Sometimes it is useful to define a named scope by implementing a static method with the scope's name on the scoped class. For instance, when a method should decide which existing scope should be the next link in the scope chain. Take this class for example:

class Meal < ActiveRecord::Base

  named_scope :for_date, lambda { |date| :conditions => { :date => date }}
  named_scope :with_meat, :conditions => { :meat => true }
  named_scope :without_meat, :conditions => { :meat => false }

  def self.suitable_for(user)
    if user.vegetar...

Split an array into groups

Given group size

If you would like to split a Ruby array into pairs of two, you can use the Rails ActiveSupport method in_groups_of:

>> [1, 2, 3, 4].in_groups_of(2)
=> [[1, 2], [3, 4]]

>> [1, 2, 3, 4, 5].in_groups_of(2)
=> [[1, 2], [3, 4], [5, nil]]

>> [1, 2, 3, 4, 5].in_groups_of(2, 'abc')
=> [[1, 2], [3, 4], [5, 'abc']]

>> [1, 2, 3, 4, 5].in_groups_of(2, false)
=> [[1, 2], [3, 4], [5]]

Given group cou...

Test the status code of a response in Cucumber

Webrat

Then /^I should get a response with status (\d+)$/ do |status|
  response.status.should include(status)
end

Capybara

Then /^I should get a response with status (\d+)$/ do |status|
  page.status_code.should include(status.to_i)
end

Perform HTTP basic authentication in Cucumber (with or without Selenium)

This card describes a Cucumber step that lets you say:

When I perform basic authentication as "username/password" and I visit the admin area

The path component ("... the admin area") is parsed through your path_to helper in features/support/paths.rb.

Capybara

The step definition is part of Spreewald. The step has been tested with multiple versions of Capybara, Rack::Test and Selenium.

Webrat (legacy)

This is a simpler version of the step above:

When /...

How to define constants with traits

When defining a trait using the Modularity gem, you must take extra steps to define constants to avoid caveats (like when defining subclasses through traits).

tl;dr

In traits, always define constants with explicit self.

If your trait defines a constant inside the as_trait block, it will be bound to the trait module, not the class including the trait.
While this may seem unproblematic at first glance, it becomes a problem when including trai...

Setting up Tomcat to use an existing OpenSSL certificate

This is for those who already own an SSL certificate (e.g. using it in the Apache HTTP Server) and need to feed it to a Tomcat application. The main issue is that you need to convert your OpenSSL certificate for Java to use it in its keystore.

Create a keystore

This is the place where the application usually gets its keys from. The keytool is able to read certificate files but does not understand private key files which you need to use y...

Speed up RSpec by deferring garbage collection

Update: This trick probably isn't very useful anymore in Ruby 2.x. The Ruby GC has improved a lot over the years.


Joe Van Dyk discovered that running the Ruby garbage collector only every X seconds can speed up your tests. I found that deferring garbage collection would speed up my RSpec examples by about 15%, but it probably depends on the nature of your tests. I also tried applying it to Cucumber f...

Test a download's filename with Cucumber

These steps are now part of Spreewald.

The step definitions below allow you to test the filename suggested by the server:

When I follow "Export as ZIP"
Then I should get a download with the filename "contacts_20110203.zip"

Capybara

Then /^I should get a download with the filename "([^\"]*)"$/ do |filename|
  page.driver.response.headers['Content-Disposition'].should include("filename=\"#{filename}\"")
end

Webrat

Then /...

Test the content-type of a response in Cucumber

The step definitions below allow you to write this in both Webrat and Capybara:

When I follow "Download as PDF"
Then I should get a response with content-type "application/pdf"

Capybara

Then /^I should get a response with content-type "([^"]*)"$/ do |content_type|
  page.response_headers['Content-Type'].should == content_type
end

Webrat

Then /^I should get a response with content-type "([^"]*)"$/ do |content_type|
  response.content_type.should == content_type
end

Unfortunatly this do...

Cucumber steps to test input fields for equality (with wildcard support)

Our collection of the most useful Cucumber steps, Spreewald, now supports exact matching of form fields and lets you use wildcards.

Examples:

And the "Money" field should contain "134"
# -> Only is green if that field contains the exact string "134", neither "134,50" nor "1000134"

And the "Name" field should contain "*Peter*"
# -> Accepts if the field contains "Peter" or "Anton Peter" or "Peter Schödl" etc.

And the "Comment" field should contain "Dear*bye"
# -> Accepts if the field contains "De...

Apache: Redirect all requests from one host to another

In order to redirect all requests from redirecting-host.com to desired-host.com while keeping path and query params unchanged, change your Apache VHost to something like this:

ServerName desired-host.com
ServerAlias redirecting-host.com
RewriteEngine On
RewriteCond %{HTTP_HOST} !^desired-host.com$
RewriteRule ^.*$ http://desired-host.com%{REQUEST_URI} [R=301,L]

Take care to keep all those ^, $ and ! as seen in the example.

Parametrized shared example groups in RSpec

RSpec 2

In RSpec 2 shared_examples_for can have parameters. You can simply hand over arguments from it_should_behave_like:

shared_examples_for 'string equaling another string' do |expected_string|
  it 'should be equal to another string' do
    subject.should == expected_string
  end
end

describe 'some string' do
  subject { 'foo' }
  it_should_behave_like 's...

Inspect the page content in a Cucumber session

When you need to see the content of a page (i.e. not all the HTML but the relevant text body)

  • you can do pp (html_content)
    • pp will format the html String human readable pretty printed
  • where html_content can be replaced by one of the following commands:

Rails

body or response.body

Capybara:

  • page.driver.html.content
  • page.body

Webrat:

Nokogiri::HTML(response.body).content

The returned strings can be cleaned up by calling .gsub(/^\s*$/, '').squeeze("\n") on them.\
Although this may be useful for d...

RSpec matcher to check if two numbers are the same

You can usually just use the eq matched to compare two numbers:

expect(deal.total).to eq(120)

If the actual value is a BigDecimal, you might have issues when you match it against a Float:

expect(deal.total_price).to eq(1200.99)

In these cases, try matching it against another BigDecimal:

expect(deal.total_price).to eq BigDecimal(1200.99)

If you don't like the syntax, our rspec_candy gem has a matcher that will compare Fixnums (integers), Floats and `BigDecima...

Test that an exception or error page is raised in Capybara

You can use these step definitions:

Then /^I should not see an error$/ do
  (200 .. 399).should include(page.status_code)
end

Then /^I should see an error$/ do
  (400 .. 599).should include(page.status_code)
end

Note that you need to tag the scenario with @allow-rescue to test that an error is shown like this

@allow-rescue
Scenario: Accessing the admin area requires a login
  When I go to the admin area
  Then I should see an error

These step definitions will not work for @javascript scena...

Preloaded associations are filtered by conditions on the same table

When you eagerly load an association list using the .include option, and at the same time have a .where on an included table, two things happen:

  1. Rails tries to load all involved records in a huge single query spanning multiple database tables.
  2. The preloaded association list is filtered by the where condition, even though you only wanted to use the where condition to filter the containing model.

The second case's behavior is mostly unexpected, because pre-loaded associations usually don't care about the circumstances under whi...