3

Railsモデルにスコープを構築しようとしています。このスコープを呼び出すと、7つのブールフィールドにネストされたAND句とOR句のセットが表示されます。わかりやすくするために、列名を簡略化した例を次に示します。

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

aは3つの句すべてに存在することに注意してください。それを書く別の方法は次のようになります:

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

アレルは2番目のフォームでは寛容ではないようです。私は次のスコープに本当に近づきました:

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')
  )
)

近いですが、3番目のANDグループは2番目のグループから分離されていませんAND。3番目のグループの最後に偽orの句を追加すると、Arelは3番目の句を適切にグループ化することがわかりました...しかし、それはハックのようです。

そこにレール/アレルの達人が何かアイデアがあるかどうか疑問に思います。ありがとう!

4

3 に答える 3

4

あなたが私のようで、この機能のために Arel を本当に使い続けたいのであれば、新しい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して、 or-ed グループ化されたステートメントを句の最後に追加できます。これは、私にとって期待される方法です。Arel::Nodes::Groupingこれが意味をなさない場合は、その部分を自由に削除してください。

注意すべきことの 1 つは、正しい要素を括弧で囲むために、グループ化を手動で配置する必要がある場合があることです。例えば:

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')
)
于 2015-09-10T18:46:38.103 に答える
1

私が間違って読んでいない限り、arel を直接使用するよりも、active_record_orなどを使用した方が簡単かもしれません。

そのgemを使用すると、次のようなことをして正しい結果を得ることができるはずです:

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))
于 2013-02-23T07:10:15.657 に答える