如果存在第二个表中的列值,如何覆盖表中的列值?

时间:2019-06-18 09:39:06

标签: ruby-on-rails

我正在构建电子商务应用程序,您可以在其中设置用户特定于产品的价格。如果未设置特定用户的特定产品价格,则会显示默认产品价格。

它可以正常工作,但是我对目前的解决方案不满意,因此我正在寻找更有效的解决方案。

表格:

  • 用户
  • 产品(所有产品信息+常规价格)
  • 价格(用户ID,产品ID,用户价格)

型号:

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(价格表)(如果存在),否则显示regular_price(产品表)。

我很乐意在一个查询中解决所有问题,仅根据current_user保留一个具有适当值的价格列

2 个答案:

答案 0 :(得分:2)

您可以使用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方法,由于LEFT JOIN上存在条件,它将生成SQL prices查询。

答案 1 :(得分:1)

新答案:

请不要将此答案标记为正确,因为我基本上只是将Marek的代码和您的代码扩展到我的代码中,因为经过几次尝试,我还是得出您已经完成的操作,但我只是将其放在此处它可以帮助任何人:

app / models / 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 %>

旧答案(无效代码):

未经测试,但您可以尝试以下方法吗? (不确定这是比您的方法有效还是无效)

app / models / 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 %>