elastic-rails 0.5.0 → 0.6.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.
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