Testing time-dependent named_scopes


#1

Suppose I have the following model, which defines a named_scope that
filters out some instances based on the current time:

class Post < ActiveRecord::Base
named_scope :current, lambda {
{ :conditions => [‘do_not_show_before < ?’, Time.now] }
}
end

I’ve been trying to come up with a good strategy for testing this,
since the test depends on time of execution. If I were using a
conventional method, say:

def self.current
find :all, :conditions => [‘do_not_show_before < ?’, Time.now]
end

I would simply rewrite it like this:

def self.current(time = Time.now)
find :all, :conditions => [‘do_not_show_before < ?’, time]
end

and then only pass in a time parameter during testing. But I don’t
know how to do with with named scopes because I cannot have optional
arguments to a block.

For right now I’ve compromised by making a required time parameter as
follows:

class Post < ActiveRecord::Base
named_scope :current, lambda { |time|
{ :conditions => [‘do_not_show_before < ?’, time] }
}
end

This means that every time I use the named_scope I have to call it
like this:

Post.current(Time.now).

I really don’t like this, but it does facilitate testing, which I
accomplish (using shoulda and factory_girl) thus:

context ‘An embargoed post’ do
setup do
@now = Time.now
@post = Factory(:post, :no_not_show_before => @now)
end

should 'be in the named_scope "current" after the

do_not_show_before datetime’ do
assert_contains Post.current(@now + 1.second), @post
end

should 'not be in the named_scope "current" before the

embargoed_until datetime’ do
assert_does_not_contain Post.current(@now - 1.second), @post
end
end

Is there a better way to accomplish this using named_scopes?

-Sven


#2

On Wed, Oct 22, 2008 at 11:51 AM, Sven removed_email_address@domain.invalid wrote:

Is there a better way to accomplish this using named_scopes?

My first thought is to define the named_scope as you have and also
define a
wrapper method. In the wrapper method, you can make the time parameter
optional with a default of Time.now. Then you’d just use the wrapper
method
instead of the named_scope directly. I don’t know how well that would
play
with chaining of named_scopes.

Regards,
Craig


#3

So something like this, perhaps:

class Post < ActiveRecord::Base
named_scope :current_as_of, lambda { |time|
{ :conditions => [‘do_not_show_before < ?’, time] }
}

def self.current(time = Time.now)
  self.current_as_of(time)
end

end

I’m also concerned about chaining, and in particular about the
behavior of things like will_paginate. Hmmm.

Thanks,

Sven


#4

On Wed, Oct 22, 2008 at 12:05 PM, Sven removed_email_address@domain.invalid wrote:

end
end

I’m also concerned about chaining, and in particular about the
behavior of things like will_paginate. Hmmm.

Yeah, that’s what I was thinking. Let us know how it works or if you do
something else.

Regards,
Craig


#5

Yes he did. I hadn’t seen that one and didn’t think of checking it.
Ryan suggests exactly what you’ve proposed; I’ve tried it and it
works. Thank you! This is much cleaner, and I get to keep using the
named_scope instead of a method.

Now if I can just find a way around my other method-to-named_scope
workaround problem (http://groups.google.com/group/rubyonrails-talk/
browse_thread/thread/a12f471c54a26b20) my code will be consistent once
again :slight_smile:

-Sven


#6

Actually, you could do something like this:

named_scope :current, lambda { |*args| { :conditions =>
[‘do_not_show_before < ?’, (args.first || Time.now) ] } }

I knew Ryan B.’ screencast on named_scope covered defaults. See:
http://railscasts.com/episodes/108 .

Regards,
Craig


#7

On Wed, Oct 22, 2008 at 12:57 PM, Sven removed_email_address@domain.invalid wrote:

Yes he did. I hadn’t seen that one and didn’t think of checking it.
Ryan suggests exactly what you’ve proposed; I’ve tried it and it
works. Thank you! This is much cleaner, and I get to keep using the
named_scope instead of a method.

Just to be sure Ryan gets the credit, I checked his site and then
modified
your code according to his example. :slight_smile:

Craig