indexes 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +245 -0
- data/Rakefile +19 -0
- data/lib/generators/indexes/index_generator.rb +15 -0
- data/lib/generators/indexes/install_generator.rb +15 -0
- data/lib/generators/indexes/templates/index.rb +12 -0
- data/lib/generators/indexes/templates/initializer.rb +10 -0
- data/lib/indexes.rb +102 -0
- data/lib/indexes/collection.rb +195 -0
- data/lib/indexes/concern.rb +23 -0
- data/lib/indexes/configuration.rb +39 -0
- data/lib/indexes/dsl/api.rb +85 -0
- data/lib/indexes/dsl/mappings.rb +14 -0
- data/lib/indexes/dsl/search.rb +25 -0
- data/lib/indexes/dsl/serializer.rb +17 -0
- data/lib/indexes/index.rb +149 -0
- data/lib/indexes/pagination.rb +33 -0
- data/lib/indexes/proxy.rb +17 -0
- data/lib/indexes/railtie.rb +15 -0
- data/lib/indexes/version.rb +5 -0
- data/lib/tasks/indexes.rake +11 -0
- data/test/dsl_test.rb +92 -0
- data/test/dummy/Rakefile +5 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/indexes/products_index.rb +51 -0
- data/test/dummy/app/indexes/shops_index.rb +28 -0
- data/test/dummy/app/models/product.rb +5 -0
- data/test/dummy/app/models/shop.rb +5 -0
- data/test/dummy/app/views/layouts/application.html.erb +12 -0
- data/test/dummy/bin/bundle +4 -0
- data/test/dummy/bin/rails +5 -0
- data/test/dummy/bin/rake +5 -0
- data/test/dummy/bin/setup +30 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +25 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +7 -0
- data/test/dummy/config/database.yml.travis +3 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/indexes.rb +67 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/db/migrate/20161104164148_create_products.rb +14 -0
- data/test/dummy/db/migrate/20161104182219_create_shops.rb +9 -0
- data/test/dummy/db/schema.rb +36 -0
- data/test/dummy/log/development.log +38 -0
- data/test/dummy/log/test.log +408 -0
- data/test/dummy/public/404.html +61 -0
- data/test/dummy/public/422.html +61 -0
- data/test/dummy/public/500.html +60 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/generators_test.rb +25 -0
- data/test/indexes_test.rb +46 -0
- data/test/record_test.rb +25 -0
- data/test/search_test.rb +164 -0
- data/test/tasks_test.rb +25 -0
- data/test/test_helper.rb +14 -0
- metadata +230 -0
@@ -0,0 +1,195 @@
|
|
1
|
+
module Indexes
|
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 = Indexes.configuration.mappings
|
48
|
+
sort = []
|
49
|
+
options.each do |property, direction|
|
50
|
+
if block = Indexes.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 Indexes
|
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 Indexes
|
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 Indexes
|
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,25 @@
|
|
1
|
+
module Indexes
|
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] = Indexes[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 Indexes
|
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 Indexes
|
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
|
+
Indexes.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 Indexes::Concern
|
142
|
+
define_singleton_method :index do
|
143
|
+
index
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|