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!