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たとえば、いくつかのロジックを逆にして、最初に and をチェックし、次に句で をチェックする場合console.nil?genre.nil?これらelseのチェックの多くを捨てることもできますgenre == "all"

これらのいくつかをモデルの名前付きスコープとして定義することもできます (または、より良い方法については、名前付きスコープは死んでいるというこのブログ投稿を参照してください)、コードの一部をドライアップして読みやすくすることもできます。

上記の私の例はまだ多くの作業を必要としていますが、そのパターンに従って素敵なコードを組み立てることができると思います。

于 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が作成されることはなくなりましたが、結果は同じであると推測しています。チェーンが常に適切にAND xxxxx追加されるレールARELはORより困難になる可能性がありますが、サンプルコードでOR ean ilike :qは、すべてのSQLにあるように見えましたこれらのクエリのうち、括弧は使用されていないので、コアに i を入れます。実際には括弧が必要で、別の結果が必要な場合があります。AND console_id = :cこれらのクエリの一部でなぜ 2 回表示されるのか理解できませんでした。

于 2013-03-07T00:30:48.747 に答える