[Rails] How best to name route associated with a join table that has no join model? #288

Closed
rhjones opened this Issue Nov 5, 2016 · 4 comments

Projects

None yet

3 participants

@rhjones
rhjones commented Nov 5, 2016

I'm experimenting with using has_and_belongs_to_many relationships between two of my resources (users and patterns; users can "favorite" a pattern) rather than a has_many :through. I don't need any additional attributes on the join table, which is why (based on the Rails guide to Active Record Associations) I've chosen this path.

The join table that is created (using bundle exec rails g migration CreateJoinTablePatternUser pattern user) is called patterns_users. I don't have a controller or a model for this join, as per the advice in the guide.

Obtaining all of a user's favorites is possible through user.patterns, so I don't think I need a route there. To create a new favorite, though, I'll need some way to post to the join table directly.

I'm leaning toward making a POST request to /patterns/:pattern_id/favorite, and defining a create_favorite route in PatternsController to add the pattern in question to the user's list of favorites.

Is there a better/more semantic approach? Specific questions:

  • I have in my head for some reason that POST generally goes to a route that ends in a plural. If that's an accurate heuristic, should my route be /patterns/:pattern_id/favorites?
  • Is favorites/:pattern_id better than a nested route?
  • Should I scrap the has_and_belongs_to_many approach and just do a has_many :through, with a favorite model / favorites controller?

Not sure any of this will affect my functionality in any way, but I'm curious whether there's a best practice.

@rhjones rhjones changed the title from How best to name route associated with a join table that has no join model? to [Rails] How best to name route associated with a join table that has no join model? Nov 5, 2016
@jrhorn424
Member

I understand you want you to try has_and_belongs_to_many. However, to quote some sage advice I once received from a mentor: "How can you be sure you won't want additional properties on the (nonexistent) join model later?"

There's a few reasons we don't teach habtm.

  • hmt forces you to think about modeling and choosing semantic names for relationships
  • hmt provides more flexibility for future change
  • hmt provides the same functionality as habtm with only a minor additional cost
  • going from habtm to hmt is more expensive than the reverse

Not sure any of this will affect my functionality in any way, but I'm curious whether there's a best practice.

The best practice really is to avoid habtm.

I'm leaning toward making a POST request to /patterns/:pattern_id/favorite, and defining a create_favorite route in PatternsController to add the pattern in question to the user's list of favorites.

If you want to stick strictly to convention, I'd have a resources :pattern_users, only: [:create] to start. The associated request would be POST /pattern_users.

Better yet, if you want your path to dictate your architecture, you could go with a a namespaced Patterns::FavoritesController < ProtectedController with a semantic, conventional create action.

Finally, I'd argue that what you really have here is a relationship between users and patterns called favorites. So User has_many :patterns, through: :favorites. Then you can just have a FavoritesController with a semantic, conventional create action, and a simpler request of POST /favorites.

Whether you POST /patterns/:pattern_id/favorite or POST /favorites, you're still send a pattern id. In the former, it's part of the URL. In the latter, it's part of the request body. Both requests require a current_user, but we have that inside our ProtectedControllers.

I have in my head for some reason that POST generally goes to a route that ends in a plural. If that's an accurate heuristic, should my route be /patterns/:pattern_id/favorites?

Well, almost all routes end in plural because we try to stick to RESTful, conventional, resourceful routes. favorites is clearly a noun. favorite could be a verb, and is in fact how I parsed it in the previous section of my response. I prefer nouns and leave verbs to HTTP when dealing with resources. And I think I've made a case you have a hidden favorites resource in your modeling.

Is favorites/:pattern_id better than a nested route?

I almost want to say that "anything is better than a nested route" but for some reason I'm still fond of them. I think we're at the point where we can manage flat routes well. Especially since Ember is your client choice, flat routes seem a win here.

What isn't a win is mixing favorites and patterns. If I sent you a URL of /favorites/2 would you think that is the second favorite in the database or a verb (what's with the "s") to "favorite" the second pattern in the database? It's a bit vague, really.

Should I scrap the has_and_belongs_to_many approach and just do a has_many :through, with a favorite model / favorites controller?

I hope I've convinced you this is a good move. And I think it will save a bit of headache. Thanks for giving me an excuse to wrestle with some really solid questions and I hope my answer has been helpful!

@gaand
gaand commented Nov 5, 2016

@rhjones What @jrhorn424 said ๐Ÿค– ๐Ÿ˜„

@jrhorn424
Member

@rhjones What @jrhorn424 said ๐Ÿค– ๐Ÿ˜„

@rhjones
rhjones commented Nov 5, 2016

Finally, I'd argue that what you really have here is a relationship between users and patterns called favorites. So User has_many :patterns, through: :favorites. Then you can just have a FavoritesController with a semantic, conventional create action, and a simpler request of POST /favorites.

@jrhorn424 Convinced! And yeah, the noun/verb(/adjective) issue here is tricky. I blame social media (see: Facebook/Twitter "likes").

@rhjones rhjones closed this Nov 5, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment