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).