• Stars
    star
    180
  • Rank 213,097 (Top 5 %)
  • Language
    Ruby
  • License
    MIT License
  • Created about 11 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Search object DSL

Gem Version Code Climate Code coverage

SearchObject

DSL for building search objects.

Search objects start with an initial collection (scope) and allow it to be filtered based on various options.

Uses:

  • complicated search forms (example)
  • API endpoints with multiple filter conditions
  • GraphQL resolvers (example)
  • ... search objects πŸ˜€

Installation

Add this line to your application's Gemfile:

gem 'search_object'

And then execute:

$ bundle

Or install it yourself as:

$ gem install search_object

Changelog

Changes are available in CHANGELOG.md

Usage

Just include the SearchObject.module and define your search options:

class 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(:created_at)       { |scope, dates| scope.created_after dates }
  option(:published, false) { |scope, value| value ? scope.unopened : scope.opened }
end

Then you can just search the given scope:

search = PostSearch.new(filters: params[:filters])

# accessing search options
search.name                        # => name option
search.created_at                  # => created at option

# accessing results
search.count                       # => number of found results
search.results?                    # => is there any results found
search.results                     # => found results

# params for url generations
search.params                      # => option values
search.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.

class ProductSearch
  include SearchObject.module(:paging)

  scope { Product.all }

  option :name
  option :category_name

  # per page defaults to 10
  per_page 10

  # range of values is also possible
  min_per_page 5
  max_per_page 100
end

search = ProductSearch.new(filters: params[:filters], page: params[:page], per_page: params[:per_page])

search.page                                                 # => page number
search.per_page                                             # => per page (10)
search.results                                              # => paginated page results

Of course if you want more sophisticated pagination plugins you can use:

include SearchObject.module(:will_paginate)
include SearchObject.module(:kaminari)

Enum Plugin

Gives you filter with pre-defined options.

class ProductSearch
  include SearchObject.module(:enum)

  scope { Product.all }

  option :order, enum: %w(popular date)

  private

  # Gets called when order with 'popular' is given
  def apply_order_with_popular(scope)
    scope.by_popularity
  end

  # Gets called when order with 'date' is given
  def apply_order_with_date(scope)
    scope.by_date
  end

  # (optional) Gets called when invalid enum is given
  def handle_invalid_order(scope, invalid_value)
    scope
  end
end

Model Plugin

Extends your search object with ActiveModel, so you can use it in Rails forms.

class ProductSearch
  include SearchObject.module(:model)

  scope { Product.all }

  option :name
  option :category_name
end
<%# in some view: %>

<%= form_for ProductSearch.new do |form| %>
  <% form.label :name %>
  <% form.text_field :name %>
  <% form.label :category_name %>
  <% form.text_field :category_name %>
<% end %>

GraphQL Plugin

Installed as separate gem, it is designed to work with GraphQL:

gem 'search_object_graphql'
class 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.

class ProductSearch
  include SearchObject.module(:sorting)

  scope { Product.all }

  sort_by :name, :price
end

search = ProductSearch.new(filters: {sort: 'price desc'})

search.results                                # => Product sorted my price DESC
search.sort_attribute                         # => 'price'
search.sort_direction                         # => 'desc'

# Smart sort checking
search.sort?('price')                         # => true
search.sort?('price desc')                    # => true
search.sort?('price asc')                     # => false

# Helpers for dealing with reversing sort direction
search.reverted_sort_direction                # => 'asc'
search.sort_direction_for('price')            # => 'asc'
search.sort_direction_for('name')             # => 'desc'

# Params for sorting links
search.sort_params_for('name')

Tips & Tricks

Results Shortcut

Very often you will just need results of search:

ProductSearch.new(params).results == ProductSearch.results(params)

Passing Scope as Argument

class ProductSearch
  include SearchObject.module
end

# 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

class 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

class 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

class ProductSearch
  include SearchObject.module

  scope { Product.all }

  option(:date) { |scope, value| scope.by_date parse_dates(value) }

  private

  def parse_dates(date_string)
    # some "magic" method to parse dates
  end
end

Using Instance Method for Straight Dispatch

class ProductSearch
  include SearchObject.module

  scope { Product.all }

  option :date, with: :parse_dates

  private

  def parse_dates(scope, value)
    # some "magic" method to parse dates
  end
end

Active Record Is Not Required

class ProductSearch
  include SearchObject.module

  scope { RemoteEndpoint.fetch_product_as_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:

class ProductSearch
  include SearchObject.module

  option :name
  option :category_name

  def initialize(user, options = {})
    super options.merge(scope: Product.visible_to(user))
  end
end

Or you can add simple pagination by overwriting both initialize and fetch_results (used for fetching results):

class 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
  end
end

Extracting Basic Module

You can extarct a basic search class for your application.

class BaseSearch
  include SearchObject.module

  # ... options and configuration
end

Then use it like:

class ProductSearch < BaseSearch
 scope { Product }
end

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Run the tests (rake)
  6. Create new Pull Request

Authors

See also the list of contributors who participated in this project.

License

MIT License

More Repositories

1

SearchObjectGraphQL

GraphQL plugin for SearchObject gem
Ruby
155
star
2

FocusedTask

MacOS menu bar todo app built with Electron, React and Redux. It goals is to help you do deep work.
TypeScript
131
star
3

backbone-bind-to

Backbone.js extension for automatic binding and unbinding of model events to views.
CoffeeScript
81
star
4

backbone-handlebars

Mixing Backbone and Handlebars
CoffeeScript
78
star
5

MiniForm

Sugar around ActiveModel::Model
Ruby
29
star
6

config_files

My config files
Vim Script
20
star
7

talks-code

Demo code from my talks
JavaScript
15
star
8

RDSActionLabel

Custom text highlighting in UILabel
Swift
9
star
9

angular-simple-format

Angular directive for applying simple html formatting to text
8
star
10

graphql-playground-rails

A mountable GraphQL Playground endpoint for Rails
Ruby
7
star
11

playground

My playground for various javascript/css/ruby and other ideas
JavaScript
6
star
12

controldepo-3-widgets

A collection of widgets
JavaScript
6
star
13

rstankov_com

My Personal Website
TypeScript
5
star
14

CD3.UI.Upload

ControlDepo 3 UI Upload is multiple file uploader via flash, build on top of Prototype.js
ActionScript
4
star
15

ConferenceBox

CMS for conferences
Ruby
4
star
16

OpenFest-2010

My OpenFest 2010 presentation - JavaScript event-driven architecture - demo
Ruby
4
star
17

BackToScriptaculous

A backward lib between scriptaculous and scripty2
JavaScript
3
star
18

setty

Minimal application configuration for Rails projects.
Ruby
3
star
19

Taskar

Todo Project Manager, used for my diploma thesis
Ruby
2
star
20

toolbox

Files I copy from project to project
2
star
21

blog

My personal blog
CSS
2
star
22

BachkovoTheTurnaround

Sofia Game Jam project
Objective-C
1
star
23

cyoa

Technology playground project
Go
1
star
24

rstankov.github.io

rstankov.com
CSS
1
star
25

talk-angular-tips-tricks

Sample code for my Angular tips and tips talk
JavaScript
1
star
26

Backbone.js-brown-bag-seminar

Backbone.js demo. I created for a brown bag seminar
CoffeeScript
1
star
27

WLHeatMap

RSSI heatmap generator
Java
1
star
28

CoinBox

Hackathon project
Ruby
1
star
29

rails-angular-form-builder

Ruby
1
star
30

backbone-presentation

Slides and code from my Backbone presentation at initLab
JavaScript
1
star