Получение записей из отношения has_many: through, включая столбцы модели соединения и с учетом ограничения? - PullRequest
0 голосов
/ 09 февраля 2019

Я настроил три модели: User, List и UserList - последняя представляет собой модель соединения между User и List в отношении has_many_through.

UserList "модель соединения" также имеетстолбец с именем "rating".

Я хочу получить все записи "List" для данного пользователя, а также оценки для каждой записи List из UserList.Тем не менее, я делаю что-то не так и ходил кругами немного.Был бы признателен за руководство по тому, что я не понимаю.

Технические детали

Вот три модели:

class User < ApplicationRecord
  has_many :user_lists
  has_many :lists, through: :user_lists, dependent: :destroy
End

class List < ApplicationRecord
  has_many :user_lists
  has_many :users, through: :user_lists, dependent: :destroy

  # no duplicate titles in the List table
  validates :title, uniqueness: true
End

class UserList < ApplicationRecord
  belongs_to :list
  belongs_to :user

  # a given user can only have one copy of a list item
  validates :list_id, uniqueness: { scope: :user_id }
end

Желаемый результат

Получить все(и только) Элементы списка, которые принадлежат текущему пользователю, плюс рейтинг каждого элемента списка из модели UserList.

Мой код до сих пор

# list_controller.rb

def index
  @lists = current_user
    .lists
    .joins(:user_lists)
    .select('lists.*, user_lists.id as user_lists_id, user_lists.rating as rating')
    .order(:id)
  render json: @lists
end

Я взломал вышеупомянутое вместе из различных стековПереполнение статей.Но это не совсем правильно, и я не уверен, почему.

Вот вывод консоли rails, когда выполняется индексный код контроллера:

List Load (1.2ms)
  SELECT DISTINCT
    lists.*,
    user_lists.id as user_lists_id,
    user_lists.rating as rating
  FROM "lists"
  INNER JOIN "user_lists" "user_lists_lists"
    ON "user_lists_lists"."list_id" = "lists"."id"
  INNER JOIN "user_lists"
    ON "lists"."id" = "user_lists"."list_id"
  WHERE "user_lists"."user_id" = $1
  ORDER BY "lists"."id" ASC  [["user_id", 1]]

[#<List:0x00007fef741da338
  id: 2,
  title: "useronly",
  created_at: Sat, 09 Feb 2019 09:22:44 UTC +00:00,
  updated_at: Sat, 09 Feb 2019 09:22:44 UTC +00:00>,
 #<List:0x00007fef741d9f78
  id: 3,
  title: "The Art of Gathering",
  created_at: Sat, 09 Feb 2019 09:23:05 UTC +00:00,
  updated_at: Sat, 09 Feb 2019 09:23:05 UTC +00:00>]

Элементы списка извлекаются правильно -т. е. идентификатор, название, созданный и обновленный.И только для current_user (т.е. не для элементов другого пользователя). Однако столбец рейтинга из списков пользователей не извлекается правильно.

Эта часть вывода консоли рельсов выглядит не совсем правильно:

  INNER JOIN "user_lists" "user_lists_lists"
    ON "user_lists_lists"."list_id" = "lists"."id"

Ночестно говоря, я не уверен, как это исправить.Я пробовал различные заклинания, основанные на Googling + Stack-Overflowing, но ничто не попадает в цель.

Есть идеи?Большое спасибо.

РЕДАКТИРОВАТЬ, основываясь на предложении Obermillerk:

Основываясь на руководстве delegate от Obermillerk, я думаю, что для того, чтобы добавить "title" из модели List в каждую "user_list ", мне нужно сделать что-то вроде следующего:

# lists_controller.rb (lines 9 + 11 indicated below)
    def index
      lists = current_user.user_lists
9:       lists.each do |list|
        # retrieve title to each record in user_list. Title is delegated to List
11:         list.title = list.title
      end
      render json: lists
    end

Однако, это не совсем работает - представление пусто;Консоль сервера показывает:

