ScopedFrom
Provides a simple mapping between scopes and controller parameters for Ruby On Rails 5.
Installation
Just add this into your Gemfile
:
gem 'scoped_from'
Then, just run bundle install
.
Example
First, a model with some scopes:
class Post < ActiveRecord::Base
scope :commented, where('comments_count > 0')
scope :created_between, lambda { |after, before|
where('created_at >= ? AND created_at <= ?', after, before)
}
scope :search, lambda { |pattern|
where('body LIKE ?', "%#{pattern}%")
}
scope :with_category, lambda { |category_id|
where(:category_id, category_id)
}
end
After, a controller:
class PostsController < ActionController::Base
def index
@posts = Post.scoped_from(params)
end
end
Then, it just filter your model from params:
/posts?commented=1
/posts?search=rails
/posts?search=rails&commented=1&with_category=42
Accepted scopes
All scopes can be mapped with scoped_from
method except scopes taking a
lambda
(or a Proc
) with an arity greater than 1 (for example:
created_between
in the above code).
Scopes with no argument are invoked if parameter value is evaluated as true
.
It includes "true"
, "yes"
, "y"
, "on"
, and "1"
strings.
Columns are also automatically scoped.
Scopes restriction
You can restrict mapping to some scopes with :only
option:
@posts = Post.scoped_from(params, only: ['commented', 'search'])
You can also exclude some scopes from mapping with :except
option:
@posts = Post.scoped_from(params, except: 'commented')
Mapping order
If you need to map an SQL order, just pass order
parameter:
@posts = Post.scoped_from(order: 'created_at')
Order direction can be specified using a dot, space or :
as delimiter:
@posts = Post.scoped_from(order: 'created_at.desc')
Note that order is SQL safe with scoped_from
method (columns names are
checked).
Some cool stuff
If your provide an array as parameter value, scope is invoked with each item of the array:
@posts = Post.scoped_from(search: ['bar', 'foo'])
is equivalent to
@posts = Post.search('bar').search('foo')
You may also not want to filter on columns, just specify :exclude_columns
option:
@posts = Post.scoped_from(params, exclude_columns: true)
A query string can also be given to scoped_from
method:
@posts = Post.scoped_from('with_category=24&search[]=foo&search[]=bar')
Returned scope from scoped_from
method gives access to an internal query
object:
@posts = Post.scoped_from(params)
@query = @posts.query
This query provides you some convenience methods like params
, order_column
and order_direction
. This object can also be used to save user's search into
a database or other storage system.
But, you may also have to subclass this query class. You have to create a
subclass of ScopedFrom::Query
named #{RecordClassName}Query
. Here is an
example:
class PostQuery < ScopedFrom::Query
def category
Category.find_by_id(params[:with_category]) if params[:with_category]
end
end
This class has to be in load path.
Then into a view:
<% if @query.category %>
<p>All posts of category <%= @query.category.name %></p>
<% else %>
<p>All posts</p>
<% end %>
Executing test suite
This project is fully tested with Rspec 3.
Just run bundle exec rake
(after a bundle install
).