elastic-rails 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/lib/elastic/commands/build_agg_from_params.rb +37 -20
  3. data/lib/elastic/commands/build_query_from_params.rb +109 -79
  4. data/lib/elastic/commands/build_sort_from_params.rb +41 -0
  5. data/lib/elastic/commands/import_index_documents.rb +1 -1
  6. data/lib/elastic/configuration.rb +14 -11
  7. data/lib/elastic/core/adaptor.rb +0 -1
  8. data/lib/elastic/core/base_middleware.rb +2 -2
  9. data/lib/elastic/core/default_middleware.rb +1 -1
  10. data/lib/elastic/core/definition.rb +53 -34
  11. data/lib/elastic/core/query_assembler.rb +51 -19
  12. data/lib/elastic/core/query_config.rb +4 -5
  13. data/lib/elastic/core/source_formatter.rb +12 -31
  14. data/lib/elastic/datatypes/date.rb +32 -0
  15. data/lib/elastic/datatypes/default.rb +74 -0
  16. data/lib/elastic/datatypes/string.rb +7 -0
  17. data/lib/elastic/datatypes/term.rb +10 -0
  18. data/lib/elastic/datatypes/time.rb +29 -0
  19. data/lib/elastic/dsl/bool_query_builder.rb +4 -0
  20. data/lib/elastic/fields/nested.rb +24 -6
  21. data/lib/elastic/fields/value.rb +69 -25
  22. data/lib/elastic/nested_query.rb +34 -0
  23. data/lib/elastic/nested_type.rb +10 -0
  24. data/lib/elastic/nodes/agg/average.rb +3 -1
  25. data/lib/elastic/nodes/agg/base_metric.rb +6 -5
  26. data/lib/elastic/nodes/agg/date_histogram.rb +4 -4
  27. data/lib/elastic/nodes/agg/maximum.rb +3 -1
  28. data/lib/elastic/nodes/agg/minimum.rb +3 -1
  29. data/lib/elastic/nodes/agg/stats.rb +3 -1
  30. data/lib/elastic/nodes/agg/sum.rb +3 -1
  31. data/lib/elastic/nodes/agg/terms.rb +4 -4
  32. data/lib/elastic/nodes/agg/top_hits.rb +6 -6
  33. data/lib/elastic/nodes/and.rb +2 -2
  34. data/lib/elastic/nodes/base.rb +5 -3
  35. data/lib/elastic/nodes/base_agg.rb +2 -2
  36. data/lib/elastic/nodes/boolean.rb +34 -14
  37. data/lib/elastic/nodes/concerns/aggregable.rb +12 -8
  38. data/lib/elastic/nodes/concerns/bucketed.rb +4 -7
  39. data/lib/elastic/nodes/concerns/field_query.rb +10 -0
  40. data/lib/elastic/nodes/concerns/hit_provider.rb +11 -0
  41. data/lib/elastic/nodes/function_score.rb +8 -7
  42. data/lib/elastic/nodes/match.rb +6 -5
  43. data/lib/elastic/nodes/nested.rb +28 -7
  44. data/lib/elastic/nodes/range.rb +9 -8
  45. data/lib/elastic/nodes/search.rb +11 -10
  46. data/lib/elastic/nodes/sort.rb +82 -0
  47. data/lib/elastic/nodes/term.rb +7 -6
  48. data/lib/elastic/query.rb +24 -12
  49. data/lib/elastic/railtie.rb +7 -0
  50. data/lib/elastic/railties/ar_helpers.rb +2 -1
  51. data/lib/elastic/railties/configuration_extensions.rb +13 -0
  52. data/lib/elastic/railties/indexable_record.rb +1 -2
  53. data/lib/elastic/railties/indexing_job.rb +8 -0
  54. data/lib/elastic/railties/type_extensions.rb +1 -1
  55. data/lib/elastic/results/aggregations.rb +1 -1
  56. data/lib/elastic/results/bucket.rb +3 -2
  57. data/lib/elastic/results/grouped_result.rb +31 -1
  58. data/lib/elastic/results/hit.rb +8 -20
  59. data/lib/elastic/results/hit_collection.rb +2 -33
  60. data/lib/elastic/results/root.rb +3 -2
  61. data/lib/elastic/results/scored_collection.rb +44 -0
  62. data/lib/elastic/results/scored_item.rb +10 -0
  63. data/lib/elastic/shims/base.rb +6 -4
  64. data/lib/elastic/shims/concerns/hit_picker.rb +41 -0
  65. data/lib/elastic/shims/field_picking.rb +20 -0
  66. data/lib/elastic/shims/grouping.rb +17 -8
  67. data/lib/elastic/shims/id_picking.rb +15 -0
  68. data/lib/elastic/shims/populating.rb +4 -11
  69. data/lib/elastic/shims/reducing.rb +3 -7
  70. data/lib/elastic/shims/total_picking.rb +16 -0
  71. data/lib/elastic/type.rb +6 -3
  72. data/lib/elastic/types/base_type.rb +16 -9
  73. data/lib/elastic/types/faceted_type.rb +1 -1
  74. data/lib/elastic/types/nestable_type.rb +2 -2
  75. data/lib/elastic/version.rb +1 -1
  76. data/lib/elastic.rb +15 -0
  77. data/lib/generators/elastic/index_generator.rb +1 -1
  78. data/lib/generators/elastic/init_generator.rb +10 -0
  79. data/lib/generators/elastic/templates/elastic.yml +14 -0
  80. data/lib/generators/elastic/templates/index.rb +0 -1
  81. metadata +21 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a5c4d94d6d4b35572694f0968dfcb60251cd531a
