Как переопределить значение столбца таблицы значением столбца из второй таблицы, если оно существует? - PullRequest
2 голосов
/ 18 июня 2019

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

Это отлично работает, но я ищу более эффективное решение, так как меня не устраивает текущее.

Таблицы:

  • Пользователи
  • товаров (вся информация о товаре + регулярная цена)
  • цены (user_id, product_id, user_price)

Модели:

class User < ApplicationRecord
  has_many :prices
end
class Product < ApplicationRecord
  has_many :prices
  validates :name, presence: true

  def self.with_user_prices(current_user)
    Product.joins(
      Product.sanitize_sql_array(['LEFT OUTER JOIN prices ON prices.user_id = ?
        AND products.id = prices.product_id', current_user])
    ).select('products.*, prices.user_price')
  end
end
class Price < ApplicationRecord
  belongs_to :product
  belongs_to :user
end

Как получить все продукты по индивидуальным ценам в контроллере:

@products = Product.with_user_prices(current_user)

Как я отображаю их в поле зрения:

<% @products.each do |product| %>

  <%= product.user_price ? product.user_price : product.regular_price %>

<% end %>

Как вы видите, я в настоящее время присоединяюсь к таблице цен, а затем в поле зрения я отображаю user_price (таблица цен), если он существует, в противном случае регулярная_причина (таблица продуктов).

Я бы хотел решить все за один запрос, сохранив только один ценовой столбец с соответствующим значением в соответствии с current_user

Ответы [ 2 ]

2 голосов
/ 18 июня 2019

Вы можете использовать функцию SQL COALESCE :

class Product < ApplicationRecord
  # ...

  def self.with_user_prices(user)
    includes(:prices).where(prices: { user_id: user.id }).select(
      'products.*, COALESCE(prices.user_price, products.regular_price) as price'
    )
  end
end

Тогда вы можете использовать его просто:

<%= product.price %>

Обратите внимание, что я немного упростил метод Product.with_user_prices, используя includes, который будет генерировать запрос SQL LEFT JOIN, так как есть условие на prices.

1 голос
/ 18 июня 2019

Новый ответ:

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

приложение / модели / product.rb

class Product < ApplicationRecord
  def self.with_user_prices(user)
    joins(
      sanitize_sql_array([
        "LEFT OUTER JOIN prices on prices.product_id = products.id AND prices.user_id = ?", user.id
      ])
    ).select(
      'products.*',
      'COALESCE(prices.user_price, products.regular_price) AS price_for_user'
    )
  end
end

контроллер:

@products = Product.with_user_prices(current_user)

вид:

<% @products.each do |product| %>
  <%= product.price_for_user %>
<% end %>

Старый ответ (неэффективный код):

Не проверено, но можете ли вы попробовать следующее? (Не уверен, что это более или менее эффективно, чем ваш подход)

приложение / модели / product.rb

class Product < ApplicationRecord
  has_many :prices

  def price_for_user(user)
    prices.includes(:user).where(
      users: { id: user.id }
    ).first&.user_price || regular_price
  end
end

контроллер:

# will perform LEFT OUTER JOIN (to eager load both `prices` and `prices -> user`) preventing N+1 queries
@products = Product.eager_load(prices: :user)

вид:

<% @products.each do |product| %>
  <%= product.price_for_user(current_user) %>
<% end %>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...