1

Let's say I have a user model and a movie model as well as tables to store them. Let's say I want to add a watchlist feature by adding a third table called watchlist_movies that simply maps a user_id to a movie_id.

I now want to add a watchlist_movie model. I want to be able to query ActiveRecord with user.watchlist_movies and get a collection of movie objects. So far, I've tried (in user.rb)

has_many :watchlist_movies 

and

has_many :movies, through: watchlist_movies

The first results in user.watchlist_movies returning a record of and the second will return a collection of movie records at user.movies. Essentially what I want is for user.watchlist_movies to return a collection of movie records, so I want the access as defined in the first relationship to return the content of the second relationship. How do I do this?

Captain Stack
  • 3,572
  • 5
  • 31
  • 56

1 Answers1

1

You're defining the relationships correctly here, but you may have a case of your expectations not aligning with how Rails does things. If your structure is that of Movie being related to User through WatchlistMovie, which is a simple join model, you're on the right track.

Remember you can call your relationships anything you want:

has_many :watchlist_movie_associations,
  class_name: 'WatchlistMovie'

has_many :watchlist_movies,
  class_name: 'Movie',
  through: :watchlist_movie_associations

I'd advise against this since it goes against the grain of how ActiveRecord prefers to name things. If it just bugs you right now, that's understandable, but if you embrace the style instead of fighting it you'll have an application that's a lot more consistent and understandable.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • So I'm unclear on what you think the best way to model this relationship is. Are you saying that has_many :movies, through: watchlist_movies is ideal? – Captain Stack May 01 '16 at 02:32
  • What you have in your original question is the textbook way to do it and I'd avoid changing the names unless you had a very good reason. Personal preference, when it comes into contradiction with Rails conventions, should usually be ignored. Consistency leads to fewer surprises in the future. You might find it odd now, but most Rails developers find your code there to be exactly as expected, it's how they'd do it too. Calling `user.watchlist_movies` and getting a Movie model back is really confusing to someone not familiar with your naming conventions. – tadman May 01 '16 at 02:36
  • Fair enough. If I go that route though, should I even have a watchlist_movies model? Couldn't I just have the join table without a model and use the has_and_belongs_to_many association? Also, one reason I didn't want to use user.movies is because in the future they may have other lists. They'd have movies through other associations too. – Captain Stack May 01 '16 at 02:38
  • A case where you'd want to name it something different is if you had two or more relationships to the same model. For example, if users were listed in the credits of movies, and could also favourite them as well. Then you'd have two `has_many :through` relationships, which means you need to pick names for them. I'd go with something like `movies_on_watchlist` and `movies_with_credits` or something similar. – tadman May 01 '16 at 02:38
  • That's what I think might happen in the future. – Captain Stack May 01 '16 at 02:39
  • The `has_and_belongs_to_many` method is from Rails 1 and is really weak compared to having a first-class join model like you have here. I'd avoid using it unless you're dealing with legacy code. Sometimes you need to put data into the join relationship, and HABTM joins don't let you do that easily. – tadman May 01 '16 at 02:40
  • If you're planning for the future that's a perfectly valid consideration. I'd just use a name like `movies_on_watchlist` to avoid ambiguity and make it clear that these are movies. Using the name that another model would inherit by default leads to confusion. Remember when retrieving the movies on a person's watchlist you can do `@user.watchlist_movies.include(:movie)` to fetch both records at once. – tadman May 01 '16 at 02:40
  • I might do that. I'm also thinking I might just add a t.boolean for "watchlist" and then put an ActiveRecord query in a function called user_watchlist that queries User.movies.where("watchlist = true"). – Captain Stack May 01 '16 at 02:50
  • The default name for a relationship like that is `user_movies` where that clearly communicates it's a User-Movie join table. Then you do `User.user_movies.where(watchlist: true).include(:movie)` but of course you can easily wrap that up into a scope or method called `movies_on_watchlist`. Ultimately up to you, but do try and go with the one that's the least surprising and unconventional in terms of naming. – tadman May 01 '16 at 02:51
  • 1
    So I've opted to go with this last approach (user_movies). The only issue is that when I try to call .include(:movie) on an ActiveRecord query, it gives me a TypeError and says it wasn't expecting a symbol but a module. Do you have any idea what might be causing that? – Captain Stack May 01 '16 at 04:16
  • Sorry, that should be `includes(...)` as `include` is a reserved name. That's how you pre-load associations to avoid a secondary query. – tadman May 01 '16 at 04:24