简体   繁体   中英

More than one has_many :through?

So. I have users and movies. Users have watched some movies and not others. I want to express this relationship something like this: 用户和电影

Note:

  • Not sure if it matters; but movies don't have to be connected to a user; they can exist independently (ie Movie 1 has no relationship to User 2). Users can also exist independently; they don't have to have watched or unwatched movies (not pictured here, but you get the idea)
  • One movie can be watched by one user but unwatched by another (grey vs. black connections)

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!

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM