Reputation: 23
So. I have users and movies. Users have watched some movies and not others. I want to express this relationship something like this:
Note:
My initial reaction is that this a has_many :through
relationship, something like:
/models/user.rb:
def User
has_many :movies, :through => :unwatched_movies
has_many :movies, :through => :watched_movies
end
/models/movie.rb:
def Movie
has_many :users, :through => :unwatched_movies
has_many :users, :through => :watched_movies
end
But first of all, that code definitely doesn't work...
I want to be able to query for, say, u.unwatched_movies
(where u
is an instance of User
, which doesn't seem to jive with the above.
I have a feeling this has something to do with :source
or :as
... but I'm feeling a little lost. Am I right in thinking that this is a 3-level hierarchy, where I need models for User
, UnwatchedMovieList
/WatchedMovieList
, and Movie
? This question feels very close but I can't seem to make it work in this context.
Any help on how to write these models and migrations would be super helpful. Thank you!
Upvotes: 1
Views: 237
Reputation: 361
The following set of associations should cover your use case of being able to explicitly mark movies watched and unwatched. It makes use of a join table called user_movies
that simply contains the following fields: user_id, movie_id, and watched
class User
has_many :unwatched_user_movies, -> { where(watched: false) }, class_name: 'UserMovie'
has_many :unwatched_movies, through: :unwatched_user_movies, class_name: 'Movie'
has_many :watched_user_movies, -> { where(watched: true) }, class_name: 'UserMovie'
has_many :watched_movies, through: :watched_user_movies, class_name: 'Movie'
end
class UserMovie
belongs_to :movie
belongs_to :user
end
class Movie
has_many :user_movies
end
Upvotes: 0
Reputation: 2359
Here is my solution
Create these models
class User < ActiveRecord::Base
has_many :user_movies
# Use a block to add extensions
has_many :movies, through: :user_movies, source: 'movie' do
# this is an extension
def watched
where('user_movies.watched = ?', true)
end
def unwatched
where('user_movies.watched = ?', false)
end
end
end
class Movie < ActiveRecord::Base
has_many :user_movies
has_many :watchers, through: :user_movies, source: :user do
# users who is an effective watcher
def watchers
where('user_movies.watched = ?', true)
end
# users how marked it but did not watch it yet
def markers
where('user_movies.watched = ?', false)
end
end
end
class UserMovie < ActiveRecord::Base
belongs_to :user
belongs_to :movie
end
class CreateUserMovies < ActiveRecord::Migration
def change
create_table :user_movies do |t|
t.belongs_to :user, index: true
t.belongs_to :movie, index: true
t.boolean :watched, default: false, null: false
t.timestamps null: false
end
add_foreign_key :user_movies, :users
add_foreign_key :user_movies, :movies
end
end
then for queries
@user = User.first
@user.movies.watched
@user.movies.unwatched
@movie = Movie.first
@movie.watchers.watchers
@movie.watchers.markers
Upvotes: 1
Reputation: 4877
You're trying to create a relationship of omission - "unmatched movies". Which isn't a good idea, you should build up a history of movies watch (which is watched_movies) but then for unwatched you would want to find all movies minus watched movies. Then stick it in a function in User, like so:
def unwatched_movies
Movie.where("id NOT IN ?", self.watched_movies.collect(&:movie_id))
end
Upvotes: 1