Building external URLs for a Rails application

Having a central representation of a resource eliminates the need to adjust all depending URLs when a change becomes necessary.
Spiderweb in the sun

When referring to URLs there are valid reasons for avoiding hard-coded string manipulation in models, controllers or views. One reason is the DRY principle. Having a central representation of a resource eliminates the need to adjust all depending URLs when a change becomes necessary.

The Rails router generates helper methods for paths and URLs for exactly this reason. Suppose we define a route for a patients resource as follows.

get '/patients/:id', to: 'patients#show', as: 'patient'

Then, for a given Patient with id 17 initialized in the controller, we can call the following path or url helpers in a view.

patient_path(@patient)   # /patients/17

patient_url(@patient)    # http://www.example-app.com/patients/17

Using the Rails path and url helpers is a common best practice. However, there is no clear best practice for building external URLs, i.e. URLs not belonging to the application.

In this post we suggest using URI Templates with the Addressable gem for building external URLs and we outline an example implementation.


Building external URLs in one place

Let’s consider a use case. Suppose we were running a business offering several apps through a third-party app store and we wanted to link to the app store’s resource for each app. We need to provide a method for producing parameterised URLs for models representing our apps.

To make the external URL building available in our views, we include a UrlBuilding module in the ApplicationController. The module provides the external_url_builder method.

module UrlBuilding

  def self.included(base)
    base.helper_method :external_url_builder
  end

  private

  def external_url_builder
    ExternalUrlBuilder.new
  end

  # ...
end

URI Templates

The ExternalUrlBuilder provides all actual methods for building specific URLs. Our implementation of the ExternalUrlBuilder relies on the Addressable gem which provides an implementation of URI Templates (RFC 6570).

A URI Template is a string conforming to a special syntax which describes URIs by variable expansion.

(This post uses URL and URI interchangeably in particular in the naming scheme suggested. See defintions for URIs, URLs, and URNs.)

Coming back to our app store example, we can describe the URLs for our Windows App Store with the following URI Template.

http://www.windowsphone.com{/language}/store/app/-{/app_id}

Here, {/language} and {/app_id} are variables to substitute for specific language locales or app ids respectively.

The advantages of using URI Templates are:

  • better readability than home-made string concatenation
  • check of parameters to be interpolated
  • delegation of interpolation to a dedicated implementation
  • the code becomes re-usable
  • extra functionality (e.g. switching the protocol)

Addressable

Addressable::Template constructs URI Template objects from strings conforming to the URI Template syntax.

Addressable::Template.new('http://www.windowsphone.com{/language}/store/app/-{/app_id}')

We define the URI Templates as constants and refer to them in methods returning parameterised instances of the templates.

class ExternalUrlBuilder

  WINDOWS_8_APP_URL = ::Addressable::Template.new("http://apps.microsoft.com/windows{/language}/app{/app_id}")

  # ...

  def windows_phone_8_store_url(app, language)
    WINDOWS_8_APP_URL
      .expand(app_id: app.id, language: language)
      .to_s
  end

  # ...
end

Addressable::Template#expand passes all variables required by the template for instantiating it. We have to stringify the result because the calling context may not be able to process the Addressable::URI instance returned.

With this setup, we can produce app store URL in views.

<%= link_to "App", external_url_builder.windows_phone_8_store_url(@app, current_language) %>

Among other things, the syntax of URI Templates supports expansions of query parameters. These can be explicitly named.

http://www.windowsphone.com{/language}/store/search{?q}

Or they can be a list.

http://www.windowsphone.com{/language}/store/search{?q*}

In the latter case, expand can be called with a hash of query parameters.

Addressable::Template.new("http://www.windowsphone.com{/language}/store/search{?q*}")
                     .expand( q: { search: "achme", created_after: "2014-01-01", limit: 10 } )

The URI Template syntax offers more forms of variable expansions than described above and the Addressable gem offers more functionality than just expanding templates.

Summary

To the best of our knowledge, no common best practice for generating external URIs in Rails applications has emerged so far. This post described how to build external URIs in a centralised way, supporting URI Templates and using the Addressable gem. The suggested implementation is currently used in one of our applications.

References

The Addressable maintainers, Addressable. http://github.com/sporkmonger/addressable

Internet Engineering Task Force (IETF), Request for Comments: 6570. http://tools.ietf.org/html/rfc6570

The RailsGuides Project, Rails Routing from the Outside In. http://guides.rubyonrails.org/routing.html#connecting-urls-to-code

W3C Note 21 September 2001, URIs, URLs, and URNs: Clarifications and Recommendations.http://www.w3.org/TR/uri-clarification/

Wikipedia, Don’t repeat yourself. http://en.wikipedia.org/wiki/Don’t_repeat_yourself

Photo by Bas van den Eijkhof on Unsplash

Want to join our Engineering team?
Apply today!
Share: