Skip to content

Commit

Permalink
new docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ajb committed Jun 3, 2015
1 parent bcc8493 commit 0b962af
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 92 deletions.
124 changes: 50 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,22 @@ Another answer could be in your models. But passing a bunch of query parameters

First, add `gem 'filterer'` to your `Gemfile`.


Then generate the Filterer class:

```
rails generate filterer PersonFilterer
```

And then instead of throwing all of this logic into a controller or model, you create a `Filterer` that looks like this:
Next, you create a `Filterer` that looks like this:

```ruby
# app/filterers/person_filterer.rb

class PersonFilterer < Filterer::Base
def starting_query
Person.where('deleted_at IS NULL')
end

def param_name(x)
@results.where(name: x)
results.where(name: x)
end

def param_email(x)
@results.where('LOWER(email) = ?', x)
results.where('LOWER(email) = ?', x)
end

def param_admin(x)
@results.where(admin: true)
results.where(admin: true)
end

# Optional default params
Expand All @@ -75,54 +64,34 @@ And in your controller:
```ruby
class PeopleController < ApplicationController
def index
@filterer = PersonFilterer.new(params)
@people = Person.filter(params)
end
end
```

And in your views:

```erb
<% @filterer.results.each do |person| %>
...
<% end %>
```

Now, when a user visits `/people`, they'll see Adam, Barack, and Joe, all three people. But when they visit `/people?name=Adam%20Becker`, they'll see only Adam. Or when they visit `/people?admin=t`, they'll see only Adam and Joe.

#### Pagination
#### Specifying the Filterer class to use

Filterer includes its own pagination logic (described here). To use filterer with other gems, see the [alternative solutions section](#alternative-pagination-solutions). (This will likely be the supported behavior in the next major release.)
Filterer includes a lightweight ActiveRecord adapter that allows us to call `filter` on any `ActiveRecord::Relation` like in the example above. By default, it will look for a class named `[ModelName]Filterer`. If you wish to override this, you have a couple of options:

In your controller:
```ruby
helper Filterer::PaginationHelper
```
You can pass a `:filterer_class` option to the call to `filter`:

In your view:
```erb
<%= render_filterer_pagination(@filterer) %>
```rb
Person.filter(params, filterer_class: 'AdvancedPersonFilterer')
```

#### Passing options to the Filterer
Or you can bypass the ActiveRecord adapter altogether:

