2013-02-23 71 views
2

我想在我的rails模型中構建一個範圍,當它被調用時,會給我一組嵌套的AND和OR子句7個布爾字段。下面是用簡單的列名的例子爲清楚:分組的AND和OR子句Arel

SELECT * FROM mytable 
WHERE (a AND b AND c) OR (a AND d AND e) OR (a AND f AND g); 

注意,列a存在於所有的三個條款。編寫它的另一種方法是:

SELECT * FROM mytable 
WHERE a AND ((b AND c) OR (d AND e) OR (f AND g)); 

Arel在第二種形式上似乎不那麼寬容。我已經得到非常接近與follwing範圍:

scope :needs_any_delivery, lambda { 
    table = self.arel_table 
    common_assert = table[:a].eq(true) 
    where(
    common_assert.and(
     table[:b].eq(true).and(
     table[:c].eq(false) 
    ) 
    ).or(
     common_assert.and(
     table[:d].eq(true).and(
      table[:e].eq(false) 
     ) 
    ).or(
     common_assert.and(
      table[:f].eq(true).and(
      table[:g].eq(false) 
     ) 
     ) 
    ) 
    ) 
) 
} 

這將產生以下查詢:

SELECT * FROM mytable 
WHERE (
    (a = 't' AND b = 't' AND c = 'f' 
    OR (a = 't' AND d = 't' AND e = 'f' OR a = 't' AND f = 't' AND g = 'f') 
) 
) 

已經很接近了,但第三AND組未從第二AND組分開。我發現,如果我在第三組的末尾添加了一些額外僞造的or子句,那麼Arel會自行將第三個子句合併......但這看起來像是一種破解。

想知道如果任何rails/arel大師有任何想法。謝謝!

回答

1

除非我看錯了,否則使用類似active_record_or而不是直接使用arel可能更容易。

使用寶石,你應該能夠得到正確的結果做這樣的事情:

common_assert = where(a: true) # WHERE a 
option_one = where(b: true).where(c: true) # b AND c 
option_two = where(d: true).where(e: true) # d AND e 
option_three = where(f: true).where(g: true) # f AND g 
combined_optionals = option_one.or.option_two.or.option_three # (b AND c) OR (d AND e) OR (f AND g) 
common_assert.merge(combined_optionals) # WHERE a AND ((b AND c) OR (d AND e) OR (f AND g)) 
+1

有趣。在這種情況下,我不確定我會使用除Arel以外的其他任何內容,但感謝回覆和答覆! – localshred 2013-02-25 15:50:49

3

如果你和我一樣,真的想繼續使用阿雷爾此功能,我發現創建一個新的or方法似乎是最好的路線。

我添加了一個包含以下內容的名爲arel_fixed_or.rb新初始化:

Arel::Nodes::Node.class_eval do 

    ### 
    # Factory method to create a Nodes:Or that has a Nodes::Grouping 
    # node as a child. 
    def fixed_or right 
    Arel::Nodes::Or.new self, Arel::Nodes::Grouping.new(right) 
    end 

end 

何地,你通常會想使用一個or語句,可以繼續使用fixed_or然後,將添加或-ED將語句分組到您的子句的末尾,這對我來說應該是預期的方法。如果這對你沒有意義,請隨意刪除Arel::Nodes::Grouping部分。

有一點需要注意的是,您可能仍然需要手動放置分組,以便正確的元素被括號包圍。例如:

table = Arel::Table.new(:mytable) 
common_assert = table[:a].eq(true) 

first_or = table[:b].eq(true).and(table[:c].eq(true)) 
second_or = table[:d].eq(true).and(table[:e].eq(true)) 
third_or = table[:f].eq(true).and(table[:g].eq(true)) 

common_assert.and(
    table.grouping(
    table.grouping(first_or) 
     .fixed_or(second_or) 
     .fixed_or(third_or) 
) 
) 

而且其to_sql輸出變爲:

"mytable"."a" = 't' AND (
    ("mytable"."b" = 't' AND "mytable"."c" = 't') OR 
    ("mytable"."d" = 't' AND "mytable"."e" = 't') OR 
    ("mytable"."f" = 't' AND "mytable"."g" = 't') 
)