4
- data.tar.gz: 8fc9e4861325fb3e7ddf90d5751b7dd69892a0ac
3
+ metadata.gz: a85d4715234d67d4dac798756ad8c63b62b8aeb2
4
+ data.tar.gz: fa4bb1a83eae9d73ab8c1ce7717f4232922bd6f0
5
5
  SHA512:
6
- metadata.gz: 7084256b0dca89eecfdb1a0b2b2c8784e52b155da96fc60b9419913314fe7fb06c01525669a10414cb807e0ec75d6538d8d0c7f344d14eb6fdde16abe3f8442c
7
- data.tar.gz: 7f38c177624c2df6216e821701507a91000bd95980226bdad50bf1fab18d6146f1041bd4ffc1845c6824bfd819a8d6b31cba12b789abfd05d875ef13efe7418b
6
+ metadata.gz: c95fda8f2273c68285ee7bfc02c7f5b01bceccdfef74c3ad2dfa86d8d5cd27420a4c4598c608ba535900c2268030dea3ced38bbed62db35a6b0f037c3630866d
7
+ data.tar.gz: 1600194a988639918c4810d0bfebf9be14c24c9b9f78eb2ed64b9f64ed8e46ae82c03c2b38d8c563f229f42910c2823284ec8fafd34097ff8bd41f1b77484c10
@@ -2,7 +2,8 @@ module Elastic::Commands
2
2
  class BuildAggFromParams < Elastic::Support::Command.new(:index, :params)
3
3
  def perform
4
4
  parse_params
5
- raise ArgumentError, "field not mapped: #{@field}" unless index.definition.has_field? @field
5
+ raise ArgumentError, "#{@field} not mapped" if field_definition.nil?
6
+ raise ArgumentError, "cant aggregate on #{@field}" if field_definition.nested?
6
7
 
7
8
  path = parse_nesting_path
8
9
  raise NotImplementedError, "nested paths not yet supported in aggregations" if path
@@ -12,10 +13,6 @@ module Elastic::Commands
12
13
 
13
14
  private
14
15
 
15
- def agg_name
16
- @options.fetch(:as, @field)
17
- end
18
-
19
16
  def parse_params
20
17
  @field = params[0].to_s
21
18
  @options = params[1] || {}
@@ -28,36 +25,56 @@ module Elastic::Commands
28
25
  end
29
26
 
30
27
  def build_node
31
- type = @options[:type]
32
- type = infer_type_from_options_and_mapping if type.nil?
28
+ agg_type = infer_agg_type
29
+ raise "aggregation not supported by #{@field}" if agg_type.nil?
30
+
31
+ node_options = field_definition.public_send("#{agg_type}_aggregation_defaults")
32
+ node_options = node_options.merge(@options)
33
+ send("build_#{agg_type}", node_options)
34
+ end
33
35
 
34
- send("build_#{type}")
36
+ def infer_agg_type
37
+ alternatives = infer_type_options
38
+ if alternatives.nil?
39
+ field_definition.supported_aggregations.first
40
+ else
41
+ field_definition.supported_aggregations.find { |q| alternatives.include? q }
42
+ end
35
43
  end
36
44
 
37
- def infer_type_from_options_and_mapping
38
- return :range if @options.key? :ranges
45
+ def infer_type_options
46
+ return [@options[:type].to_sym] if @options.key? :type
47
+ return [:range] if @options.key? :ranges
48
+ return [:histogram, :date_histogram] if @options.key? :interval
49
+ nil
50
+ end
39
51
 
40
- properties = index.mapping.get_field_options @field
41
- return :date_histogram if properties['type'] == 'date'
42
- return :histogram if @options.key? :interval
52
+ def apply_query_defaults(_agg_type, _options)
53
+ _definition.default_options_for(query: _query_type).merge(_options)
54
+ end
43
55
 
44
- :terms
56
+ def field_definition
57
+ @field_definition ||= index.definition.get_field @field
45
58
  end
46
59
 
47
- def build_range
60
+ def build_range(_options)
48
61
  raise NotImplementedError, 'range aggregation not yet implemented'
49
62
  end
50
63
 
51
- def build_histogram
64
+ def build_histogram(_options)
52
65
  raise NotImplementedError, 'histogram aggregation not yet implemented'
53
66
  end
54
67
 
55
- def build_date_histogram
56
- Elastic::Nodes::Agg::DateHistogram.build(agg_name, @field, interval: @options[:interval])
68
+ def build_date_histogram(_options)
69
+ Elastic::Nodes::Agg::DateHistogram.build(agg_name, @field, interval: _options[:interval])
70
+ end
71
+
72
+ def build_terms(_options)
73
+ Elastic::Nodes::Agg::Terms.build(agg_name, @field, size: _options[:size])
57
74
  end
58
75
 
59
- def build_terms
60
- Elastic::Nodes::Agg::Terms.build(agg_name, @field, size: @options[:size])
76
+ def agg_name
77
+ @options.fetch(:as, @field)
61
78
  end
62
79
  end
63
80
  end
@@ -1,20 +1,10 @@
1
1
  module Elastic::Commands
2
- class BuildQueryFromParams < Elastic::Support::Command.new(
3
- :index, :params, block: nil, prefix: nil
4
- )
5
-
2
+ class BuildQueryFromParams < Elastic::Support::Command.new(:index, :params, block: nil)
6
3
  def perform
7
4
  if block
8
5
  # TODO: builder mode, support nesting through first parameter
9
6
  else
10
- node = Elastic::Nodes::Boolean.build_or(params.map do |part|
11
- Elastic::Nodes::Boolean.build_and(part.map do |field, options|
12
- field = field.to_s
13
- path = get_nesting_path field
14
- query_node = build_query_node(field, options)
15
- path.nil? ? query_node : Elastic::Nodes::Nested.build(with_prefix(path), query_node)
16
- end)
17
- end)
7
+ node = build_or_node(params)
18
8
  end
19
9
 
20
10
  node.simplify
@@ -22,111 +12,151 @@ module Elastic::Commands
22
12
 
23
13
  private
24
14
 
25
- def get_nesting_path(_field)
26
- dot_index = _field.rindex('.')
27
- return nil if dot_index.nil?
28
- _field.slice 0, dot_index
15
+ def build_or_node(_array)
16
+ Elastic::Nodes::Boolean.build_or(_array.map do |part|
17
+ case part
18
+ when Elastic::Query
19
+ extract_query_node part
20
+ when Hash
21
+ build_and_node part
22
+ else
23
+ raise ArgumentError, "expected hash or query but got #{part.class}"
24
+ end
25
+ end)
29
26
  end
30
27
 
31
- def build_query_node(_field, _options)
32
- _field = with_prefix _field
33
- _options = prepare_options _field, _options
28
+ def extract_query_node(_query)
29
+ raise ArgumentError, "query type mismatch, expected #{index.class}" if _query.index != index
30
+ _query.as_query_node
31
+ end
34
32
 
35
- type = infer_type_from_params(_options)
36
- send("build_#{type}", _field, _options)
33
+ def build_and_node(_hash)
34
+ Elastic::Nodes::Boolean.build_and(_hash.map do |field, options|
35
+ build_query_node field, options
36
+ end)
37
37
  end
38
38
 
39
- def infer_type_from_params(_query)
40
- return :term if _query.key? :term
41
- return :term if _query.key? :terms
42
- return :match if _query.key? :matches
43
- return :range if _query.key? :gte
44
- return :range if _query.key? :gt
45
- return :range if _query.key? :lte
46
- return :range if _query.key? :lt
47
- return :nested if _query.key? :nested
48
- nil
39
+ def build_query_node(_field, _options)
40
+ path, field = split_nesting_path(_field.to_s)
41
+ if path
42
+ definition = resolve_field_defintion! path
43
+ raise ArgumentError, "invalid nesting path #{path}" unless definition.nested?
44
+ build_nested_query(path, definition, field => _options)
45
+ else
46
+ build_regular_query(field, _options)
47
+ end
49
48
  end
50
49
 
51
- def with_prefix(_path)
52
- return _path if prefix.nil?
53
- "#{prefix}.#{_path}"
50
+ def split_nesting_path(_field)
51
+ dot_index = _field.rindex('.')
52
+ return [nil, _field] if dot_index.nil?
53
+ [_field[0..dot_index - 1], _field[dot_index + 1..-1]]
54
54
  end
55
55
 
56
- def prepare_options(_field, _options)
57
- properties = index.mapping.get_field_options _field.to_s
58
- raise ArgumentError, "field not mapped: #{_field}" if properties.nil?
56
+ def build_regular_query(_field, _options)
57
+ definition = resolve_field_defintion!(_field)
59
58
 
60
- case properties['type']
61
- when 'nested'
62
- prepare_nested_options _options, properties
63
- when 'string'
64
- prepare_string_options _options, properties
59
+ if definition.nested?
60
+ build_nested_query(_field, definition, _options)
65
61
  else
66
- prepare_default_options _options, properties
62
+ query_type = infer_query_type definition, _options
63
+ raise "query not supported by #{_field}" if query_type.nil?
64
+ _options = option_to_hash(query_type, _options) unless _options.is_a? Hash
65
+ _options = definition.public_send("#{query_type}_query_defaults").merge(_options)
66
+
67
+ send("build_#{query_type}", definition, _options)
67
68
  end
68
69
  end
69
70
 
70
- def prepare_nested_options(_options, _properties)
71
- return _options if _options.is_a?(Hash) && _options[:nested]
72
- { nested: _options }
71
+ def resolve_field_defintion!(_path)
72
+ definition = index.definition.get_field _path
73
+ raise ArgumentError, "field not mapped: #{_path}" if definition.nil?
74
+ definition
73
75
  end
74
76
 
75
- def prepare_string_options(_options, _properties)
76
- return _options if _options.is_a? Hash
77
- _properties['index'] == 'not_analyzed' ? { term: _options } : { matches: _options }
77
+ def infer_query_type(_definition, _options)
78
+ alternatives = infer_query_type_from_options(_options)
79
+ if alternatives.nil?
80
+ _definition.supported_queries.first
81
+ else
82
+ _definition.supported_queries.find { |q| alternatives.include? q }
83
+ end
78
84
  end
79
85
 
80
- def prepare_default_options(_options, _properties)
81
- return _options if _options.is_a? Hash
82
- return range_to_options(_options) if _options.is_a? Range
83
- { term: _options }
86
+ def build_nested_query(_path, _definition, _query)
87
+ case _query
88
+ when Elastic::NestedQuery
89
+ if _query.index != _definition.index
90
+ raise ArgumentError,
91
+ "query type mismatch for #{_path}, expected #{_definition.index.class}"
92
+ end
93
+
94
+ return _query.as_node.tap { |node| node.path = _path }
95
+ when Hash
96
+ _query = [_query]
97
+ end
98
+
99
+ nested_node = BuildQueryFromParams.for(index: _definition.index, params: _query)
100
+ Elastic::Nodes::Nested.build _path, nested_node
84
101
  end
