elastics 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,11 +7,17 @@ module Elastics
7
7
  @mappings_paths ||= base_paths.map { |x| File.join x, 'mappings' }
8
8
  end
9
9
 
10
- def put_mappings
11
- mappings.each do |type, mapping|
10
+ # Mappings to put can be filtered with `:indices` & `:types` arrays.
11
+ def put_mappings(options = {})
12
+ version = options.fetch :version, :current
13
+ filter = options[:indices].try!(:map, &:to_s)
14
+ each_filtered(types, options[:types]) do |type|
12
15
  index = index_for_type(type)
13
- log "Put mapping #{index}/#{type}"
14
- client.put_mapping index: index, type: type, data: mapping
16
+ next if filter && !filter.include?(index)
17
+ versioned_index = versioned_index_name(index, version)
18
+ log "Putting mapping #{index}/#{type} (#{versioned_index}/#{type})"
19
+ client.put_mapping index: versioned_index, type: type,
20
+ data: mappings[type]
15
21
  end
16
22
  end
17
23
 
@@ -33,7 +39,7 @@ module Elastics
33
39
  end
34
40
 
35
41
  def index_for_type(type)
36
- config[:index] || "#{config[:index_prefix]}#{type}"
42
+ config[:index] || type
37
43
  end
38
44
  end
39
45
  end
@@ -0,0 +1,42 @@
1
+ module Elastics
2
+ module Tasks
3
+ module Migrations
4
+ def migrate(options = {})
5
+ create_indices(options)
6
+ put_mappings(options)
7
+ end
8
+
9
+ def migrate!(options = {})
10
+ options_next = options.merge version: :next
11
+ drop_indices(options_next)
12
+ create_indices(options_next)
13
+ put_mappings(options_next)
14
+ reindex(options_next) if options.fetch(:reindex, true)
15
+ forward_aliases(options)
16
+ end
17
+
18
+ def reindex(options = {})
19
+ version = options.fetch(:version, :current)
20
+ Rails.application.eager_load! if defined?(Rails)
21
+ VersionManager.use_version version do
22
+ models_to_reindex(options).each do |model|
23
+ log "Reindexing #{model.elastics_index_base} into " \
24
+ "#{model.elastics_index_name}/#{model.elastics_type_name}"
25
+ model.reindex_elastics
26
+ end
27
+ end
28
+ end
29
+
30
+ def models_to_reindex(options = {})
31
+ indices = options[:indices].try!(:map, &:to_s)
32
+ types = options[:types].try!(:map, &:to_s)
33
+ models = Elastics.models.select do |model|
34
+ next if indices && !indices.include?(model.elastics_index_base)
35
+ next if types && !types.include?(model.elastics_type_name)
36
+ true
37
+ end
38
+ models.reject { |model| models.find { |other| model < other } }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,5 +1,5 @@
1
1
  module Elastics
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2.0'
3
3
 
4
4
  def self.gem_version
5
5
  Gem::Version.new VERSION
@@ -0,0 +1,86 @@
1
+ module Elastics
2
+ class VersionManager
3
+ class << self
4
+ def default_version
5
+ @default_version = ENV['ELASTICS_MAPPING_VERSION'] unless defined?(@default_version)
6
+ @default_version
7
+ end
8
+
9
+ def default_version=(version)
10
+ @default_version = version
11
+ Elastics.models.each &:reset_elastics_index_name
12
+ end
13
+
14
+ def use_version(version)
15
+ old_version = default_version
16
+ self.default_version = version
17
+ yield
18
+ ensure
19
+ self.default_version = old_version
20
+ end
21
+ end
22
+
23
+ attr_reader :service_index
24
+
25
+ def initialize(client, options = {})
26
+ @service_index = options[:service_index] || '.elastics'
27
+ @index_prefix = options[:index_prefix]
28
+ @client = client
29
+ end
30
+
31
+ def reset
32
+ @versions = nil
33
+ self
34
+ end
35
+
36
+ def update(index, data)
37
+ set index, versions[index].merge(data)
38
+ end
39
+
40
+ def set(index, data)
41
+ @client.post index: @service_index, type: :mapping_versions,
42
+ id: prefixed_index(index), data: data
43
+ @versions[index] = data.with_indifferent_access
44
+ end
45
+
46
+ def versions
47
+ @versions ||= Hash.new do |hash, index|
48
+ result = @client.get index: @service_index, type: :mapping_versions,
49
+ id: prefixed_index(index)
50
+ if result
51
+ hash[index] = result['_source'].with_indifferent_access
52
+ else
53
+ set index, current: 0
54
+ end
55
+ end
56
+ end
57
+
58
+ def current_version(index)
59
+ versions[index][:current]
60
+ end
61
+
62
+ def next_version(index)
63
+ current_version(index) + 1
64
+ end
65
+
66
+ def version_number(index, version)
67
+ case version.to_s
68
+ when 'current' then current_version(index)
69
+ when 'next' then next_version(index)
70
+ else raise NameError, "Invalid version alias: #{version}"
71
+ end
72
+ end
73
+
74
+ def prefixed_index(index)
75
+ "#{@index_prefix}#{index}"
76
+ end
77
+
78
+ def index_name(index, version = self.class.default_version)
79
+ if version && version != :alias
80
+ "#{prefixed_index(index)}-v#{version_number index, version}"
81
+ else
82
+ prefixed_index(index)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,20 +1,43 @@
1
1
  namespace 'elastics' do
