3

特定の日付範囲に一致するレコード、または nil 値を持つレコードの Mongoid クエリを作成しようとしています。これは、Mongoid クエリに変換したい関数を実行する私の ruby​​ コードです。

class MyModel
  include Mongoid::Document
  field :name
  field :enabled, type: Boolean, default: false
  field :start_date, type: DateTime
  field :end_date, type: DateTime

  def self.active
    documents = where(enabled: true)
    documents = documents.keep_if {|doc| doc.start_date.nil? || doc.start_date <= Date.today}
    documents = documents.keep_if {|doc| doc.end_date.nil? || doc.end_date >= Date.tomorrow}
    documents
  end
end

このメソッドを Mongoid クエリに変換してパフォーマンスを改善するにはどうすればよいですか?

アップデート:

正しい動作を検証するために使用しているRSpecテストは次のとおりです。

describe '.active' do
  let!(:disabled){ Fabricate(:model, enabled: false, name: 'disabled') }
  let!(:enabled_without_date){ Fabricate(:active_model, name: 'enabled_without_date') }
  let!(:past){ Fabricate(:active_model, start_date: 1.week.ago, end_date: Date.yesterday, name: 'past') }
  let!(:current){ Fabricate(:active_model, start_date: Date.today, end_date: Date.tomorrow, name: 'current') }
  let!(:future){ Fabricate(:active_model, start_date: Date.tomorrow, end_date: 1.week.from_now, name: 'future') }
  
  it 'returns only enabled and within the current time' do
    MyModel.count.should == 5
    models = MyModel.active.to_a
    models.should_not be_empty
    models.should_not include disabled
    models.should_not include past
    models.should_not include future
    models.should include enabled_without_date
    models.should include current
  end
end
4

6 に答える 6

3

基準を変換する場合:

(start <= X OR start.nil?) AND (end >= Y OR end.nil?)

選言形式にすると、次のようになります。

(start <= X AND end >= Y) OR (start <= X and end.nil?) OR (start.nil? and end >= Y) or (start.nil? and end.nil?)

次に、これを単一の $or 句で表現できます。

$or: [
  {:start_date.lte => start_date, :end_date.gte => end_date},
  {:start_date => nil, :end_date.gte => end_date},
  {:start_date.lte => start_date, :end_date => nil},
  {:start_date => nil, :end_date => nil},
]

両方の値が set または nil でなければならない場合 (つまり、1 つの set と 1 つの nil を持つことはできません)、これはさらに簡単になります。

$or: [
  {:start_date.lte => start_date, :end_date.gte => end_date},
  {:start_date => nil},
]

仕様を満たすには、完全なクエリは次のようになります。

Model.where({
  :enabled => true,
  :$or => [
    {:start_date.lte => Date.today.to_time, :end_date.gte => Date.tomorrow.to_time},
    {:start_date => nil},
  ]
})
于 2013-07-22T19:05:47.227 に答える
0

これまでに与えられたすべての答えは、私には問題ないようです。ただし、以前のプロジェクトで機能した別の構文バリアントを追加します。これは、冗長な $or キー シナリオも処理します。

Document.where({
   '$and' => [
       :enabled => true,
       '$or' => [
           :start_date => nil,
           :start_date => { '$lte' => Date.today.to_time }
       ],
       '$or' => [
           :end_date => nil,
           :end_date => { '$gte' => Date.tomorrow.to_time }
       ]
    ]
})

さらなるアドバイスとして: 仕様も見直しましたか? (あなたは、決して知らない ... ;))。また、問題の一部のみをデバッグしてテストするようにしてください。たとえば、:enabled フィルターを独自に動作させ、日付フィルターを独自に動作させ、nil フィルターを独自に動作させてから、それらを再度組み合わせてみてください。それが問題の核心につながるのかもしれません。また、ここで $lte と $gte を比較する Date を指定するさまざまなバリエーションが見られます。私自身Time、比較するクラスを提供することに成功しました。それも試してみてください!

于 2013-07-25T11:23:12.227 に答える