elastics 0.1.1 → 0.2.0
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 +4 -4
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/README.md +80 -9
- data/Rakefile +3 -8
- data/elastics.gemspec +4 -1
- data/lib/elastics.rb +11 -0
- data/lib/elastics/active_record.rb +19 -4
- data/lib/elastics/active_record/helper_methods.rb +27 -26
- data/lib/elastics/active_record/instrumentation.rb +63 -7
- data/lib/elastics/active_record/model_schema.rb +22 -17
- data/lib/elastics/active_record/search_result.rb +49 -0
- data/lib/elastics/active_record/tasks_config.rb +25 -0
- data/lib/elastics/capistrano.rb +14 -0
- data/lib/elastics/client.rb +49 -22
- data/lib/elastics/client/cluster.rb +70 -0
- data/lib/elastics/railtie.rb +5 -4
- data/lib/elastics/tasks.rb +20 -19
- data/lib/elastics/tasks/config.rb +38 -0
- data/lib/elastics/tasks/indices.rb +72 -10
- data/lib/elastics/tasks/mappings.rb +11 -5
- data/lib/elastics/tasks/migrations.rb +42 -0
- data/lib/elastics/version.rb +1 -1
- data/lib/elastics/version_manager.rb +86 -0
- data/lib/tasks/elastics.rake +31 -8
- data/spec/lib/elastics/client/cluster_spec.rb +108 -0
- data/spec/spec_helper.rb +9 -0
- metadata +62 -7
- data/lib/elastics/active_record/log_subscriber.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 39da83ca0918ecd5e5bced988a20a0f43c4a0651
|
4
|
+
data.tar.gz: 1b71e46b7873678e169586cf4f16204c3f01d01c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc66215c7c93d9209828494f50aa2ed7a39ec4477924728955641ee6d8ed4cfa0fe09ed7388e7d6c599923c3eef28b729c1c3fd92f0c370a9f02b88bf16d2240
|
7
|
+
data.tar.gz: b03818b919cb671ec17c259f30527d18d994a5dc3c05377b1fbacd87bcbb73b68f4de5bdff16af9f43500f465f950d795be53a82562b7ec0eec86f03b7defe6c
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,13 +1,21 @@
|
|
1
1
|
# elastics
|
2
|
+
[](http://badge.fury.io/rb/elastics)
|
3
|
+
[](https://codeclimate.com/github/printercu/elastics-rb)
|
4
|
+
[](https://travis-ci.org/printercu/elastics-rb)
|
2
5
|
|
3
6
|
Simple ElasticSearch client.
|
7
|
+
- basic API only
|
8
|
+
- transparent aliases management & zero-downtime migrations
|
9
|
+
- capistrano integration
|
4
10
|
|
5
|
-
Fast and thread-safe [httpclient](https://github.com/nahi/httpclient) under the hood.
|
11
|
+
Fast and thread-safe [httpclient](https://github.com/nahi/httpclient) is under the hood.
|
6
12
|
|
7
13
|
## Install
|
8
14
|
|
9
15
|
```ruby
|
10
16
|
# Gemfile
|
17
|
+
gem 'elastics', '~> 0.2' # use version from the badge above
|
18
|
+
# or
|
11
19
|
gem 'elastics', github: 'printercu/elastics-rb'
|
12
20
|
```
|
13
21
|
|
@@ -19,10 +27,11 @@ gem 'elastics', github: 'printercu/elastics-rb'
|
|
19
27
|
# initialize client with
|
20
28
|
client = Elastics::Client.new(options)
|
21
29
|
# options is hash with
|
22
|
-
# :host
|
23
|
-
# :port
|
30
|
+
# :host - hostname with port or array with hosts (default 127.0.0.1:9200)
|
24
31
|
# :index - (default index)
|
25
32
|
# :type - (default type)
|
33
|
+
# :connect_timeout - timeout to mark the host as dead in cluster-mode (default 10)
|
34
|
+
# :resurrect_timeout - timeout to mark dead host as alive in cluster-mode (default 10)
|
26
35
|
|
27
36
|
# basic request
|
28
37
|
client.request(options)
|
@@ -49,6 +58,8 @@ client.index(params) # PUT if :id is set, otherwise POST
|
|
49
58
|
client.index_exists?(name)
|
50
59
|
```
|
51
60
|
|
61
|
+
When using cluster-mode you should also install `gem 'thread_safe'`.
|
62
|
+
|
52
63
|
### ActiveRecord
|
53
64
|
|
54
65
|
```ruby
|
@@ -63,6 +74,7 @@ class User < ActiveRecord::Base
|
|
63
74
|
end
|
64
75
|
|
65
76
|
User.search_elastics(data)
|
77
|
+
# Returns Elastics::ActiveRecord::SearchResult object with some useful methods
|
66
78
|
```
|
67
79
|
|
68
80
|
#### Configure
|
@@ -78,26 +90,85 @@ development:
|
|
78
90
|
|
79
91
|
production:
|
80
92
|
elastics:
|
81
|
-
host: 10.0.0.1
|
82
|
-
|
93
|
+
host: 10.0.0.1:1234
|
94
|
+
# or
|
95
|
+
host:
|
96
|
+
- 10.0.0.1:1234
|
97
|
+
- 10.0.0.2:1234
|
83
98
|
|
84
99
|
index: app
|
85
100
|
# or
|
86
101
|
index_prefix: app_
|
87
102
|
```
|
88
103
|
|
104
|
+
#### Create mappings & import data
|
105
|
+
```
|
106
|
+
$ rake elastics:migrate elastics:reindex
|
107
|
+
```
|
108
|
+
|
89
109
|
#### Mappings & index settings
|
90
110
|
Mappings & index settings `.yml` files are placed in
|
91
111
|
`db/elastics/mappings` & `db/elastics/indices`.
|
92
112
|
For now this files are not related to models and only used by rake tasks.
|
93
113
|
|
94
|
-
|
114
|
+
### Index management
|
115
|
+
When index is created elastics transparently manages aliases for it.
|
116
|
+
Instead of creating `index1` it creates `index1-v0` and create `index1` alias for it.
|
117
|
+
When you perform normal migration, mappings are applied to the current version.
|
118
|
+
Later when you perform full migration `index1-v1` is created, after reindexing
|
119
|
+
aliases are changed and `index-v0` is droped.
|
120
|
+
|
121
|
+
Versions of indices are stored in ElasticSearch in `.elastics` index.
|
122
|
+
|
123
|
+
### Rake tasks
|
124
|
+
All rake tasks except `purge` accepts list of indices to process
|
125
|
+
(`rake elastics:create[index1,index2]`).
|
126
|
+
Also you can specify index version like this `rake elastics:migrate version=next`.
|
127
|
+
Version can be set to `next` or `current` (default).
|
128
|
+
|
129
|
+
Rake tasks are just frontend for `Elastics::Tasks`'s methods.
|
130
|
+
For complex migrations, when you need partially reindex data,
|
131
|
+
you may want to write custom scripts using this methods.
|
132
|
+
|
133
|
+
- `rake elastics:create` (`.create_indices`)
|
95
134
|
creates index with settings for each file from `indices` folder.
|
96
|
-
For single index it only processes file with index name.
|
97
|
-
For multiple indices each index name is `#{index_prefix}#{file.basename}`
|
98
135
|
|
99
|
-
- `rake elastics:migrate` (
|
136
|
+
- `rake elastics:migrate` (`.migrate`)
|
100
137
|
puts mappings from `mappings` folder.
|
101
138
|
|
139
|
+
- `rake elastics:migrate full=true` (`.migrate!`)
|
140
|
+
performs full migration.
|
141
|
+
|
142
|
+
- `rake elastics:reindex` (`.reindex`)
|
143
|
+
reindexes data.
|
144
|
+
|
145
|
+
#### Using without Rails
|
146
|
+
You need to setup `Elastics::Tasks` yourself. This can be done in `environment` or
|
147
|
+
`db:load_config` rake tasks.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
task :environment do
|
151
|
+
Elastics::Tasks.base_paths = '/path/to/your/elastics/folder'
|
152
|
+
Elastics::Tasks.config = your_configuration
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
Also you need to install `active_support` & require
|
157
|
+
`active_support/core_ext/object` to be able to run tasks.
|
158
|
+
|
159
|
+
### Use with capistrano
|
160
|
+
Add following lines to your `deploy.rb` and all rake tasks will be available in cap.
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
role :elastics, '%HOSTNAME%', primary: true
|
164
|
+
|
165
|
+
require 'elastics/capistrano'
|
166
|
+
```
|
167
|
+
|
168
|
+
Indices & rake options can be passed like this:
|
169
|
+
```
|
170
|
+
cap --dry-run elastics:migrate INDICES=index1,index2 ES_OPTIONS='full=true no_drop=true'
|
171
|
+
```
|
172
|
+
|
102
173
|
## License
|
103
174
|
MIT
|
data/Rakefile
CHANGED
@@ -1,11 +1,6 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
|
-
require '
|
2
|
+
require 'rspec/core/rake_task'
|
3
3
|
|
4
|
-
|
5
|
-
t.libs << 'lib/elastics'
|
6
|
-
t.libs << 'test'
|
7
|
-
t.test_files = FileList['test/lib/elastics/*_test.rb']
|
8
|
-
t.verbose = true
|
9
|
-
end
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
5
|
|
11
|
-
task default: :
|
6
|
+
task default: :spec
|
data/elastics.gemspec
CHANGED
@@ -21,5 +21,8 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_runtime_dependency 'httpclient', '~> 2.4.0'
|
22
22
|
|
23
23
|
spec.add_development_dependency 'bundler', '~> 1.5'
|
24
|
-
spec.add_development_dependency 'rake', '~> 10'
|
24
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.1.0'
|
26
|
+
spec.add_development_dependency 'thread_safe', '~> 0.3.4'
|
27
|
+
spec.add_development_dependency 'activesupport', '~> 4.1.6'
|
25
28
|
end
|
data/lib/elastics.rb
CHANGED
@@ -3,11 +3,22 @@ module Elastics
|
|
3
3
|
class NotFound < Error; end
|
4
4
|
|
5
5
|
require 'elastics/client'
|
6
|
+
require 'elastics/version_manager'
|
6
7
|
require 'elastics/query_helper'
|
7
8
|
|
8
9
|
autoload :Tasks, 'elastics/tasks'
|
9
10
|
|
10
11
|
extend QueryHelper
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_reader :models
|
15
|
+
|
16
|
+
def reset_models
|
17
|
+
@models = []
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
reset_models
|
11
22
|
end
|
12
23
|
|
13
24
|
require 'elastics/railtie' if defined?(Rails)
|
@@ -2,10 +2,18 @@ module Elastics
|
|
2
2
|
module ActiveRecord
|
3
3
|
extend ActiveSupport::Autoload
|
4
4
|
|
5
|
+
autoload :SearchResult
|
5
6
|
autoload :ModelSchema
|
6
7
|
autoload :HelperMethods
|
7
8
|
autoload :Instrumentation
|
8
|
-
autoload :LogSubscriber
|
9
|
+
autoload :LogSubscriber, 'elastics/active_record/instrumentation'
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def install
|
13
|
+
::ActiveRecord::Base.extend self
|
14
|
+
Instrumentation.install
|
15
|
+
end
|
16
|
+
end
|
9
17
|
|
10
18
|
def elastics_config
|
11
19
|
@elastics_config ||= connection_config[:elastics].try!(:with_indifferent_access) ||
|
@@ -16,16 +24,23 @@ module Elastics
|
|
16
24
|
@elastics ||= Client.new elastics_config.slice(:host, :port)
|
17
25
|
end
|
18
26
|
|
27
|
+
# Don't memoize to GC it after initialization
|
28
|
+
def elastics_version_manager
|
29
|
+
VersionManager.new(elastics, elastics_config.slice(
|
30
|
+
:service_index,
|
31
|
+
:index_prefix,
|
32
|
+
))
|
33
|
+
end
|
34
|
+
|
19
35
|
def indexed_with_elastics(options = {})
|
20
36
|
options = {
|
21
37
|
hooks: [:update, :destroy],
|
22
|
-
}.merge(options)
|
38
|
+
}.merge!(options)
|
23
39
|
|
24
40
|
extend ModelSchema
|
25
41
|
include HelperMethods
|
26
|
-
extend Instrumentation
|
27
42
|
|
28
|
-
self.
|
43
|
+
self.elastics_index_base = options[:index] if options[:index]
|
29
44
|
self.elastics_type_name = options[:type] if options[:type]
|
30
45
|
|
31
46
|
hooks = options[:hooks]
|
@@ -3,42 +3,41 @@ module Elastics
|
|
3
3
|
module HelperMethods
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
+
included do
|
7
|
+
alias_method :to_elastics, :as_json unless instance_methods.include?(:to_elastics)
|
8
|
+
end
|
9
|
+
|
6
10
|
module ClassMethods
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
collection: collection,
|
15
|
-
relation: relation,
|
16
|
-
search: es_results,
|
17
|
-
}
|
11
|
+
def request_elastics(params)
|
12
|
+
request = {
|
13
|
+
index: elastics_index_name,
|
14
|
+
type: elastics_type_name,
|
15
|
+
model: self,
|
16
|
+
}.merge!(params)
|
17
|
+
elastics.request(request)
|
18
18
|
end
|
19
19
|
|
20
|
-
def search_elastics(data = {},
|
20
|
+
def search_elastics(data = {}, options = {})
|
21
21
|
request = {
|
22
|
-
id:
|
22
|
+
id: :_search,
|
23
23
|
data: data,
|
24
24
|
}
|
25
|
-
|
26
|
-
|
25
|
+
if routing = options[:routing]
|
26
|
+
request[:query] = {routing: routing}
|
27
|
+
end
|
28
|
+
SearchResult.new self, request_elastics(request), options
|
27
29
|
end
|
28
30
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
type: elastics_type_name,
|
33
|
-
}.merge!(params)
|
34
|
-
elastics.request(request)
|
31
|
+
def find_all_ordered(ids)
|
32
|
+
items_by_id = where(id: ids).index_by(&:id)
|
33
|
+
ids.map { |i| items_by_id[i] }
|
35
34
|
end
|
36
35
|
|
37
36
|
def elastics_mapping
|
38
37
|
request_elastics(method: :get, id: :_mapping)
|
39
38
|
end
|
40
39
|
|
41
|
-
def
|
40
|
+
def reindex_elastics(*args)
|
42
41
|
find_each(*args, &:index_elastics)
|
43
42
|
end
|
44
43
|
end
|
@@ -47,12 +46,14 @@ module Elastics
|
|
47
46
|
self.class.request_elastics(method: :post, id: id, data: to_elastics)
|
48
47
|
end
|
49
48
|
|
50
|
-
def
|
51
|
-
self.class.request_elastics(method: :
|
49
|
+
def update_elastics(fields)
|
50
|
+
self.class.request_elastics(method: :post, id: "#{id}/_update", data: {
|
51
|
+
doc: fields
|
52
|
+
})
|
52
53
|
end
|
53
54
|
|
54
|
-
def
|
55
|
-
|
55
|
+
def delete_elastics
|
56
|
+
self.class.request_elastics(method: :delete, id: id)
|
56
57
|
end
|
57
58
|
end
|
58
59
|
end
|
@@ -1,15 +1,71 @@
|
|
1
1
|
module Elastics
|
2
2
|
module ActiveRecord
|
3
|
+
# To be included in `Elastics::Client`
|
3
4
|
module Instrumentation
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
class << self
|
6
|
+
def install
|
7
|
+
if Client.respond_to?(:prepend)
|
8
|
+
Client.prepend self
|
9
|
+
else
|
10
|
+
Client.include Fallback
|
11
|
+
end
|
12
|
+
unless ::ActiveRecord::LogSubscriber < LogSubscriber
|
13
|
+
::ActiveRecord::LogSubscriber.send :include, LogSubscriber
|
14
|
+
end
|
11
15
|
end
|
12
16
|
end
|
17
|
+
|
18
|
+
def http_request(*args)
|
19
|
+
ActiveSupport::Notifications.instrument 'request_elastics.active_record', args: args do
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# old rubies support
|
25
|
+
module Fallback
|
26
|
+
extend ActiveSupport::Concern
|
27
|
+
|
28
|
+
included do
|
29
|
+
alias_method_chain :http_request, :instrumentation
|
30
|
+
end
|
31
|
+
|
32
|
+
def http_request_with_instrumentation(*args)
|
33
|
+
ActiveSupport::Notifications.instrument 'request_elastics.active_record', args: args do
|
34
|
+
http_request_without_instrumentation(*args)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module LogSubscriber
|
41
|
+
def self.included(base)
|
42
|
+
instance_methods.each { |method| base.method_added(method) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def request_elastics(event)
|
46
|
+
return unless logger.debug?
|
47
|
+
|
48
|
+
payload = event.payload[:args]
|
49
|
+
method, path, query, body, params = payload
|
50
|
+
path = '/' if path.blank?
|
51
|
+
path << "?#{query.to_param}" if query.present?
|
52
|
+
model = params[:model]
|
53
|
+
|
54
|
+
name = ""
|
55
|
+
name << "#{model.name} " if model
|
56
|
+
name << "elastics (#{event.duration.round(1)}ms)"
|
57
|
+
request = "#{method.to_s.upcase} #{path}"
|
58
|
+
request << " #{body}" if body.present?
|
59
|
+
|
60
|
+
if odd?
|
61
|
+
name = color(name, ActiveSupport::LogSubscriber::CYAN, true)
|
62
|
+
request = color(request, nil, true)
|
63
|
+
else
|
64
|
+
name = color(name, ActiveSupport::LogSubscriber::MAGENTA, true)
|
65
|
+
end
|
66
|
+
|
67
|
+
debug " #{name} #{request}"
|
68
|
+
end
|
13
69
|
end
|
14
70
|
end
|
15
71
|
end
|
@@ -1,7 +1,17 @@
|
|
1
1
|
module Elastics
|
2
2
|
module ActiveRecord
|
3
3
|
module ModelSchema
|
4
|
-
|
4
|
+
class << self
|
5
|
+
def track_model(model)
|
6
|
+
Elastics.models << model unless model.abstract_class?
|
7
|
+
end
|
8
|
+
|
9
|
+
def extended(base)
|
10
|
+
track_model(base)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_writer :elastics_index_base, :elastics_type_name
|
5
15
|
|
6
16
|
def elastics_index_name
|
7
17
|
reset_elastics_index_name unless defined?(@elastics_index_name)
|
@@ -9,30 +19,25 @@ module Elastics
|
|
9
19
|
end
|
10
20
|
|
11
21
|
def elastics_type_name
|
12
|
-
|
13
|
-
@elastics_type_name
|
22
|
+
@elastics_type_name ||= model_name.to_s.demodulize.underscore.singularize
|
14
23
|
end
|
15
24
|
|
16
25
|
def reset_elastics_index_name
|
17
|
-
|
18
|
-
|
19
|
-
superclass == ::ActiveRecord::Base ? nil : superclass.elastics_index_name
|
20
|
-
elsif superclass.abstract_class? && superclass_responds
|
21
|
-
superclass.elastics_index_name || compute_elastics_index_name
|
22
|
-
else
|
23
|
-
compute_elastics_index_name
|
26
|
+
@elastics_index_name = if self != ::ActiveRecord::Base && !abstract_class?
|
27
|
+
superclass.try(:elastics_index_name) || compute_elastics_index_name
|
24
28
|
end
|
25
|
-
@elastics_index_name = index
|
26
|
-
@elastics_type_name = compute_elastics_type_name
|
27
29
|
end
|
28
30
|
|
29
|
-
def compute_elastics_index_name
|
30
|
-
|
31
|
-
|
31
|
+
def compute_elastics_index_name
|
32
|
+
elastics_version_manager.index_name(elastics_index_base)
|
33
|
+
end
|
34
|
+
|
35
|
+
def elastics_index_base
|
36
|
+
@elastics_index_base || elastics_config[:index] || elastics_type_name
|
32
37
|
end
|
33
38
|
|
34
|
-
def
|
35
|
-
|
39
|
+
def inherited(base)
|
40
|
+
super.tap { ::Elastics::ActiveRecord::ModelSchema.track_model(base) }
|
36
41
|
end
|
37
42
|
end
|
38
43
|
end
|