Как заставить ассоциацию Rails использовать определенное имя для доступа к модели с другим именем?

Допустим, у меня есть модель пользователя и модель фильма, а также таблицы для их хранения. Допустим, я хочу добавить функцию списка наблюдения, добавив третью таблицу с именем watchlist_movies, которая просто сопоставляет user_id с movie_id.

Теперь я хочу добавить модель watchlist_movie. Я хочу иметь возможность запрашивать ActiveRecord с помощью user.watchlist_movies и получать коллекцию объектов фильма. До сих пор я пробовал (в user.rb)

has_many :watchlist_movies 

а также

has_many :movies, through: watchlist_movies

Первое приводит к тому, что user.watchlist_movies возвращает запись, а второе возвращает коллекцию записей фильмов в user.movies. По сути, я хочу, чтобы user.watchlist_movies возвращал коллекцию записей фильмов, поэтому я хочу, чтобы доступ, определенный в первом отношении, возвращал содержимое второго отношения. Как мне это сделать?


person Captain Stack    schedule 01.05.2016    source источник


Ответы (1)


Вы правильно определяете отношения здесь, но у вас может быть случай, когда ваши ожидания не совпадают с тем, как работает Rails. Если ваша структура такова, что фильм связан с пользователем через WatchlistMovie, что представляет собой простую модель соединения, вы на правильном пути.

Помните, что вы можете называть свои отношения как угодно:

has_many :watchlist_movie_associations,
  class_name: 'WatchlistMovie'

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

Я бы не советовал этого делать, так как это идет вразрез с тем, как ActiveRecord предпочитает называть вещи. Если это вас просто беспокоит прямо сейчас, это понятно, но если вы примете стиль вместо того, чтобы бороться с ним, у вас будет приложение, которое будет намного более последовательным и понятным.

person tadman    schedule 01.05.2016
comment
Так что мне не совсем понятно, что, по-твоему, лучший способ смоделировать эти отношения. Вы говорите, что идеально подходит has_many :movies, через: watchlist_movies? - person Captain Stack; 01.05.2016
comment
То, что у вас есть в исходном вопросе, - это способ сделать это из учебника, и я бы не стал менять имена, если у вас нет очень веской причины. Личные предпочтения, когда они вступают в противоречие с соглашениями Rails, обычно следует игнорировать. Последовательность приводит к меньшему количеству сюрпризов в будущем. Сейчас это может показаться вам странным, но большинство разработчиков Rails считают, что ваш код именно такой, как и ожидалось, и они бы тоже так поступили. Вызов user.watchlist_movies и получение обратно модели Movie действительно сбивает с толку тех, кто не знаком с вашими соглашениями об именах. - person tadman; 01.05.2016
comment
Справедливо. Однако, если я пойду по этому пути, должна ли у меня вообще быть модель watchlist_movies? Разве я не мог просто иметь таблицу соединения без модели и использовать ассоциацию has_and_belongs_to_many? Кроме того, одна из причин, по которой я не хотел использовать user.movies, заключается в том, что в будущем у них могут быть другие списки. У них были бы фильмы и через другие ассоциации. - person Captain Stack; 01.05.2016
comment
Случай, когда вы хотели бы назвать его как-то по-другому, — это если у вас было два или более отношений с одной и той же моделью. Например, если бы пользователи были указаны в титрах фильмов, а также могли бы добавлять их в избранное. Тогда у вас будет два отношения has_many :through, что означает, что вам нужно выбрать для них имена. Я бы выбрал что-то вроде movies_on_watchlist и movies_with_credits или что-то подобное. - person tadman; 01.05.2016
comment
Вот что, я думаю, может произойти в будущем. - person Captain Stack; 01.05.2016
comment
Метод has_and_belongs_to_many взят из Rails 1 и действительно слаб по сравнению с первоклассной моделью соединения, как у вас здесь. Я бы не стал использовать его, если вы не имеете дело с устаревшим кодом. Иногда вам нужно поместить данные в отношения соединения, а соединения HABTM не позволяют вам сделать это легко. - person tadman; 01.05.2016
comment
Если вы планируете будущее, это совершенно правильное соображение. Я бы просто использовал имя вроде movies_on_watchlist, чтобы избежать двусмысленности и дать понять, что это фильмы. Использование имени, которое по умолчанию унаследовала бы другая модель, приводит к путанице. Помните, что при извлечении фильмов из списка наблюдения человека вы можете сделать @user.watchlist_movies.include(:movie), чтобы получить обе записи одновременно. - person tadman; 01.05.2016
comment
Я мог бы это сделать. Я также думаю, что могу просто добавить t.boolean для списка наблюдения, а затем поместить запрос ActiveRecord в функцию с именем user_watchlist, которая запрашивает User.movies.where(watchlist = true). - person Captain Stack; 01.05.2016
comment
Имя по умолчанию для такого отношения — user_movies, где это ясно указывает на то, что это таблица соединения User-Movie. Затем вы делаете User.user_movies.where(watchlist: true).include(:movie), но, конечно, вы можете легко обернуть это в область действия или метод с именем movies_on_watchlist. В конечном счете решать вам, но постарайтесь выбрать тот, который наименее удивителен и нетрадиционен с точки зрения именования. - person tadman; 01.05.2016
comment
Поэтому я решил использовать этот последний подход (user_movies). Единственная проблема заключается в том, что когда я пытаюсь вызвать .include(:movie) в запросе ActiveRecord, он выдает ошибку TypeError и говорит, что ожидал не символ, а модуль. У вас есть идеи, что может быть причиной этого? - person Captain Stack; 01.05.2016
comment
Извините, это должно быть includes(...), так как include — зарезервированное имя. Вот как вы предварительно загружаете ассоциации, чтобы избежать вторичного запроса. - person tadman; 01.05.2016