00:01:22 api.1  |   UserList Load (2.2ms)  SELECT "user_lists".* FROM "user_lists" WHERE "user_lists"."user_id" = $1  [["user_id", 1]]
00:01:22 api.1  |   ↳ app/controllers/api/v1/lists_controller.rb:9
00:01:22 api.1  |   User Update (1.5ms)  UPDATE "users" SET "last_login" = $1, "updated_at" = $2 WHERE "users"."id" = $3  [["last_login", "2019-02-12 08:01:22.664620"], ["updated_at", "2019-02-12 08:01:22.676667"], ["id", 1]]
00:01:22 api.1  |   ↳ app/controllers/api/v1/users_controller.rb:44
00:01:22 api.1  |   List Load (0.5ms)  SELECT  "lists".* FROM "lists" WHERE "lists"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
00:01:22 api.1  |   ↳ app/models/user_list.rb:6
00:01:22 api.1  | Completed 500 Internal Server Error in 55ms (ActiveRecord: 11.1ms)
00:01:22 api.1  | 
00:01:22 api.1  | 
00:01:22 api.1  |   
00:01:22 api.1  | NoMethodError (undefined method `title=' for #<UserList:0x00007f9220002b48>
00:01:22 api.1  | Did you mean?  title):
00:01:22 api.1  |   
00:01:22 api.1  | app/controllers/api/v1/lists_controller.rb:11:in `block in index'
00:01:22 api.1  | app/controllers/api/v1/lists_controller.rb:9:in `index'

Ответы [ 2 ]

0 голосов
/ 09 февраля 2019

Дайте мне посмотреть, смогу ли я помочь вам снова, я чувствую себя облеченным сейчас;)

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

Таким образом, цель объединения состоит не в том, чтобы добавить поля данных в вашу модель, а в том, чтобы позволить вам уточнить результаты на основе данных изсвязанные записи в других таблицах.То есть, если вы хотите включить только элементы списка, у которых рейтинг конкретного пользователя превышает 3, вы можете использовать объединение, чтобы включить таблицу UserList, а затем использовать предложения WHERE, чтобы отфильтровать результаты только до тех, у которых рейтинг выше.чем 3. Однако ваши результирующие объекты List будут содержать только свои собственные данные, ничего из данных UserList.Я не верю, что есть какой-либо способ ввести поле.

Я думаю, что на самом деле вам следует работать с моделью UserList вместо модели List.Модель UserList имеет рейтинг, который вы ищете, и вы всегда можете получить доступ к списку непосредственно из UserList, поскольку она имеет прямую связь один к одному.Переход от Списка к Списку пользователей требует дополнительного ввода того, для какого пользователя вы ищете Список пользователей, что усложняет ситуацию.

Классная вещь, которую вы можете сделать в активной записи, которая может быть тем, чем вы являетесьищет делегат обработки метода для отношения.То есть вы можете указать UserLists вместо того, чтобы вызывать title для себя и ничего не придумывать, отправлять его на правильный объект List и возвращать результат.При этом вы можете делегировать все поля данных для элементов списка, например, title и того, что у вас есть, в объекте UserList, а затем объект UserList может вести себя как обертка вокруг своего объекта List.Вы делаете это, добавляя строку, подобную этой, в верхней части вашей модели, вероятно, лучше всего ставить после ваших ассоциаций:

class UserList < ApplicationRecord
  belongs_to :list
  belongs_to :user

  # delegate the :title method to the :list relation above
  delegate :title, to: :list

  # a given user can only have one copy of a list item
  validates :list_id, uniqueness: { scope: :user_id }
end

Вы можете добавить столько делегатов для делегирования, сколько вы хотите в одном вызове, так сказатьбыли также поля year и director, вы могли бы сделать delegate :title, :year, :director, to: :list

Возможно, лучшая вещь, если вы действительно хотите, чтобы UserList стал оберткой вокруг объектов List, вы можете сказать delegate_missing_to :list, а затем любые методы, которые не определены в UserList, будут автоматически пытаться пройти по списку.Таким образом, вы можете добавить новые методы и поля в модель List, и они будут автоматически делегированы в UserList.

Надеюсь, это поможет, с удовольствием проясню что-нибудь по мере необходимости!

0 голосов
/ 09 февраля 2019

Я проверил ваш скрипт, все работает отлично.Я не нашел проблему, которую вы упоминаете в своем вопросе.Ниже приводится результат, когда я запускаю это действие индекса.Вы можете заметить рейтинг в конце.

[{"id":3,"title":"This is list","created_at":"2019-02-09T16:19:18.552Z","updated_at":"2019-02-09T16:19:18.552Z","user_lists_id":2,"rating":"5"}]

Я думаю, вы не указали достаточно данных.Можете ли вы попробовать поставить следующие данные

u = User.create(:name => "another", :email => "ok@gmail.com")
l = List.create("This is list") // creating first
UserList.create(:user_id => 1, :list_id => 3,:rating => 5)

Пожалуйста, убедитесь, что вы проверили идентификаторы и поместили правильные идентификаторы в UserList.Обратите внимание, что рекомендуется использовать имя ListUser в алфавитном порядке для объединения таблиц.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...