0

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:

  • Messing with to_param. That's a dirty hack and completely breaks separation of concerns.
  • Resource/RESTful routing. There are no CRUD operations here other than "read."
  • link_to 'text', course_path(id: course.id, slug: course.slug). This completely defeats the purpose of not requiring views to know what params are required to generate a URL for a course.
  • EDIT: I know FriendlyId exists, but I'm precisely trying to understand how this sort of thing can be done and what the mechanics are, so that's not a solution for now.

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!

Mohammad AbuShady
  • 40,884
  • 11
  • 78
  • 89
honktronic
  • 353
  • 1
  • 15
  • `friendly_id` it self uses `to_param`, why do you think that's bad? you're telling the model how to represent it self in a url, you think it's a hack but i see it as the cleanest way to reflect on the whole system – Mohammad AbuShady Feb 22 '15 at 06:28
  • @MohammadAbuShady, `to_param` is functionality equivalent to having a `to_partial` method on models that spits out the HTML representation for the model. There is a reason that we separate model from view, and the URL is just another sort of view. There's no reason that models should have ***any*** idea what the routing/URL scheme for the app is. – honktronic Feb 22 '15 at 06:56
  • well I if want to be strict (or silly) and look at it from the other side, i could also say that the routing should not know what attribute name is used for the routing and in which order, so i can't say there's a true separation that's going to happen in either ways, anyways i don't know if what you want is possible or not, you should find how `url_for` is actually implemented, and how it behaves when an activerecord object is passed to it, i'm interested in knowing what you find out at the end, good luck, – Mohammad AbuShady Feb 22 '15 at 07:06
  • I'll keep digging and I'll definitely post back if I figure it out. As for your point, that's seems to be like saying "I could also say that a view shouldn't know what model fields to put on the page in what order"...which clearly doesn't make sense. That is the very purpose of the view, or in the case of URLs, a routing scheme. View->Model is an observer->observed relationship. The dependency should go one way and one way only. – honktronic Feb 22 '15 at 07:10
  • i guess that makes sense, you could ask the people at friendly_id, they probably did a lot of digging about this already, someone might be able to help. – Mohammad AbuShady Feb 22 '15 at 07:14

2 Answers2

1

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.

honktronic
  • 353
  • 1
  • 15
0

You can use FriendlyId gem to achieve that.

Here's the link:

https://github.com/norman/friendly_id/blob/master/README.md

Let me know if you have questions.

neo
  • 4,078
  • 4
  • 25
  • 41
  • Thanks @neo, but I'm trying to understand ***how*** one would do this with vanilla Rails, not just find somebody's else's solution. – honktronic Feb 22 '15 at 02:32
  • 2
    You can go through the code base if you'd like to know how it works. – neo Feb 22 '15 at 02:40
  • I appreciate the thought, but I have. FriendlyId a) is absurdly complicated (including the introduction of new models just for slugs) and b) tries to solve many more problems than I'm interested in solving. – honktronic Feb 22 '15 at 03:00