Reputation: 417
I have the following scopes defined in my model:
scope :upcoming, -> { where(:start_time.gt => Time.now).asc(:start_time) }
scope :in_progress, -> {
now = Time.now
where(:start_time.lte => now).where(:end_time.gte => now).asc(:start_time)
}
I want to create another scope that combines the results of both of those scopes called current. I tried something like this:
scope :current, -> { self.in_progress | self.upcoming }
But this just ends up treating them both like arrays and concatenating them. The problem with this is that when I try to call my scope with Model.current, I get the following error message:
NoMethodError: undefined method `as_conditions' for #<Array:0xaceb008>
This is because it converted the Mongoid Criteria object to an array, but I don't want that. I want the object to stay as a Mongoid Criteria object.
What I really want is the union of the in_progress set and the upcoming set.
Any ideas? Thanks.
Upvotes: 9
Views: 4226
Reputation: 838
You have to map your Array back to a Mongoid::Criteria. Any array of yours can be translated to a criteria with any_in:
scope :has_data, -> { any_in(:_id => all.select{ |record| record.data.size > 0 }.map{ |r| r.id }) }
So, something like this should do the trick: (untested)
scope :current, -> { any_in(:_id => (self.in_progress + self.upcoming).map{ |r| r.id }) }
I hope there exists better solutions, but this solves the equation at least.
Upvotes: 2
Reputation: 3402
You can try to compose your criteria using Mongoid's query methods and dereferencing into the criteria's selector, but I wouldn't necessarily recommend this -- see below for an example. I second the recommendation to craft your third scope. Remember that these scopes correspond to db queries that you want to be efficient, so it is probably worth your time to examine and understand the resulting and underlying MongoDB queries that are generated.
Model
class Episode
include Mongoid::Document
field :name, type: String
field :start_time, type: Time
field :end_time, type: Time
scope :upcoming, -> { where(:start_time.gt => Time.now).asc(:start_time) }
scope :in_progress, -> {
now = Time.now
where(:start_time.lte => now).where(:end_time.gte => now).asc(:start_time)
}
scope :current, -> { any_of([upcoming.selector, in_progress.selector]) }
scope :current_simpler, -> { where(:end_time.gte => Time.now) }
end
Test
require 'test_helper'
class EpisodeTest < ActiveSupport::TestCase
def setup
Episode.delete_all
end
test "scope composition" do
#p Episode.in_progress
#p Episode.upcoming
#p Episode.current
#p Episode.current_simpler
in_progress_name = 'In Progress'
upcoming_name = 'Upcoming'
Episode.create(:name => in_progress_name, :start_time => Time.now, :end_time => 1.hour.from_now)
Episode.create(:name => upcoming_name, :start_time => 1.hour.from_now, :end_time => 2.hours.from_now)
assert_equal([in_progress_name], Episode.in_progress.to_a.map(&:name))
assert_equal([upcoming_name], Episode.upcoming.to_a.map(&:name))
assert_equal([in_progress_name, upcoming_name], Episode.current.to_a.map(&:name))
assert_equal([in_progress_name, upcoming_name], Episode.current_simpler.to_a.map(&:name))
end
end
Upvotes: 7