Rails- 构建用于过滤搜索结果的动态查询
Rails- Building dynamic query for filtering search results
我正在尝试构建一个动态查询方法来过滤搜索结果。
我的模特:
class Order < ActiveRecord::Base
scope :by_state, -> (state) { joins(:states).where("states.id = ?", state) }
scope :by_counsel, -> (counsel) { where("counsel_id = ?", counsel) }
scope :by_sales_rep, -> (sales) { where("sales_id = ?", sales) }
scope :by_year, -> (year) { where("title_number LIKE ?", "%NYN#{year}%") }
has_many :properties, :dependent => :destroy
has_many :documents, :dependent => :destroy
has_many :participants, :dependent => :destroy
has_many :states, through: :properties
belongs_to :action
belongs_to :role
belongs_to :type
belongs_to :sales, :class_name => 'Member'
belongs_to :counsel, :class_name => 'Member'
belongs_to :deal_name
end
class Property < ActiveRecord::Base
belongs_to :order
belongs_to :state
end
class State < ActiveRecord::Base
has_many :properties
has_many :orders, through: :properties
end
我有一个默认显示所有订单的页面。我想要复选框以允许过滤结果。过滤器是:年份、州、销售和顾问。一个查询示例是:2016 年、2015 年的所有订单("order.title_number LIKE ?"、“%NYN#{year}%”)在州(has_many 到)新泽西州、宾夕法尼亚州、加利福尼亚州等 sales_id 无限 ids 和 counsel_id 无限 counsel_ids.
简而言之shell 我正在尝试弄清楚如何创建一个考虑用户检查的所有选项的查询。这是我当前的查询代码:
def Order.query(opt = {})
results = []
orders = []
if !opt["state"].empty?
opt["state"].each do |value|
if orders.empty?
orders = Order.send("by_state", value)
else
orders << Order.send("by_state", value)
end
end
orders = orders.flatten
end
if !opt["year"].empty?
new_orders = []
opt["year"].each do |y|
new_orders = orders.by_year(y)
results << new_orders
end
end
if !opt["sales_id"].empty?
end
if !opt["counsel_id"].empty?
end
if !results.empty?
results.flatten
else
orders.flatten
end
end
这是我想出的允许无限量过滤的解决方案。
def self.query(opts = {})
orders = Order.all
opts.delete_if { |key, value| value.blank? }
const_query = ""
state_query = nil
counsel_query = nil
sales_query = nil
year_query = nil
queries = []
if opts["by_year"]
year_query = opts["by_year"].map do |val|
" title_number LIKE '%NYN#{val}%' "
end.join(" or ")
queries << year_query
end
if opts["by_sales_rep"]
sales_query = opts["by_sales_rep"].map do |val|
" sales_id = '#{val}' "
end.join(" or ")
queries << sales_query
end
if opts["by_counsel"]
counsel_query = opts["by_counsel"].map do |val|
" counsel_id = '#{val}' "
end.join(" or ")
queries << counsel_query
end
if opts["by_state"]
state_query = opts["by_state"].map do |val|
"states.id = '#{val}'"
end.join(" or ")
end
query_string = queries.join(" AND ")
if state_query
@orders = Order.joins(:states).where("#{state_query}")
@orders = @orders.where(query_string)
else
@orders = orders.where("#{query_string}")
end
@orders.order("title_number DESC")
end
你要找的是一个query/filter对象,这是一个常见的模式。我写了一个与此类似的answer,但我会尝试提取重要部分。
首先你应该将这些逻辑移动到它自己的对象中。当 search/filter 对象被初始化时,它应该从一个关系查询(Order.all
或一些基本查询)开始,然后在你进行时过滤它。
这是一个超级基本的示例,虽然没有充实,但应该能让您走上正轨。你可以这样称呼它,orders = OrderQuery.call(params)
.
# /app/services/order_query.rb
class OrderQuery
def call(opts)
new(opts).results
end
private
attr_reader :opts, :orders
def new(opts={})
@opts = opts
@orders = Order.all # If using Rails 3 you'll need to use something like
# Order.where(1=1) to get a Relation instead of an Array.
end
def results
if !opt['state'].empty?
opt['state'].each do |state|
@orders = orders.by_state(state)
end
end
if !opt['year'].empty?
opt['year'].each do |year|
@orders = orders.by_year(year)
end
end
# ... all filtering logic
# you could also put this in private functions for each
# type of filter you support.
orders
end
end
编辑:使用 OR 逻辑而不是 AND 逻辑
# /app/services/order_query.rb
class OrderQuery
def call(opts)
new(opts).results
end
private
attr_reader :opts, :orders
def new(opts={})
@opts = opts
@orders = Order.all # If using Rails 3 you'll need to use something like
# Order.where(1=1) to get a Relation instead of an Array.
end
def results
if !opt['state'].empty?
@orders = orders.where(state: opt['state'])
end
if !opt['year'].empty?
@orders = orders.where(year: opt['year'])
end
# ... all filtering logic
# you could also put this in private functions for each
# type of filter you support.
orders
end
end
以上语法基本上过滤了说法 if state is in this array of states
和 year is within this array of years
.
在我的例子中,过滤器选项来自控制器的参数,所以我做了这样的事情:
ActionController::Parameters
结构:
{
all: <Can be true or false>,
has_planned_tasks: <Can be true or false>
... future filters params
}
过滤方法:
def self.filter(filter_params)
filter_params.reduce(all) do |queries, filter_pair|
filter_key = filter_pair[0]
filter_value = filter_pair[1]
return {
all: proc { queries.where(deleted_at: nil) if filter_value == false },
has_planned_tasks: proc { queries.joins(:planned_tasks).distinct if filter_value == true },
}.fetch(filter_key).call || queries
end
end
然后我在控制器中调用 ModelName.filter(filter_params.to_h)
。我能够像这样轻松地添加更多条件过滤器。
这里有 space 可以改进的地方,例如提取过滤器逻辑或整个过滤器对象,但我让您决定在您的上下文中哪个更好。
这是我在 Rails 中使用来自控制器的参数为电子商务订单仪表板构建的一个。
这个查询会执行两次,一次是统计订单,一次是根据请求中的参数return请求的订单。
该查询支持:
- 按列排序
- 排序方向
- 增量搜索 - 它将搜索给定字段的开头,并 returns 那些匹配的记录在搜索时启用实时建议
- 分页(每页限制 100 条记录)
我也有预定义的值来清理一些数据。
这种风格非常干净,易于其他人阅读和修改。
这是一个示例查询:
api/shipping/orders?pageNumber=1&orderStatus=unprocessedOrders&filters=standard,second_day&stores=82891&sort_column=Date&sort_direction=dsc&search_query=916
控制器代码如下:
user_id = session_user.id
order_status = params[:orderStatus]
status = {
"unprocessedOrders" => ["0", "1", "4", "5"],
"processedOrders" => ["2", "3", "6"],
"printedOrders" => ["3"],
"ratedOrders" => ["1"],
}
services = [
"standard",
"expedited",
"next_day",
"second_day"
]
countries = [
"domestic",
"international"
]
country_defs = {
domestic: ['US'],
international: ['CA', 'AE', 'EU', 'GB', 'MX', 'FR']
}
columns = {
Number: "order_number",
QTY: "order_qty",
Weight: "weight",
Status: "order_status",
Date: "order_date",
Carrier: "ship_with_carrier",
Service: "ship_with_carrier_code",
Shipping: "requestedShippingService",
Rate: "cheapest_rate",
Domestic: "country",
Batch: "print_batch_id",
Skus: "skus"
}
# sort_column=${sortColumn}&sort_direction=${sortDirection}&search_query=${searchQuery}
filters = params[:filters].split(',')
stores = params[:stores].split(',')
sort_column = params[:sort_column]
sort_direction = params[:sort_direction]
search_query = params[:search_query]
sort_by_column = columns[params[:sort_column].to_sym]
sort_direction = params[:sort_direction] == "asc" ? "asc" : "desc"
service_params = filters.select{ |p| services.include?(p) }
country_params = filters.select{ |p| countries.include?(p) }
order_status_params = filters.select{ |p| status[p] != nil }
query_countries = []
query_countries << country_defs[:"#{country_params[0]}"] if country_params[0]
query_countries << country_defs[:"#{country_params[1]}"] if country_params[1]
active_filters = [service_params, country_params].flatten
query = Order.where(user_id: user_id)
query = query.where(order_status: status[order_status]) if order_status_params.empty?
query = query.where("order_number ILIKE ? OR order_id::TEXT ILIKE ? OR order_info->'advancedOptions'->>'customField2' ILIKE ?", "%#{search_query}%", "%#{search_query}%", "%#{search_query}%") unless search_query.gsub(/\s+/, "").length == 0
query = query.where(requestedShippingService: service_params) unless service_params.empty?
query = query.where(country: "US") if country_params.include?("domestic") && !country_params.include?("international")
query = query.where.not(country: "US") if country_params.include?("international") && !country_params.include?("domestic")
query = query.where(order_status: status[order_status_params[0]]) unless order_status_params.empty?
query = query.where(store_id: stores) unless stores.empty?\
order_count = query.count
num_of_pages = (order_count.to_f / 100).ceil()
requested_page = params[:pageNumber].to_i
formatted_number = (requested_page.to_s + "00").to_i
query = query.offset(formatted_number - 100) unless requested_page == 1
query = query.limit(100)
query = query.order("#{sort_by_column}": :"#{sort_direction}") unless sort_by_column == "skus"
query = query.order("skus[1] #{sort_direction}") if sort_by_column == "skus"
query = query.order(order_number: :"#{sort_direction}")
orders = query.all
puts "After querying orders mem:" + mem.mb.to_s
requested_page = requested_page <= num_of_pages ? requested_page : 1
options = {}
options[:meta] = {
page_number: requested_page,
pages: num_of_pages,
type: order_status,
count: order_count,
active_filters: active_filters
}
render json: OrderSerializer.new(orders, options).serialized_json
我正在尝试构建一个动态查询方法来过滤搜索结果。
我的模特:
class Order < ActiveRecord::Base
scope :by_state, -> (state) { joins(:states).where("states.id = ?", state) }
scope :by_counsel, -> (counsel) { where("counsel_id = ?", counsel) }
scope :by_sales_rep, -> (sales) { where("sales_id = ?", sales) }
scope :by_year, -> (year) { where("title_number LIKE ?", "%NYN#{year}%") }
has_many :properties, :dependent => :destroy
has_many :documents, :dependent => :destroy
has_many :participants, :dependent => :destroy
has_many :states, through: :properties
belongs_to :action
belongs_to :role
belongs_to :type
belongs_to :sales, :class_name => 'Member'
belongs_to :counsel, :class_name => 'Member'
belongs_to :deal_name
end
class Property < ActiveRecord::Base
belongs_to :order
belongs_to :state
end
class State < ActiveRecord::Base
has_many :properties
has_many :orders, through: :properties
end
我有一个默认显示所有订单的页面。我想要复选框以允许过滤结果。过滤器是:年份、州、销售和顾问。一个查询示例是:2016 年、2015 年的所有订单("order.title_number LIKE ?"、“%NYN#{year}%”)在州(has_many 到)新泽西州、宾夕法尼亚州、加利福尼亚州等 sales_id 无限 ids 和 counsel_id 无限 counsel_ids.
简而言之shell 我正在尝试弄清楚如何创建一个考虑用户检查的所有选项的查询。这是我当前的查询代码:
def Order.query(opt = {})
results = []
orders = []
if !opt["state"].empty?
opt["state"].each do |value|
if orders.empty?
orders = Order.send("by_state", value)
else
orders << Order.send("by_state", value)
end
end
orders = orders.flatten
end
if !opt["year"].empty?
new_orders = []
opt["year"].each do |y|
new_orders = orders.by_year(y)
results << new_orders
end
end
if !opt["sales_id"].empty?
end
if !opt["counsel_id"].empty?
end
if !results.empty?
results.flatten
else
orders.flatten
end
end
这是我想出的允许无限量过滤的解决方案。
def self.query(opts = {})
orders = Order.all
opts.delete_if { |key, value| value.blank? }
const_query = ""
state_query = nil
counsel_query = nil
sales_query = nil
year_query = nil
queries = []
if opts["by_year"]
year_query = opts["by_year"].map do |val|
" title_number LIKE '%NYN#{val}%' "
end.join(" or ")
queries << year_query
end
if opts["by_sales_rep"]
sales_query = opts["by_sales_rep"].map do |val|
" sales_id = '#{val}' "
end.join(" or ")
queries << sales_query
end
if opts["by_counsel"]
counsel_query = opts["by_counsel"].map do |val|
" counsel_id = '#{val}' "
end.join(" or ")
queries << counsel_query
end
if opts["by_state"]
state_query = opts["by_state"].map do |val|
"states.id = '#{val}'"
end.join(" or ")
end
query_string = queries.join(" AND ")
if state_query
@orders = Order.joins(:states).where("#{state_query}")
@orders = @orders.where(query_string)
else
@orders = orders.where("#{query_string}")
end
@orders.order("title_number DESC")
end
你要找的是一个query/filter对象,这是一个常见的模式。我写了一个与此类似的answer,但我会尝试提取重要部分。
首先你应该将这些逻辑移动到它自己的对象中。当 search/filter 对象被初始化时,它应该从一个关系查询(Order.all
或一些基本查询)开始,然后在你进行时过滤它。
这是一个超级基本的示例,虽然没有充实,但应该能让您走上正轨。你可以这样称呼它,orders = OrderQuery.call(params)
.
# /app/services/order_query.rb
class OrderQuery
def call(opts)
new(opts).results
end
private
attr_reader :opts, :orders
def new(opts={})
@opts = opts
@orders = Order.all # If using Rails 3 you'll need to use something like
# Order.where(1=1) to get a Relation instead of an Array.
end
def results
if !opt['state'].empty?
opt['state'].each do |state|
@orders = orders.by_state(state)
end
end
if !opt['year'].empty?
opt['year'].each do |year|
@orders = orders.by_year(year)
end
end
# ... all filtering logic
# you could also put this in private functions for each
# type of filter you support.
orders
end
end
编辑:使用 OR 逻辑而不是 AND 逻辑
# /app/services/order_query.rb
class OrderQuery
def call(opts)
new(opts).results
end
private
attr_reader :opts, :orders
def new(opts={})
@opts = opts
@orders = Order.all # If using Rails 3 you'll need to use something like
# Order.where(1=1) to get a Relation instead of an Array.
end
def results
if !opt['state'].empty?
@orders = orders.where(state: opt['state'])
end
if !opt['year'].empty?
@orders = orders.where(year: opt['year'])
end
# ... all filtering logic
# you could also put this in private functions for each
# type of filter you support.
orders
end
end
以上语法基本上过滤了说法 if state is in this array of states
和 year is within this array of years
.
在我的例子中,过滤器选项来自控制器的参数,所以我做了这样的事情:
ActionController::Parameters
结构:
{
all: <Can be true or false>,
has_planned_tasks: <Can be true or false>
... future filters params
}
过滤方法:
def self.filter(filter_params)
filter_params.reduce(all) do |queries, filter_pair|
filter_key = filter_pair[0]
filter_value = filter_pair[1]
return {
all: proc { queries.where(deleted_at: nil) if filter_value == false },
has_planned_tasks: proc { queries.joins(:planned_tasks).distinct if filter_value == true },
}.fetch(filter_key).call || queries
end
end
然后我在控制器中调用 ModelName.filter(filter_params.to_h)
。我能够像这样轻松地添加更多条件过滤器。
这里有 space 可以改进的地方,例如提取过滤器逻辑或整个过滤器对象,但我让您决定在您的上下文中哪个更好。
这是我在 Rails 中使用来自控制器的参数为电子商务订单仪表板构建的一个。
这个查询会执行两次,一次是统计订单,一次是根据请求中的参数return请求的订单。
该查询支持:
- 按列排序
- 排序方向
- 增量搜索 - 它将搜索给定字段的开头,并 returns 那些匹配的记录在搜索时启用实时建议
- 分页(每页限制 100 条记录)
我也有预定义的值来清理一些数据。
这种风格非常干净,易于其他人阅读和修改。
这是一个示例查询:
api/shipping/orders?pageNumber=1&orderStatus=unprocessedOrders&filters=standard,second_day&stores=82891&sort_column=Date&sort_direction=dsc&search_query=916
控制器代码如下:
user_id = session_user.id
order_status = params[:orderStatus]
status = {
"unprocessedOrders" => ["0", "1", "4", "5"],
"processedOrders" => ["2", "3", "6"],
"printedOrders" => ["3"],
"ratedOrders" => ["1"],
}
services = [
"standard",
"expedited",
"next_day",
"second_day"
]
countries = [
"domestic",
"international"
]
country_defs = {
domestic: ['US'],
international: ['CA', 'AE', 'EU', 'GB', 'MX', 'FR']
}
columns = {
Number: "order_number",
QTY: "order_qty",
Weight: "weight",
Status: "order_status",
Date: "order_date",
Carrier: "ship_with_carrier",
Service: "ship_with_carrier_code",
Shipping: "requestedShippingService",
Rate: "cheapest_rate",
Domestic: "country",
Batch: "print_batch_id",
Skus: "skus"
}
# sort_column=${sortColumn}&sort_direction=${sortDirection}&search_query=${searchQuery}
filters = params[:filters].split(',')
stores = params[:stores].split(',')
sort_column = params[:sort_column]
sort_direction = params[:sort_direction]
search_query = params[:search_query]
sort_by_column = columns[params[:sort_column].to_sym]
sort_direction = params[:sort_direction] == "asc" ? "asc" : "desc"
service_params = filters.select{ |p| services.include?(p) }
country_params = filters.select{ |p| countries.include?(p) }
order_status_params = filters.select{ |p| status[p] != nil }
query_countries = []
query_countries << country_defs[:"#{country_params[0]}"] if country_params[0]
query_countries << country_defs[:"#{country_params[1]}"] if country_params[1]
active_filters = [service_params, country_params].flatten
query = Order.where(user_id: user_id)
query = query.where(order_status: status[order_status]) if order_status_params.empty?
query = query.where("order_number ILIKE ? OR order_id::TEXT ILIKE ? OR order_info->'advancedOptions'->>'customField2' ILIKE ?", "%#{search_query}%", "%#{search_query}%", "%#{search_query}%") unless search_query.gsub(/\s+/, "").length == 0
query = query.where(requestedShippingService: service_params) unless service_params.empty?
query = query.where(country: "US") if country_params.include?("domestic") && !country_params.include?("international")
query = query.where.not(country: "US") if country_params.include?("international") && !country_params.include?("domestic")
query = query.where(order_status: status[order_status_params[0]]) unless order_status_params.empty?
query = query.where(store_id: stores) unless stores.empty?\
order_count = query.count
num_of_pages = (order_count.to_f / 100).ceil()
requested_page = params[:pageNumber].to_i
formatted_number = (requested_page.to_s + "00").to_i
query = query.offset(formatted_number - 100) unless requested_page == 1
query = query.limit(100)
query = query.order("#{sort_by_column}": :"#{sort_direction}") unless sort_by_column == "skus"
query = query.order("skus[1] #{sort_direction}") if sort_by_column == "skus"
query = query.order(order_number: :"#{sort_direction}")
orders = query.all
puts "After querying orders mem:" + mem.mb.to_s
requested_page = requested_page <= num_of_pages ? requested_page : 1
options = {}
options[:meta] = {
page_number: requested_page,
pages: num_of_pages,
type: order_status,
count: order_count,
active_filters: active_filters
}
render json: OrderSerializer.new(orders, options).serialized_json