```ruby
class PersonFilterer < Filterer::Base
def starting_query
@opts[:organization].people.where('deleted_at IS NULL')
end
end
```rb
AdvancedPersonFilterer.filter(params, starting_query: Person.all)
```

or
### Pagination

```ruby
class PersonFilterer < Filterer::Base
end
Filterer relies on either [Kaminari](https://github.com/amatsuda/kaminari) or [will_paginate](https://github.com/mislav/will_paginate) for pagination. *You must install one of them if you want to paginate your records.*

# In your controller...
PersonFilterer.new(params, starting_query: @organization.people)
```
If you have either of the above gems installed, Filterer will automatically paginate your records, fetching the correct page for the `?page=X` URL parameter. By default, filterer will display 20 records per page.

#### Overriding per_page

Expand All @@ -145,13 +114,26 @@ Now you can append `?per_page=50` to the URL.

> Note: To prevent abuse, this value will still max-out at `1000` records per page.
#### Sorting the results
#### Disabling pagination

```rb
class NoPaginationFilterer < PersonFilterer
self.per_page = nil
end
```

or

```rb
Person.filter(params, skip_pagination: true)
```

### Sorting the results

Filterer provides a slightly different DSL for sorting your results. Here's a quick overview of the different ways to use it:

```ruby
class PersonFilterer < Filterer::Base

# '?sort=name' will order by LOWER(people.name). If there is no sort parameter,
# we'll default to this anyway.
sort_option 'name', 'LOWER(people.name)', default: true
Expand All @@ -165,46 +147,40 @@ class PersonFilterer < Filterer::Base

# '?sort=data1', '?sort=data2', etc. will call the following proc, passing the
# query and match data
sort_option Regexp.new('data([0-9]+)'), -> (query, match_data, filterer) {
query.order('data -> ?', match_data[1])
sort_option Regexp.new('data([0-9]+)'), -> (query, matches, filterer) {
query.order "(ratings -> '#{matches[1]}') #{filterer.direction}"
}

end
```

#### Chaining

An option is available to chain additional calls onto the filterer query.

```ruby
class PeopleController < ApplicationController
def index
@filterer = PersonFilterer.chain(params).my_custom_method
end
end
```

In the view, we can then skip the call to `results` (i.e. `@filterer.each` vs `@filterer.results.each`).
Since paginating records without an explicit `ORDER BY` clause is a no-no, Filterer orders by `[table_name].id asc` if no sort options are provided.

(By default, chaining will _not_ apply ordering clauses. To obey ordering params, pass the `:include_ordering` option to `chain`.)
#### Disabling the ordering of results

#### Alternative Pagination Solutions
For certain queries, you might want to bypass the ordering of results:

Filterer supports basic pagination. This can be replaced by alternative pagination tools such as [Kaminari](https://github.com/amatsuda/kaminari) or [will_paginate](https://github.com/mislav/will_paginate).
```rb
Person.filter(params, skip_ordering: true)
```

Using the `chain` approach, we can control pagination ourselves:
### Passing arbitrary data to the Filterer

```ruby
class PeopleController < ApplicationController
def index
@filterer = PersonFilterer.chain(params).page(params[:page]).per(10)
class OrganizationFilterer < Filterer::Base
def starting_query
if opts[:is_admin]
Organization.all.with_deleted_records
else
Organization.all
end
end
end
```

The views should then use the helpers appropriate for the pagination gem used.
OrganizationFilterer.filter(params, is_admin: current_user.admin?)
```

#### License

[MIT](http://dobt.mit-license.org)

[status]: https://circleci-badges.herokuapp.com/dobtco/filterer/4227dad9a04a91b070e9c25174f4035a2da6a828
Expand Down
2 changes: 1 addition & 1 deletion lib/filterer/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module ActiveRecord
included do
def self.filter(params = {}, opts = {})
filterer_class(opts[:filterer_class]).
filter(params, opts.merge(starting_query: all))
filter(params, { starting_query: all }.merge(opts))
end

def self.filterer_class(override)
Expand Down
7 changes: 2 additions & 5 deletions lib/filterer/base.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
module Filterer
class Base
IGNORED_PARAMS = %w(page)

attr_accessor :results,
:meta,
:direction,
Expand Down Expand Up @@ -61,7 +59,7 @@ def filter(*args)
def initialize(params = {}, opts = {})
self.params = defaults.merge(params).with_indifferent_access
self.opts = opts
self.results = opts.delete(:starting_query) || starting_query
self.results = opts[:starting_query] || starting_query
add_params_to_query
order_results unless opts[:skip_ordering]
paginate_results unless opts[:skip_pagination]
Expand Down Expand Up @@ -113,8 +111,7 @@ def add_params_to_query
end

def present_params
params.select do |k, v|
!k.to_s.in?(IGNORED_PARAMS) &&
params.select do |_k, v|
v.present?
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/dummy/app/filterers/person_filterer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def starting_query
end

def param_name(x)
@results.where(name: x)
results.where(name: x)
end

sort_option 'name', default: true
Expand Down
12 changes: 6 additions & 6 deletions spec/dummy/app/filterers/slow_filterer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,26 @@ def starting_query
end

def param_name(x)
@results.where(name: x)
results.where(name: x)
end

def param_a(x)
@results
results
end

def param_b(x)
@results
results
end

def param_c(x)
@results
results
end

def param_d(x)
@results
results
end

def param_e(x)
@results
results
end
end
5 changes: 0 additions & 5 deletions spec/lib/filterer/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,6 @@ class PaginationFiltererInherit < PaginationFiltererB
SmokeTestFilterer.filter(foo: 'bar')
end

it 'does not pass the :page parameter' do
expect_any_instance_of(SmokeTestFilterer).not_to receive(:param_page)
SmokeTestFilterer.filter(page: 'bar')
end

it 'does not pass blank parameters' do
expect_any_instance_of(SmokeTestFilterer).not_to receive(:param_foo)
SmokeTestFilterer.new(foo: '')
Expand Down

0 comments on commit 0b962af

Please sign in to comment.