indices 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,195 @@
1
+ module Indices
2
+ class Collection
3
+ include Enumerable
4
+
5
+ delegate :model, to: :index
6
+ delegate :model_name, to: :model
7
+ delegate :each, :map, :size, :length, :count, :[], :to_a, to: :records
8
+
9
+ alias_method :to_ary, :to_a
10
+
11
+ def initialize(index, *args, &block)
12
+ @loaded = false
13
+ @index = index
14
+ @options = args.extract_options!
15
+ @args = args
16
+ @block = block
17
+ end
18
+
19
+ def page(number, options={})
20
+ length = (fetch_option(options, :length) || 10)
21
+ padding = (fetch_option(options, :padding) || 0)
22
+ current_page = [number.to_i, 1].max
23
+ values = Module.new do
24
+ define_method :page_length do
25
+ length
26
+ end
27
+ define_method :padding do
28
+ padding
29
+ end
30
+ define_method :current_page do
31
+ current_page
32
+ end
33
+ end
34
+ overrides = {
35
+ from: ((length * (current_page - 1)) + padding),
36
+ size: length
37
+ }
38
+ %i(with without).each do |name|
39
+ if options.has_key?(name)
40
+ overrides[name] = options[name]
41
+ end
42
+ end
43
+ chain Pagination, values, overrides
44
+ end
45
+
46
+ def order(options)
47
+ mappings = Indices.configuration.mappings
48
+ sort = []
49
+ options.each do |property, direction|
50
+ if block = Indices.configuration.computed_sorts[property]
51
+ sort << Dsl::Api.new(direction, &block).to_h
52
+ elsif property == :id
53
+ sort << { _uid: { order: direction } }
54
+ elsif mappings.has_key?(property) && mappings[property][:type] == 'string'
55
+ sort << { "#{property}.raw" => { order: direction } }
56
+ end
57
+ end
58
+ if sort.any?
59
+ chain sort: sort
60
+ else
61
+ chain
62
+ end
63
+ end
64
+
65
+ def response
66
+ if @loaded == true
67
+ @response
68
+ else
69
+ @loaded = true
70
+ @response = index.raw_search(query)
71
+ end
72
+ end
73
+
74
+ def query
75
+ @query ||= begin
76
+ pagination = options.slice(:from, :size, :sort)
77
+ without_ids = fetch_ids(options[:without])
78
+ body = Dsl::Search.new(args.append(options), &block).to_h[:query]
79
+ request = Dsl::Search.new do
80
+ if without_ids.any?
81
+ query do
82
+ filtered do
83
+ filter do
84
+ bool do
85
+ must_not do
86
+ without_ids.each do |id|
87
+ term do
88
+ _id id
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ query body
95
+ end
96
+ end
97
+ else
98
+ query body
99
+ end
100
+ %i(from size).each do |option|
101
+ if pagination.has_key?(option)
102
+ send option, pagination[option]
103
+ end
104
+ end
105
+ if pagination.has_key?(:sort)
106
+ sort pagination[:sort]
107
+ else
108
+ sort do
109
+ _uid do
110
+ order 'desc'
111
+ end
112
+ end
113
+ end
114
+ end
115
+ request.to_h
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ attr_reader :index, :args, :options, :block
122
+
123
+ def records
124
+ @records ||= begin
125
+ if missing_ids.any?
126
+ last_index = -(missing_ids.length + 1)
127
+ ids = (missing_ids.sort.reverse + hit_ids.to(last_index))
128
+ else
129
+ ids = hit_ids
130
+ end
131
+ index.model.includes(includes).where(id: ids).sort do |a,b|
132
+ ids.index(a.id) <=> ids.index(b.id)
133
+ end
134
+ end
135
+ end
136
+
137
+ def with_ids
138
+ @with_ids ||= fetch_ids(options[:with])
139
+ end
140
+
141
+ def hit_ids
142
+ @hit_ids ||= response['hits']['hits'].map{ |hit| hit['_id'].to_i }
143
+ end
144
+
145
+ def missing_ids
146
+ @missing_ids ||= (with_ids - hit_ids)
147
+ end
148
+
149
+ def includes
150
+ @inclues ||= begin
151
+ if options.has_key?(:includes)
152
+ Array options[:includes]
153
+ else
154
+ []
155
+ end
156
+ end
157
+ end
158
+
159
+ def fetch_option(options, name)
160
+ options[name] || begin
161
+ if Rails.configuration.cache_classes == false
162
+ Rails.application.eager_load!
163
+ end
164
+ if defined?(Pagers)
165
+ Pagers.config[name]
166
+ end
167
+ end
168
+ end
169
+
170
+ def fetch_ids(source)
171
+ case source
172
+ when Fixnum,String
173
+ [source.to_i]
174
+ when ActiveRecord::Base
175
+ [source.id]
176
+ when ActiveRecord::Relation
177
+ source.ids
178
+ when Array
179
+ source.map{ |value| fetch_ids(value) }.flatten
180
+ else
181
+ []
182
+ end
183
+ end
184
+
185
+ def chain(*extensions)
186
+ overrides = extensions.extract_options!
187
+ Collection.new(index, *args.append(options.merge(overrides)), &block).tap do |collection|
188
+ extensions.each do |extension|
189
+ collection.extend extension
190
+ end
191
+ end
192
+ end
193
+
194
+ end
195
+ end
@@ -0,0 +1,23 @@
1
+ module Indices
2
+ module Concern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ after_commit :index, on: :create
7
+ after_commit :reindex, on: :update
8
+ after_commit :unindex, on: :destroy
9
+ end
10
+
11
+ %i(index reindex unindex).each do |name|
12
+ define_method name do
13
+ self.class.index.send name, self
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ delegate :search, to: :index
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ module Indices
2
+ class Configuration
3
+
4
+ attr_accessor :hosts, :log, :trace
5
+
6
+ def computed_sorts
7
+ @computed_sorts ||= {}
8
+ end
9
+
10
+ def mappings(&block)
11
+ if block_given?
12
+ @mappings = Dsl::Mappings.new(&block).to_h
13
+ else
14
+ @mappings
15
+ end
16
+ end
17
+
18
+ def analysis(&block)
19
+ if block_given?
20
+ @analysis = { analysis: Dsl::Api.new(&block).to_h }
21
+ else
22
+ @analysis
23
+ end
24
+ end
25
+
26
+ def suggestions(&block)
27
+ if block_given?
28
+ @suggestions = block
29
+ else
30
+ @suggestions
31
+ end
32
+ end
33
+
34
+ def add_computed_sort(name, &block)
35
+ self.computed_sorts[name] = block
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,85 @@
1
+ module Indices
2
+ module Dsl
3
+ class Api
4
+
5
+ def initialize(args=[], parent={}, &block)
6
+ @args = args
7
+ @parent = parent
8
+ instance_exec *args, &block
9
+ end
10
+
11
+ def method_missing(name, *args, &block)
12
+ options = args.extract_options!
13
+ name = name.to_sym
14
+ if block_given?
15
+ child = add_block(name, args, options)
16
+ continue child, &block
17
+ elsif args.size > 0
18
+ add_argument name, args, options
19
+ elsif options.any?
20
+ add_options name, options
21
+ else
22
+ add_empty name
23
+ end
24
+ end
25
+
26
+ def to_h
27
+ @parent
28
+ end
29
+
30
+ private
31
+
32
+ def add_block(name, args, options)
33
+ case @parent
34
+ when Array
35
+ item = options.merge(name => {})
36
+ @parent << item
37
+ child = item[name]
38
+ when Hash
39
+ if @parent.has_key?(name)
40
+ child = @parent[name].merge!(options)
41
+ else
42
+ child = @parent[name] = {}
43
+ end
44
+ end
45
+ if args.any?
46
+ child[args.first.to_sym] = {}
47
+ else
48
+ child
49
+ end
50
+ end
51
+
52
+ def add_argument(name, args, options)
53
+ @parent[name] = args.first
54
+ end
55
+
56
+ def add_options(name, options)
57
+ options.symbolize_keys!
58
+ case @parent
59
+ when Array
60
+ @parent << { name => options }
61
+ when Hash
62
+ if @parent.has_key?(name)
63
+ @parent[name].merge! options
64
+ else
65
+ @parent[name] = options
66
+ end
67
+ end
68
+ end
69
+
70
+ def add_empty(name)
71
+ case @parent
72
+ when Array
73
+ @parent << { name => {} }
74
+ when Hash
75
+ @parent[name] = {}
76
+ end
77
+ end
78
+
79
+ def continue(child, &block)
80
+ self.class.new @args, child, &block
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,14 @@
1
+ module Indices
2
+ module Dsl
3
+ class Mappings < Api
4
+
5
+ def properties(*names)
6
+ @parent[:properties] ||= {}
7
+ names.each do |name|
8
+ @parent[:properties][name] = Indices.configuration.mappings[name]
9
+ end
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ module Indices
2
+ module Dsl
3
+ class Search < Api
4
+
5
+ private
6
+
7
+ def add_block(name, args, options)
8
+ if %i(functions must must_not should).include?(name)
9
+ @parent[name] = []
10
+ else
11
+ super
12
+ end
13
+ end
14
+
15
+ def add_argument(name, args, options)
16
+ if name == :query && args.first.is_a?(Symbol)
17
+ @parent[name] = Indices[args.first].search(options).query[:query]
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module Indices
2
+ module Dsl
3
+ class Serializer < Api
4
+
5
+ def set(object, *names)
6
+ names.each do |name|
7
+ send name, object.send(name)
8
+ end
9
+ end
10
+
11
+ def transliterate(value)
12
+ ActiveSupport::Inflector.transliterate value
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,149 @@
1
+ module Indices
2
+ class Index
3
+ include Comparable
4
+
5
+ attr_reader :name, :type, :options
6
+
7
+ def initialize(name, options={})
8
+ @name = name
9
+ @type = @name.to_s.singularize
10
+ @options = options
11
+ make_indexable
12
+ end
13
+
14
+ def mappings
15
+ @mappings ||= Dsl::Mappings.new(&options[:mappings]).to_h
16
+ end
17
+
18
+ def model
19
+ (options[:class_name] || type.classify).constantize
20
+ end
21
+
22
+ def has_parent?
23
+ mappings.has_key? :_parent
24
+ end
25
+
26
+ def <=>(other)
27
+ if has_parent? && other.has_parent?
28
+ 0
29
+ elsif other.has_parent?
30
+ 1
31
+ else
32
+ -1
33
+ end
34
+ end
35
+
36
+ def any?(*args)
37
+ search(*args).count > 0
38
+ end
39
+
40
+ def none?(*args)
41
+ !any?(*args)
42
+ end
43
+
44
+ def search(*args)
45
+ Collection.new self, *args, &options[:search]
46
+ end
47
+
48
+ def raw_search(query)
49
+ client.search(
50
+ index: namespace,
51
+ type: type,
52
+ body: query
53
+ )
54
+ end
55
+
56
+ def exists?(record)
57
+ client.exists?(
58
+ with_parent(
59
+ record,
60
+ index: namespace,
61
+ type: type,
62
+ id: record.id
63
+ )
64
+ )
65
+ end
66
+
67
+ def index(record)
68
+ client.create(
69
+ with_parent(
70
+ record,
71
+ index: namespace,
72
+ type: type,
73
+ id: record.id,
74
+ body: serialize(record)
75
+ )
76
+ )
77
+ end
78
+
79
+ def reindex(record)
80
+ client.bulk(
81
+ index: namespace,
82
+ type: type,
83
+ body: [
84
+ { delete: with_parent(record, _id: record.id) },
85
+ { index: with_parent(record, _id: record.id, data: serialize(record)) }
86
+ ]
87
+ )
88
+ end
89
+
90
+ def unindex(record)
91
+ client.delete(
92
+ with_parent(
93
+ record,
94
+ index: namespace,
95
+ type: type,
96
+ id: record.id
97
+ )
98
+ )
99
+ end
100
+
101
+ def build
102
+ client.indices.put_mapping(
103
+ index: namespace,
104
+ type: type,
105
+ body: mappings
106
+ )
107
+ model.find_in_batches do |records|
108
+ client.bulk(
109
+ index: namespace,
110
+ type: type,
111
+ body: records.map do |record|
112
+ { index: with_parent(record, _id: record.id, data: serialize(record)) }
113
+ end
114
+ )
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ %i(client namespace).each do |name|
121
+ define_method name do
122
+ Indices.send name
123
+ end
124
+ end
125
+
126
+ def with_parent(record, hash)
127
+ if has_parent?
128
+ hash.merge parent: record.send("#{mappings[:_parent][:type].singularize}_id")
129
+ else
130
+ hash
131
+ end
132
+ end
133
+
134
+ def serialize(record)
135
+ Dsl::Serializer.new(record, &options[:serializer]).to_h
136
+ end
137
+
138
+ def make_indexable
139
+ index = self
140
+ model.class_eval do
141
+ include Indices::Concern
142
+ define_singleton_method :index do
143
+ index
144
+ end
145
+ end
146
+ end
147
+
148
+ end
149
+ end