elastic-rails 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,7 +18,7 @@ module Elastic::Nodes
18
18
  @must_nots = []
19
19
  @shoulds = []
20
20
  @filters = []
21
- @disable_coord = !Elastic::Configuration.coord_similarity
21
+ @disable_coord = !Elastic.config.coord_similarity
22
22
  end
23
23
 
24
24
  def must(_node)
data/lib/elastic/query.rb CHANGED
@@ -86,7 +86,7 @@ module Elastic
86
86
  end
87
87
 
88
88
  def execute(_query)
89
- raw = @index.adaptor.query(
89
+ raw = @index.connector.query(
90
90
  type: @index.definition.types,
91
91
  query: _query.render
92
92
  )
@@ -4,13 +4,16 @@ require "elastic/railties/ar_middleware"
4
4
  require "elastic/railties/configuration_extensions"
5
5
  require "elastic/railties/type_extensions"
6
6
  require "elastic/railties/query_extensions"
7
- require "elastic/railties/indexing_job"
7
+ # disabled for now: require "elastic/railties/indexing_job"
8
8
  require "elastic/railties/indexable_record"
9
9
 
10
10
  module Elastic
11
11
  class Railtie < Rails::Railtie
12
12
  initializer "elastic.configure_rails_initialization" do
13
- Elastic.configure Rails.application.config_for(:elastic).merge(logger: Rails.logger)
13
+ Elastic.configure Rails.application.config_for(:elastic).merge(
14
+ time_zone: Rails.application.config.time_zone,
15
+ logger: Rails.logger
16
+ )
14
17
 
15
18
  # Make every activerecord model indexable
16
19
  ActiveRecord::Base.send(:include, Elastic::Railties::IndexableRecord)
@@ -30,7 +33,7 @@ module Elastic
30
33
  end
31
34
 
32
35
  # Add activerecord related configuration parameters
33
- module Elastic::Configuration
36
+ class Elastic::Configuration
34
37
  include Elastic::Railties::ConfigurationExtensions
35
38
  end
36
39
 
@@ -1,13 +1,10 @@
1
1
  module Elastic::Railties
2
2
  module ConfigurationExtensions
3
3
  def self.included(_klass)
4
- _klass.extend ClassMethods
4
+ _klass::DEFAULTS[:active_job_queue] = :default
5
+ _klass::DEFAULTS[:indices_path] = 'app/indices'
5
6
  end
6
7
 
7
- module ClassMethods
8
- def active_job_queue
9
- config[:active_job_queue] || :default
10
- end
11
- end
8
+ attr_accessor :active_job_queue, :indices_path
12
9
  end
13
10
  end
@@ -18,21 +18,31 @@ module Elastic::Railties
18
18
  @constantized_index_class ||= index_class.constantize
19
19
  end
20
20
 
21
- def index(_options)
22
- on = _options.delete(:on)
21
+ def index(on: nil, unindex: true, delayed: false)
22
+ raise NotImplementedError, 'delayed indexing not implemented' if delayed
23
+
23
24
  if on == :create
24
- index_on_create _options
25
+ index_on_create
25
26
  elsif on == :save
26
- index_on_save _options
27
+ index_on_save
28
+ else
29
+ raise ArgumentError, 'must provide an indexing target when calling index \
30
+ (ie: `index on: :save`)'
27
31
  end
32
+
33
+ unindex_on_destroy if unindex
28
34
  end
29
35
 
30
36
  def index_on_create(_options = {})
31
- after_create(_options) { index_later }
37
+ after_create(_options) { index_now }
32
38
  end
33
39
 
34
40
  def index_on_save(_options = {})
35
- after_save(_options) { index_later }
41
+ after_save(_options) { index_now }
42
+ end
43
+
44
+ def unindex_on_destroy(_options = {})
45
+ before_destroy(_options) { unindex_now }
36
46
  end
37
47
  end
38
48
 
@@ -40,8 +50,8 @@ module Elastic::Railties
40
50
  self.class.constantized_index_class.index self
41
51
  end
42
52
 
43
- def index_later
44
- IndexingJob.set(queue: Elastic::Configuration.active_job_queue).perform_later(self)
53
+ def unindex_now
54
+ self.class.constantized_index_class.delete self
45
55
  end
46
56
  end
47
57
  end
@@ -1,19 +1,25 @@
1
1
  namespace :es do
2
2
  desc "Elastic: Updates indices mappings"
3
- task migrate: :environment do
3
+ task :remap, [:index] => :environment do |_, args|
4
4
  Elastic.configure logger: Logger.new(STDOUT)
5
- Elastic.migrate
5
+ Elastic.remap args.index
6
+ end
7
+
8
+ desc "Elastic: Updates indices mappings, rebuilding index if necessary"
9
+ task :migrate, [:index] => :environment do |_, args|
10
+ Elastic.configure logger: Logger.new(STDOUT)
11
+ Elastic.migrate args.index
6
12
  end
7
13
 
8
14
  desc "Elastic: Rebuilds indices from source data"
9
- task reindex: :environment do
15
+ task :reindex, [:index] => :environment do |_, args|
10
16
  Elastic.configure logger: Logger.new(STDOUT)
11
- Elastic.reindex
17
+ Elastic.reindex args.index
12
18
  end
13
19
 
14
20
  desc "Elastic: Lists indices stats"
15
- task stats: :environment do
21
+ task :stats, [:index] => :environment do |_, args|
16
22
  Elastic.configure logger: Logger.new(STDOUT)
17
- Elastic.stats
23
+ Elastic.stats args.index
18
24
  end
19
25
  end
@@ -8,11 +8,23 @@ module Elastic::Railties
8
8
  end
9
9
  end
10
10
 
11
+ def remap(_index = nil)
12
+ logger.info "Remapping all indices" if _index.nil?
13
+ indices(_index).each do |index|
14
+ logger.info "Remapping index #{index.suffix}"
15
+ handle_errors do
16
+ unless index.connector.remap
17
+ logger.info 'Mapping couldnt be changed, make sure you call migrate'
18
+ end
19
+ end
20
+ end
21
+ end
22
+
11
23
  def migrate(_index = nil)
12
24
  logger.info "Migrating all indices" if _index.nil?
13
25
  indices(_index).each do |index|
14
26
  logger.info "Migrating index #{index.suffix}"
15
- handle_errors { index.mapping.migrate }
27
+ handle_errors { index.migrate }
16
28
  end
17
29
  end
18
30
 
@@ -45,7 +57,7 @@ module Elastic::Railties
45
57
  end
46
58
 
47
59
  def indices_paths
48
- Rails.root.join(Elastic::Configuration.indices_path)
60
+ Rails.root.join(Elastic.config.indices_path)
49
61
  end
50
62
 
51
63
  def handle_errors
@@ -56,7 +68,7 @@ module Elastic::Railties
56
68
  end
57
69
 
58
70
  def logger
59
- Elastic::Configuration.logger
71
+ Elastic.logger
60
72
  end
61
73
  end
62
74
  end
data/lib/elastic/type.rb CHANGED
@@ -10,6 +10,10 @@ module Elastic
10
10
  :coord_similarity, :limit, :offset, :pluck, :ids, :total
11
11
  end
12
12
 
13
+ def self.default_suffix
14
+ to_s.underscore
15
+ end
16
+
13
17
  def self.suffix
14
18
  @suffix || default_suffix
15
19
  end
@@ -19,38 +23,49 @@ module Elastic
19
23
  end
20
24
 
21
25
  def self.import_batch_size
22
- @import_batch_size || Configuration.import_batch_size
26
+ @import_batch_size || Elastic.config.import_batch_size
23
27
  end
24
28
 
25
29
  def self.import_batch_size=(_value)
26
30
  @import_batch_size = _value
27
31
  end
28
32
 
29
- def self.adaptor
30
- @adaptor ||= Elastic::Core::Adaptor.new(suffix)
33
+ def self.connector
34
+ @connector ||= begin
35
+ Elastic::Core::Connector.new(
36
+ suffix,
37
+ definition.types,
38
+ definition.as_es_mapping
39
+ ).tap do |conn|
40
+ if Elastic.config.whiny_indices && conn.status != :ready
41
+ raise 'elastic index out of sync, try migrating'
42
+ end
43
+ end
44
+ end
31
45
  end
32
46
 
33
- def self.mapping
34
- @mapping ||= load_mapping
47
+ def self.index_name
48
+ connector.index_name
35
49
  end
36
50
 
37
- def self.reindex(verbose: true, batch_size: nil)
38
- drop
39
- mapping.migrate
40
- batch_size = batch_size || import_batch_size
51
+ def self.migrate
52
+ connector.migrate(batch_size: import_batch_size)
53
+ self
54
+ end
41
55
 
42
- Commands::ImportIndexDocuments.for(
43
- index: self,
44
- verbose: verbose,
45
- batch_size: batch_size
46
- )
56
+ def self.reindex(verbose: true)
57
+ connector.rollover do
58
+ Commands::ImportIndexDocuments.for(
59
+ index: self,
60
+ verbose: verbose,
61
+ batch_size: import_batch_size
62
+ )
63
+ end
47
64
 
48
- ensure_full_mapping
49
65
  self
50
66
  end
51
67
 
52
68
  def self.import(_collection, batch_size: nil)
53
- enforce_mapping!
54
69
  batch_size = batch_size || import_batch_size
55
70
 
56
71
  Commands::ImportIndexDocuments.for(
@@ -59,7 +74,6 @@ module Elastic
59
74
  batch_size: batch_size
60
75
  )
61
76
 
62
- ensure_full_mapping
63
77
  self
64
78
  end
65
79
 
@@ -67,52 +81,37 @@ module Elastic
67
81
  new(_object).save
68
82
  end
69
83
 
84
+ def self.delete(_object)
85
+ wrapper = new(_object)
86
+ id = wrapper.read_elastic_id
87
+ raise ArgumentError, 'index does not provide an id' if id.nil?
88
+
89
+ connector.delete(
90
+ wrapper.read_elastic_type,
91
+ wrapper.read_elastic_id
92
+ )
93
+
94
+ nil
95
+ end
96
+
70
97
  def self.query
71
- enforce_mapping!
72
- ensure_full_mapping
73
98
  Query.new self
74
99
  end
75
100
 
76
101
  def self.drop
77
- adaptor.drop if adaptor.exists?
102
+ connector.drop
78
103
  self
79
104
  end
80
105
 
81
106
  def self.refresh
82
- adaptor.refresh
107
+ connector.refresh
83
108
  self
84
109
  end
85
110
 
86
- def self.enforce_mapping!
87
- if mapping.out_of_sync?
88
- raise 'elastic mapping out of sync, run `rake es:migrate`'
89
- end
90
- end
91
-
92
- def self.ensure_full_mapping
93
- if mapping.incomplete?
94
- mapping.fetch
95
- end
96
- end
97
-
98
111
  def save
99
112
  self.class.tap do |klass|
100
- klass.enforce_mapping!
101
- klass.adaptor.index as_es_document
102
- klass.ensure_full_mapping
113
+ klass.connector.index as_elastic_document
103
114
  end
104
115
  end
105
-
106
- def self.load_mapping
107
- Elastic::Core::MappingManager.new(adaptor, definition).tap(&:fetch)
108
- end
109
-
110
- private_class_method :load_mapping
111
-
112
- def self.default_suffix
113
- to_s.underscore
114
- end
115
-
116
- private_class_method :default_suffix
117
116
  end
118
117
  end
@@ -1,3 +1,3 @@
1
1
  module Elastic
2
- VERSION = "0.6.4"
2
+ VERSION = "0.7.0"
3
3
  end
data/lib/elastic.rb CHANGED
@@ -2,6 +2,7 @@ require "elasticsearch"
2
2
 
3
3
  require "elastic/version"
4
4
  require "elastic/configuration"
5
+ require "elastic/errors"
5
6
 
6
7
  require "elastic/support/command"
7
8
  require "elastic/support/transform"
@@ -11,6 +12,7 @@ require "elastic/commands/import_index_documents"
11
12
  require "elastic/commands/build_query_from_params"
12
13
  require "elastic/commands/build_agg_from_params"
13
14
  require "elastic/commands/build_sort_from_params"
15
+ require "elastic/commands/compare_mappings"
14
16
 
15
17
  require "elastic/results/base"
16
18
  require "elastic/results/scored_item"
@@ -64,9 +66,8 @@ require "elastic/datatypes/time"
64
66
  require "elastic/fields/value"
65
67
  require "elastic/fields/nested"
66
68
 
69
+ require "elastic/core/connector"
67
70
  require "elastic/core/definition"
68
- require "elastic/core/adaptor"
69
- require "elastic/core/mapping_manager"
70
71
  require "elastic/core/serializer"
71
72
  require "elastic/core/middleware"
72
73
  require "elastic/core/base_middleware"
@@ -89,8 +90,17 @@ require "elastic/query"
89
90
  require "elastic/nested_query"
90
91
 
91
92
  module Elastic
92
- def self.configure(*_args)
93
- Configuration.configure(*_args)
93
+ def self.config
94
+ @config ||= Configuration.new
95
+ end
96
+
97
+ def self.logger
98
+ config.logger
99
+ end
100
+
101
+ def self.configure(_options = nil, &_block)
102
+ config.assign_attributes(_options) unless _options.nil?
103
+ _block.call(config) unless _block.nil?
94
104
  end
95
105
 
96
106
  def self.register_middleware(_middleware)
@@ -2,6 +2,7 @@ development:
2
2
  host: '127.0.0.1'
3
3
  port: 9200
4
4
  index: '<%= Rails.application.class.parent_name.to_s.underscore %>_develop'
5
+ whiny_indices: true
5
6
 
6
7
  test:
7
8
  host: '127.0.0.1'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ignacio Baixas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-18 00:00:00.000000000 Z
11
+ date: 2016-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: elasticsearch
@@ -228,13 +228,13 @@ files:
228
228
  - lib/elastic/commands/build_agg_from_params.rb
229
229
  - lib/elastic/commands/build_query_from_params.rb
230
230
  - lib/elastic/commands/build_sort_from_params.rb
231
+ - lib/elastic/commands/compare_mappings.rb
231
232
  - lib/elastic/commands/import_index_documents.rb
232
233
  - lib/elastic/configuration.rb
233
- - lib/elastic/core/adaptor.rb
234
234
  - lib/elastic/core/base_middleware.rb
235
+ - lib/elastic/core/connector.rb
235
236
  - lib/elastic/core/default_middleware.rb
236
237
  - lib/elastic/core/definition.rb
237
- - lib/elastic/core/mapping_manager.rb
238
238
  - lib/elastic/core/middleware.rb
239
239
  - lib/elastic/core/query_assembler.rb
240
240
  - lib/elastic/core/query_config.rb
@@ -248,6 +248,7 @@ files:
248
248
  - lib/elastic/dsl/bool_query_builder.rb
249
249
  - lib/elastic/dsl/bool_query_context.rb
250
250
  - lib/elastic/dsl/metric_builder.rb
251
+ - lib/elastic/errors.rb
251
252
  - lib/elastic/fields/nested.rb
252
253
  - lib/elastic/fields/value.rb
253
254
  - lib/elastic/nested_query.rb
@@ -1,126 +0,0 @@
1
- module Elastic::Core
2
- class Adaptor
3
- DEFAULT_SETTINGS = {
4
- refresh_interval: '1s',
5
- number_of_replicas: 1
6
- }
7
-
8
- def initialize(_suffix)
9
- @suffix = _suffix
10
- end
11
-
12
- def index_name
13
- @index_name ||= "#{Elastic::Configuration.index_name}_#{@suffix}"
14
- end
15
-
16
- def remap(_type, _mapping)
17
- # TODO
18
- end
19
-
20
- def exists?
21
- api_client.indices.exists? build_options
22
- end
23
-
24
- def ensure_index
25
- create unless exists?
26
- self
27
- end
28
-
29
- def create
30
- api_client.indices.create build_options
31
- api_client.cluster.health wait_for_status: 'yellow'
32
- self
33
- end
34
-
35
- def drop
36
- api_client.indices.delete build_options
37
- self
38
- end
39
-
40
- def exists_type?(_type)
41
- api_client.indices.exists_type build_options(type: _type)
42
- end
43
-
44
- def exists_mapping?(_type)
45
- !api_client.indices.get_mapping(build_options(type: _type)).empty?
46
- end
47
-
48
- def get_mappings(type: nil)
49
- mappings = api_client.indices.get_mapping build_options(type: type)
50
- mappings[index_name]['mappings']
51
- end
52
-
53
- def set_mapping(_type, _mapping)
54
- api_client.indices.put_mapping build_options(
55
- type: _type,
56
- body: _mapping
57
- )
58
- self
59
- end
60
-
61
- def index(_document)
62
- api_client.index build_options(
63
- id: _document['_id'],
64
- type: _document['_type'],
65
- body: _document['data']
66
- )
67
- self
68
- end
69
-
70
- def bulk_index(_documents)
71
- body = _documents.map { |doc| { 'index' => doc } }
72
-
73
- retry_on_temporary_error('bulk indexing') do
74
- api_client.bulk build_options(body: body)
75
- end
76
-
77
- self
78
- end
79
-
80
- def with_settings(_options)
81
- _options = DEFAULT_SETTINGS.merge _options
82
- api_client.indices.put_settings build_options(body: { index: _options })
83
- yield
84
- ensure
85
- api_client.indices.put_settings build_options(body: { index: DEFAULT_SETTINGS })
86
- end
87
-
88
- def refresh
89
- api_client.indices.refresh build_options
90
- self
91
- end
92
-
93
- def find(_id, type: '_all')
94
- api_client.get build_options(type: type, id: _id)
95
- end
96
-
97
- def count(type: nil, query: nil)
98
- api_client.count(build_options(type: type, body: query))['count']
99
- end
100
-
101
- def query(type: nil, query: nil)
102
- api_client.search build_options(type: type, body: query)
103
- end
104
-
105
- private
106
-
107
- def retry_on_temporary_error(_action, retries: 3)
108
- return yield
109
- rescue Elasticsearch::Transport::Transport::Errors::ServiceUnavailable,
110
- Elasticsearch::Transport::Transport::Errors::GatewayTimeout => exc
111
- raise if retries <= 0
112
-
113
- Configuration.logger.warn("#{exc.class} error during '#{_action}', retrying!")
114
- retries -= 1
115
- retry
116
- end
117
-
118
- def api_client
119
- Elastic::Configuration.api_client
120
- end
121
-
122
- def build_options(_options = {})
123
- { index: index_name }.merge! _options
124
- end
125
- end
126
- end
@@ -1,120 +0,0 @@
1
- module Elastic::Core
2
- class MappingManager
3
- attr_reader :adaptor, :definition
4
-
5
- def initialize(_adaptor, _definition)
6
- @adaptor = _adaptor
7
- @definition = _definition
8
- @status = :pending
9
- end
10
-
11
- def out_of_sync?
12
- @status == :out_of_sync
13
- end
14
-
15
- def incomplete?
16
- @status == :incomplete
17
- end
18
-
19
- def fetch
20
- begin
21
- mappings = @adaptor.get_mappings
22
- mappings = @definition.types.map { |t| mappings[t] }.reject(&:nil?)
23
- @index = merge_mappings_into_index mappings
24
- rescue Elasticsearch::Transport::Transport::Errors::NotFound
25
- # ignore not-found errors when fetching mappings
26
- @index = nil
27
- end
28
-
29
- @status = compute_status
30
- self
31
- end
32
-
33
- def unmapped_fields
34
- @definition.expanded_field_names.reject { |f| has_field? f }
35
- end
36
-
37
- def has_field?(_name)
38
- @index.key? _name
39
- end
40
-
41
- def get_field_options(_name)
42
- @index[_name]
43
- end
44
-
45
- def migrate
46
- # TODO: make this a command
47
- @adaptor.create unless @adaptor.exists?
48
- begin
49
- @definition.types.each { |t| @adaptor.set_mapping(t, user_mapping) }
50
- rescue Elasticsearch::Transport::Transport::Errors::BadRequest
51
- # TODO: https://www.elastic.co/guide/en/elasticsearch/guide/current/reindex.html
52
- end
53
- fetch
54
- end
55
-
56
- private
57
-
58
- def compute_status
59
- if !synchronized?
60
- :out_of_sync
61
- elsif unmapped_fields.count > 0
62
- :incomplete
63
- else
64
- :ready
65
- end
66
- end
67
-
68
- def synchronized?
69
- return false if @index.nil?
70
- flatten(user_mapping).all? do |field, properties|
71
- compare_field_properties(@index[field], properties)
72
- end
73
- end
74
-
75
- def compare_field_properties(_current, _user)
76
- return false if _current.nil?
77
-
78
- case _current['type']
79
- when 'date'
80
- _current = { 'format' => 'dateOptionalTime' }.merge(_user)
81
- else
82
- _current == _user
83
- end
84
- end
85
-
86
- def user_mapping
87
- @user_mapping ||= definition.as_es_mapping
88
- end
89
-
90
- def flatten(_raw, _prefix = '')
91
- _raw['properties'].flat_map do |name, raw_field|
92
- if raw_field['type'] == 'nested'
93
- childs = flatten(raw_field, name + '.')
94
- childs << [
95
- _prefix + name,
96
- raw_field.slice(*(raw_field.keys - ['properties']))
97
- ]
98
- else
99
- [[_prefix + name, raw_field.dup]]
100
- end
101
- end
102
- end
103
-
104
- def merge_mappings_into_index(_mappings)
105
- {}.tap do |result|
106
- _mappings.each do |mapping|
107
- index = flatten(mapping)
108
- index.each do |field, properties|
109
- if result.key? field
110
- result[field].merge! properties
111
- else
112
- result[field] = properties
113
- end
114
- end
115
- end
116
- result.each_value(&:freeze)
117
- end
118
- end
119
- end
120
- end