85
102
 
86
- def build_nested(_field, _options)
87
- query = _options[:nested]
88
- query = [query] unless query.is_a? Array
103
+ def infer_query_type_from_options(_options)
104
+ case _options
105
+ when Hash
106
+ return [_options[:type].to_sym] if _options.key?(:type)
107
+ return [:term] if _options.key?(:term) || _options.key?(:terms)
108
+ return [:match] if _options.key? :matches
109
+ return [:range] if _options.key?(:gte) || _options.key?(:gt)
110
+ return [:range] if _options.key?(:lte) || _options.key?(:lt)
111
+ when String, Symbol
112
+ return [:term, :match]
113
+ when Array
114
+ return [:term]
115
+ when Range
116
+ return [:range]
117
+ end
118
+
119
+ nil
120
+ end
89
121
 
90
- nested_query = BuildQueryFromParams.for(index: index, params: query, prefix: _field)
91
- Elastic::Nodes::Nested.build _field, nested_query
122
+ def option_to_hash(_query_type, _value)
123
+ case _query_type
124
+ when :term
125
+ { terms: _value }
126
+ when :match
127
+ { matches: _value.to_s }
128
+ when :range
129
+ { gte: _value.begin, (_value.exclude_end? ? :lt : :lte) => _value.end }
130
+ end
92
131
  end
93
132
 
133
+ # NOTE: the following methods could be placed in separate factories.
134
+
94
135
  def build_term(_field, _options)
95
- terms = Array(_options[:term] || _options[:terms])
136
+ terms = Array(_options.fetch(:term, _options[:terms]))
96
137
 
97
138
  Elastic::Nodes::Term.new.tap do |node|
98
- node.field = _field
139
+ node.field = _field.name
99
140
  node.mode = _options[:mode]
100
- node.terms = terms.map { |t| prep(_field, t) }
141
+ node.terms = terms.map { |t| _field.prepare_value_for_query(t) }
101
142
  end
102
143
  end
103
144
 
104
145
  def build_range(_field, _options)
105
146
  Elastic::Nodes::Range.new.tap do |node|
106
- node.field = _field
107
- node.gte = prep(_field, _options[:gte]) if _options.key? :gte
108
- node.gt = prep(_field, _options[:gt]) if _options.key? :gt
109
- node.lte = prep(_field, _options[:lte]) if _options.key? :lte
110
- node.lt = prep(_field, _options[:lt]) if _options.key? :lt
147
+ node.field = _field.name
148
+ node.gte = _field.prepare_value_for_query(_options[:gte]) if _options.key? :gte
149
+ node.gt = _field.prepare_value_for_query(_options[:gt]) if _options.key? :gt
150
+ node.lte = _field.prepare_value_for_query(_options[:lte]) if _options.key? :lte
151
+ node.lt = _field.prepare_value_for_query(_options[:lt]) if _options.key? :lt
111
152
  end
112
153
  end
113
154
 
114
155
  def build_match(_field, _options)
115
156
  Elastic::Nodes::Match.new.tap do |node|
116
- node.field = _field
117
- node.query = prep(_field, _options[:matches])
157
+ node.field = _field.name
158
+ node.query = _field.prepare_value_for_query(_options[:matches])
118
159
  end
119
160
  end
120
-
121
- def range_to_options(_range)
122
- {
123
- gte: _range.begin,
124
- (_range.exclude_end? ? :lt : :lte) => _range.end
125
- }
126
- end
127
-
128
- def prep(_field, _value)
129
- index.definition.get_field(_field).prepare_value_for_query(_value)
130
- end
131
161
  end
132
162
  end
