ga-wdi-boston / capstone-project

Other
3 stars 29 forks source link

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

Closed rhjones closed 7 years ago

rhjones commented 7 years ago

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:

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

jrhorn424 commented 7 years ago

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.

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 commented 7 years ago

@rhjones What @jrhorn424 said :robot: :smile:

jrhorn424 commented 7 years ago

@rhjones What @jrhorn424 said 🤖 😄

rhjones commented 7 years ago

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").