Thursday, February 15, 2007

More on scope_out

Dan Manges, one of my coworkers, wrote an excellent post on some of the reasons why you would want to use the scope_out plugin. You should go read it because he does a much better job of explaining it than I would. I just wanted to take this opportunity to point out some of the newer features that weren't included in the first release.

Dynamic conditions are now accepted if you pass a block to scope out. Notice how the block evaluates to a Hash. This is important because the block is evaluated each time the scope is called and the result is fed directly to Rails' with_scope method as the options parameter.

class Article < ActiveRecord::Base
scope_out :published_today do
{:conditions => {:publish_date => Date.today}}
end
end


You can combine scopes with a new method called (predictably) combined_scope. This method takes previously defined scopes which were created with scope_out and nests them in order to create a new scope.

class Person < ActiveRecord::Base
scope_out :software_developers, :conditions => {:job => "Software Developer"}
scope_out :ruby_programmers, :conditions => {:primary_language => "Ruby"}
combined_scope :happy_programmers, [:software_developers, :ruby_programmers]
end

Saturday, January 13, 2007

XOR

While making my scope_out plugin, I wanted to ensure that exactly one of the 2 possible hash keys (:value or :conditions) were passed to the method. Rather than using a long and ugly if statement, I wanted to use the xor operator (^). Unfortunately ^ is not a regular boolean operator and is not defined for strings or arrays which can be passed as values to my hash. So I came up with this as a workaround:

if (options[:value] && true) ^ (options[:conditions] && true)
#do stuff
else
raise "use either :value or :conditions, but not both"
end


The return value of an and expression is the right hand side of that expression if the left part is true. Therefore the hash values are both coerced into either boolean or nil types, both of which define the ^ method.



Update:
Since publishing the plugin I realized that there are reasons where you would want to pass nil or false to options[:value] so I have changed the code to the following:


if (options.has_key?(:value) ^ options.has_key?(:conditions)
#do stuff
else
raise "use either :value or :conditions, but not both"
end

scope_out your models

After reading this excellent post on with_scope, I started using the with_x, find_x pattern all over the place. As a first step toward becoming a real contributing member of the rails community, I took the time to turn the pattern into a Rails plugin. And so scope_out was born.

Briefly, you would use scope_out like so:
class Thing < ActiveRecord::Base
scope_out :active, value => true
end

This simple declaration defines three class methods for you, like so:

def Thing.with_active
with_scope :find => {:conditions => ['active => ?', true]} do
yield
end
end
def Thing.find_active(*args)
with_active {find(*args)}
end
def Thing.calculate_active(*args)
with_active {calculate(*args)}
end


There are 2 basic syntaxes for using scope_out. In the first, you may omit :field if the name of the field is the same as the name of the scope. :value can be a boolean, integer, or string. :conditions can take any of rails' normal condition syntaxes.


scope_out(:scope_name, :field => 'field_name', :value => value)
scope_out(:scope_name, :conditions => ['field => ?', value])

So there you have it, pure syntactic sugar. 100% meta. Enjoy!