@@ -0,0 +1,41 @@
1
+ module Elastic::Commands
2
+ class BuildSortFromParams < Elastic::Support::Command.new(:index, :params)
3
+ def perform
4
+ params.each do |param|
5
+ case param
6
+ when Hash
7
+ param.each { |field, options| add_sort field, options }
8
+ else
9
+ add_sort param
10
+ end
11
+ end
12
+
13
+ node.add_score_sort
14
+ node
15
+ end
16
+
17
+ private
18
+
19
+ def node
20
+ @node ||= Elastic::Nodes::Sort.new
21
+ end
22
+
23
+ def add_sort(_field, _options = {})
24
+ _field = _field.to_s
25
+ _options = { order: _options } unless _options.is_a? Hash
26
+
27
+ raise ArgumentError, "field not mapped: #{_field}" unless index.definition.has_field? _field
28
+
29
+ path = parse_nesting_path(_field)
30
+ raise NotImplementedError, "nested fields not yet supported in sorting" if path
31
+
32
+ node.add_sort(_field, _options)
33
+ end
34
+
35
+ def parse_nesting_path(_field)
36
+ dot_index = _field.rindex('.')
37
+ return nil if dot_index.nil?
38
+ _field.slice(0, dot_index)
39
+ end
40
+ end
41
+ end
@@ -14,7 +14,7 @@ module Elastic::Commands
14
14
  private
15
15
 
16
16
  def import_collection
17
- main_target.collect_for(collection, middleware_options) { |obj| queue obj }
17
+ main_target.collect_from(collection, middleware_options) { |obj| queue obj }
18
18
  end
19
19
 
20
20
  def import_target(_target)
@@ -1,13 +1,26 @@
1
1
  module Elastic
2
2
  module Configuration
3
+ DEFAULT = {
4
+ host: '127.0.0.1',
5
+ port: 9200,
6
+ page_size: 20,
7
+ coord_similarity: true
8
+ }
9
+
3
10
  extend self
4
11
 
12
+ def reset
13
+ @config = nil
14
+ self
15
+ end
16
+
5
17
  def configure(_options = nil, &_block)
6
18
  if _options.nil?
7
19
  _block.call self
8
20
  else
9
21
  @config = config.merge _options.symbolize_keys
10
22
  end
23
+ self
11
24
  end
12
25
 
13
26
  def api_client
@@ -30,10 +43,6 @@ module Elastic
30
43
  @config[:coord_similarity]
31
44
  end
32
45
 
33
- def strict_mode
34
- @config[:strict_types]
35
- end
36
-
37
46
  def logger
38
47
  @config[:logger] || default_logger
39
48
  end
@@ -41,13 +50,7 @@ module Elastic
41
50
  private
42
51
 
43
52
  def config
44
- @config ||= {
45
- host: '127.0.0.1',
46
- port: 9200,
47
- page_size: 20,
48
- coord_similarity: true,
49
- strict_types: true
50
- }
53
+ @config ||= DEFAULT
51
54
  end
52
55
 
53
56
  def default_logger
@@ -48,7 +48,6 @@ module Elastic::Core
48
48
  def set_mapping(_type, _mapping)
49
49
  api_client.indices.put_mapping build_options(
50
50
  type: _type,
51
- update_all_types: true,
52
51
  body: _mapping
53
52
  )
54
53
  self
@@ -22,8 +22,8 @@ module Elastic::Core
22
22
  not_supported :collect_all
23
23
  end
24
24
 
25
- def collect_for(_collection, _options, &_block)
26
- not_supported :collect_for
25
+ def collect_from(_collection, _options, &_block)
26
+ not_supported :collect_from
27
27
  end
28
28
 
29
29
  def find_by_ids(_ids, _options)
@@ -18,7 +18,7 @@ module Elastic::Core
18
18
  target.public_send(method, &_block) if method
19
19
  end
20
20
 
21
- def collect_for(_collection, _options, &_block)
21
+ def collect_from(_collection, _options, &_block)
22
22
  method = collect_method_for(_collection)
23
23
  raise ArgumentError, "Could not find a method to iterate over collection" if method.nil?
24
24
  _collection.public_send(method, &_block)
@@ -7,11 +7,11 @@ module Elastic::Core
7
7
  end
8
8
 
9
9
  def targets
10
- @target_cache ||= load_targets.freeze
10
+ raise 'attempting to access targets before definition has been frozen' if @target_cache.nil?
11
+ @target_cache
11
12
  end
12
13
 
13
14
  def targets=(_values)
14
- @target_cache = nil
15
15
  @targets = _values
