1

一連の基準に基づいて、データベースから多数のアイテムを返すことを目的としたメソッドがあります。

scope :expired_not_marked, lambda { |client|
items = where('items.status > 0 AND items.expires_at < ? AND items.expired_at IS NULL AND (winning_bid_id IS NULL OR winner_id IS NULL)', Time.now)
unless client.nil?
    items.where('items.client_id = ?', client.id)
end
}

と呼ばれていItem.expired_not_marked nilます。IRB からこれを実行すると、多くの結果が得られますが、SQL クエリが次のように実行されていることが示されます。

SELECT `items`.* FROM `items` 

それが原作者の意図ではないことは明らかです。その結果、同じアイテムが何度も処理されています。

なぜこれが壊れているのですか、どうすれば修正できますか。where句は正しいようです。上記のメソッドはitem.rbモデル内にあります。

4

2 に答える 2

5

あなたの問題は、lambda時々返さnilれ、返されるスコープがnil役に立たないことです。

lambda最後の式の値を返します。あなたの場合、その式はunless. clientではない場合はnil、次のように返されます。

items.where('items.client_id = ?', client.id)

そしてすべてがうまくいくでしょう。しかし、client.nil?が true の場合、 はunlessに評価されnil、スコープは を返しnilます。次のような方がいいと思います。

scope :expired_not_marked, lambda { |client|
  items = where('items.status > 0 AND items.expires_at < ? AND items.expired_at IS NULL AND (winning_bid_id IS NULL OR winner_id IS NULL)', Time.now)
  unless client.nil?
    items = items.where('items.client_id = ?', client.id)
  end
  items
}

そうすれば、明確で明示的で明確に定義された戻り値が常に得られます。


ActiveRecord Query Interface Guideでは、引数を取るスコープにクラス メソッドを使用することを推奨しています。

スコープの引数を受け入れるには、クラス メソッドを使用することをお勧めします。

lambdaしたがって、アプローチがうるさすぎる場合は、これを行うこともできます。

def self.expired_not_marked(client)
  items = where('items.status > 0')
  items = items.where('items.expires_at < ?', Time.now)
  items = items.where('items.expired_at IS NULL')
  items = items.where('winning_bid_id IS NULL OR winner_id IS NULL')
  unless client.nil?
    items = items.where('items.client_id = ?', client.id)
  end
  items
}

もちろん、クラスメソッドを使用する必要はありません。また、クエリを各コンポーネントの小さな呼び出しの束に分割する必要はありませんが、whereこの方法で読みやすくなる場合があります。

于 2013-02-06T18:52:30.250 に答える
1

ラムダから一貫して返されるものはありません。return明示的なステートメントがない場合、ラムダの最後に評価された式が戻り値になります。

scope :expired_not_marked, lambda { |client|
  items = where('items.status > 0 AND items.expires_at < ? AND items.expired_at IS NULL AND (winning_bid_id IS NULL OR winner_id IS NULL)', Time.now)
  unless client.nil?
    items.where('items.client_id = ?', client.id)
  end
  items
}

私がこれを書いているとしたら、私はscopedオブジェクトを提案します。これにより、SQL クエリが 1 つだけ実行されるようになります。

scope :expired_not_marked, lambda { |client|
  items = Items.scoped.where('items.status > 0 AND items.expires_at < ? AND items.expired_at IS NULL AND (winning_bid_id IS NULL OR winner_id IS NULL)', Time.now)
  items = items.where('items.client_id = ?', client.id) unless client.nil?
  items.all
}
于 2013-02-06T18:52:50.030 に答える