elastics 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c5c6528a83756461e3f88a64c932e6f6cd4a572e
4
- data.tar.gz: 329c63ad74333cee9dc57581ca8ac626973dea3b
3
+ metadata.gz: ca7add0bf62591b992bdcbb03cf08461cf208204
4
+ data.tar.gz: 5f2b845c2cf49a89e6f4414dfad2dd505ed56784
5
5
  SHA512:
6
- metadata.gz: aba73c791bb368035b51ed5d93994fa27648c442f77ed7992749b5bb5ee5ee823a17e33d5449d0d8a1f293dd830ff01501405287ca1dc563df644c3bb3da68ba
7
- data.tar.gz: d687176c8013f6196d0ebc0340aaa6e194e5719b700fdbb688159ca2ca6ffda3694e144e0b55c064fcb63b629299288121b9dbd25ba61876318092089360cafa
6
+ metadata.gz: da3920ea1f157297b6c3e5fea2b7ead3620493fb887ee34bd6191b8428ab74763fb324246be193fbd5e2fc32268f7f61331680a02f087d6ce3c619d9d7da1820
7
+ data.tar.gz: 262e2e0efec270bed5eac991e706298220bf67f5024efd6ae99782d94efb86380c95bf40bcc9dc2779e423205d6c00441a9b72968921894d3bd5fb3869034cab
@@ -3,6 +3,8 @@ rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
5
  - 2.1.4
6
+ - jruby-19mode
7
+ - jruby-head
6
8
  services:
7
9
  - elasticsearch
8
10
  notifications:
data/Gemfile CHANGED
@@ -1,3 +1,9 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ group :development do
6
+ gem 'pry', '~> 0.10.1'
7
+ gem 'pry-byebug', '~> 2.0.0', platforms: [:mri_20, :mri_21]
8
+ gem 'pry-coolline', '~> 0.2.5'
9
+ end
data/README.md CHANGED
@@ -32,8 +32,6 @@ client = Elastics::Client.new(options)
32
32
  # :host - hostname with port or array with hosts (default 127.0.0.1:9200)
33
33
  # :index - (default index)
34
34
  # :type - (default type)
35
- # :connect_timeout - timeout to mark the host as dead in cluster-mode (default 10)
36
- # :resurrect_timeout - timeout to mark dead host as alive in cluster-mode (default 10)
37
35
 
38
36
  # basic request
39
37
  client.request(params)
@@ -70,7 +68,21 @@ client.bulk(params) do |bulk|
70
68
  end
71
69
  ```
72
70
 
73
- When using cluster-mode you should also install `gem 'thread_safe'`.
71
+ ### Models
72
+ Most of ActiveRecord integration functionality is available for plain ruby.
73
+
74
+ ```ruby
75
+ class User
76
+ include Elastics::Model
77
+
78
+ # set connection config, check 'configure section' for available options
79
+ self.elastics_config = {host: 'hostname:port'}
80
+ # set index base and version manager will manage aliases
81
+ self.elastics_index_base = 'user'
82
+ self.elastics_type_name = 'user'
83
+ end
84
+ ```
85
+ Check out available [HelperMethods](https://github.com/printercu/elastics-rb/blob/master/lib/elastics/model/helper_methods.rb).
74
86
 
75
87
  ### ActiveRecord
76
88
 
@@ -91,8 +103,13 @@ User.elastics_params # hash with index & type values for the model
91
103
  User.request_elastics(params) # performs request merging params with elastics_params
92
104
  User.search_elastics(data)
93
105
  # Returns Elastics::ActiveRecord::SearchResult object with some useful methods
106
+
107
+ # Indexing on create/update can be skipped with skip_elastics
108
+ User.skip_elastics { users.each { |x| x.update_attributes(smth: 'not indexed') } }
109
+ user.skip_elastics { user.update_attributes(smth: 'not indexed') }
94
110
  ```
