Reputation: 2533
In my Rails 4 app I have an Item model and a Flag model. Item has_many
Flags. Flags belong_to
Item. Flag has the attributes item_id
, user_id
, and reason
. I need a way to find a random pending item that is not flagged. I am using enum
for pending status.
I believe I can find a random pending item that is flagged with:
@pending_item = Item.pending.joins(:flags).order("RANDOM()").first
but how can I find a random pending item that is not flagged?
Upvotes: 1
Views: 69
Reputation: 37627
Use where
with not exists
to get Item
s without Flag
s:
@pending_item = Item.pending.
where("not exists (select 1 from flags where flags.item_id = items.id)").
order("RANDOM()").first
Side note: order("RANDOM()").first
is not efficient if there are many Item
s that meet the criteria. This will probably be more efficient for a large table:
unflagged_pending_items = Item.pending.
where("not exists (select 1 from flags where flags.item_id = items.id)")
@pending_item = unflagged_pending_items.offset(rand unflagged_pending_items.count).first
Alternatively, if the first query isn't too slow and you don't need a different random item every time, you could just cache the result for a while.
Upvotes: 1
Reputation: 6692
Solution #1
Item.pending.joins("LEFT OUTER JOIN ( SELECT id, item_id
FROM flags
) AS temp
ON temp.item_id = items.id")
.where('temp.id = null')
.order("RANDOM()")
.first
So if we use LEFT OUTER JOIN
, temp.id
will equal null
if that item is not flagged.
Solution #2
Item.pending.where("id NOT IN ( SELECT item_id FROM flags )")
.order("RAMDOM()")
.first
This is clear, we find an item which wasn't flagged.
Upvotes: 0
Reputation: 3310
I don't know, wether it's better to use a sub-select or not. I prefer to use ids and foreign keys where I can use it.
# initialize my instance variable to have it in the view always
@pending_item = nil
# get an array with all flagged item ids
flagged_item_ids = Flag.group(:item_id).pluck(:item_id)
# search for an unflagged item, if there are item ids in the array only,
# because the statement could return a flagged item with an empty array in the condition
if flagged_item_ids > 0
@pending_item = Item.pending.where.not(id: flagged_item_ids).order("RANDOM()").first
end
@pending_item
# => an unflagged random item or nil
Keep in mind that the array flagged_item_ids
could hold a huge amount of flagged item ids. This can consume a lot of memory.
Upvotes: 1