2

如何重构此代码?有没有办法拆分 where 子句、includes 和 order 函数?

def self.product_search(query, console, genre, sort, order)
        if query
            #search(query)
            if !console.nil? && console != "all" && !genre.nil? && genre != "all"
                where("name_en ilike :q AND console_id = :c AND genre_id = :g OR ean ilike :q AND console_id = :c AND genre_id = :g", q: "%#{query}%", c: console, g: genre).includes(:genre, :console, :brand, :images).order("#{sort} #{order}")
            elsif !console.nil? && console != "all"
                where("name_en ilike :q AND console_id = :c OR ean ilike :q AND console_id = :c", q: "%#{query}%", c: console).includes(:genre, :console, :brand, :images).order("#{sort} #{order}")
                elsif !genre.nil? && genre != "all"
                where("name_en ilike :q AND genre_id = :g OR ean ilike :q AND genre_id = :g", q: "%#{query}%", g: genre).includes(:genre, :console, :brand, :images).order("#{sort} #{order}")
            else
                where("name_en ilike :q OR ean ilike :q", q: "%#{query}%").includes(:genre, :console, :brand, :images).order("#{sort} #{order}")
            end
        end
end 
4

2 回答 2

1

您可以分段构建 AREL 表达式;它们仅在被迭代或以其他方式使用时才会执行。例如,您可以执行以下操作:

def self.product_search(query, console, genre, sort, order)
  if query
    clause = all # Start with all, filter down.
    if !console.nil? && console != "all" && !genre.nil? && genre != "all"
      clause = clause.where("name_en ilike :q AND console_id = :c AND genre_id = :g OR ean ilike :q AND console_id = :c AND genre_id = :g", q: "%#{query}%", c: console, g: genre)
    elsif !console.nil? && console != "all"
      clause = clause.where("name_en ilike :q AND console_id = :c OR ean ilike :q AND console_id = :c", q: "%#{query}%", c: console)
    elsif !genre.nil? && genre != "all"
      clause = clause.where("name_en ilike :q AND genre_id = :g OR ean ilike :q AND genre_id = :g", q: "%#{query}%", g: genre)
    else
      clause = clause.where("name_en ilike :q OR ean ilike :q", q: "%#{query}%")
    end
    clause.includes(:genre, :console, :brand, :images).order("#{sort} #{order}")
  end
end

您可以继续链接和分配,直到您构建了您想要的整个搜索子句。这可以进一步优化,但我认为这足以证明链接 AREL 表达式的要点。

nil如果您颠倒某些逻辑并首先检查console.nil?and genre.nil?,然后在子句中,例如else仅检查 ,您也可以放弃其中的许多检查。genre == "all"

也可以将其中的一些定义为模型上的命名范围(或查看这篇名为Named Scopes Are Dead的博客文章以获得更好的方法),以干燥一些代码并使其更具可读性。

我上面的示例仍然需要大量工作,但我认为您可以按照该模式组装一些不错的代码。

于 2013-03-06T23:32:01.740 回答
0

这可能会让你走得太远,但我会将该代码移动到另一个对象中

# code in Product model
def self.product_search(search_criteria, console, genre, sort, order)
  return nil unless search_criteria.present?
  ProductSearch.new(search_criteria, genre, sort, order).find
end

# new class to handle Product search
class ProductSearch
  def initialize(search_criteria, console, genre, sort, order)
    @search_criteria = search_criteria
    @console = console
    @genre = genre
    @sort = sort
    @order = order
  end

  attr_reader :search_criteria, :console, :genre, :sort, :order

  def core_query_for_product_search
    # WARNING: .order("#{sort} #{order}") is open to sql injection attacks
    self.includes(:genre, :console, :brand, :images)
      .order("#{sort} #{order}")
      .where("name_en ilike :q OR ean ilike :q", q: "%#{search_criteria}%")
  end

  def with_console?
    !console.nil? && console != "all"
  end

  def with_genre?
    !genre.nil? && genre != "all" # you might want genre.present? instead of !genre.nil?
  end

  def find
    query = core_query_for_product_search
    query = query.where("genre_id = :g", g: genre) if with_genre?
    query = query.where("console_id = :c", c: console) if with_console?

    query
  end
end

需要注意的几点:

1) order 子句中的 sql 注入,rails 擅长保护 where 子句但不保护 order,请参阅rails 3 activerecord order - 什么是正确的 sql 注入解决方法?

2)这不再创建与您的查询完全相同的 sql,但我猜结果是相同的,rails AREL 链接总是会正确AND xxxxx添加OR可能会更困难,但在您的示例代码中,它出现OR ean ilike :q在每个这些查询中没有使用括号,所以我将 i 放在核心中,也许您实际上想要括号和不同的结果,不明白为什么AND console_id = :c在其中一些查询中出现两次

于 2013-03-07T00:30:48.747 回答