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,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