All Projects → RStankov → Searchobject

RStankov / Searchobject

Licence: mit
Search object DSL

Programming Languages

ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to Searchobject

Searchobjectgraphql
GraphQL plugin for SearchObject gem
Stars: ✭ 118 (-22.37%)
Mutual labels:  search, gem, filter
React Search Input
🔍 Simple react.js component for a search input, providing a filter function.
Stars: ✭ 300 (+97.37%)
Mutual labels:  search, filter
Fuzzysort
Fast SublimeText-like fuzzy search for JavaScript.
Stars: ✭ 2,569 (+1590.13%)
Mutual labels:  search, filter
Ngx Mat Select Search
Angular component providing an input field for searching / filtering MatSelect options of the Angular Material library.
Stars: ✭ 416 (+173.68%)
Mutual labels:  search, filter
Search
CakePHP: Easy model searching
Stars: ✭ 153 (+0.66%)
Mutual labels:  search, filter
Algoliasearch Rails
AlgoliaSearch integration to your favorite ORM
Stars: ✭ 352 (+131.58%)
Mutual labels:  search, gem
Jquery Searchable
Tiny, fast jQuery plugin to search through elements as you type.
Stars: ✭ 142 (-6.58%)
Mutual labels:  search, filter
Laravel Api Handler
Package providing helper functions for a Laravel REST-API
Stars: ✭ 150 (-1.32%)
Mutual labels:  search, filter
Searchable
Search/filter functionality for Laravel's Eloquent models
Stars: ✭ 113 (-25.66%)
Mutual labels:  search, filter
Faltu
Search sort, filter, limit an array of objects in Mongo-style.
Stars: ✭ 112 (-26.32%)
Mutual labels:  search, filter
Lara Eye
Filter your Query\Builder using a structured query language
Stars: ✭ 39 (-74.34%)
Mutual labels:  search, filter
Holmes
Fast and easy searching inside a page
Stars: ✭ 1,679 (+1004.61%)
Mutual labels:  search, filter
Ng2 Search Filter
Angular 2 / Angular 4 / Angular 5 custom pipe npm module to make a search filter on any input, 🔥 100K+ downloads
Stars: ✭ 137 (-9.87%)
Mutual labels:  search, filter
Stelace
Open-source marketplace backend in Node.js, empowering Web platforms with Search API, Automation, Auth, Headless CMS… ⚡ 💻
Stars: ✭ 144 (-5.26%)
Mutual labels:  search
Scallop
Ergonomic shell wrapper for Ruby.
Stars: ✭ 150 (-1.32%)
Mutual labels:  gem
Wordmove
Multi-stage command line deploy/mirroring and task runner for Wordpress
Stars: ✭ 1,791 (+1078.29%)
Mutual labels:  gem
Docsearch
📘 The easiest way to add search to your documentation.
Stars: ✭ 2,266 (+1390.79%)
Mutual labels:  search
Bump
Bump is a gem that will simplify the way you build gems.
Stars: ✭ 150 (-1.32%)
Mutual labels:  gem
Cape Webservices
Entrypoint for all backend cape webservices
Stars: ✭ 149 (-1.97%)
Mutual labels:  search
Rummage phoenix
Full Phoenix Support for Rummage. It can be used for searching, sorting and paginating collections in phoenix.
Stars: ✭ 144 (-5.26%)
Mutual labels:  search

Gem Version Code Climate Build Status Code coverage

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:

gem '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:

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

License

MIT License

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].