如何重构此代码?有没有办法拆分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
答案 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表达式的要点。
如果您撤销某些逻辑并先检查nil
和console.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
在某些查询中出现两次