indices 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +245 -0
  4. data/Rakefile +19 -0
  5. data/lib/generators/indices/index_generator.rb +15 -0
  6. data/lib/generators/indices/install_generator.rb +15 -0
  7. data/lib/generators/indices/templates/index.rb +12 -0
  8. data/lib/generators/indices/templates/initializer.rb +10 -0
  9. data/lib/indices.rb +102 -0
  10. data/lib/indices/collection.rb +195 -0
  11. data/lib/indices/concern.rb +23 -0
  12. data/lib/indices/configuration.rb +39 -0
  13. data/lib/indices/dsl/api.rb +85 -0
  14. data/lib/indices/dsl/mappings.rb +14 -0
  15. data/lib/indices/dsl/search.rb +25 -0
  16. data/lib/indices/dsl/serializer.rb +17 -0
  17. data/lib/indices/index.rb +149 -0
  18. data/lib/indices/pagination.rb +33 -0
  19. data/lib/indices/proxy.rb +17 -0
  20. data/lib/indices/railtie.rb +15 -0
  21. data/lib/indices/version.rb +5 -0
  22. data/lib/tasks/indices.rake +11 -0
  23. data/test/dsl_test.rb +92 -0
  24. data/test/dummy/Rakefile +5 -0
  25. data/test/dummy/app/assets/javascripts/application.js +13 -0
  26. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  27. data/test/dummy/app/controllers/application_controller.rb +5 -0
  28. data/test/dummy/app/helpers/application_helper.rb +2 -0
  29. data/test/dummy/app/indices/products_index.rb +51 -0
  30. data/test/dummy/app/indices/shops_index.rb +28 -0
  31. data/test/dummy/app/models/product.rb +5 -0
  32. data/test/dummy/app/models/shop.rb +5 -0
  33. data/test/dummy/app/views/layouts/application.html.erb +12 -0
  34. data/test/dummy/bin/bundle +4 -0
  35. data/test/dummy/bin/rails +5 -0
  36. data/test/dummy/bin/rake +5 -0
  37. data/test/dummy/bin/setup +30 -0
  38. data/test/dummy/config.ru +4 -0
  39. data/test/dummy/config/application.rb +25 -0
  40. data/test/dummy/config/boot.rb +5 -0
  41. data/test/dummy/config/database.yml +7 -0
  42. data/test/dummy/config/database.yml.travis +3 -0
  43. data/test/dummy/config/environment.rb +5 -0
  44. data/test/dummy/config/environments/development.rb +41 -0
  45. data/test/dummy/config/environments/production.rb +79 -0
  46. data/test/dummy/config/environments/test.rb +42 -0
  47. data/test/dummy/config/initializers/assets.rb +11 -0
  48. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  49. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  50. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  51. data/test/dummy/config/initializers/indices.rb +67 -0
  52. data/test/dummy/config/initializers/inflections.rb +16 -0
  53. data/test/dummy/config/initializers/mime_types.rb +4 -0
  54. data/test/dummy/config/initializers/session_store.rb +3 -0
  55. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  56. data/test/dummy/config/locales/en.yml +23 -0
  57. data/test/dummy/config/routes.rb +56 -0
  58. data/test/dummy/config/secrets.yml +22 -0
  59. data/test/dummy/db/migrate/20161104164148_create_products.rb +12 -0
  60. data/test/dummy/db/migrate/20161104182219_create_shops.rb +7 -0
  61. data/test/dummy/db/schema.rb +36 -0
  62. data/test/dummy/log/development.log +156 -0
  63. data/test/dummy/log/test.log +10249 -0
  64. data/test/dummy/public/404.html +67 -0
  65. data/test/dummy/public/422.html +67 -0
  66. data/test/dummy/public/500.html +66 -0
  67. data/test/dummy/public/favicon.ico +0 -0
  68. data/test/generators_test.rb +25 -0
  69. data/test/indices_test.rb +46 -0
  70. data/test/record_test.rb +25 -0
  71. data/test/search_test.rb +164 -0
  72. data/test/tasks_test.rb +25 -0
  73. data/test/test_helper.rb +14 -0
  74. metadata +230 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b72b3ef891bbf4ba001c080c8a73a0b567f8098d