95
- Check out [HelperMethods](https://github.com/printercu/elastics-rb/blob/master/lib/elastics/active_record/helper_methods.rb)
111
+ Check out [Model::HelperMethods](https://github.com/printercu/elastics-rb/blob/master/lib/elastics/model/helper_methods.rb)
112
+ and [AR::HelperMethods](https://github.com/printercu/elastics-rb/blob/master/lib/elastics/active_record/helper_methods.rb)
96
113
  for more information.
97
114
 
98
115
  #### Configure
@@ -171,8 +188,7 @@ task :environment do
171
188
  end
172
189
  ```
173
190
 
174
- Also you need to install `active_support` & require
175
- `active_support/core_ext/object` to be able to run tasks.
191
+ Also you need to install `active_support` to be able to run tasks.
176
192
 
177
193
  ### Auto refresh index
178
194
  Add `Elastics::AutoRefresh.enable!` to your test helper,
@@ -189,6 +205,48 @@ Elastics::AutoRefresh.disable! { Model.reindex_elastics }
189
205
  Model.refresh_elastics
190
206
  ```
191
207
 
208
+ ### Instrumentation
209
+ Instrumentation works out of box in rails. For pure ruby there is only
210
+ ActiveSupport version (remember to install gem).
211
+
212
+ ```ruby
213
+ # Activate instrumentation (no need for rails)
214
+ Elastics::Instrumentation::ActiveSupport.install
215
+
216
+ # Activate body prettifier (off by default)
217
+ Elastics::Instrumentation.body_prettifier = true
218
+ # can be
219
+ # true - JSON.pretty_generate
220
+ # :ap - awesome_print
221
+ # :pp - pretty_print
222
+ # Proc - ->(str) { your_prettifier(str) }
223
+ ```
224
+
225
+ ### Connecting to cluster
226
+ When you pass array in `:host` option to initializer, client will work in cluster mode.
227
+ There are some options for this mode:
228
+ - `:connect_timeout` - timeout to mark the host as dead in cluster-mode (default 10)
229
+ - `:resurrect_timeout` - timeout to mark dead host as alive in cluster-mode (default 60)
230
+ - `:discover` - enable nodes discovering
231
+
232
+ In plain ruby you should also install `thread_safe` gem.
233
+
234
+ ##### Note about nodes discovering
235
+ Client will perform requests to discover nodes with enabled http module. After
236
+ discovering hosts will be overwritten with discovered ones.
237
+
238
+ `discover: true` will keep you from editing config too frequently,
239
+ but keep in mind that you should set enough hosts explicitly, so that discover
240
+ will be able to continue if some of hosts are down.
241
+ Also this is performed automaticaly only once, when client is initialized.
242
+ It will not track nodes that go online after client was instantiated. Anyway you
243
+ still can call `.discover_cluster` whenever you want, or just restart app when
244
+ you add more nodes.
245
+
246
+ ### Thread-safety
247
+ Elastics designed to be thread-safe. It should be ok to have single client instance
248
+ for the whole application.
249
+
192
250
  ### Use with capistrano
193
251
  Add following lines to your `deploy.rb` and all rake tasks will be available in cap.
194
252
 
@@ -4,21 +4,13 @@ module Elastics
4
4
 
5
5
  require 'elastics/client'
6
6
 
7
- autoload :AutoRefresh, 'elastics/auto_refresh'
8
- autoload :Tasks, 'elastics/tasks'
9
- autoload :QueryHelper, 'elastics/query_helper'
10
- autoload :Result, 'elastics/result'
11
- autoload :VersionManager, 'elastics/version_manager'
12
-
13
- class << self
14
- attr_reader :models
15
-
16
- def reset_models
17
- @models = []
18
- end
19
- end
20
-
21
- reset_models
7
+ autoload :AutoRefresh, 'elastics/auto_refresh'
8
+ autoload :Instrumentation, 'elastics/instrumentation'
9
+ autoload :Model, 'elastics/model'
10
+ autoload :QueryHelper, 'elastics/query_helper'
11
+ autoload :Result, 'elastics/result'
12
+ autoload :Tasks, 'elastics/tasks'
13
+ autoload :VersionManager, 'elastics/version_manager'
22
14
  end
23
15
 
24
16
  require 'elastics/railtie' if defined?(Rails)
@@ -5,33 +5,21 @@ module Elastics
5
5
  autoload :SearchResult
6
6
  autoload :ModelSchema
7
7
  autoload :HelperMethods
8
- autoload :Instrumentation
9
- autoload :LogSubscriber, 'elastics/active_record/instrumentation'
10
8
 
11
9
  class << self
12
10
  def install
13
11
  ::ActiveRecord::Base.extend self
14
- Instrumentation.install
12
+ Instrumentation::ActiveSupport.install
15
13
  end
16
14
  end
17
15
 
16
+ include Model::Connection
17
+
18
18
  def elastics_config
19
19
  @elastics_config ||= connection_config[:elastics].try!(:with_indifferent_access) ||
20
20
  raise('No elastics configuration in database.yml')
21
21
  end
22
22
 
23
- def elastics
24
- @elastics ||= Client.new elastics_config.slice(:host, :port)
25
- end
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
-
35
23
  def indexed_with_elastics(options = {})
36
24
  options = {
37
25
  hooks: [:update, :destroy],
@@ -39,13 +27,22 @@ module Elastics
39
27
 
40
28
  extend ModelSchema
41
29
  include HelperMethods
30
+ extend Model::Tracking
42
31
 
43
32
  self.elastics_index_base = options[:index] if options[:index]
44
33
  self.elastics_type_name = options[:type] if options[:type]
45
34
 
46
- hooks = options[:hooks]
47
- after_commit :index_elastics, on: [:create, :update] if hooks.include?(:update)
48
- after_commit :delete_elastics, on: [:destroy] if hooks.include?(:destroy)
35
+ install_elastics_hooks(options[:hooks])
49
36
  end
37
+
38
+ private
39
+ def install_elastics_hooks(hooks)
40
+ if hooks.include?(:update)
41
+ after_commit :index_elastics, on: [:create, :update], unless: :skip_elastics?
42
+ end
43
+ if hooks.include?(:destroy)
44
+ after_commit :delete_elastics, on: [:destroy], unless: :skip_elastics?
45
+ end
46
+ end
50
47
  end
51
48
  end
@@ -3,27 +3,13 @@ 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)
6
+ def self.append_features(base)
7
+ base.send :include, Model::HelperMethods
8
+ base.send :include, Model::Skipping
9
+ super
8
10
  end
9
11
 
10
12
  module ClassMethods
11
- def elastics_params
12
- {
13
- index: elastics_index_name,
14
- type: elastics_type_name,
15
- model: self,
16
- }
17
- end
18
-
19
- def request_elastics(params)
20
- elastics.request(elastics_params.merge!(params))
21
- end
22
-
23
- def bulk_elastics(params = {}, &block)
24
- elastics.bulk(elastics_params.merge!(params), &block)
25
- end
26
-
27
13
  def search_elastics(data = {}, options = {})
28
14
  request = {
29
15
  id: :_search,
@@ -46,11 +32,7 @@ module Elastics
46
32
 
47
33
  def index_all_elastics(*args)
48
34
  find_in_batches(*args) do |batch|
49
- bulk_elastics do |bulk|
50
- batch.each do |record|
51
- bulk.index record.id, record.to_elastics
52
- end
53
- end
35
+ index_batch_elastics(batch)
54
36
  end
55
37
  end
56
38
 
@@ -71,27 +53,6 @@ module Elastics
71
53
  end
72
54
  scope.index_all_elastics(options)
73
55
  end
74
-
75
- def refresh_elastics
76
- request_elastics(method: :post, type: nil, id: :_refresh)
77
- end
78
- end
79
-
80
- def index_elastics
81
- self.class.request_elastics(method: :post, id: id, body: to_elastics)
82
- end
83
-
84
- def update_elastics(data)
85
- self.class.request_elastics(method: :post, id: "#{id}/_update", body: data)
86
- end
87
-
88
- def update_elastics_doc(fields)
89
- update_elastics(doc: fields)
90
- end
91
-
92
- def delete_elastics
93
- self.class.request_elastics(method: :delete, id: id)
94
- rescue NotFound
95
56
  end
96
57
  end
97
58
  end
@@ -1,22 +1,7 @@
1
1
  module Elastics
2
2
  module ActiveRecord
3
3
  module ModelSchema
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
15
-
16
- def elastics_index_name
17
- reset_elastics_index_name unless defined?(@elastics_index_name)
18
- @elastics_index_name
19
- end
4
+ include Model::Schema
20
5
 
21
6
  def elastics_type_name
22
7
  @elastics_type_name ||= model_name.to_s.demodulize.underscore.singularize
@@ -28,16 +13,8 @@ module Elastics
28
13
  end
29
14
  end
30
15
 
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
37
- end
38
-
39
- def inherited(base)
40
- super.tap { ::Elastics::ActiveRecord::ModelSchema.track_model(base) }
16
+ def track_elastics_model?
17
+ !abstract_class?
41
18
  end
42
19
  end
43
20
  end
@@ -14,15 +14,15 @@ module Elastics
14
14
  attr_reader :client
15
15
 
16
16
  def initialize(defaults = {})
17
+ @index = defaults[:index]
18
+ @type = defaults[:type]
19
+ @client = HTTPClient.new
17
20
  if defaults[:host].is_a?(Array)
18
21
  extend Cluster
19
22
  initialize_cluster(defaults)
20
23
  else
21
24
  @host = defaults[:host] || '127.0.0.1:9200'
22
25
  end
23
- @index = defaults[:index]
24
- @type = defaults[:type]
25
- @client = HTTPClient.new
26
26
  end
27
27
 
28
28
  def debug!(dev = STDOUT)
@@ -133,7 +133,7 @@ module Elastics
133
133
  # You probably don't want to use this method directly.
134
134
  def http_request(method, path, query, body, params = nil, host = @host)
135
135
  uri = "http://#{host}#{path}"
136
- @client.request(method, uri, query, body, HEADERS)
136
+ client.request(method, uri, query, body, HEADERS)
137
137
  end
138
138
  end
139
139
  end
@@ -7,14 +7,27 @@ module Elastics
7
7
  module Cluster
8
8
  class NoAliveHosts < Error; end
9
9
 
10
+ def discover_cluster
11
+ # `nothing` allows not to fetch all unnecessary data
12
+ discovered = request(index: '_nodes', type: '_all', id: 'nothing')['nodes'].
13
+ map { |id, node|
14
+ match = node['http_address'].match(/inet\[.*?\/([\da-f.:]+)/i)
15
+ match && match[1]
16
+ }.compact
17
+ @cluster_mutex.synchronize do
18
+ @hosts.clear.concat(discovered - @dead_hosts.keys)
19
+ end
20
+ end
21
+
10
22
  private
11
23
  def initialize_cluster(defaults)
12
24
  @hosts = ThreadSafe::Array.new defaults[:host]
13
25
  @dead_hosts = ThreadSafe::Hash.new
14
26
  @connect_timeout = defaults[:connect_timeout] || 10
15
- @resurrect_timeout = defaults[:resurrect_timeout] || 10
27
+ @resurrect_timeout = defaults[:resurrect_timeout] || 60
16
28
  @current_host_n = 0
17
29
  @cluster_mutex = Mutex.new
30
+ discover_cluster if defaults[:discover]
18
31
  end
19
32
 
20
33
  def http_request(method, path, query, body, params = nil)
@@ -27,44 +40,46 @@ module Elastics
27
40
  retry
28
41
  end
29
42
 
30
- # TODO: check Enumerable#cycle for thread-safety and use it if possible
31
- def next_cluster_host
32
- if @resurrect_at
33
- time = Time.now.to_i
34
- resurrect_cluster(time) if @resurrect_at <= time
35
- end
36
- host_n = @current_host_n
37
- loop do
38
- host = @hosts[host_n]
39
- if !host
40
- raise NoAliveHosts if host_n == 0
41
- host_n = 0
42
- else
43
- @current_host_n = host_n + 1
44
- return host
43
+ # Very simple implementation. It may skip some hosts on current cycle
44
+ # when other is marked as dead. This should not be a problem.
45
+ # TODO: check Enumerable#cycle for thread-safety and use it if possible
46
+ def next_cluster_host
47
+ if @resurrect_at
48
+ time = Time.now.to_i
49
+ resurrect_cluster(time) if @resurrect_at <= time
50
+ end
51
+ host_n = @current_host_n
52
+ loop do
53
+ host = @hosts[host_n]
54
+ if !host
55
+ raise NoAliveHosts if host_n == 0
56
+ host_n = 0
57
+ else
58
+ @current_host_n = host_n + 1
59
+ return host
60
+ end
45
61
  end
46
62
  end
47
- end
48
63
 
49
- def resurrect_cluster(time = Time.now.to_i)
50
- @cluster_mutex.synchronize do
51
- @dead_hosts.delete_if do |host, resurrect_at|
52
- # skip the rest because values are sorted
53
- if time < resurrect_at
54
- @resurrect_at = resurrect_at
55
- break
64
+ def resurrect_cluster(time = Time.now.to_i)
65
+ @cluster_mutex.synchronize do
66
+ @dead_hosts.delete_if do |host, resurrect_at|
67
+ # skip the rest because values are sorted
68
+ if time < resurrect_at
69
+ @resurrect_at = resurrect_at
70
+ break
71
+ end
72
+ @hosts << host
56
73
  end
57
- @hosts << host
58
74
  end
59
75
  end
60
- end
61
76
 
62
- def add_dead_host(host, resurrect_at = nil)
63
- resurrect_at ||= Time.now.to_i + @resurrect_timeout
64
- @hosts.delete(host)
65
- @dead_hosts[host] = resurrect_at
66
- @resurrect_at ||= resurrect_at
67
- end
77
+ def add_dead_host(host, resurrect_at = nil)
78
+ resurrect_at ||= Time.now.to_i + @resurrect_timeout
79
+ @hosts.delete(host)
80
+ @dead_hosts[host] = resurrect_at
81
+ @resurrect_at ||= resurrect_at
82
+ end
68
83
  end
69
84
  end
70
85
  end
@@ -0,0 +1,38 @@
1
+ module Elastics
2
+ module Instrumentation
3
+ autoload :ActiveSupport, 'elastics/instrumentation/active_support'
4
+
5
+ PRETTIFIERS = {
6
+ ap: ->(str) { prettify_json(str, &:awesome_inspect) },
7
+ pp: ->(str) { prettify_json(str, &:pretty_inspect) },
8
+ true => ->(str) { prettify_json(str, &JSON.method(:pretty_generate)) },
9
+ }
10
+
11
+ class << self
12
+ def body_prettifier=(value)
13
+ @body_prettifier = case value
14
+ when Proc, nil, false then value
15
+ else PRETTIFIERS[value] or raise 'Invalid prettifier'
16
+ end
17
+ end
18
+
19
+ def prettify_body(str)
20
+ if @body_prettifier
21
+ @body_prettifier.call(str)
22
+ else
23
+ str
24
+ end
25
+ end
26
+
27
+ def prettify_json(str, &block)
28
+ data = [JSON.parse(str)] rescue nil
29
+ data ||= str.split("\n").map { |x| JSON.parse(x) } rescue nil
30
+ if data
31
+ data.map(&block).join("\n")
32
+ else
33
+ str
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,70 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'active_support/log_subscriber'
3
+ require 'active_support/notifications'
4
+
5
+ module Elastics
6
+ module Instrumentation
7
+ module ActiveSupport
8
+ class << self
9
+ def install
10
+ if Client.respond_to?(:prepend)
11
+ Client.prepend self
12
+ else
13
+ Client.send :include, Ruby19Fallback
14
+ end
15
+ LogSubscriber.attach_to :elastics
16
+ end
17
+ end
18
+
19
+ def http_request(*args)
20
+ ::ActiveSupport::Notifications.instrument 'http_request.elastics', args: args do
21
+ super
22
+ end
23
+ end
24
+
25
+ # old rubies support
26
+ module Ruby19Fallback
27
+ def self.included(base)
28
+ base.alias_method_chain :http_request, :as_instrumentation
29
+ end
30
+
31
+ def http_request_with_as_instrumentation(*args)
32
+ ::ActiveSupport::Notifications.instrument 'http_request.elastics', args: args do
33
+ http_request_without_as_instrumentation(*args)
34
+ end
35
+ end
36
+ end
37
+
38
+ class LogSubscriber < ::ActiveSupport::LogSubscriber
39
+ def http_request(event)
40
+ return unless logger.debug?
41
+
42
+ payload = event.payload[:args]
43
+ method, path, query, body, params = payload
44
+ path = '/' if path.blank?
45
+ path << "?#{query.to_param}" if query.present?
46
+ model = params[:model]
47
+
48
+ name = ""
49
+ name << "#{model.name} " if model
50
+ name << "elastics (#{event.duration.round(1)}ms)"
51
+ request = "#{method.to_s.upcase} #{path}"
52
+ request << " #{Instrumentation.prettify_body(body)}" if body.present?
53
+
54
+ if odd?
55
+ name = color(name, ::ActiveSupport::LogSubscriber::CYAN, true)
56
+ request = color(request, nil, true)
57
+ else
58
+ name = color(name, ::ActiveSupport::LogSubscriber::MAGENTA, true)
59
+ end
60
+
61
+ debug " #{name} #{request}"
62
+ end
63
+
64
+ def odd?
65
+ @odd = !@odd
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,18 @@
1
+ module Elastics
2
+ module Model
3
+ autoload :Connection, 'elastics/model/connection'
4
+ autoload :HelperMethods, 'elastics/model/helper_methods'
5
+ autoload :Schema, 'elastics/model/schema'
6
+ autoload :Skipping, 'elastics/model/skipping'
7
+
8
+ require 'elastics/model/tracking'
9
+
10
+ def self.included(base)
11
+ base.extend Connection
12
+ base.extend Schema
13
+ base.extend Tracking
14
+ base.send :include, HelperMethods
15
+ base.send :include, Skipping
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module Elastics
2
+ module Model
3
+ module Connection
4
+ attr_accessor :elastics_config
5
+
6
+ def elastics
7
+ @elastics ||= Client.new elastics_config.slice(:host)
8
+ end
9
+
10
+ # Don't memoize to GC it after initialization
11
+ def elastics_version_manager
12
+ VersionManager.new(elastics, elastics_config.slice(
13
+ :service_index,
14
+ :index_prefix,
15
+ ))
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,67 @@
1
+ module Elastics
2
+ module Model
3
+ module HelperMethods
4
+ def self.included(base)
5
+ base.class_eval do
6
+ extend ClassMethods
7
+
8
+ # don't override to_elastics method, if it already exists
9
+ if !instance_methods.include?(:to_elastics) && instance_methods.include?(:as_json)
10
+ alias_method :to_elastics, :as_json
11
+ end
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def elastics_params
17
+ {
18
+ index: elastics_index_name,
19
+ type: elastics_type_name,
20
+ model: self,
21
+ }
22
+ end
23
+
24
+ def request_elastics(params)
25
+ elastics.request(elastics_params.merge!(params))
26
+ end
27
+
28
+ def bulk_elastics(params = {}, &block)
29
+ elastics.bulk(elastics_params.merge!(params), &block)
30
+ end
31
+
32
+ def refresh_elastics
33
+ request_elastics(method: :post, type: nil, id: :_refresh)
34
+ end
35
+
36
+ def index_batch_elastics(batch)
37
+ bulk_elastics do |bulk|
38
+ batch.each do |record|
39
+ bulk.index record.id, record.to_elastics
40
+ end
41
+ end
42
+ end
43
+
44
+ def reindex_elastics(options = {})
45
+ raise 'Not implemented'
46
+ end
47
+ end
48
+
49
+ def index_elastics
50
+ self.class.request_elastics(method: :post, id: id, body: to_elastics)
51
+ end
52
+
53
+ def update_elastics(data)
54
+ self.class.request_elastics(method: :post, id: "#{id}/_update", body: data)
55
+ end
56
+
57
+ def update_elastics_doc(fields)
58
+ update_elastics(doc: fields)
59
+ end
60
+
61
+ def delete_elastics
62
+ self.class.request_elastics(method: :delete, id: id)
63
+ rescue NotFound
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,32 @@
1
+ module Elastics
2
+ module Model
3
+ module Schema
4
+ attr_writer :elastics_index_base, :elastics_type_name
5
+
6
+ def elastics_index_name
7
+ reset_elastics_index_name unless defined?(@elastics_index_name)
8
+ @elastics_index_name
9
+ end
10
+
11
+ def elastics_type_name
12
+ @elastics_type_name ||= name.split('::').last.downcase
13
+ end
14
+
15
+ def reset_elastics_index_name
16
+ @elastics_index_name = if superclass.respond_to?(:elastics_index_name)
17
+ superclass.elastics_index_name
18
+ else
19
+ compute_elastics_index_name
20
+ end
21
+ end
22
+
23
+ def compute_elastics_index_name
24
+ elastics_version_manager.index_name(elastics_index_base)
25
+ end
26
+
27
+ def elastics_index_base
28
+ @elastics_index_base || elastics_config[:index] || elastics_type_name
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ module Elastics
2
+ module Model
3
+ module Skipping
4
+ class << self
5
+ def models
6
+ Thread.current[:elastics_skip_models] ||= {}
7
+ end
8
+
9
+ def included(base)
10
+ base.extend self
11
+ base.extend ClassMethods
12
+ base.send :include, InstanceMethods
13
+ end
14
+ end
15
+
16
+ def skip_elastics
17
+ self.skip_elastics = true
18
+ yield
19
+ ensure
20
+ self.skip_elastics = false
21
+ end
22
+
23
+ module ClassMethods
24
+ def skip_elastics?
25
+ Skipping.models[self]
26
+ end
27
+
28
+ def skip_elastics=(val)
29
+ Skipping.models[self] = val
30
+ end
31
+ end
32
+
33
+ module InstanceMethods
34
+ attr_writer :skip_elastics
35
+
36
+ def skip_elastics?
37
+ @skip_elastics || self.class.skip_elastics?
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ module Elastics
2
+ module Model
3
+ class << self
4
+ attr_reader :list
5
+
6
+ def reset_list
7
+ @list = []
8
+ end
9
+
10
+ def track(model)
11
+ if !model.respond_to?(:track_elastics_model?) || model.track_elastics_model?
12
+ list << model
13
+ end
14
+ end
15
+ end
16
+
17
+ reset_list
18
+
19
+ module Tracking
20
+ def self.extended(base)
21
+ Model.track(base)
22
+ end
23
+
24
+ def inherited(base)
25
+ super.tap { Model.track(base) }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -11,7 +11,7 @@ module Elastics
11
11
  end
12
12
 
13
13
  config.to_prepare do
14
- Elastics.reset_models
14
+ Model.reset_list
15
15
  end
16
16
  end
17
17
  end
@@ -10,7 +10,7 @@ module Elastics
10
10
  end
11
11
 
12
12
  def client
13
- @client ||= Client.new config.slice(:host, :port)
13
+ @client ||= Client.new config.slice(:host)
14
14
  end
15
15
 
16
16
  def client=(val)
@@ -41,7 +41,7 @@ module Elastics
41
41
  def models_to_reindex(options = {})
42
42
  indices = options[:indices].try!(:map, &:to_s)
43
43
  types = options[:types].try!(:map, &:to_s)
44
- models = Elastics.models.select do |model|
44
+ models = Model.list.select do |model|
45
45
  next if indices && !indices.include?(model.elastics_index_base)
46
46
  next if types && !types.include?(model.elastics_type_name)
47
47
  true
@@ -1,5 +1,5 @@
1
1
  module Elastics
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
 
4
4
  def self.gem_version
5
5
  Gem::Version.new VERSION
@@ -8,7 +8,7 @@ module Elastics
8
8
 
9
9
  def default_version=(version)
10
10
  @default_version = version
11
- Elastics.models.each &:reset_elastics_index_name
11
+ Model.list.each &:reset_elastics_index_name
12
12
  end
13
13
 
14
14
  def use_version(version)
@@ -21,14 +21,14 @@ describe Elastics::AutoRefresh do
21
21
  it 'invokes refresh after each request' do
22
22
  expect(client).to receive(:refresh).exactly(3).times
23
23
  client.post id: 1
24
- client.put_mapping type: {fields: {}}
24
+ client.put_mapping type: :test, body: {fields: {}}
25
25
  client.delete id: 2
26
26
  end
27
27
 
28
28
  it 'invokes refresh after each request, not wrapped in .disable! block' do
29
29
  expect(client).to receive(:refresh).exactly(2).times
30
30
  client.post id: 1
31
- described_class.disable! { client.put_mapping type: {fields: {}} }
31
+ described_class.disable! { client.put_mapping type: :test, body: {fields: {}} }
32
32
  client.delete id: 2
33
33
  end
34
34
  end
@@ -105,4 +105,63 @@ describe Elastics::Client do
105
105
  it { should raise_error(Elastics::Client::Cluster::NoAliveHosts) }
106
106
  end
107
107
  end
108
+
109
+ describe '#discover_cluster' do
110
+ let(:instance) { described_class.new(host: initial_host, discover: true).tap { |x|
111
+ x.singleton_class.send :attr_reader, :hosts
112
+ } }
113
+
114
+ let(:initial_host) { ['10.0.0.1:9200', '10.0.0.2:9200', '10.0.0.3:9200']}
115
+
116
+ let(:successful_response) do
117
+ {"cluster_name"=>"elasticsearch_max",
118
+ "nodes"=>
119
+ {"one"=>{"http_address"=>"inet[/10.0.0.2:9200]"},
120
+ "other"=>{"http_address"=>"inet[/10.0.0.1:9200]"}}}
121
+ end
122
+
123
+ context 'when nodes discover request fails' do
124
+ before do
125
+ expect_any_instance_of(described_class).to receive(:request) { raise Elastics::Error }
126
+ end
127
+
128
+ it 'fails on initialization' do
129
+ expect { instance }.to raise_error(Elastics::Error)
130
+ end
131
+ end
132
+
133
+ context 'when request is performed on first node' do
134
+ before do
135
+ expect_any_instance_of(described_class).to receive(:request) { successful_response }
136
+ end
137
+
138
+ it 'overwrites settings' do
139
+ expect(instance.hosts).to eq ['10.0.0.2:9200', '10.0.0.1:9200']
140
+ end
141
+ end
142
+
143
+ context 'when first is not available' do
144
+ before do
145
+ client = Object.new
146
+
147
+ expect(client).to receive(:request).
148
+ with(:get, "http://#{initial_host[0]}/_nodes/_all/nothing", nil, nil, Elastics::Client::HEADERS) do
149
+ raise Timeout::Error
150
+ end
151
+
152
+ expect(client).to receive(:request).
153
+ with(:get, "http://#{initial_host[2]}/_nodes/_all/nothing", nil, nil, Elastics::Client::HEADERS) do
154
+ OpenStruct.new(status: 200, body: successful_response.to_json)
155
+ end
156
+
157
+ allow_any_instance_of(described_class).to receive(:client) { client }
158
+ end
159
+
160
+ it 'keeps first node in @dead_hosts' do
161
+ expect(instance.hosts).to eq ['10.0.0.2:9200']
162
+ instance.send(:resurrect_cluster, 1.minute.from_now.to_i)
163
+ expect(instance.hosts).to eq ['10.0.0.2:9200', '10.0.0.1:9200']
164
+ end
165
+ end
166
+ end
108
167
  end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elastics::Instrumentation do
4
+ describe '.prettify_json' do
5
+ context 'when string is json' do
6
+ it 'parses it and yields hash into block' do
7
+ result = described_class.prettify_json('{"a":1}') { |x| x.keys[0] }
8
+ expect(result).to eq 'a'
9
+ end
10
+ end
11
+
12
+ context 'when string is multiple jsons joined with new line (bulk request)' do
13
+ it 'parses it into array and yields hashes into block one by one' do
14
+ result = described_class.prettify_json("{\"a\":1}\n{\"b\":2}") do |x|
15
+ x.keys[0]
16
+ end
17
+ expect(result).to eq "a\nb"
18
+ end
19
+ end
20
+
21
+ context 'when string is not json' do
22
+ it 'returns original string' do
23
+ [
24
+ "{\"a\":1",
25
+ "{\"a\":1}\n{b\":2}",
26
+ "{\"a\":1}\n{\"b\":2}\n{",
27
+ 'not json'
28
+ ].each do |str|
29
+ expect(described_class.prettify_json(str) { raise }).to eq(str)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '.prettify_body' do
36
+ context 'when body_prettifier is true' do
37
+ before { described_class.body_prettifier = true }
38
+
39
+ it 'uses JSON.pretty_generate to prettify' do
40
+ expect(JSON).to receive(:pretty_generate).with('a' => 1).and_return(1)
41
+ expect(JSON).to receive(:pretty_generate).with('b' => 2).and_return(2)
42
+ expect(described_class.prettify_body("{\"a\":1}\n{\"b\":2}")).to eq "1\n2"
43
+ end
44
+ end
45
+ end
46
+
47
+ describe '.body_prettifier=' do
48
+ context 'when new value is false/nil, Proc, or valid prettifier id' do
49
+ it 'accepts it' do
50
+ [
51
+ :ap,
52
+ true,
53
+ ->(str) { str * 2 },
54
+ false,
55
+ ].each { |value| described_class.body_prettifier = value }
56
+ end
57
+ end
58
+
59
+ context 'when new value is invalid' do
60
+ it 'raises error' do
61
+ [
62
+ 1,
63
+ :invalid,
64
+ Object.new,
65
+ ].each do |value|
66
+ expect { described_class.body_prettifier = value }.to raise_error
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elastics::Model::Schema do
4
+ let(:model) { Class.new.tap { |x| x.extend described_class } }
5
+
6
+ describe '#elastics_type_name' do
7
+ subject { model.elastics_type_name }
8
+
9
+ context 'when type was manually set' do
10
+ before { model.elastics_type_name = :test }
11
+ it { should be :test }
12
+ end
13
+
14
+ context 'when class name does not include modules' do
15
+ before { expect(model).to receive(:name) { 'Test' } }
16
+ it { should eq 'test' }
17
+ end
18
+
19
+ context 'when class name includes modules' do
20
+ before { expect(model).to receive(:name) { 'Module::Other::Index' } }
21
+ it { should eq 'index' }
22
+ end
23
+ end
24
+ end
@@ -1,5 +1,6 @@
1
1
  require 'bundler/setup'
2
2
  Bundler.setup
3
+ require 'pry'
3
4
 
4
5
  require 'elastics'
5
6
  require 'active_support/all'
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Melentiev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-11 00:00:00.000000000 Z
11
+ date: 2014-12-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -111,7 +111,6 @@ files:
111
111
  - lib/elastics.rb
112
112
  - lib/elastics/active_record.rb
113
113
  - lib/elastics/active_record/helper_methods.rb
114
- - lib/elastics/active_record/instrumentation.rb
115
114
  - lib/elastics/active_record/model_schema.rb
116
115
  - lib/elastics/active_record/search_result.rb
117
116
  - lib/elastics/active_record/tasks_config.rb
@@ -120,6 +119,14 @@ files:
120
119
  - lib/elastics/client.rb
121
120
  - lib/elastics/client/bulk.rb
122
121
  - lib/elastics/client/cluster.rb
122
+ - lib/elastics/instrumentation.rb
123
+ - lib/elastics/instrumentation/active_support.rb
124
+ - lib/elastics/model.rb
125
+ - lib/elastics/model/connection.rb
126
+ - lib/elastics/model/helper_methods.rb
127
+ - lib/elastics/model/schema.rb
128
+ - lib/elastics/model/skipping.rb
129
+ - lib/elastics/model/tracking.rb
123
130
  - lib/elastics/query_helper.rb
124
131
  - lib/elastics/railtie.rb
125
132
  - lib/elastics/result.rb
@@ -136,6 +143,8 @@ files:
136
143
  - spec/lib/elastics/client/bulk_spec.rb
137
144
  - spec/lib/elastics/client/cluster_spec.rb
138
145
  - spec/lib/elastics/client_spec.rb
146
+ - spec/lib/elastics/instrumentation_spec.rb
147
+ - spec/lib/elastics/model/schema_spec.rb
139
148
  - spec/spec_helper.rb
140
149
  homepage: http://github.com/printercu/elastics-rb
141
150
  licenses:
@@ -166,4 +175,7 @@ test_files:
166
175
  - spec/lib/elastics/client/bulk_spec.rb
167
176
  - spec/lib/elastics/client/cluster_spec.rb
168
177
  - spec/lib/elastics/client_spec.rb
178
+ - spec/lib/elastics/instrumentation_spec.rb
179
+ - spec/lib/elastics/model/schema_spec.rb
169
180
  - spec/spec_helper.rb
181
+ has_rdoc:
@@ -1,71 +0,0 @@
1
- module Elastics
2
- module ActiveRecord
3
- # To be included in `Elastics::Client`
4
- module Instrumentation
5
- class << self
6
- def install
7
- if Client.respond_to?(:prepend)
8
- Client.prepend self
9
- else
10
- Client.send :include, Fallback
11
- end
12
- unless ::ActiveRecord::LogSubscriber < LogSubscriber
13
- ::ActiveRecord::LogSubscriber.send :include, LogSubscriber
14
- end
15
- end
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
69
- end
70
- end
71
- end