elasticated 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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