Blog

Showing posts with label named scopes. Show all posts
Showing posts with label named scopes. Show all posts

Saturday, September 11, 2010

How to get the options for a named_scope

class Video < ActiveRecord::Base
  named_scope :highest_scores, :order => "score DESC, id"
end

Video.highest_scores.proxy_options # => {:order=>"score DESC, id"}

Sunday, December 6, 2009

Use Stubs and Expectations to Simplify Testing with Named Scopes

Summary: Use stubs to return the original class for a named scope, and use expectations to specify scope requirements to simplify testing

Testing with chained named scopes that have complex attributes such as conditions can be tricky. I try to combine named scopes with clear names to communicate intent, rather than have long chains floating around in code. This can be done simply by creating a plain old method, with scoped procedures or this hack. This is a good way to keep named scopes DRY when using a specific combination across your application code. Of course you should test your named scopes.

However, consider the situation when adding a simple extension to your named scope that doesn't warrant a new named scope or combination. As an example I have the following code in a catalog application:

class Catalog
  ...
  # Returns a set of ordered page numbers based on the product page numbers
  def page_numbers
    Product.printable.find(:all, :select => "DISTINCT products.page_number").
      collect {|product| product.page_number}
  end
  ...
end

The printable named scope is quite complex and has been tested. I wanted to avoid duplicating test data (although factories would help here). For this method I was only concerned about a collection of products and it's page numbers, not that fact it was scoped. But it is a requirement that the products are scoped to only those that are printable.

Stubbing and setting expectations help us simplify the test and express the requirement. By stubbing the named scope and returning the Product class we can focus on creating test data for which we are concerned -- products with page numbers. The expectation "find printable products", ensures that we have scoped products as required.

class CatalogTest < ActiveSupport::TestCase

  context "with page numbers" do

    setup do
      Product.stubs(:printable).returns(Product)      
    end

    context "generally" do
      setup do
         Catalog.page_numbers
      end

      before_should "find printable products" do
        Product.expects(:printable).returns(Product)
      end      
    end

    context "when no products" do
      should "return an empty array" do
        assert_equal [], Catalog.page_numbers
      end
    end

    context "when multiple products" do
      setup do
        Factory(:catalog_with_page_numbers)
      end

      should "return an array of page numbers" do
         assert_equal [0, 1, 2, 3], Catalog.page_numbers
      end     
    end
  end
end

Although setting this expectation increases coupling to the implementation, we are justified in that it expresses the requirement in the test example.

Combining Named Scopes

This is a useful way to combine named scopes:
# add to lib/named_scopes.rb for example
class ActiveRecord::Base
  def self.named_scopes(name, &block)
    metaclass.send(:define_method, name) {|*args| block.call(*args) }
  end
end 
Usage:
class Product
  named_scope :live, :conditions => {:active => true}
  named_scope :created_recently, :conditions => [created_at > ?", Time.now - 10.days]
  named_scopes(:recently_released) { live.created_recently  }   
end
Source: http://august.lilleaas.net/combining_named_scopes (no longer available)

Please note this blog is no longer maintained. Please visit CivilCode Inc - Custom Software Development.