16
16
  end
17
17
 
@@ -26,12 +26,11 @@ module Elastic::Core
26
26
  def initialize
27
27
  @targets = []
28
28
  @field_map = {}
29
- @frozen = false
29
+ @field_cache = {}
30
30
  @middleware_options = HashWithIndifferentAccess.new
31
31
  end
32
32
 
33
33
  def register_field(_field)
34
- raise 'definition has been frozen' if @frozen
35
34
  @field_map[_field.name] = _field
36
35
  end
37
36
 
@@ -40,59 +39,60 @@ module Elastic::Core
40
39
  end
41
40
 
42
41
  def expanded_field_names
43
- @expanded_field_names ||= @field_map.map { |_, field| field.expanded_names }.flatten
42
+ @field_map.map { |_, field| field.expanded_names }.flatten
43
+ end
44
+
45
+ def freeze
46
+ return if frozen?
47
+ cache_targets
48
+ complete_and_validate_fields
49
+ freeze_fields
50
+ @middleware_options.freeze
51
+ super
44
52
  end
45
53
 
46
54
  def get_field(_name)
55
+ ensure_frozen!
56
+
47
57
  _name = _name.to_s
48
- separator = _name.index '.'
49
- if separator.nil?
50
- @field_map[_name]
51
- else
52
- parent = @field_map[_name[0...separator]]
53
- parent.try(:get_field, _name[separator + 1..-1])
54
- end
58
+ @field_cache[_name] = resolve_field(_name) unless @field_cache.key? _name
59
+ @field_cache[_name]
55
60
  end
56
61
 
57
62
  def has_field?(_name)
63
+ ensure_frozen!
64
+
58
65
  !get_field(_name).nil?
59
66
  end
60
67
 
61
68
  def as_es_mapping
62
- # TODO: Make this a command
69
+ ensure_frozen!
70
+
63
71
  properties = {}
64
72
  @field_map.each_value do |field|
65
- field_def = field.mapping_options
66
-
67
- if !field_def.key?(:type) && field.mapping_inference_enabled?
68
- inferred = infer_mapping_options(field.name)
69
- field_def.merge! inferred.symbolize_keys unless inferred.nil?
70
- end
71
-
72
- if Elastic::Configuration.strict_mode && !field_def.key?(:type)
73
- raise "explicit field type for #{field.name} required"
74
- end
75
-
76
- properties[field.name] = field_def if field_def.key? :type
73
+ properties[field.name] = field.mapping_options
77
74
  end
78
75
 
79
76
  { 'properties' => properties.as_json }
80
77
  end
81
78
 
82
- def freeze
83
- unless @frozen
84
- @field_map.each_value(&:freeze)
85
- @frozen = true
86
- @middleware_options.freeze
79
+ private
80
+
81
+ def resolve_field(_name)
82
+ separator = _name.index '.'
83
+ if separator.nil?
84
+ @field_map[_name]
85
+ else
86
+ parent = @field_map[_name[0...separator]]
87
+ return nil if parent.nil?
88
+ parent.get_field(_name[separator + 1..-1])
87
89
  end
88
90
  end
89
91
 
90
- def frozen?
91
- !!@frozen
92
+ def cache_targets
93
+ @target_cache = load_targets.freeze
92
94
  end
93
95
 
94
- private
95
-
96
96
  def load_targets
97
97
  mode = nil
98
98
  @targets.map do |target|
@@ -107,6 +107,25 @@ module Elastic::Core
107
107
  end
108
108
  end
109
109
 
110
+ def complete_and_validate_fields
111
+ @field_map.each_value do |field|
112
+ field.merge! infer_mapping_options(field.name) if field.needs_inference?
113
+
114
+ error = field.validate
115
+ raise error unless error.nil?
116
+ end
117
+
118
+ @field_map.freeze
119
+ end
120
+
121
+ def ensure_frozen!
122
+ raise 'definition needs to be frozen' unless frozen?
123
+ end
124
+
125
+ def freeze_fields
126
+ @field_map.each_value(&:freeze)
127
+ end
128
+
110
129
  def load_target_middleware(_target)
111
130
  Middleware.wrap(_target)
112
131
  end