Skip to content

CODETUNES

This is Codetunes, a blog by Monterail, an offshore Ruby on Rails development agency.

Posts authored by Michał Szajbe

Nginx+Unicorn configuration for multi-app servers

Platform as a service is getting more and more traction recently. However it doesn’t mean that one should start developing his applications with Heroku or Engine Yard in mind from now on. Self-managed solutions, be it own dedicated servers or using cloud infrastructure (like Rackspace Cloud), are still valid choices in most situations.

At Monterail we’re maintaining tens of web applications that are hosted in all of above-mentioned environments. We developed most of them, but there are also some legacy ones (even Rails 1.2 or PHP). So there is plenty of factors that affect hosting infrastructure, and basically each app need to be evaluated individually.

Testing AJAX-reloaded elements with Capybara

Capybara is a great tool for testing application flow and user interfaces. Thanks to Culerity and Selenium web drivers you can test javascript and AJAX features of your apps.

However testing AJAX is not that straightforward because of asynchonous nature of these requests. In order to test effects of AJAX calls a web driver must wait until they finish which is difficult if not impossible to detect. However this can be worked around using some tricks.

Scenario 1

Let’s say you want to test a link on the page that triggers an AJAX request which eventually inserts some element in DOM. This could be an “Edit” link that retrieves object’s data from database (via AJAX call) and then shows an HTML form on the page. Test scenario might look like this:

@selenium
Scenario: Clicking "Edit" should show edit form
  Given I am on product's page
  And I click "Edit" link
  Then I should see form

The form will not appear immediately after the link is clicked because it takes some time for your app to process the AJAX request and return the response. Capybara is intelligent enough to acknowledge this and instead of expecting a form to appear immediately it periodically looks for it in page’s DOM. You can define for how many seconds it should keep looking by setting following option:

Capybara.default_wait_time = 5

If the form doesn’t appear in this specified time frame, the test fails. Note that Capybara doesn’t always wait full 5 seconds, it simply moves on to the next step as soon as the form appears.

 Scenario 2

Now you want to test a link that removes an element from DOM. This could be a “Save” link that saves object to database (via AJAX call) and then removes the form. Test scenario:

@selenium
Scenario: Clicking "Save" should remove edit form
  Given I am on product's page
  And I click "Save"
  Then I should not see form

This test fails even if your app works as expected. I guess you see the problem. Capybara finds the form on first lookup which is performed immediately after the click, but at that point the AJAX call has not completed yet, so the form is still there.

Popular solution is to explicitely tell Capybara to wait until it starts looking for changes in DOM. In other words, give the AJAX calls chance to complete. Here’s an adequate cucumber step:

Given /^I wait (\d+) seconds?$/ do |sec|
  sleep(sec.to_i)
end

Rebuilt scenario would look like this:

@selenium
Scenario: Clicking "Save" should remove edit form
  Given I am on product's page
  And I click "Save"
  And I wait 5 seconds
  Then I should not see form

The downside is that it will always wait 5 full seconds now.

Other aspects

Neither explicit waiting with sleep, nor Capybara’s default_wait_time option guarantees 100% success. Your tests may still ocassionally fail if the AJAX requests they perform takes longer than you assume. The time it takes for the app to process such request may be quite random as it depends on many aspects like machine load or external services it hits (database, search engines, etc.). So remember to set the option to a value high enough for your app.


CakePHP moved to Github!

After two of the core developers, Nate Abele and Gwoo, left the project lately, many feared the worst. Now it’s a different story! The CakePHP core team decided to move the project to Github.

I’d say: finally!

Now it’s truly open to the community. That’s because Github makes it so easy to collaborate on open source projects. All you need to do is to fork a project, merge your changes, and send a pull request to project’s maintainer. Can’t imagine more painless process.

What I expect now is a real boost in CakePHP development, with many great features delivered by the community. Just check how other projects benefited from the move, for example Ruby on Rails that has more than 700 forks now. So…

