Testing time-dependent named_scopes

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

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

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

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

Regards, Craig

Actually, you could do something like this:

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

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

Regards, Craig

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

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

Craig