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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +245 -0
- data/Rakefile +19 -0
- data/lib/generators/indices/index_generator.rb +15 -0
- data/lib/generators/indices/install_generator.rb +15 -0
- data/lib/generators/indices/templates/index.rb +12 -0
- data/lib/generators/indices/templates/initializer.rb +10 -0
- data/lib/indices.rb +102 -0
- data/lib/indices/collection.rb +195 -0
- data/lib/indices/concern.rb +23 -0
- data/lib/indices/configuration.rb +39 -0
- data/lib/indices/dsl/api.rb +85 -0
- data/lib/indices/dsl/mappings.rb +14 -0
- data/lib/indices/dsl/search.rb +25 -0
- data/lib/indices/dsl/serializer.rb +17 -0
- data/lib/indices/index.rb +149 -0
- data/lib/indices/pagination.rb +33 -0
- data/lib/indices/proxy.rb +17 -0
- data/lib/indices/railtie.rb +15 -0
- data/lib/indices/version.rb +5 -0
- data/lib/tasks/indices.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/indices/products_index.rb +51 -0
- data/test/dummy/app/indices/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/indices.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 +12 -0
- data/test/dummy/db/migrate/20161104182219_create_shops.rb +7 -0
- data/test/dummy/db/schema.rb +36 -0
- data/test/dummy/log/development.log +156 -0
- data/test/dummy/log/test.log +10249 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/generators_test.rb +25 -0
- data/test/indices_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 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,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
|