Find an article

Start typing to search the archive.

← All articles

Weaknesses of Rails' url_for

url_for is used by link_to, which every Rails developer uses. This method accepts a lot of types of arguments. And sometimes it can be slow. See how to use it with maximum performance.

The Rails helper link_to is very well known and used by every Rails developer. Usually, the first argument is the link’s name, and the second contains the parameters used to build the URL. You can pass 4 types of parameters: a helper generated by routes, a string, a hash and, since Rails 3, a record. But when should you use each one for your task? This is an interesting question.

url_for

The parameters passed to link_to go to the url_for method. This method builds the URL eventually passed to the generated link’s href attribute. So to answer this question, we should determine what to pass to url_for and when.

As I’ve already said, url_for accepts 4 types of parameters. Here is how you can pass them:

url_for("/events/#{@event.id}") # string
url_for(event_path(@event)) # generated helper
url_for(controller: 'events', action: 'show', id: @event.id) # hash
url_for(@event) # polymorphic

All these url_for calls generate the same result, /events/:id, for example /events/1.

The last call seems the most convenient: you just pass the @event record, and that’s it. There is no brainstorming or browsing generated routes with the rake routes task (this task shows all available routes for your Rails application; run it if you haven’t done so yet). It’s convenient not only for that reason, but also when you change the controller or route that serves the event. You won’t have to change all the code in your Rails application where you used old route helpers, string URLs, hashes and so on.

The hash option seems the most complicated. It looks the longest, and there is no doubt why it is quietly deprecated by the Rails community. Rails developers avoid using it altogether. But that’s not the only reason; I will tell you another in a few seconds.

There is no rule about which of these methods to use, and of course you are free to use any of these call types.

But let’s see how fast they are. I wrote a benchmark especially for this case:

class EventsHelperTest < ActionView::TestCase
  def setup
    country = Country.create(name: 'Belarus')
    state = State.create(name: 'Minsk')
    @event = Event.create(country: country, state: state, name: 'Drinking bear')
  end

  def test_url_for
    n = 10000
    Benchmark.bmbm do |x|
      x.report('string') { n.times { url_for("/events/#{@event.id}") } }
      x.report('helper') { n.times { url_for(event_path(@event)) } }
      x.report('hash') { n.times { url_for(controller: 'events', action: 'show', id: @event.id) } }
      x.report('polymorphic') { n.times { url_for(@event) } }
    end
  end
end

And these are the results of running it:

rake test test/helpers/events_helper_test.rb
Run options: --seed 23668

# Running tests:

Rehearsal -----------------------------------------------
string        0.010000   0.000000   0.010000 (  0.019028)
helper        0.480000   0.010000   0.490000 (  0.498942)
hash          1.250000   0.000000   1.250000 (  1.253836)
polymorphic   0.920000   0.010000   0.930000 (  0.924670)
-------------------------------------- total: 2.680000sec

                  user     system      total        real
string        0.010000   0.000000   0.010000 (  0.013747)
helper        0.480000   0.000000   0.480000 (  0.478646)
hash          1.200000   0.000000   1.200000 (  1.200571)
polymorphic   0.890000   0.000000   0.890000 (  0.892464)
.

Finished tests in 5.542858s, 0.1804 tests/s, 0.0000 assertions/s.

1 tests, 0 assertions, 0 failures, 0 errors, 0 skips

As you can see, the hash style is the slowest. Then comes polymorphic generation. The helper style is in 3rd place, and it’s no surprise that string generation wins.

Conclusion

In my opinion, the polymorphic style is the most convenient here. But it’s 2× slower than the helper style. The string style is the fastest, but we can’t use it because, if you change your routes, your tests may not show an error in route generation. The string will be generated as before, while a missing helper method would cause the page to fail.

So my conclusion is to use helper methods wherever possible. If you have a place in your application where you have to choose a helper by record type or another property, the polymorphic way is preferable.

And the last conclusion: avoid using hashes to generate URLs altogether.