2
- task load_config: [:environment, 'db:load_config'] do
2
+ task :load_config do |task, args|
3
+ [:environment, 'db:load_config'].each do |dep|
4
+ Rake::Task[dep].invoke if Rake::Task.task_defined?(dep)
5
+ end
6
+ @elastics_options = {
7
+ version: ENV['version'] || :current,
8
+ reindex: !ENV.key?('no_reindex'),
9
+ drop: !ENV.key?('no_drop'),
10
+ types: ENV['types'] && ENV['types'].split(',').map(&:strip),
11
+ indices: args.extras.empty? ? nil : args.extras
12
+ }
3
13
  end
4
14
 
5
- desc 'Creates indices and applies mappings (Use NOFLUSH to prevent old indices from deletion)'
6
- task migrate: :load_config do
7
- flush = !ENV.key?('NOFLUSH')
8
- Elastics::Tasks.migrate(flush: flush)
15
+ desc 'Drop administrative index'
16
+ task :purge, [:keep_data] => :load_config do |task, args|
17
+ Elastics::Tasks.purge args[:keep_data]
9
18
  end
10
19
 
11
20
  desc 'Creates indices'
12
21
  task create: :load_config do
13
- Elastics::Tasks.create_indices
22
+ Elastics::Tasks.create_indices @elastics_options
14
23
  end
15
24
 
16
25
  desc 'Drops indices'
17
- task delete: :load_config do
18
- Elastics::Tasks.delete_indices
26
+ task drop: :load_config do
27
+ Elastics::Tasks.drop_indices @elastics_options
28
+ end
29
+
30
+ desc 'Creates indices and applies mappings. Full migration when param is present'
31
+ task migrate: :load_config do
32
+ if ENV['full']
33
+ Elastics::Tasks.migrate! @elastics_options
34
+ else
35
+ Elastics::Tasks.migrate @elastics_options
36
+ end
37
+ end
38
+
39
+ desc 'Reindex'
40
+ task reindex: :load_config do
41
+ Elastics::Tasks.reindex @elastics_options
19
42
  end
