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