predicate_scope
Have you ever written code like this?
class User < ActiveRecord::Base
scope :active, -> { where(deleted: false, state: "confirmed") }
def active?
!deleted && state == "confirmed"
end
end
And noticed that the definition of an "active" user is duplicated? What if the
definition changes, and you forget to update both places?
With predicate_scope
you can write it once:
class User < ActiveRecord::Base
include PredicateScope
predicate_scope :active, -> { where(deleted: false, state: "confirmed") }
end
This defines two methods:
- A
User.active
scope, as if you had used scope
like normal. - A predicate instance method
User#active?
, which behaves just like the
hand-written version of active?
in the original example:
user = User.new(deleted: false, state: "confirmed")
user.active?
user.state = "unconfirmed"
user.active?
The predicate method checks the conditions of the scope against the instance in
memory, without querying the database. Again, just like the hand-written active?
.
Implementation
In addition to predicate_scope
, this gem also defines a instance method
#satisfies_conditions_of?
. This takes an ActiveRecord::Relation
and returns
whether the instance it is called on satisfies its conditions. The predicate
methods defined by predicate_scope
call satisfies_conditions_of?
with the
relation from the scope. So in the above example user.active?
is implemented
as user.satisfies_conditions_of?(User.active)
.
satisfies_conditions_of?
extracts the Arel
abstract syntax tree (AST)
from the given ActiveRecord::Relation
and interprets its conditions as Ruby
predicates. These predicates are evaluated against the instance.
Limitations
Not all Arel
operations are implemented. If you define
a predicate_scope
that uses an unsupported operation
PredicateScope::Errors::UnsupportedOperation
will be raised when the predicate
method is called. PRs to implement additional operations are appreciated!
Installation
Add this line to your application's Gemfile:
gem 'predicate_scope'
And then execute:
$ bundle
Or install it yourself as:
$ gem install predicate_scope
In any models where you want to use predicate_scope
add
include PredicateScope
Contributing
Bug reports and pull requests are welcome on GitHub at
https://github.com/CJStadler/predicate_scope.
License
The gem is available as open source under the terms of the
MIT License.