Let’s fork it! ;)

Fixtures without validation with Factory Girl

Factory Girl is my fixture replacement library of choice. It improves tests readability and maintainability. It’s also customizable.

There are sometimes situations when you want to create test scenarios that checks how your app is handling invalid data (not user input, but invalid records that already sit in your database). To do this you first need to put this invalid data to your db.

You could accomplish this with such line:

@user = Factory(:user, :email => "not a correct email address")

However, factory girl would raise an exception here, because that’s the default strategy of creating new fixtures – raise exception if save fails (because of validation errors for example).

Thankfully we can use our own strategy of creating new fixtures, such that does save records without validation.

First let’s define our new strategy:

class Factory::Proxy::CreateWithoutValidation < Factory::Proxy::Build
  def result
    @instance.save(false)
    @instance
  end
end

class Factory
  def self.create_without_validation (name, overrides = {})
    factory_by_name(name).run(Proxy::CreateWithoutValidation, overrides)
  end
end

Now we can use it while defining new factory:

Factory.define :invalid_user, :class => User, :default_strategy => :create_without_validation do |f|
  ...
end

And then we can happily create invalid fixtures without any exceptions raised.

Turning off auto timestamping for testing in Rails

Suppose that you implemented a functionality that depends on values of created_at or updated_at fields of your models. How do you test it?

