elasticated 1.0.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 (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +35 -0
  3. data/Gemfile +4 -0
  4. data/README.md +3 -0
  5. data/Rakefile +6 -0
  6. data/elasticated.gemspec +29 -0
  7. data/lib/elasticated.rb +102 -0
  8. data/lib/elasticated/aggregation.rb +36 -0
  9. data/lib/elasticated/aggregations/cardinality_aggregation.rb +15 -0
  10. data/lib/elasticated/aggregations/count_aggregation.rb +15 -0
  11. data/lib/elasticated/aggregations/count_distinct_aggregation.rb +15 -0
  12. data/lib/elasticated/aggregations/count_filtered_aggregation.rb +29 -0
  13. data/lib/elasticated/aggregations/custom_aggregation.rb +25 -0
  14. data/lib/elasticated/aggregations/date_histogram_aggregation.rb +35 -0
  15. data/lib/elasticated/aggregations/filter_aggregation.rb +33 -0
  16. data/lib/elasticated/aggregations/filter_aggregation_evaluator.rb +22 -0
  17. data/lib/elasticated/aggregations/group_aggregation.rb +29 -0
  18. data/lib/elasticated/aggregations/histogram_aggregation.rb +34 -0
  19. data/lib/elasticated/aggregations/nested_aggregation.rb +30 -0
  20. data/lib/elasticated/aggregations/range_aggregation.rb +35 -0
  21. data/lib/elasticated/aggregations/range_aggregation_evaluator.rb +22 -0
  22. data/lib/elasticated/aggregations/ranges_builder.rb +35 -0
  23. data/lib/elasticated/aggregations/single_value_aggregation.rb +47 -0
  24. data/lib/elasticated/aggregations/subaggregated.rb +27 -0
  25. data/lib/elasticated/aggregations/sum_distinct_aggregation.rb +20 -0
  26. data/lib/elasticated/aggregations/terms_aggregation.rb +63 -0
  27. data/lib/elasticated/aggregations/top_hits_aggregation.rb +25 -0
  28. data/lib/elasticated/block_evaluation.rb +15 -0
  29. data/lib/elasticated/boolean_clause.rb +43 -0
  30. data/lib/elasticated/client.rb +84 -0
  31. data/lib/elasticated/clonable.rb +58 -0
  32. data/lib/elasticated/conditions/custom_condition.rb +19 -0
  33. data/lib/elasticated/conditions/exists_condition.rb +11 -0
  34. data/lib/elasticated/conditions/missing_condition.rb +11 -0
  35. data/lib/elasticated/conditions/nested_condition.rb +19 -0
  36. data/lib/elasticated/conditions/range_condition.rb +27 -0
  37. data/lib/elasticated/conditions/script_condition.rb +22 -0
  38. data/lib/elasticated/conditions/standard_condition.rb +26 -0
  39. data/lib/elasticated/conditions/terms_condition.rb +22 -0
  40. data/lib/elasticated/conditions/wildcard_condition.rb +18 -0
  41. data/lib/elasticated/conditions_builder.rb +75 -0
  42. data/lib/elasticated/configurable.rb +9 -0
  43. data/lib/elasticated/configuration.rb +9 -0
  44. data/lib/elasticated/default_logger.rb +27 -0
  45. data/lib/elasticated/delimiters/date_field_delimiter.rb +33 -0
  46. data/lib/elasticated/delimiters/standard_field_delimiter.rb +33 -0
  47. data/lib/elasticated/delimiters/term_field_delimiter.rb +24 -0
  48. data/lib/elasticated/document.rb +46 -0
  49. data/lib/elasticated/helpers.rb +28 -0
  50. data/lib/elasticated/index_selector.rb +44 -0
  51. data/lib/elasticated/inspectionable.rb +9 -0
  52. data/lib/elasticated/mapping.rb +19 -0
  53. data/lib/elasticated/mapping/builder.rb +36 -0
  54. data/lib/elasticated/mapping/fields_builder.rb +148 -0
  55. data/lib/elasticated/mapping/nested_builder.rb +15 -0
  56. data/lib/elasticated/mapping/object_builder.rb +15 -0
  57. data/lib/elasticated/mapping/partial.rb +11 -0
  58. data/lib/elasticated/mapping/type_builder.rb +14 -0
  59. data/lib/elasticated/partitioned_repository.rb +27 -0
  60. data/lib/elasticated/query.rb +159 -0
  61. data/lib/elasticated/query_aggregations.rb +71 -0
  62. data/lib/elasticated/query_conditions.rb +89 -0
  63. data/lib/elasticated/repositories/monthly_partitioned_repository.rb +96 -0
  64. data/lib/elasticated/repository.rb +139 -0
  65. data/lib/elasticated/results.rb +43 -0
  66. data/lib/version.rb +92 -0
  67. data/spec/aggregation_spec.rb +587 -0
  68. data/spec/date_field_delimiter_spec.rb +67 -0
  69. data/spec/document_spec.rb +44 -0
  70. data/spec/elasticsearch_hit_1.json +14 -0
  71. data/spec/elasticsearch_response_1.json +29 -0
  72. data/spec/elasticsearch_response_2.json +44 -0
  73. data/spec/elasticsearch_top_hits_response.json +20 -0
  74. data/spec/integration_spec.rb +184 -0
  75. data/spec/mapping_spec.rb +219 -0
  76. data/spec/monthly_partitioned_repository_spec.rb +99 -0
  77. data/spec/query_aggregations_spec.rb +44 -0
  78. data/spec/query_conditions_spec.rb +314 -0
  79. data/spec/query_spec.rb +265 -0
  80. data/spec/results_spec.rb +69 -0
  81. data/spec/spec_helper.rb +2 -0
  82. data/spec/term_field_delimiter_spec.rb +39 -0
  83. metadata +225 -0
@@ -0,0 +1,46 @@
1
+ module Elasticated
2
+ class Document
3
+
4
+ class << self
5
+
6
+ def create(opts={}, &block)
7
+ raise if block && !opts.empty?
8
+ ret = new
9
+ if opts.empty?
10
+ ret.evaluate block
11
+ else
12
+ opts.each{ |k, v| ret.send "#{k}=", v if v }
13
+ end
14
+ ret
15
+ end
16
+
17
+ def from_elasticsearch_hit(hit)
18
+ document = new hit['_source']
19
+ document.id = hit['_id']
20
+ document.type = hit['_type']
21
+ document.index = hit['_index']
22
+ document.score = hit['_score']
23
+ document.version = hit['_version']
24
+ document
25
+ end
26
+
27
+ end
28
+
29
+ include BlockEvaluation
30
+
31
+ attr_accessor :id, :type, :index, :score, :version, :document_source
32
+
33
+ def initialize(source=nil)
34
+ self.source = source if source
35
+ end
36
+
37
+ def source=(hash)
38
+ self.document_source = Hash::Accessible.new hash
39
+ end
40
+
41
+ def source
42
+ self.document_source ||= Hash::Accessible.new
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,28 @@
1
+ module Elasticated
2
+ module Helpers
3
+
4
+ def self.string_to_agg_name(element)
5
+ element.to_s.gsub /(?![a-zA-Z0-9])./, '_'
6
+ end
7
+
8
+ def self.string_to_camel_case(string)
9
+ return string if string !~ /_/ && string =~ /[A-Z]+.*/
10
+ string.split('_').map(&:capitalize).join
11
+ end
12
+
13
+ def self.hash_deep_dup(hash)
14
+ duplicate = hash.dup
15
+ duplicate.each_pair do |k, v|
16
+ if v.is_a? Hash
17
+ duplicate[k] = hash_deep_dup v
18
+ elsif v.is_a? Array
19
+ duplicate[k] = v.clone
20
+ else
21
+ duplicate[k] = v
22
+ end
23
+ end
24
+ duplicate
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ module Elasticated
2
+ class IndexSelector
3
+
4
+ # abstract class
5
+ # child must implement 'delimiters()'
6
+ # child must implement 'strategy()'
7
+ # child can override 'strategy_params_for(document)'
8
+
9
+ include BlockEvaluation
10
+
11
+ def indices_for_query(query)
12
+ strategy_params = strategy_params_for_query query
13
+ indices = strategy.call strategy_params
14
+ raise "At least one index should be affected for a search" if indices.count < 1
15
+ indices
16
+ end
17
+
18
+ def index_for_document(document)
19
+ params = strategy_params_for_document document
20
+ indices = strategy.call params
21
+ raise "Only one index can be affected for a document indexation" if indices.count > 1
22
+ raise "At least one index should be affected for a document indexation" if indices.count < 1
23
+ indices.first
24
+ end
25
+
26
+ protected
27
+
28
+ def strategy_params_for_document(document)
29
+ delimiters.inject Hash.new do |params, delimiter|
30
+ key = delimiter.filter_name
31
+ value = document.source[delimiter.field_name]
32
+ value ? params.merge(key => value) : params
33
+ end
34
+ end
35
+
36
+ def strategy_params_for_query(query)
37
+ delimiters.inject Hash.new do |params, delimiter|
38
+ query.fill_delimiter delimiter
39
+ params.merge delimiter.build_strategy_params
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ module Elasticated
2
+ module Inspectionable
3
+
4
+ def inspect
5
+ "#<#{self.class.name}:#{build}>"
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module Elasticated
2
+ module Mapping
3
+
4
+ class << self
5
+
6
+ def partial(&block)
7
+ Partial.new &block
8
+ end
9
+
10
+ def build(&block)
11
+ instance = Builder.new
12
+ instance.evaluate block
13
+ instance.build
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ module Elasticated
2
+ module Mapping
3
+ class Builder
4
+
5
+ class << self
6
+ def build(&block)
7
+ instance = new
8
+ instance.evaluate block
9
+ instance.build
10
+ end
11
+ end
12
+
13
+ include BlockEvaluation
14
+
15
+ attr_accessor :mapping_types
16
+
17
+
18
+ def initialize
19
+ self.mapping_types = Array.new
20
+ end
21
+
22
+ def type(name, &block)
23
+ mapping_type = TypeBuilder.new name
24
+ mapping_type.evaluate block
25
+ mapping_types << mapping_type
26
+ end
27
+
28
+ def build
29
+ mapping_types.inject({}) do |hash, mapping_type|
30
+ hash.merge mapping_type.build
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,148 @@
1
+ module Elasticated
2
+ module Mapping
3
+ class FieldsBuilder
4
+
5
+ include BlockEvaluation
6
+
7
+ attr_accessor :hash, :name, :sub_objects, :nesteds
8
+
9
+ def initialize(name)
10
+ self.hash = Hash.new
11
+ self.sub_objects = Array.new
12
+ self.nesteds = Array.new
13
+ self.name = name
14
+ end
15
+
16
+ def date(field_name)
17
+ hash[field_name] = build_date_field
18
+ end
19
+
20
+ def string(field_name)
21
+ hash[field_name] = build_string_field
22
+ end
23
+
24
+ def float(field_name)
25
+ hash[field_name] = build_float_field
26
+ end
27
+
28
+ def double(field_name)
29
+ hash[field_name] = build_double_field
30
+ end
31
+
32
+ def integer(field_name)
33
+ hash[field_name] = build_integer_field
34
+ end
35
+
36
+ def long(field_name)
37
+ hash[field_name] = build_long_field
38
+ end
39
+
40
+ def analyzed_string(field_name)
41
+ hash[field_name] = build_analyzed_string_field field_name
42
+ end
43
+
44
+ def bool(field_name)
45
+ hash[field_name] = build_bool_field
46
+ end
47
+
48
+ def object(object_name, &block)
49
+ sub_object = ObjectBuilder.new object_name
50
+ sub_object.evaluate block
51
+ sub_objects << sub_object
52
+ end
53
+
54
+ def nested(nested_name, &block)
55
+ nested = NestedBuilder.new nested_name
56
+ nested.evaluate block
57
+ nesteds << nested
58
+ end
59
+
60
+ def partial(partial_mapping)
61
+ partial_mapping.apply_over self
62
+ end
63
+
64
+ def build_body
65
+ ret = hash
66
+ sub_objects.each do |sub_object|
67
+ ret.merge! sub_object.build
68
+ end
69
+ nesteds.each do |nested|
70
+ ret.merge! nested.build
71
+ end
72
+ ret
73
+ end
74
+
75
+ def build
76
+ { name => build_body }
77
+ end
78
+
79
+ private
80
+
81
+ def build_date_field
82
+ {
83
+ type: :date,
84
+ fielddata: { format: :doc_values }
85
+ }
86
+ end
87
+
88
+ def build_string_field
89
+ {
90
+ type: :string,
91
+ index: :not_analyzed,
92
+ fielddata: { format: :doc_values }
93
+ }
94
+ end
95
+
96
+ def build_integer_field
97
+ {
98
+ type: :integer,
99
+ fielddata: { format: :doc_values }
100
+ }
101
+ end
102
+
103
+ def build_float_field
104
+ {
105
+ type: :float,
106
+ fielddata: { format: :doc_values }
107
+ }
108
+ end
109
+
110
+ def build_double_field
111
+ {
112
+ type: :double,
113
+ fielddata: { format: :doc_values }
114
+ }
115
+ end
116
+
117
+ def build_long_field
118
+ {
119
+ type: :long,
120
+ fielddata: { format: :doc_values }
121
+ }
122
+ end
123
+
124
+ def build_analyzed_string_field(field_name)
125
+ {
126
+ type: :multi_field,
127
+ fields: {
128
+ field_name => {
129
+ type: :string,
130
+ index: :not_analyzed,
131
+ fielddata: { format: :doc_values }
132
+ },
133
+ analyzed: {
134
+ type: :string
135
+ }
136
+ }
137
+ }
138
+ end
139
+
140
+ def build_bool_field
141
+ {
142
+ type: :boolean
143
+ }
144
+ end
145
+
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,15 @@
1
+ module Elasticated
2
+ module Mapping
3
+ class NestedBuilder < FieldsBuilder
4
+
5
+ # override
6
+ def build_body
7
+ {
8
+ type: :nested,
9
+ properties: super
10
+ }
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Elasticated
2
+ module Mapping
3
+ class ObjectBuilder < FieldsBuilder
4
+
5
+ # override
6
+ def build_body
7
+ {
8
+ type: :object,
9
+ properties: super
10
+ }
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Elasticated
2
+ module Mapping
3
+ class Partial < Proc
4
+
5
+ def apply_over(mapping_builder)
6
+ call mapping_builder
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module Elasticated
2
+ module Mapping
3
+ class TypeBuilder < FieldsBuilder
4
+
5
+ # override
6
+ def build_body
7
+ {
8
+ properties: super
9
+ }
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ module Elasticated
2
+ class PartitionedRepository < Repository
3
+
4
+ attr_accessor :index_selector
5
+
6
+ def initialize(index_selector, opts={})
7
+ self.index_selector = index_selector
8
+ super opts
9
+ end
10
+
11
+ protected
12
+
13
+ # override
14
+ def execute(action, query, opts={})
15
+ affected_indices = index_selector.indices_for_query(query)
16
+ affected_indices = affected_indices*','
17
+ super action, query, opts.merge(index: affected_indices)
18
+ end
19
+
20
+ # override
21
+ def prepare(action, document, opts={})
22
+ affected_index = index_selector.index_for_document(document)
23
+ super action, document, opts.merge(index: affected_index)
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,159 @@
1
+ module Elasticated
2
+ class Query
3
+
4
+ class << self
5
+ def build(&block)
6
+ q = new
7
+ q.evaluate block
8
+ q
9
+ end
10
+ end
11
+
12
+ include BlockEvaluation
13
+ include Clonable
14
+
15
+ attr_accessor :_conditions, :_filter_conditions, :_post_conditions
16
+ attr_accessor :_source, :_sort, :_size, :_from
17
+ attr_accessor :_aggregations
18
+
19
+ def initialize
20
+ self._conditions = QueryConditions.new
21
+ self._filter_conditions = QueryConditions.new
22
+ self._post_conditions = QueryConditions.new
23
+ self._aggregations = QueryAggregations.new
24
+ end
25
+
26
+ # builders & attrs
27
+
28
+ def conditions(&block)
29
+ _conditions.evaluate block
30
+ self
31
+ end
32
+
33
+ def filter(&block)
34
+ _filter_conditions.evaluate block
35
+ self
36
+ end
37
+
38
+ def post(&block)
39
+ _post_conditions.evaluate block
40
+ self
41
+ end
42
+ alias_method :post_filter, :post
43
+
44
+ def size(value)
45
+ self._size = value
46
+ self
47
+ end
48
+ alias_method :limit, :size
49
+
50
+ def from(value)
51
+ self._from = value
52
+ self
53
+ end
54
+ alias_method :offset, :from
55
+
56
+ def sort(field, method=nil)
57
+ self._sort ||= Array.new
58
+ _sort << { field => { order: method || :asc } }
59
+ self
60
+ end
61
+
62
+ def source(*fields_array)
63
+ self._source = fields_array.flatten
64
+ self
65
+ end
66
+ alias_method :fields, :source
67
+
68
+ def aggregations(&block)
69
+ _aggregations.evaluate block
70
+ self
71
+ end
72
+
73
+ # misc getters
74
+
75
+ def limited?
76
+ !!_size
77
+ end
78
+
79
+ def sorted?
80
+ !!_sort
81
+ end
82
+
83
+ def aggregated?
84
+ !_aggregations.empty?
85
+ end
86
+
87
+ # to_hash methods
88
+
89
+ def build_for_count
90
+ return { query: _conditions.build } if _filter_conditions.empty?
91
+ filtered = { filter: _filter_conditions.build }
92
+ filtered.merge!(query: _conditions.build) unless _conditions.empty?
93
+ { query: { filtered: filtered } }
94
+ end
95
+
96
+ def build_for_aggregations
97
+ raise "No aggregations present in query" unless aggregated?
98
+ ret = build_for_count
99
+ ret.merge! size: 0
100
+ ret.merge! aggs: _aggregations.build
101
+ ret
102
+ end
103
+
104
+ def build_for_top_hits
105
+ ret = Hash.new
106
+ ret.merge! sort: _sort if _sort
107
+ ret.merge! size: _size if _size
108
+ ret.merge! from: _from if _from
109
+ ret.merge! _source: _source if _source
110
+ ret
111
+ end
112
+
113
+ def build_for_search
114
+ ret = build_for_count.merge build_for_top_hits
115
+ ret.merge! post_filter: _post_conditions.build unless _post_conditions.empty?
116
+ ret
117
+ end
118
+
119
+ def build_for_aggregated_search
120
+ ret = build_for_search
121
+ ret.merge! aggs: _aggregations.build unless _aggregations.empty?
122
+ ret
123
+ end
124
+ alias_method :build, :build_for_aggregated_search
125
+
126
+ # parse methods
127
+
128
+ def parse_aggregations(response)
129
+ _aggregations.parse response
130
+ end
131
+
132
+ # delimiters
133
+
134
+ def fill_delimiter(field_delimiter)
135
+ _conditions.fill_delimiter field_delimiter
136
+ _filter_conditions.fill_delimiter field_delimiter
137
+ end
138
+
139
+ # conditions & filter shorthands
140
+
141
+ private
142
+
143
+ def method_missing(name, *args, &block)
144
+ # delegates any "filter_*" method missing to the _filter_conditions object
145
+ # delegates any other method missing to the _conditions object
146
+ conditions_obj = name.to_s.start_with?('filter_') ? _filter_conditions : _conditions
147
+ real_method_name = name.to_s.gsub(/^filter\_/, '').to_sym
148
+ super unless conditions_obj.respond_to? real_method_name
149
+ conditions_obj.send real_method_name, *args, &block
150
+ self
151
+ end
152
+
153
+ def respond_to_missing?(name, include_private=false)
154
+ real_method_name = name.to_s.gsub(/^filter\_/, '').to_sym
155
+ _conditions.respond_to?(real_method_name) || super
156
+ end
157
+
158
+ end
159
+ end