github.com-RStankov-SearchObject_-_2017-05-16_12-51-33
Item Preview
Share or Embed This Item
Flag this item for
- Publication date
- 2017-05-16
git clone RStankov-SearchObject_-_2017-05-16_12-51-33.bundle -b master
Search object DSL
SearchObject
In many of my projects I needed an object that performs several fairly complicated queries. Most times I hand-coded them, but they would get complicated over time when other concerns like sorting, pagination and so are being added. So I decided to abstract this away and created SearchObject
, a DSL for creating such objects.
It is useful for:
- complicated search forms
- api endpoints with multiple filter conditions
- GraphQL resolvers
- ... search objects 😀
Table of Contents
Installation
Add this line to your application's Gemfile:
rubygem 'search_object'
And then execute:
$ bundle
Or install it yourself as:
$ gem install search_object
Usage
Just include the SearchObject.module
and define your search options:
```rubyclass PostSearch include SearchObject.module
# Use .all (Rails4) or .scoped (Rails3) for ActiveRecord objects scope { Post.all }
option(:name) { |scope, value| scope.where name: value } option(:createdat) { |scope, dates| scope.createdafter dates } option(:published, false) { |scope, value| value ? scope.unopened : scope.opened }end```
Then you can just search the given scope:
```rubysearch = PostSearch.new filters: params[:filters]
accessing search options
search.name # => name optionsearch.created_at # => created at option
accessing results
search.count # => number of found resultssearch.results? # => is there any results foundsearch.results # => found results
params for url generations
search.params # => option valuessearch.params opened: false # => overwrites the 'opened' option```
Example
You can find example of most important features and plugins - here.
Plugins
SearchObject
support plugins, which are passed to SearchObject.module
method.
Plugins are just plain Ruby modules, which are included with SearchObject.module
. They are located under SearchObject::Plugin
module.
Paginate Plugin
Really simple paginate plugin, which uses the plain .limit
and .offset
methods.
```rubyclass ProductSearch include SearchObject.module(:paging)
scope { Product.all }
option :name option :category_name
# per page defaults to 25 per_page 10
# range of values is also possible minperpage 5 maxperpage 100end
search = ProductSearch.new filters: params[:filters], page: params[:page], perpage: params[:perpage]
search.page # => page numbersearch.per_page # => per page (10)search.results # => paginated page results```
Of course if you want more sophisticated pagination plugins you can use:
rubyinclude SearchObject.module(:will_paginate)include SearchObject.module(:kaminari)
Enum Plugin
Gives you filter with pre-defined options.
```rubyclass ProductSearch include SearchObject.module(:enum)
scope { Product.all }
option :order, enum: %w(popular date)
private
# Gets called when order with 'popular' is given def applyorderwithpopular(scope) scope.bypopularity end
# Gets called when order with 'date' is given def applyorderwithdate(scope) scope.bydate end
# (optional) Gets called when invalid enum is given def handleinvalidorder(scope, invalid_value) scope endend```
Model Plugin
Extends your search object with ActiveModel
, so you can use it in Rails forms.
```rubyclass ProductSearch include SearchObject.module(:model)
scope { Product.all }
option :name option :category_nameend```
```erb<%# in some view: %>
<%= formfor ProductSearch.new do |form| %> <% form.label :name %> <% form.textfield :name %> <% form.label :categoryname %> <% form.textfield :category_name %><% end %>```
GraphQL Plugin
Installed as separate gem, it is designed to work with GraphQL:
gem 'search_object_graphql'
```rubyclass PostResolver include SearchObject.module(:graphql)
type PostType
scope { Post.all }
option(:name, type: types.String) { |scope, value| scope.where name: value } option(:published, type: types.Boolean) { |scope, value| value ? scope.published : scope.unpublished }end```
Sorting Plugin
Fixing the pain of dealing with sorting attributes and directions.
```rubyclass ProductSearch include SearchObject.module(:sorting)
scope { Product.all }
sort_by :name, :priceend
search = ProductSearch.new filters: {sort: 'price desc'}
search.results # => Product sorted my price DESCsearch.sortattribute # => 'price'search.sortdirection # => 'desc'
Smart sort checking
search.sort?('price') # => truesearch.sort?('price desc') # => truesearch.sort?('price asc') # => false
Helpers for dealing with reversing sort direction
search.revertedsortdirection # => 'asc'search.sortdirectionfor('price') # => 'asc'search.sortdirectionfor('name') # => 'desc'
Params for sorting links
search.sortparamsfor('name')
```
Tips & Tricks
Results Shortcut
Very often you will just need results of search:
rubyProductSearch.new(params).results == ProductSearch.results(params)
Passing Scope as Argument
``` rubyclass ProductSearch include SearchObject.moduleend
first arguments is treated as scope (if no scope option is provided)
search = ProductSearch.new scope: Product.visible, filters: params[:f]search.results # => includes only visible products```
Handling Nil Options
```rubyclass ProductSearch include SearchObject.module
scope { Product.all }
# nil values returned from option blocks are ignored option(:sold) { |scope, value| scope.sold if value }end```
Default Option Block
```rubyclass ProductSearch include SearchObject.module
scope { Product.all }
option :name # automaticly applies => { |scope, value| scope.where name: value unless value.blank? }end```
Using Instance Method in Option Blocks
```rubyclass ProductSearch include SearchObject.module
scope { Product.all }
option(:date) { |scope, value| scope.bydate parsedates(value) }
private
def parsedates(datestring) # some "magic" method to parse dates endend```
Using Instance Method for Straight Dispatch
```rubyclass ProductSearch include SearchObject.module
scope { Product.all }
option :date, with: :parse_dates
private
def parse_dates(scope, value) # some "magic" method to parse dates endend```
Active Record Is Not Required
```rubyclass ProductSearch include SearchObject.module
scope { RemoteEndpoint.fetchproductas_hashes }
option(:name) { |scope, value| scope.select { |product| product[:name] == value } } option(:category) { |scope, value| scope.select { |product| product[:category] == value } }end```
Overwriting Methods
You can have fine grained scope, by overwriting initialize
method:
```rubyclass ProductSearch include SearchObject.module
option :name option :category_name
def initialize(user, options = {}) super options.merge(scope: Product.visible_to(user)) endend```
Or you can add simple pagination by overwriting both initialize
and fetch_results
(used for fetching results):
```rubyclass ProductSearch include SearchObject.module
scope { Product.all }
option :name option :category_name
attr_reader :page
def initialize(filters = {}, page = 0) super filters @page = page.to_i.abs end
def fetch_results super.paginate page: @page endend```
Extracting Basic Module
You can extarct a basic search class for your application.
```rubyclass BaseSearch include SearchObject.module
# ... options and configurationend```
Then use it like:
rubyclass ProductSearch < BaseSearch scope { Product }end
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Run the tests (
rake
) - Create new Pull Request
License
Source: https://github.com/RStankov/SearchObject
Uploader: RStankov
Upload date: 2017-05-16
- Addeddate
- 2017-05-21 04:07:26
- Identifier
- github.com-RStankov-SearchObject_-_2017-05-16_12-51-33
- Originalurl
-
https://github.com/RStankov/SearchObject
- Pushed_date
- 2017-05-16 12:51:33
- Scanner
- Internet Archive Python library 1.5.0
- Uploaded_with
- iagitup - v1.0
- Year
- 2017