4
+ data.tar.gz: 54850246383eba634fd80a68b48069ae87464370
5
+ SHA512:
6
+ metadata.gz: a487528f3d53b1dfc5429d6e6ea0496f3da9aa99883750b96464890af023899a9a1e9e27c305eb977ab94316fbd8ffbbd2510099c2a458e05d6a013007cae2c8
7
+ data.tar.gz: 5e44c2f59d06adf3f0faf0e3cdfb6b7689f8f007195a73cdfbd4ba100be9abb9514fe515c479cec7c6271e56f05f38bde58f9e2135e845de7bbe0c298a4385e4
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Mathías Montossi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,245 @@
1
+ [![Gem Version](https://badge.fury.io/rb/indices.svg)](http://badge.fury.io/rb/indices)
2
+ [![Code Climate](https://codeclimate.com/github/mmontossi/indices/badges/gpa.svg)](https://codeclimate.com/github/mmontossi/indices)
3
+ [![Build Status](https://travis-ci.org/mmontossi/indices.svg)](https://travis-ci.org/mmontossi/indices)
4
+ [![Dependency Status](https://gemnasium.com/mmontossi/indices.svg)](https://gemnasium.com/mmontossi/indices)
5
+
6
+ # Indices
7
+
8
+ Model search indices with elasticsearch in rails.
9
+
10
+ ## Why
11
+
12
+ I did this gem to:
13
+
14
+ - Gain control of the queries without losing simplicity.
15
+ - Have out of the box integration with activerecord and pagers.
16
+ - Deal with the just in time nature of elasticsearch.
17
+ - Integrate activerecord tool on it.
18
+ - Have a convention of how to integrate suggestions.
19
+
20
+ ## Install
21
+
22
+ Put this line in your Gemfile:
23
+ ```ruby
24
+ gem 'indices'
25
+ ```
26
+
27
+ Then bundle:
28
+ ```
29
+ $ bundle
30
+ ```
31
+
32
+ To install Redis you can use homebrew:
33
+ ```
34
+ brew install elasticsearch24
35
+ ```
36
+
37
+ NOTE: This gem is tested agains version 2.4.
38
+
39
+ ## Configuration
40
+
41
+ Generate the configuration file:
42
+ ```
43
+ bundle exec rails g indices:install
44
+ ```
45
+
46
+ Configure the global settings:
47
+ ```ruby
48
+ Indices.configure do |config|
49
+
50
+ config.hosts = %w(localhost:9200)
51
+ config.log = false
52
+ config.trace = false
53
+
54
+ config.mappings do
55
+ name do
56
+ type 'string'
57
+ fields do
58
+ raw do
59
+ type 'string'
60
+ index 'not_analyzed'
61
+ end
62
+ end
63
+ end
64
+ category type: 'string'
65
+ price type: 'long'
66
+ currency type: 'string'
67
+ product_suggestions do
68
+ type 'completion'
69
+ analyzer 'simple'
70
+ end
71
+ end
72
+
73
+ config.analysis do
74
+ filter do
75
+ ngram do
76
+ type 'nGram'
77
+ min_gram 2
78
+ max_gram 20
79
+ end
80
+ end
81
+ end
82
+
83
+ config.suggestions do |name, term, options={}|
84
+ type = name.to_s.singularize
85
+ text (term || '')
86
+ completion do
87
+ field "#{type}_suggestions"
88
+ end
89
+ end
90
+
91
+ config.add_computed_sort :price do |direction|
92
+ _script do
93
+ type 'number'
94
+ script do
95
+ inline "if (_source.currency == 'UYU') { doc['price'].value * 30 }"
96
+ end
97
+ order direction
98
+ end
99
+ end
100
+
101
+ end
102
+ ```
103
+
104
+ Generate an index:
105
+ ```
106
+ bundle exec rails g indices:index products
107
+ ```
108
+
109
+ Configure the index:
110
+ ```ruby
111
+ Indices.define :products do
112
+
113
+ mappings do
114
+ properties :name, :category, :price, :product_suggestions
115
+ end
116
+
117
+ serializer do |record|
118
+ set record, :name, :category, :price
119
+ product_suggestions do
120
+ input [record.name, transliterate(record.name)].uniq
121
+ output record.name
122
+ end
123
+ end
124
+
125
+ search do |*args|
126
+ options = args.extract_options!
127
+ term = args.first
128
+ query do
129
+ if term.present?
130
+ multi_match do
131
+ query term
132
+ type 'phrase_prefix'
133
+ fields %w(name category)
134
+ end
135
+ else
136
+ match_all
137
+ end
138
+ end
139
+ end
140
+
141
+ end
142
+ ```
143
+
144
+ ## Usage
145
+
146
+ ### Indexing
147
+
148
+ Ocurrs everytime you create or destroy a record:
149
+ ```ruby
150
+ product = Product.create(name: 'Les Paul', category: 'Gibson')
151
+ ```
152
+
153
+ You can force it manually by:
154
+ ```ruby
155
+ product.index
156
+ product.reindex
157
+ product.unindex
158
+ ```
159
+
160
+ At any time you can force a full rebuild:
161
+ ```
162
+ bundle exec rake indices:rebuild
163
+ ```
164
+
165
+ Or if you need it, just a build:
166
+ ```
167
+ bundle exec rake indices:build
168
+ ```
169
+
170
+ ### Search
171
+
172
+ The search parameters are sent to previous configured block:
173
+ ```ruby
174
+ @products = Product.search(name: 'Test')
175
+ ```
176
+
177
+ You can use the returned value as a collection in views:
178
+ ```erb
179
+ <%= render @products %>
180
+ ```
181
+
182
+ ### Includes
183
+
184
+ Same as using activerecord relations:
185
+ ```ruby
186
+ Product.search(includes: :shop)
187
+ ```
188
+
189
+ ### With / Without
190
+
191
+ You can force a record to be part of the results by id:
192
+ ```ruby
193
+ Product.search(with: 4)
194
+ ```
195
+
196
+ Or the opposite:
197
+ ```ruby
198
+ Product.search(without: 4)
199
+ ```
200
+
201
+ ### Pagination
202
+
203
+ Works the same as [Pagers gem](https://github.com/mmontossi/pagers):
204
+ ```ruby
205
+ @products.page 1, padding: 4, length: 30
206
+ ```
207
+
208
+ And you can send the collection directly to the helper in views:
209
+ ```erb
210
+ <%= paginate @products %>
211
+ ```
212
+
213
+ ### Order
214
+
215
+ Works the same as in relations:
216
+ ```ruby
217
+ @products.order(name: :asc)
218
+ ```
219
+
220
+ To use a computed_sort:
221
+ ```ruby
222
+ @products.order(price: :asc)
223
+ ```
224
+
225
+ NOTE: To sort by a string column, you must declare the mapping raw.
226
+
227
+ ### Suggestions
228
+
229
+ The suggestion parameters are sent to previous configured block:
230
+ ```ruby
231
+ Indices.suggest :products, 'gibson'
232
+ ```
233
+
234
+ Returns array of hashes with a text property:
235
+ ```ruby
236
+ [{ text: 'Les Paul' }, ...]
237
+ ```
238
+
239
+ ## Credits
240
+
241
+ This gem is maintained and funded by [mmontossi](https://github.com/mmontossi).
242
+
243
+ ## License
244
+
245
+ It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ require 'rake/testtask'
10
+
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib'
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
16
+ t.warning = false
17
+ end
18
+
19
+ task default: :test
@@ -0,0 +1,15 @@
1
+ require 'rails/generators'
2
+
3
+ module Indices
4
+ module Generators
5
+ class IndexGenerator < Rails::Generators::NamedBase
6
+
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ def create_index_file
10
+ template 'index.rb', "app/indices/#{table_name}_index.rb"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'rails/generators'
2
+
3
+ module Indices
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ def create_initializer_file
10
+ copy_file 'initializer.rb', 'config/initializers/indices.rb'
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ Indices.define :<%= table_name %> do
2
+
3
+ mappings do
4
+ end
5
+
6
+ serializer do |record|
7
+ end
8
+
9
+ search do |*args|
10
+ end
11
+
12
+ end
@@ -0,0 +1,10 @@
1
+ Indices.configure do |config|
2
+
3
+ config.hosts = %w(localhost:9200)
4
+ config.log = true
5
+ config.trace = true
6
+
7
+ config.mappings do
8
+ end
9
+
10
+ end
@@ -0,0 +1,102 @@
1
+ require 'generators/indices/index_generator'
2
+ require 'generators/indices/install_generator'
3
+ require 'indices/dsl/api'
4
+ require 'indices/dsl/mappings'
5
+ require 'indices/dsl/search'
6
+ require 'indices/dsl/serializer'
7
+ require 'indices/concern'
8
+ require 'indices/configuration'
9
+ require 'indices/index'
10
+ require 'indices/pagination'
11
+ require 'indices/proxy'
12
+ require 'indices/railtie'
13
+ require 'indices/collection'
14
+ require 'indices/version'
15
+
16
+ module Indices
17
+ class << self
18
+
19
+ delegate :any?, :none?, to: :registry
20
+
21
+ def namespace
22
+ "#{Rails.application.class.parent_name} #{Rails.env}".parameterize('_')
23
+ end
24
+
25
+ def client
26
+ @client ||= begin
27
+ require 'elasticsearch'
28
+ Elasticsearch::Client.new(
29
+ hosts: configuration.hosts,
30
+ log: configuration.log,
31
+ trace: configuration.trace
32
+ )
33
+ end
34
+ end
35
+
36
+ def configure
37
+ yield configuration
38
+ end
39
+
40
+ def configuration
41
+ @configuration ||= Configuration.new
42
+ end
43
+
44
+ def define(*args, &block)
45
+ Proxy.new *args, &block
46
+ end
47
+
48
+ def add(*args)
49
+ index = Index.new(*args)
50
+ registry[index.name] = index
51
+ end
52
+
53
+ def find(name)
54
+ registry[name]
55
+ end
56
+ alias_method :[], :find
57
+
58
+ def each(&block)
59
+ registry.values.sort.each &block
60
+ end
61
+
62
+ def build
63
+ unless client.indices.exists?(index: namespace)
64
+ client.indices.create(
65
+ index: namespace,
66
+ body: { settings: configuration.analysis }
67
+ )
68
+ end
69
+ each &:build
70
+ end
71
+
72
+ def exist?(type)
73
+ client.indices.exists? index: namespace, type: type
74
+ end
75
+
76
+ def rebuild
77
+ destroy
78
+ build
79
+ end
80
+
81
+ def destroy
82
+ if client.indices.exists?(index: namespace)
83
+ client.indices.delete index: namespace
84
+ end
85
+ end
86
+
87
+ def suggest(*args)
88
+ response = client.suggest(
89
+ index: namespace,
90
+ body: { suggestions: Dsl::Api.new(args, &configuration.suggestions).to_h }
91
+ )
92
+ response['suggestions'].first['options'].map &:symbolize_keys
93
+ end
94
+
95
+ private
96
+
97
+ def registry
98
+ @registry ||= {}
99
+ end
100
+
101
+ end
102
+ end