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.
@@ -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: