Kevin Sylvestre

Rails Concerns for URL and Path Helpers

Boats

Accessing a URL or path directly from a model can help to remove repetition in code. Consider the following:

# config/routes.rb
get 'posts/:segment/:slug', to: 'posts#show', as: :post
# app/views/posts/_preview.html.haml
- @posts.each do |post|
  - # <a href='/posts/2014-05-27/an-example-post'>An Example Post</a>
  = link_to post.title, posts_path(post, segment: post.segment, slug: post.slug)

While the code to generate the URL is not overly complex, once it is repeated throughout a number of views and controllers it can lead to maintainability problems. Changing the URL for SEO optimization requires changing multiple locations. The most robust solution for this problem is to use the decorator pattern through a gem like Draper. However, an alternative (or supplementary) solution is to expose accessors directly on the model:

# app/models/concerns/links.rb

module Links
  extend ActiveSupport::Concern

  included do
    delegate :url_helpers, to: "Rails.application.routes"
    alias :h :url_helpers
  end

  def url
    h.send :"#{self.route}_url", parameterize
  end

  def path
    h.send :"#{self.route}_path", parameterize
  end

  def route
    self.class.name.parameterize
  end

  def parameterize
    self.id
  end

end
# app/models/post.rb
class Post < ActiveRecord::Base
  include Links

  # Configuration:
  def parameterize
    { segment: self.segment, slug: self.slug }
  end
end

This snippet enables a developer to refactor the view to call post.url or post.path. As a last step to ensure that the URL works outside of views add a default host in an initializer:

# config/initializers/hosts.rb
Rails.application.routes.default_url_options[:host]= 'https://ksylvest.com'