Previously on Java projects I'd used Hibernate's Criteria API to provide chaining across composed selection criteria (validation is the subject for a different post). For Ruby/ActiveRecord, there are a few projects that have implemented similar APIs, but none are especially mature. Handily, ActiveRecord's own named scopes provides a nice way to achieve what I needed (or at least the Rails 2 version of them, things have changed in Rails 3).
In particular, the undocumented
scoped
class method can be used to "disconnect" selection criteria from the model object and encapsulate them in Specification classes. A simple specification can be:
class IsEffectiveProductSpecification
def criteria
{ :conditions => ["effective_from <= :today", { :today => Date.today}] }
end
end
To chain specifications, a composite applies each composed specification's criteria against the model's scoped method:
class CompositeSpecification < Array
def all
self.inject(Product.scoped(nil)) do |combined_scope, specification|
combined_scope.scoped(specification.criteria)
end
end
Now in the model we can provide the selection interface that selects from one or more assembled specifications:
class Product
def self.select_satisfying(specification)
satisfying = specification.all
# apply more complex selection, such as selecting across different combination of model objects
end
end
I'd be interested in any Rails 3 (or other) approaches that might improve on this implementation.