elastics 0.5.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -1
- data/elastics.gemspec +1 -1
- data/lib/elastics.rb +2 -0
- data/lib/elastics/active_record.rb +4 -1
- data/lib/elastics/active_record/helper_methods.rb +4 -2
- data/lib/elastics/active_record/search_result.rb +9 -2
- data/lib/elastics/client.rb +2 -2
- data/lib/elastics/model/schema.rb +6 -5
- data/lib/elastics/query_helper.rb +1 -1
- data/lib/elastics/rspec.rb +38 -0
- data/lib/elastics/search_query.rb +99 -0
- data/lib/elastics/tasks.rb +17 -1
- data/lib/elastics/tasks/indices.rb +52 -6
- data/lib/elastics/tasks/mappings.rb +33 -2
- data/lib/elastics/version.rb +1 -1
- data/lib/tasks/elastics.rake +1 -0
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4aec32b4215bd40d80c3d367f2166a0b91a278b9
|
4
|
+
data.tar.gz: ccae5ce2bf5904bd2605f152f4b02e1556449035
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4327171fbed3358386552e0a516b55e10c13aa47efea873e80fdc122a409862a73564a744ec49e2f7889c5be6e71e4c1d8a937aba1a48e2b915d0fe5f07cedba
|
7
|
+
data.tar.gz: cf810f168e30abb9167ff719c653f9e604764eb845734a0c16fa15146db3e4c558d6206258d83d394c2fe999297c7a029c0d2515a5de0c5a7e3338141510634c
|
data/README.md
CHANGED
@@ -101,8 +101,15 @@ end
|
|
101
101
|
User.elastics # Elastics::Client instance
|
102
102
|
User.elastics_params # hash with index & type values for the model
|
103
103
|
User.request_elastics(params) # performs request merging params with elastics_params
|
104
|
-
|
104
|
+
|
105
|
+
search = User.search_elastics(data)
|
105
106
|
# Returns Elastics::ActiveRecord::SearchResult object with some useful methods
|
107
|
+
search.relation # User.where(id: found_ids)
|
108
|
+
search.collection # Array of users sorted as in elastics response
|
109
|
+
|
110
|
+
# Set scope applied to relation:
|
111
|
+
search = Project.search_elastics(data, scope: ->(s) { s.includes(:user) })
|
112
|
+
search.collection.first.user # eagger-loaded
|
106
113
|
|
107
114
|
# Indexing on create/update can be skipped with skip_elastics
|
108
115
|
User.skip_elastics { users.each { |x| x.update_attributes(smth: 'not indexed') } }
|
@@ -136,6 +143,8 @@ production:
|
|
136
143
|
index_prefix: app_
|
137
144
|
```
|
138
145
|
|
146
|
+
YAML is passed througn ERB before parsing.
|
147
|
+
|
139
148
|
#### Create mappings & import data
|
140
149
|
```
|
141
150
|
$ rake elastics:migrate elastics:reindex
|
data/elastics.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_runtime_dependency 'httpclient', '~> 2.4
|
21
|
+
spec.add_runtime_dependency 'httpclient', '~> 2.4'
|
22
22
|
|
23
23
|
spec.add_development_dependency 'bundler', '~> 1.5'
|
24
24
|
spec.add_development_dependency 'rake', '~> 10.0'
|
data/lib/elastics.rb
CHANGED
@@ -8,9 +8,11 @@ module Elastics
|
|
8
8
|
autoload :Instrumentation, 'elastics/instrumentation'
|
9
9
|
autoload :Model, 'elastics/model'
|
10
10
|
autoload :QueryHelper, 'elastics/query_helper'
|
11
|
+
autoload :SearchQuery, 'elastics/search_query'
|
11
12
|
autoload :Result, 'elastics/result'
|
12
13
|
autoload :Tasks, 'elastics/tasks'
|
13
14
|
autoload :VersionManager, 'elastics/version_manager'
|
15
|
+
autoload :RSpec, 'elastics/rspec'
|
14
16
|
end
|
15
17
|
|
16
18
|
require 'elastics/railtie' if defined?(Rails)
|
@@ -38,7 +38,10 @@ module Elastics
|
|
38
38
|
private
|
39
39
|
def install_elastics_hooks(hooks)
|
40
40
|
if hooks.include?(:update)
|
41
|
-
after_commit :index_elastics,
|
41
|
+
after_commit :index_elastics,
|
42
|
+
on: [:create, :update],
|
43
|
+
unless: :skip_elastics?,
|
44
|
+
if: -> { previous_changes.any? }
|
42
45
|
end
|
43
46
|
if hooks.include?(:destroy)
|
44
47
|
after_commit :delete_elastics, on: [:destroy], unless: :skip_elastics?
|
@@ -21,8 +21,10 @@ module Elastics
|
|
21
21
|
|
22
22
|
# Finds items by ids and returns array in the order in which ids were given.
|
23
23
|
# Every missing record is replaced with `nil` in the result.
|
24
|
-
|
25
|
-
|
24
|
+
# If `conditions_present` is `true` it doesn't add where clause.
|
25
|
+
def find_all_ordered(ids, conditions_present = false)
|
26
|
+
relation = conditions_present ? where(id: ids) : self
|
27
|
+
items_by_id = relation.index_by(&:id)
|
26
28
|
ids.map { |i| items_by_id[i] }
|
27
29
|
end
|
28
30
|
|
@@ -1,8 +1,12 @@
|
|
1
1
|
module Elastics
|
2
2
|
module ActiveRecord
|
3
3
|
class SearchResult < Result::Search
|
4
|
+
# It expects `:model` option with a model-class.
|
5
|
+
# Optionally pass `scope` option with a lambda which takes and modifies
|
6
|
+
# relation.
|
4
7
|
def initialize(response, options = {})
|
5
8
|
@model = options[:model]
|
9
|
+
@scope = options[:scope]
|
6
10
|
super response, options
|
7
11
|
end
|
8
12
|
|
@@ -12,11 +16,14 @@ module Elastics
|
|
12
16
|
end
|
13
17
|
|
14
18
|
def collection
|
15
|
-
@collection ||=
|
19
|
+
@collection ||= relation.find_all_ordered(ids_to_find, true)
|
16
20
|
end
|
17
21
|
|
18
22
|
def relation
|
19
|
-
@
|
23
|
+
@relation ||= begin
|
24
|
+
result = @model.where(id: ids_to_find)
|
25
|
+
@scope ? @scope.call(result) : result
|
26
|
+
end
|
20
27
|
end
|
21
28
|
end
|
22
29
|
end
|
data/lib/elastics/client.rb
CHANGED
@@ -13,11 +13,12 @@ module Elastics
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def reset_elastics_index_name
|
16
|
-
@elastics_index_name =
|
17
|
-
superclass.elastics_index_name
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
@elastics_index_name =
|
17
|
+
if respond_to?(:superclass) && superclass.respond_to?(:elastics_index_name)
|
18
|
+
superclass.elastics_index_name
|
19
|
+
else
|
20
|
+
compute_elastics_index_name
|
21
|
+
end
|
21
22
|
end
|
22
23
|
|
23
24
|
def compute_elastics_index_name
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Elastics
|
2
|
+
module RSpec
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# Adds around filter to perform elastics specific helper actions.
|
6
|
+
#
|
7
|
+
# - Enables autorefresh for each example,
|
8
|
+
# - Executes `clear_elastics` before each example,
|
9
|
+
# - Performs migration (once, for first occured example).
|
10
|
+
#
|
11
|
+
# Filter is applied only to tagged examples (`:elastics` by default).
|
12
|
+
#
|
13
|
+
# RSpec.configure do |config|
|
14
|
+
# Elastics::RSpec.configure(config)
|
15
|
+
# end
|
16
|
+
def configure(config, tag = :elastics)
|
17
|
+
migrated = false
|
18
|
+
error = nil
|
19
|
+
config.around tag => true do |ex|
|
20
|
+
if migrated
|
21
|
+
raise error if error
|
22
|
+
Model.list.each(&:clear_elastics)
|
23
|
+
else
|
24
|
+
begin
|
25
|
+
Tasks.drop_indices
|
26
|
+
Tasks.migrate
|
27
|
+
rescue Error => e
|
28
|
+
error = e
|
29
|
+
raise e
|
30
|
+
ensure
|
31
|
+
migrated = true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
AutoRefresh.enable! { ex.run }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Elastics
|
2
|
+
# Helpers to build search query.
|
3
|
+
#
|
4
|
+
# class Query
|
5
|
+
# include Elastics::SearchQuery
|
6
|
+
#
|
7
|
+
# # implement any of:
|
8
|
+
# # query, phrase_query, query_filters, post_filter, aggregations
|
9
|
+
#
|
10
|
+
# def phrase_query
|
11
|
+
# {bool: {should: [
|
12
|
+
# {multi_match: {
|
13
|
+
# # ...
|
14
|
+
# }}
|
15
|
+
# ]}}
|
16
|
+
# # or just
|
17
|
+
# {math: {message: params[:query_string]}}
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def query_filters
|
21
|
+
# [
|
22
|
+
# {term: {published: true}},
|
23
|
+
# terms_array_query(:tag, params[:tags], execution: :and),
|
24
|
+
# some_complex_filter,
|
25
|
+
# ]
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# def aggregations
|
29
|
+
# {
|
30
|
+
# tag: {terms: {
|
31
|
+
# field: :tag,
|
32
|
+
# size: 10,
|
33
|
+
# shard_size: 10,
|
34
|
+
# }},
|
35
|
+
# }
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# result = Model.search_elastics Query.new(params).as_json
|
40
|
+
module SearchQuery
|
41
|
+
include Elastics::QueryHelper
|
42
|
+
|
43
|
+
attr_reader :params
|
44
|
+
|
45
|
+
def initialize(params)
|
46
|
+
@params = params
|
47
|
+
end
|
48
|
+
|
49
|
+
def as_json
|
50
|
+
page = params[:page] || 1
|
51
|
+
per_page = params[:per_page] || 10
|
52
|
+
result = {
|
53
|
+
from: (page - 1) * per_page,
|
54
|
+
size: per_page,
|
55
|
+
fields: [],
|
56
|
+
query: query,
|
57
|
+
sort: sort,
|
58
|
+
}
|
59
|
+
post_filter = self.post_filter
|
60
|
+
result[:post_filter] = post_filter if post_filter
|
61
|
+
aggregations = self.aggregations
|
62
|
+
result[:aggregations] = aggregations if aggregations
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
# Builds query from phrase_query & query_filters.
|
67
|
+
def query
|
68
|
+
normalize_query(phrase_query, query_filters.compact)
|
69
|
+
end
|
70
|
+
|
71
|
+
def phrase_query
|
72
|
+
end
|
73
|
+
|
74
|
+
def query_filters
|
75
|
+
[]
|
76
|
+
end
|
77
|
+
|
78
|
+
def post_filter
|
79
|
+
end
|
80
|
+
|
81
|
+
def aggregations
|
82
|
+
end
|
83
|
+
|
84
|
+
# Takes `params[:sort]` and returns it compatible with elastics.
|
85
|
+
# Wraps scalars into array, hashes are converted into arrays,
|
86
|
+
# array are passed as is.
|
87
|
+
#
|
88
|
+
# {name: :asc, _score: :desc} => [{name: :asc}, {_score: :desc}]
|
89
|
+
# :created_at => [:created_at]
|
90
|
+
def sort
|
91
|
+
val = params[:sort]
|
92
|
+
case val
|
93
|
+
when Hash then val.map { |x| Hash[[x]] }
|
94
|
+
when Array then val
|
95
|
+
else val ? [val] : []
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/elastics/tasks.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
1
3
|
require 'active_support'
|
2
4
|
require 'active_support/core_ext'
|
3
5
|
|
@@ -23,7 +25,21 @@ module Elastics
|
|
23
25
|
extend self
|
24
26
|
|
25
27
|
def log(*args)
|
26
|
-
puts(*args)
|
28
|
+
puts(*args) if verbose
|
29
|
+
Rails.logger.info { "Elastics: #{args.join ' '}" } if defined?(Rails)
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_accessor :verbose
|
33
|
+
|
34
|
+
def suppress_messages
|
35
|
+
verbose_was, self.verbose = verbose, false
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
self.verbose = verbose_was
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_yaml(file)
|
42
|
+
YAML.load(ERB.new(File.read(file)).result)
|
27
43
|
end
|
28
44
|
|
29
45
|
private
|
@@ -11,12 +11,54 @@ module Elastics
|
|
11
11
|
@indices_paths ||= base_paths.map { |x| File.join x, 'indices' }
|
12
12
|
end
|
13
13
|
|
14
|
+
# Merges settings from single files and dirs.
|
14
15
|
def indices_settings
|
15
|
-
@indices_settings ||=
|
16
|
+
@indices_settings ||= indices_from_files.merge!(indices_from_dirs)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Reads indices settings from single yml file.
|
20
|
+
# Setting can be given specific for env or one for all envs.
|
21
|
+
#
|
22
|
+
# tweet:
|
23
|
+
# development:
|
24
|
+
# settings:
|
25
|
+
# number_of_shards: 5
|
26
|
+
# production:
|
27
|
+
# settings:
|
28
|
+
# number_of_shards: 10
|
29
|
+
#
|
30
|
+
# user:
|
31
|
+
# settings:
|
32
|
+
# number_of_shards: 5
|
33
|
+
def indices_from_files
|
34
|
+
indices_paths.each_with_object({}) do |path, hash|
|
35
|
+
file = "#{path}.yml"
|
36
|
+
next unless File.exists?(file)
|
37
|
+
load_yaml(file).each do |name, data|
|
38
|
+
hash[name] = data[Rails.env] || data
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Reads indices settings from separate files. Index name is taken from
|
44
|
+
# file name, setting can be given specific for env or one for all envs.
|
45
|
+
#
|
46
|
+
# development:
|
47
|
+
# settings:
|
48
|
+
# number_of_shards: 5
|
49
|
+
# production:
|
50
|
+
# settings:
|
51
|
+
# number_of_shards: 10
|
52
|
+
#
|
53
|
+
# # or
|
54
|
+
# settings:
|
55
|
+
# number_of_shards: 1
|
56
|
+
def indices_from_dirs
|
57
|
+
indices_paths.map { |path| Dir["#{path}/*.yml"] }.
|
16
58
|
flatten.sort.
|
17
59
|
each_with_object({}) do |file, hash|
|
18
60
|
name = File.basename file, '.yml'
|
19
|
-
data =
|
61
|
+
data = load_yaml(file)
|
20
62
|
hash[name] = data[Rails.env] || data
|
21
63
|
end
|
22
64
|
end
|
@@ -76,10 +118,14 @@ module Elastics
|
|
76
118
|
new_versions = {}
|
77
119
|
post_aliases options do |index|
|
78
120
|
new_versions[index] = version_manager.next_version index
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
121
|
+
if client.index_exists?(versioned_index_name(index, :current))
|
122
|
+
[
|
123
|
+
alias_action(:remove, index, :current),
|
124
|
+
alias_action(:add, index, :next),
|
125
|
+
]
|
126
|
+
else
|
127
|
+
alias_action(:add, index, :next)
|
128
|
+
end
|
83
129
|
end
|
84
130
|
drop_indices(options.merge version: :current) if options.fetch(:drop, true)
|
85
131
|
new_versions.each do |index, version|
|
@@ -21,12 +21,43 @@ module Elastics
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
# Merges mappings from single files and dirs.
|
24
25
|
def mappings
|
25
|
-
@mappings ||=
|
26
|
+
@mappings ||= mappings_from_files.merge!(mappings_from_dirs)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Reads mappings from single yml file.
|
30
|
+
#
|
31
|
+
# user:
|
32
|
+
# dynamic: false
|
33
|
+
# properties:
|
34
|
+
# name: string
|
35
|
+
# tweet:
|
36
|
+
# properties:
|
37
|
+
# content: string
|
38
|
+
# user_id: integer
|
39
|
+
def mappings_from_files
|
40
|
+
mappings_paths.each_with_object({}) do |path, hash|
|
41
|
+
file = "#{path}.yml"
|
42
|
+
next unless File.exists?(file)
|
43
|
+
load_yaml(file).each do |name, data|
|
44
|
+
hash[name] = fix_mapping(name, data)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Reads mappings from separate files. Type name is taken from file name.
|
50
|
+
#
|
51
|
+
# # user.yml
|
52
|
+
# dynamic: false
|
53
|
+
# properties:
|
54
|
+
# name: string
|
55
|
+
def mappings_from_dirs
|
56
|
+
mappings_paths.map { |path| Dir["#{path}/*.yml"] }.
|
26
57
|
flatten.sort.
|
27
58
|
each_with_object({}) do |file, hash|
|
28
59
|
name = File.basename file, '.yml'
|
29
|
-
hash[name] = fix_mapping(name,
|
60
|
+
hash[name] = fix_mapping(name, load_yaml(file))
|
30
61
|
end
|
31
62
|
end
|
32
63
|
|
data/lib/elastics/version.rb
CHANGED
data/lib/tasks/elastics.rake
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elastics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Max Melentiev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httpclient
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.4
|
19
|
+
version: '2.4'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.4
|
26
|
+
version: '2.4'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -131,6 +131,8 @@ files:
|
|
131
131
|
- lib/elastics/railtie.rb
|
132
132
|
- lib/elastics/result.rb
|
133
133
|
- lib/elastics/result/search.rb
|
134
|
+
- lib/elastics/rspec.rb
|
135
|
+
- lib/elastics/search_query.rb
|
134
136
|
- lib/elastics/tasks.rb
|
135
137
|
- lib/elastics/tasks/config.rb
|
136
138
|
- lib/elastics/tasks/indices.rb
|
@@ -166,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
168
|
version: '0'
|
167
169
|
requirements: []
|
168
170
|
rubyforge_project:
|
169
|
-
rubygems_version: 2.
|
171
|
+
rubygems_version: 2.6.8
|
170
172
|
signing_key:
|
171
173
|
specification_version: 4
|
172
174
|
summary: ElasticSearch client with ActiveRecord integration
|
@@ -178,4 +180,3 @@ test_files:
|
|
178
180
|
- spec/lib/elastics/instrumentation_spec.rb
|
179
181
|
- spec/lib/elastics/model/schema_spec.rb
|
180
182
|
- spec/spec_helper.rb
|
181
|
-
has_rdoc:
|