2016-12-05 115 views
0

我正在嘗試構建一個動態查詢方法來過濾搜索結果。Rails-建立動態查詢過濾搜索結果

我的模型:

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到NJ),PA,CA等中有sales_id unlimited IDS和counsel_id無限counsel_ids。

在堅果殼中,我試圖找出如何創建一個查詢,考慮到用戶檢查的所有選項。這裏是我目前的查詢代碼:

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 
+0

你的意思是你需要一個方法來調用所有這些查詢嗎?因爲在你的代碼中你調用了by_state,這是一個,那麼我認爲year是另一個等,等等。所以這不是一個查詢。在這種情況下(如果它是4個查詢)我真的不知道有什麼問題... –

+0

是的我想用一個查詢調用所有東西。目前我試圖根據傳遞給查詢方法的參數分別調用查詢。參數的例子是{「year」=> [「16」,「15」],「state」=> [「31」,「39」]}。我認爲我的問題是能夠使用_by_state條件多次調用_by_year。但是我不知道多少年,國家會發送。我希望這是有道理的@Joel_Blum –

+0

爲什麼不嘗試按照'scopes'命名opt_keys '{by_state:[「31」,「39」],by_year:「16」}''那麼你可以一般地使用'opts.inject(Order){| order,(scope,value) order.public_send(範圍,值)}'。這將與指定的所有過濾器建立一個ActiveRecord :: Relation。然後,當你採取行動時,查詢實際上會觸發。順便說一句如果你想要傳遞多年,你將需要修復'by_year'範圍。 – engineersmnky

回答

0

你在尋找一個查詢/過濾器對象,這是一種常見模式。我寫了一個類似於這個的answer,但我會嘗試提取重要的部分。

首先,你應該將這些邏輯移動到它自己的對象上。當搜索/過濾器對象初始化時,它應該從關係查詢(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 statesyear is within this array of years

+0

謝謝!我遵循邏輯並已開始實施此操作。我現在主要關心的是如果我想同時應用多個過濾器。例如,4x狀態和3年。我怎樣才能通過@訂單,並具體說,我需要在這四個州的這些3年價值的訂單?我目前看到您的解決方案只針對每個類別進行一項選擇。再次感謝你的幫助。 –

+0

這聽起來像你想要或邏輯,而不是與邏輯。上面的代碼使用AND類型語法迭代過濾。如果您希望它是OR邏輯(即2015年或2016年的訂單),您需要稍微修改邏輯。上面更新的代碼。 – Genzume

+0

我正在尋找AND和OR邏輯。用於不同過濾選項(狀態,年份等)的AND邏輯,用於何時存在多個過濾選項的OR邏輯。例。紐約州,新澤西州,賓夕法尼亞州,馬薩諸塞州的2016年,2015年銷售人員的所有訂單均爲1,2,3。 –