search_autocomplete 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9c795f6eacd1ecd7cdba955188e9250b5e0a2b8e0d208742ffd41ea78cd7a380
4
+ data.tar.gz: e47d9e1270cea9d83343213f31f3ccf00040d761df2f2113630d24e2dd93acd1
5
+ SHA512:
6
+ metadata.gz: d344fddbe8b93c3cbd31fbeb5c260a69a2b9a3c9fea650fc8f0717947d6c7b9ec1d86317860ab6eee70603d435cbaf032a897a703bad82a53f7d12058336b69f
7
+ data.tar.gz: ba277717530d72f6a7737c2d0fe816a20707687022a2cbc537aca8b7222b111dc260acf81f5f6f4716397d164348e7a38cca8f17ad3422a920a0cca2c2755b25
@@ -0,0 +1,132 @@
1
+ # SearchAutocomplete
2
+ This gem was created to add simple autocomplete and search/filter functionality to Ruby on Rails apps with minimal effort.
3
+
4
+ Other alternatives available are outdated or aren't as simple, often requiring many external libraries such as jQuery.
5
+ This gem only requires modules already shipped with Rails and the only external library required is available as a npm package you can add in your webpack files.
6
+
7
+ ## Installation
8
+
9
+ ### Ruby
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'search_autocomplete'
14
+ ```
15
+
16
+ And then execute:
17
+ ```bash
18
+ $ bundle
19
+ ```
20
+
21
+ Or install it yourself as:
22
+ ```bash
23
+ $ gem install search_autocomplete
24
+ ```
25
+
26
+ ### Webpack
27
+
28
+ Add the compatible web component for npm:
29
+ ```bash
30
+ yarn add @francisschiavo/search-autocomplete
31
+ ```
32
+
33
+ and then require it anywhere in your scripts:
34
+
35
+ ```js
36
+ require('@francisschiavo/search-autocomplete');
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ### Autocomplete
42
+
43
+ After installing this gem you can configure its options with an initializer like this:
44
+
45
+ **config/initializers/search_autocomplete.rb**
46
+ ```ruby
47
+ SearchAutocomplete.configure do |options|
48
+ options.autocomplete_size = 5
49
+ end
50
+ ```
51
+
52
+ You must mount the autocomplete engine like this:
53
+
54
+ **config/routes.rb**
55
+ ```ruby
56
+ Rails.application.routes.draw do
57
+ mount SearchAutocomplete::Engine, at: '/autocomplete'
58
+ end
59
+ ```
60
+
61
+ To allow your model on the autocomplete search you must configure it like this:
62
+ ```ruby
63
+ class Category < ApplicationRecord
64
+ belongs_to :category, optional: true
65
+
66
+ autocomplete :name
67
+ end
68
+ ```
69
+ The example above will allow searches on the `name` field for the `Category` model.
70
+
71
+ To test this you can do a get request to: `{APP URL}/autocomplete/category?term=Cat 1`
72
+
73
+ Note `/autocomplete` is the base uri you specify on the routes and `/category` is the lowercase name of the model.
74
+
75
+ You can also search namespaced models by adding the lowercase name of every namespace as part of the URI:
76
+
77
+ If your model is `Admin::User` the search would be: `{APP URL}/autocomplete/admin/user?term=Roger`
78
+
79
+ ### Search / Filter
80
+
81
+ You can use the method `search` on your controllers as a way to filter data.
82
+
83
+ This method takes 3 arguments:
84
+ * The model class to perform the search
85
+ * An array of fields to permit approximate matches (like)
86
+ * An array of fields to permit exact matches (=)
87
+
88
+ Here is a sample of the `index` action of a `category` controller:
89
+
90
+ ```ruby
91
+ def index
92
+ @categories = search(Category, %i[name], []).all
93
+ end
94
+ ```
95
+
96
+ You can also use it with pagination gems like kaminari:
97
+
98
+ ```ruby
99
+ def index
100
+ @categories = search(Category, %i[name], []).page(params[:page])
101
+ end
102
+ ```
103
+
104
+ ### Postgres Jsonb support
105
+
106
+ There is a limited support for `jsonb` fields, currently limited to one level deep fields.
107
+
108
+ To query using a jsonb field you must pass an array as the name argument:
109
+
110
+ ```ruby
111
+ class Category < ApplicationRecord
112
+ belongs_to :category, optional: true
113
+
114
+ autocomplete %i[name pt_BR]
115
+ end
116
+ ```
117
+
118
+ This will use `arel` infix operators to create the following query:
119
+
120
+ ```sql
121
+ SELECT * FROM categories WHERE category.name->>'pt_BR' ILIKE "%term%";
122
+ ```
123
+
124
+ ## Known issues
125
+
126
+ * Uses WebComponents requires modern browsers or polyfills
127
+ * There is no automated tests at this moment.
128
+ * There is no support for json fields other than `postgres`.
129
+ * Json fields are supported but limited to one level deep fields.
130
+
131
+ ## License
132
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'SearchAutocomplete'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
25
+
26
+ require 'rake/testtask'
27
+
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'test'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = false
32
+ end
33
+
34
+ task default: :test
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SearchAutocomplete
4
+ # Autocomplete
5
+ class AutocompleteController < ActionController::Base
6
+ before_action :find_model, only: :autocomplete
7
+
8
+ ##
9
+ # Main autocomplete action
10
+ def autocomplete
11
+ data = autocomplete_search
12
+ render json: data.map { |item| { id: item.id, label: label(item), value: value(item) } }
13
+ end
14
+
15
+ private
16
+
17
+ def autocomplete_search
18
+ arel_table = @model.arel_table
19
+ node = Arel::Nodes::SqlLiteral.new "'%#{params[:term]}%'"
20
+
21
+ search_field = @model.autocomplete_options[:search_field]
22
+ # Assume postgres jsonb
23
+ if search_field.is_a? Array
24
+ op = Arel::Nodes::InfixOperation.new('->>', arel_table[search_field[0]], Arel::Nodes.build_quoted(search_field[1]))
25
+ query_builder = params[:term] ? @model.where(op.matches(node)) : @model
26
+ else
27
+ query_builder = params[:term] ? @model.where(arel_table[search_field].matches(node)) : @model
28
+ end
29
+
30
+ @model.autocomplete_options[:filters].each do |filter|
31
+ next unless params[filter.to_s].present?
32
+
33
+ query_builder = query_builder.where(arel_table[filter].eq(params[filter.to_s]))
34
+ end
35
+ query_builder.limit(SearchAutocomplete.autocomplete_size)
36
+ end
37
+
38
+ def label(item)
39
+ values = @model.autocomplete_options[:display_fields].map do |field|
40
+ if field.is_a? Array
41
+ item.instance_eval(field[0].to_s)[field[1].to_s]
42
+ else
43
+ item.instance_eval(field.to_s)
44
+ end
45
+ end
46
+ values.join ' - '
47
+ end
48
+
49
+ def value(item)
50
+ search_field = @model.autocomplete_options[:search_field]
51
+ if search_field.is_a? Array
52
+ item.instance_eval(search_field[0].to_s)[search_field[1].to_s]
53
+ else
54
+ item.instance_eval(search_field.to_s)
55
+ end
56
+ end
57
+
58
+ def find_model
59
+ model_name = params[:model_name].sub('/', '::').camelize
60
+ @model = model_name.constantize
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ SearchAutocomplete::Engine.routes.draw do
4
+ get '/*model_name', to: 'autocomplete#autocomplete', as: 'search'
5
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'search_autocomplete/engine'
4
+ require 'search_autocomplete/options'
5
+
6
+ # Provides an easy way to add autocomplete and filters to rails apps
7
+ module SearchAutocomplete
8
+ extend SearchAutocomplete::Options
9
+ end
10
+
11
+ require 'search_autocomplete/autocompletable'
12
+ require 'search_autocomplete/searchable'
13
+ require 'search_autocomplete/form_builder_helper'
14
+
15
+ ::ActiveRecord::Base.include SearchAutocomplete::Autocompletable
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SearchAutocomplete
4
+ # Autocompletable
5
+ module Autocompletable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ cattr_accessor :autocomplete_options
10
+ self.autocomplete_options = { configured: false }
11
+ end
12
+
13
+ # Autocompletable methods
14
+ module ClassMethods
15
+ ##
16
+ # Configures this model to respond to autocomplete searches
17
+ #
18
+ # @param search_field [String|Array] Name of the main field to perform the search. If an array is given it will search in a jsonb structure.
19
+ # @param display_fields [Array{Symbol}] Array of field names for concatenating as display result
20
+ # @param filters [Array{Symbol}] Array of additional fields to filter
21
+ #
22
+ def autocomplete(search_field, display_fields = [], filters = [])
23
+ self.autocomplete_options = {
24
+ configured: true,
25
+ search_field: search_field,
26
+ display_fields: display_fields.size.zero? ? [search_field] : display_fields,
27
+ filters: filters
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SearchAutocomplete
4
+ # Autocomplete engine
5
+ class Engine < ::Rails::Engine
6
+ engine_name 'search_autocomplete'
7
+ isolate_namespace SearchAutocomplete
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inherited from Rails
4
+ module ActionView
5
+ # Inherited from Rails
6
+ module Helpers
7
+ # Helper method for form builder
8
+ class FormBuilder
9
+ ##
10
+ # Creates an autocomplete element from form builder
11
+ def autocomplete_field(method, display_value, autocomplete_path, options = {})
12
+ options.reverse_merge!(
13
+ 'display-value': find_autocomplete_value(display_value),
14
+ value: @object.send(method),
15
+ url: autocomplete_path,
16
+ minlength: 2,
17
+ name: "#{@object_name}[#{method}]"
18
+ )
19
+
20
+ autocomplete_options[:autofocus] = options[:autofocus] if options.include? :autofocus
21
+ @template.content_tag(:'auto-complete', nil, options)
22
+ end
23
+
24
+ private
25
+
26
+ def find_autocomplete_value(display_expression)
27
+ @object.instance_eval(display_expression)
28
+ rescue StandardError
29
+ ''
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SearchAutocomplete
4
+ # Configuration module
5
+ module Options
6
+ mattr_accessor :autocomplete_size
7
+
8
+ def configure
9
+ yield self
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inherited from Rails
4
+ module ActionController
5
+ # Searchable
6
+ class Base
7
+ ##
8
+ # Performs a search on the model based on permitted fields
9
+ #
10
+ # @param model [Class] Model to perform the search
11
+ # @param approximate_fields [Array{Symbol}] List of fields to allow as approximate filters
12
+ # @param exact_fields [Array{Symbol}] List of fields to allow as exact filters
13
+ # @param include_list [Array{Symbol}] List of related resources to include
14
+ def search(model, approximate_fields = [], exact_fields = [], include_list = nil)
15
+ arel_table = model.arel_table
16
+
17
+ search_conditions = prepare_search_fields arel_table, exact_fields
18
+ search_conditions += prepare_search_fields(arel_table, approximate_fields, false)
19
+
20
+ query = include_list.present? ? model.includes(include_list) : model
21
+ query = query.where(*search_conditions) if search_conditions.length.positive?
22
+ query
23
+ end
24
+
25
+ private
26
+
27
+ def prepare_search_fields(table, fields, exact = true)
28
+ search_conditions = []
29
+ fields.each do |field|
30
+ if field.is_a?(Array)
31
+ search_conditions.push prepare_jsonb_condition(table, field, exact)
32
+ else
33
+ search_conditions.push prepare_simple_condition(table, field, exact)
34
+ end
35
+ end
36
+ search_conditions
37
+ end
38
+
39
+ def prepare_simple_condition(table, field, exact)
40
+ return nil unless params.key? field
41
+
42
+ if exact
43
+ table[field].eq(params[field])
44
+ else
45
+ table[field].matches(Arel::Nodes::SqlLiteral.new("'%#{params[field]}%'"))
46
+ end
47
+ end
48
+
49
+ def prepare_jsonb_condition(table, field, exact)
50
+ field_name = field[0].to_s
51
+ return nil unless params.key? field_name
52
+
53
+ condition = Arel::Nodes::InfixOperation.new('->>', table[field_name], Arel::Nodes.build_quoted(field[1]))
54
+
55
+ if exact
56
+ condition.eq(params[field_name])
57
+ else
58
+ condition.matches(Arel::Nodes::SqlLiteral.new("'%#{params[field_name]}%'"))
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SearchAutocomplete
4
+ # Gem version
5
+ VERSION = '0.1.0'
6
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: search_autocomplete
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Francis Schiavo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 6.0.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 6.0.0
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 6.0.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: sqlite3
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 1.4.2
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 1.4.2
47
+ description: Search and autocomplete based on Arel and WebComponents.
48
+ email:
49
+ - francischiavo@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - README.md
55
+ - Rakefile
56
+ - app/controllers/search_autocomplete/autocomplete_controller.rb
57
+ - config/routes.rb
58
+ - lib/search_autocomplete.rb
59
+ - lib/search_autocomplete/autocompletable.rb
60
+ - lib/search_autocomplete/engine.rb
61
+ - lib/search_autocomplete/form_builder_helper.rb
62
+ - lib/search_autocomplete/options.rb
63
+ - lib/search_autocomplete/searchable.rb
64
+ - lib/search_autocomplete/version.rb
65
+ homepage: https://github.com/francis-schiavo/search_autocomplete
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 3.1.2
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: This gem adds autocomplete and filter functionality to rails apps.
88
+ test_files: []