如何重构多长的条款?

时间:2013-03-06 23:17:03

标签: ruby-on-rails

如何重构此代码?有没有办法拆分where子句,包含和顺序函数?

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 

2 个答案:

答案 0 :(得分: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表达式的要点。

如果您撤销某些逻辑并先检查nilconsole.nil?,然后再检查genre.nil?子句,您也可以放弃其中许多else项检查,例如,只需检查genre == "all"

也可以在模型中将其中一些定义为命名范围(或者参见此博客文章称为Named Scopes Are Dead以获得更好的方法),以便干掉一些代码并使其更具可读性。< / p>

上面我的例子仍然需要做很多工作,但我认为你可以按照这种模式组装一些不错的代码。

答案 1 :(得分: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)sql注入order子句,rails擅长保护where子句而不是order,请参阅rails 3 activerecord order - what is the proper sql injection work around?

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