Является ли вообще плохой практикой иметь две разные ассоциации между одними и теми же двумя объектами? - PullRequest
1 голос
/ 09 мая 2020

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

Пример сценария

Моделирование взаимосвязи между Books и Clients в библиотеке, где предположим, что по правилам библиотеки клиент может оформить только один бронируйте за раз.

Что делает для меня это сложным, так это то, что клиент проверяет книгу только на определенный период времени, а затем возвращает ее. Это почти похоже на то, что вы не хотите, чтобы эти отношения продолжались вечно ... в какой-то момент клиент освобождается от ответственности за эту книгу (например, если книга была повреждена через 2 года после того, как клиент ее вернул, вы не хотите звонить бывшему клиенту с просьбой о замене).

Тем не менее, хотя это временный, вам все равно нужна запись отношений, чтобы вы могли отслеживать историю клиентов, которые выписали книга (потому что, возможно, вы захотите разослать опрос бывшим клиентам с просьбой оставить отзывы).

Другими словами, мне кажется, что есть 2 ассоциации ... одна временная, а другая это более длительно.

Хотя технически я могу написать код, чтобы заставить эту работу работать, это кажется ужасной практикой (например ... интуитивно я чувствую, что ответ на этот вопрос - да ... это не очень хорошая практика). Но я хотел бы подтвердить и посмотреть, если да, то каковы другие решения этой проблемы (has_many :through может быть, если сквозная таблица имеет какой-то атрибут ... current_rental?)

Как будут выглядеть 2 ассоциации?

Ассоциация # 1, временная ассоциация, вы хотите знать, какому клиенту выписана книга

Client has_one :book, foreign_key: "current_id"
Book belongs_to :current_client, class_name: "Client", foreign_key: "current_id"

Эта связь очень полезна , если клиент позвонит и скажет, что забыл, какую книгу он должен был вернуть со своей личной книжной стойки, вы можете позвонить по номеру @client.book.title.

Ассоциация №2, долгосрочная ассоциация, вы хотите узнать историю книги (предположим, что история клиента не имеет значения, если бы это было более ясно, это была бы has_many :through или Взаимосвязь HABTM)

Client belongs_to :checkout_book, class_name: "Book", foreign_key: "book_id"
Book has_many :clients, foreign_key: "book_id"

Эта взаимосвязь позволяет вам просматривать историю книги, сколько клиентов она получила на @book.clients.size, или вы можете отправить им электронное письмо для опроса @book.clients.map(&:email).

Это разделение затем устраняет путаницу, когда, скажем, книгу нужно заменить, вызов @book.clients.last может вызвать неправильного клиента, поскольку резервирование выполняется заранее, и поэтому проверки не обязательно происходят по порядку. Однако вы можете @book.current_client, чтобы найти нужного человека, с которым вы будете следить.

1 Ответ

1 голос
/ 09 мая 2020

Одна из причин, по которой вы хотели бы сохранить дополнительную ассоциацию, - это оптимизация запросов чтения, если у вас есть объединенная таблица с огромным объемом данных.

Допустим, наша гипотетическая библиотека безумно популярна, а многие тысячи кредитов на книгу. И у нас это настроено так:

class Client
  has_many :loans
  has_many :books, through: :loans
end
class Book
  has_many :loans
  has_many :clients, through: :loans
end
class Loan
  belongs_to :book
  belongs_to :client
end
class LoansController ApplicationController
  def create
    @book = Books.find(id: params[:book_id])
    @loan = @book.loans.new(client: current_client)
    if @loan.save
      redirect_to @loan
    else
      render :new           
    end
  end
end

Мы получаем требование перечислить 100 самых популярных книг и клиента, который в настоящее время владеет книгой:

class BooksController < ApplicationController
  def by_popularity
     @book = Book.order(popularity: :desc).limit(100)
  end
end 
<% @books.each do |book| %>
  <tr>
    <td><%= link_to book.title, book %></td>
    <td><%= link_to book.clients.last.name, book.clients.last %></td> 
  <tr>
<% end %> 

Поскольку это вызывает запрос N + 1, мы получаем 101 запрос к базе данных. Не хорошо. Поэтому мы набрасываем на него includes, чтобы решить эту проблему.

@book = Book.includes(:clients)
            .order(popularity: :desc)
            .limit(100)

Все хорошо, правда? Нет. Для каждой записи, которую мы загружаем из книг, будут загружены и созданы все объединенные записи ссуд и клиентов. Поскольку это тысячи и тысячи, сервер резко останавливается из-за нехватки памяти.

Хотя существуют различные уловки, такие как подзапросы для ограничения вывода, самым простым и наиболее эффективным решением является:

class Book
  has_many :loans, after_add: :set_current_client
  has_many :clients, through: :loans
  belongs_to :current_client, class_name: 'Client'

  def set_current_client(client)
    update_column(current_client_id: current_client)
  end
end
class BooksController < ApplicationController
  def by_popularity
     @book = Book.order(popularity: :desc)
                 .eager_load(:current_client)
                 .limit(100)
  end
end 
<% @books.each do |book| %>
  <tr>
    <td><%= link_to book.title, book %></td>
    <td><%= link_to book.current_client.name, book.current_client %></td> 
  <tr>
<% end %> 

TL; DR;

Является ли плохой практикой иметь несколько ассоциаций между двумя одинаковыми объектами? Нет, не обязательно. Это законное решение довольно многих проблем, но оно имеет свои собственные недостатки.

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