ruby-on-railsruby-on-rails-4routesruby-on-rails-4.2named-routing

Rails named route with dynamic segments URL generation from model


Ruby on Rails 4.2+ only, please!

I've been looking all over for tips on how to make URLs pretty in Rails, and I'm struggling to see a solution I like.

What I want:

Hypothetical example: given Topic, Course, etc. models that have a bunch of fields (including URL-friendly slugs), I want to be able to

# ../routes.rb
# Match urls of the form /edu/material-engineering. These are read-only
# public URLs, not resources.
get 'edu/:slug', to: 'education#topic', as: :learn_topic
get 'edu/course/:id/slug', to: 'education#course', as: :learn_course
...

# I also have admin-only resource-oriented controllers for managing
# the content, but that's separate.
namespace :admin do
  resource :topic
  resource :course
  ...
end

# ../some_view.html.erb
# Generate URLS like this:
<%= link_to topic.name, learn_topic_path(topic) %>
<%= link_to course.name, learn_course_path(course) %>

What I don't want:

There has to be a way to tell the named route helper topic_path(topic) to take the required parameters in the route (e.g, :slug, :id, whatever else) from the topic model object.

Anybody know? Thanks!


Solution

  • The best I've been able to come up with: just override the *_path helpers with my own implementation.

    If you know a way to make the default helpers work, please chime in!

    This problem boils down to one issue: the auto-generated *_path and *_url helpers don't give me the flexibility I want. What I want them to do is trivial, so without another option, I can just write my own:

    module ApplicationHelper
      def learn_topic_path(topic)
        "/edu/#{topic.slug}"
      end
    
      ...
    end
    

    Writing a few _path/_url helper overrides avoids all kinds of complication, and allows you to keep out of to_param, avoid including new plugins, etc.

    One could probably go another step forward and generate the static components of the route from known routing rules, as well as infer what attributes one needed to extract from a model if the dynamic segment names line up to the model attribute names, but that starts to break down once you do more complicated things or add multiple models (e.g., 'edu/:topic_slug/:course_slug').

    The big downside to doing this is that you now have to update routes in two places every time you change them: the route definition itself in routes.rb as well as the corresponding route helper in application_helper.rb. I can live with that for now.