If you use fixtures that reside in test/fixture/*.yml files then there is no problem, because the values you set there for created_at and updated_at fields are saved to the database ‘as is’. So you can easily have an article created one week ago:

article:
  title: What a great day
  created_at:
  updated_at:

However, I don’t use fixtures files myself. I feel a bit dirty using them ;) I find fixture replacement tools far more maintainable. Namely, I love thoughtbot’s Factory Girl. But here comes the problem. This won’t work as expected with Factory Girl:

Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago)

That’s because ActiveRecord’s automatic timestamping feature sets Time.now for created_at and updated_at fields overriding our values. At least that’s ActiveRecord’s default behavior. Fortunately it can be disabled with:

Article.record_timestamps = false

Chances are that after creating a model with a custom timestamp we’ll want to turn automatic timestamping back on. But turning it off and on in many places in your unit tests would be pretty cumbersome. Wouldn’t it be cool if you could achieve all of this with a snippet below?

without_timestamping_of Article do
  Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago)
end

It turns timestamping off, executes the block and turns timestamping back on. I find it clean and dry. Here’s the code to place in your test_helper.rb:

# test_helper.rb
class Test::Unit::TestCase # or class ActiveSupport::TestCase in Rails 2.3.x
  def without_timestamping_of(*klasses)
    if block_given?
      klasses.delete_if { |klass| !klass.record_timestamps }
      klasses.each { |klass| klass.record_timestamps = false }
      begin
        yield
      ensure
        klasses.each { |klass| klass.record_timestamps = true }
      end
    end
  end
end

Of course you can turn off timestamping for many models at once:

without_timestamping_of Article, Comment, User do
  Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago)
  Factory(:comment, :created_at => 1.day.ago)
  Factory(:user, :updated_at => 5.hours.ago)
end

Hope you like it. If so, share :)

Dynamic cookie domains with Rack’s middleware

Handling sessions in multi-domain environment is not the simplest things to do, because of the fact that cookies are scoped to a domain they were set by.

Recently we were developing an application with such an idea in mind:

  • Application will work as a base for other mini-applications (which we call sites)
  • Each site can be accessed via different url types: site.example.org and example.org/site
  • We want the users to remain logged in when switching from one url type to another

I won’t be covering application structure, routing, etc. here, I will only write about maintaing the sessions is such an environment.

So this is pretty simple here – all that we needed to do was to set cookie domain to .example.org (note the “dot” at the beginning). This could be done via:

ActionController::Base.session = {
  :domain => ".example.org"
}

However there was an additional requirement that we need to deal with:

  • Each site can be accessed via custom domain – site.com
  • Of course there’s no way here to keep the user logged in when he’s switching from site.com to example.org/site or site.example.org, at least it cannot be done with setting cookie domain to whatever value

Technically, to access the site via site.com, that domain must point to our IP address. Then we need to detect that the site is being accessed via custom domain and set cookie domain respectively.

This could be done via some funky before_filters in an Application Controller, however we found much better and cleaner way.

Rack’s middleware to the rescue

Rack itself is a minimal interface between web server and your ruby framework. It’s used by Ruby on Rails (since 2.3) and Merb. The request comes from web server, goes through middleware layers and enters the application.

So we wrote a middleware layer that detects the host with which our application is accessed and sets cookie domain for the request. Here it is:

app/middlewares/set_cookie_domain.rb

class SetCookieDomain
  def initialize(app, default_domain)
    @app = app
    @default_domain = default_domain
  end

  def call(env)
    host = env["HTTP_HOST"].split(':').first
    env["rack.session.options"][:domain] = custom_domain?(host) ? ".#{host}" : "#{@default_domain}"
    @app.call(env)
  end

  def custom_domain?(host)
    domain = @default_domain.sub(/^\./, '')
    host !~ Regexp.new("#{domain}$", Regexp::IGNORECASE)
  end
end

Now we need to turn it on:

environment.rb

config.load_paths += %W( #{RAILS_ROOT}/app/middlewares )

production.rb

config.middleware.use "SetCookieDomain", ".example.org"

.example.org is the default domain that will be used unless the application is accessed via custom domain (like site.com), we give it different values depending on environment (production/staging/development etc).

And since we’re fans of test driven development, here’s the test that ensures us that everything works as expected:

tests/integration/set_cookie_domain_test.rb

require 'test_helper'

class SetCookieDomainTest < ActionController::IntegrationTest

  context "when accessing site at example.org" do
    setup do
      host! 'example.org'
      visit '/'
    end

    should "set cookie_domain to .example.org" do
      assert_equal '.example.org', @integration_session.controller.request.session_options[:domain]
    end
  end

  context "when accessing site at site.com" do
    setup do
      host! 'site.com'
      visit '/'
    end

    should "set cookie_domain to .site.com" do
      assert_equal '.site.com', @integration_session.controller.request.session_options[:domain]
    end
  end

  context "when accessing site at site.example.org" do
    setup do
      host! 'site.example.org'
      visit '/'
    end

    should "set cookie_domain to .example.org" do
      assert_equal '.example.org', @integration_session.controller.request.session_options[:domain]
    end
  end

end

Test is sponsored by great Shoulda and Webrat gems.

Feel free to comment and share.

Speaking at IT Underground Security Conference and Workshop in Prague

it_underground_prague_125x125_enTomorrow I will be speaking at IT Underground Security Conference and Workshop in Prague, Czech Republic. That is three day long event that starts today.

I will give a talk on “Websecurity through conventions and best practices” which will be focused on securing web applications from the most common attacks with examples from Ruby on Rails web development framework.

Expect to see materials from the conf here in few days.

Rails 2.2.2, Ajax and respond_to

As I wrote some time ago in the article about Rails, Ajax and jQuery, sometimes there are problems with Rails not interpreting correctly content type headers of ajax requests. It’s because not all web browsers send that header in the same way. 

What I proposed was to sort the request.accepts array (array containing content type headers sent by browser) so that xml content type would be the first element. That would then trigger format.xml in our respond_to block.

However that approach does not work in Rails 2.2.2, because now the request.accepts array is frozen and it cannot be modified. I spent some time googling for the solution, but with no effect. So I dived into the API and Rails’ source code and came up with pretty nice and simple solution to the problem.

class ApplicationController < ActionController::Base
  before_filter :xhr_to_xml

  def xhr_to_xml
    request.format = :xml if request.xhr?
  end
end

This piece of code is an equivalent of the snippet I proposed in the article I referred to at the beginning. Now all ajax request will trigger format.xml in respond_to blocks.