20
43
  end
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elastics::Client do
4
+ let(:client) { described_class.new(config) }
5
+ let(:config) { {
6
+ host: hosts,
7
+ resurrect_timeout: resurrect_timeout,
8
+ } }
9
+ let(:hosts) { [:one, :two, :three] }
10
+ let(:resurrect_timeout) { 5 }
11
+
12
+ describe '#next_cluster_host' do
13
+ subject { -> { client.send(:next_cluster_host) } }
14
+ context 'when @hosts contains one element' do
15
+ let(:hosts) { [:one] }
16
+
17
+ it 'returns this host on each call' do
18
+ expect(6.times.map { subject.call }).to eq([:one] * 6)
19
+ end
20
+ end
21
+
22
+ context 'when @hosts contains multiple elements' do
23
+ it 'returns host using round-robbin' do
24
+ expect(6.times.map { subject.call }).to eq(hosts.cycle.take(6))
25
+ end
26
+
27
+ context 'and one host is dead' do
28
+ before { client.send(:add_dead_host, :one, resurrect_at) }
29
+ let(:resurrect_at) {}
30
+
31
+ it 'returns other hosts using round-robbin' do
32
+ expect(6.times.map { subject.call }).to eq((hosts - [:one]).cycle.take(6))
33
+ end
34
+
35
+ context 'but should be resurrected' do
36
+ let(:resurrect_at) { 0 }
37
+
38
+ it 'resurrects this host' do
39
+ expect(6.times.map { subject.call }).to eq(hosts.cycle.take(7).drop(1))
40
+ end
41
+ end
42
+ end
43
+
44
+ context 'and all hosts are dead' do
45
+ before { hosts.each { |host| client.send(:add_dead_host, host) } }
46
+ it { should raise_error(Elastics::Client::Cluster::NoAliveHosts) }
47
+ end
48
+ end
49
+ end
50
+
51
+ describe '#resurrect_cluster' do
52
+ subject { -> { client.send(:resurrect_cluster) } }
53
+ context 'when multiple hosts are blocked' do
54
+ before { hosts.each { |host| client.send(:add_dead_host, host) } }
55
+
56
+ it { should_not change { client.instance_variable_get(:@hosts) }.from([]) }
57
+
58
+ context 'and can be resurrected' do
59
+ before { expect(Time).to receive(:now).and_return(resurrect_timeout.seconds.from_now) }
60
+ it { should change { client.instance_variable_get(:@hosts) }.from([]).to(hosts) }
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '#http_request' do
66
+ subject { -> { client.send :http_request, :method, '/path', :query, :body } }
67
+
68
+ context 'when all hosts are alive' do
69
+ it 'uses hosts using round-robbin' do
70
+ hosts.cycle.take(6).each do |host|
71
+ expect(client.client).to receive(:request).
72
+ with(:method, "http://#{host}/path", :query, :body, Elastics::Client::HEADERS)
73
+ subject.call
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'when one host is unreachable' do
79
+ before do
80
+ expect(client.client).to receive(:request).
81
+ with(:method, "http://one/path", :query, :body, Elastics::Client::HEADERS) do
82
+ raise Timeout::Error
83
+ end
84
+ end
85
+
86
+ it 'uses hosts using round-robbin' do
87
+ (hosts - [:one]).cycle.take(6).each do |host|
88
+ expect(client.client).to receive(:request).
89
+ with(:method, "http://#{host}/path", :query, :body, Elastics::Client::HEADERS)
90
+ end
91
+ 6.times { subject.call }
92
+ end
93
+ end
94
+
95
+ context 'when all hosts are unreachable' do
96
+ before do
97
+ hosts.each do |host|
98
+ expect(client.client).to receive(:request).
99
+ with(:method, "http://#{host}/path", :query, :body, Elastics::Client::HEADERS) do
100
+ raise Timeout::Error
101
+ end
102
+ end
103
+ end
104
+
105
+ it { should raise_error(Elastics::Client::Cluster::NoAliveHosts) }
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,9 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'elastics'
5
+ require 'active_support/all'
6
+
7
+ RSpec.configure do |config|
8
+ #
9
+ end
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.1.1
4
+ version: 0.2.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-09-30 00:00:00.000000000 Z
11
+ date: 2014-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -44,14 +44,56 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10'
47
+ version: '10.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '10'
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.1.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.1.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: thread_safe
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.3.4
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.3.4
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 4.1.6
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 4.1.6
55
97
  description: Lightweight and extensible elasticsearch client
56
98
  email:
57
99
  - melentievm@gmail.com
@@ -60,6 +102,8 @@ extensions: []
60
102
  extra_rdoc_files: []
61
103
  files:
62
104
  - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
63
107
  - Gemfile
64
108
  - README.md
65
109
  - Rakefile
@@ -68,16 +112,24 @@ files:
68
112
  - lib/elastics/active_record.rb
69
113
  - lib/elastics/active_record/helper_methods.rb
70
114
  - lib/elastics/active_record/instrumentation.rb
71
- - lib/elastics/active_record/log_subscriber.rb
72
115
  - lib/elastics/active_record/model_schema.rb
116
+ - lib/elastics/active_record/search_result.rb
117
+ - lib/elastics/active_record/tasks_config.rb
118
+ - lib/elastics/capistrano.rb
73
119
  - lib/elastics/client.rb
120
+ - lib/elastics/client/cluster.rb
74
121
  - lib/elastics/query_helper.rb
75
122
  - lib/elastics/railtie.rb
76
123
  - lib/elastics/tasks.rb
124
+ - lib/elastics/tasks/config.rb
77
125
  - lib/elastics/tasks/indices.rb
78
126
  - lib/elastics/tasks/mappings.rb
127
+ - lib/elastics/tasks/migrations.rb
79
128
  - lib/elastics/version.rb
129
+ - lib/elastics/version_manager.rb
80
130
  - lib/tasks/elastics.rake
131
+ - spec/lib/elastics/client/cluster_spec.rb
132
+ - spec/spec_helper.rb
81
133
  homepage: http://github.com/printercu/elastics-rb
82
134
  licenses:
83
135
  - MIT
@@ -98,8 +150,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
150
  version: '0'
99
151
  requirements: []
100
152
  rubyforge_project:
101
- rubygems_version: 2.2.2
153
+ rubygems_version: 2.4.2
102
154
  signing_key:
103
155
  specification_version: 4
104
156
  summary: ElasticSearch client with ActiveRecord integration
105
- test_files: []
157
+ test_files:
158
+ - spec/lib/elastics/client/cluster_spec.rb
159
+ - spec/spec_helper.rb
160
+ has_rdoc: