tire-erez 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/.gitignore +14 -0
  2. data/.travis.yml +32 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +10 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.markdown +775 -0
  7. data/Rakefile +51 -0
  8. data/examples/rails-application-template.rb +263 -0
  9. data/examples/tire-dsl.rb +932 -0
  10. data/lib/tire.rb +59 -0
  11. data/lib/tire/alias.rb +296 -0
  12. data/lib/tire/configuration.rb +38 -0
  13. data/lib/tire/count.rb +85 -0
  14. data/lib/tire/dsl.rb +114 -0
  15. data/lib/tire/http/client.rb +62 -0
  16. data/lib/tire/http/clients/curb.rb +61 -0
  17. data/lib/tire/http/clients/faraday.rb +71 -0
  18. data/lib/tire/http/response.rb +27 -0
  19. data/lib/tire/index.rb +443 -0
  20. data/lib/tire/logger.rb +60 -0
  21. data/lib/tire/model/callbacks.rb +40 -0
  22. data/lib/tire/model/import.rb +27 -0
  23. data/lib/tire/model/indexing.rb +134 -0
  24. data/lib/tire/model/naming.rb +100 -0
  25. data/lib/tire/model/percolate.rb +99 -0
  26. data/lib/tire/model/persistence.rb +72 -0
  27. data/lib/tire/model/persistence/attributes.rb +148 -0
  28. data/lib/tire/model/persistence/finders.rb +54 -0
  29. data/lib/tire/model/persistence/storage.rb +77 -0
  30. data/lib/tire/model/search.rb +322 -0
  31. data/lib/tire/multi_search.rb +263 -0
  32. data/lib/tire/results/collection.rb +156 -0
  33. data/lib/tire/results/item.rb +94 -0
  34. data/lib/tire/results/pagination.rb +68 -0
  35. data/lib/tire/rubyext/hash.rb +8 -0
  36. data/lib/tire/rubyext/ruby_1_8.rb +1 -0
  37. data/lib/tire/rubyext/symbol.rb +11 -0
  38. data/lib/tire/rubyext/uri_escape.rb +74 -0
  39. data/lib/tire/search.rb +211 -0
  40. data/lib/tire/search/facet.rb +81 -0
  41. data/lib/tire/search/filter.rb +28 -0
  42. data/lib/tire/search/highlight.rb +37 -0
  43. data/lib/tire/search/queries/match.rb +40 -0
  44. data/lib/tire/search/query.rb +250 -0
  45. data/lib/tire/search/scan.rb +114 -0
  46. data/lib/tire/search/script_field.rb +23 -0
  47. data/lib/tire/search/sort.rb +25 -0
  48. data/lib/tire/tasks.rb +138 -0
  49. data/lib/tire/utils.rb +17 -0
  50. data/lib/tire/version.rb +18 -0
  51. data/test/fixtures/articles/1.json +1 -0
  52. data/test/fixtures/articles/2.json +1 -0
  53. data/test/fixtures/articles/3.json +1 -0
  54. data/test/fixtures/articles/4.json +1 -0
  55. data/test/fixtures/articles/5.json +1 -0
  56. data/test/integration/active_model_indexing_test.rb +51 -0
  57. data/test/integration/active_model_searchable_test.rb +114 -0
  58. data/test/integration/active_record_searchable_test.rb +620 -0
  59. data/test/integration/boolean_queries_test.rb +43 -0
  60. data/test/integration/boosting_queries_test.rb +32 -0
  61. data/test/integration/bulk_test.rb +86 -0
  62. data/test/integration/count_test.rb +64 -0
  63. data/test/integration/custom_score_queries_test.rb +89 -0
  64. data/test/integration/dis_max_queries_test.rb +68 -0
  65. data/test/integration/dsl_search_test.rb +30 -0
  66. data/test/integration/explanation_test.rb +44 -0
  67. data/test/integration/facets_test.rb +311 -0
  68. data/test/integration/filtered_queries_test.rb +66 -0
  69. data/test/integration/filters_test.rb +75 -0
  70. data/test/integration/fuzzy_queries_test.rb +20 -0
  71. data/test/integration/highlight_test.rb +64 -0
  72. data/test/integration/index_aliases_test.rb +122 -0
  73. data/test/integration/index_mapping_test.rb +43 -0
  74. data/test/integration/index_store_test.rb +112 -0
  75. data/test/integration/index_update_document_test.rb +121 -0
  76. data/test/integration/match_query_test.rb +79 -0
  77. data/test/integration/mongoid_searchable_test.rb +309 -0
  78. data/test/integration/multi_search_test.rb +114 -0
  79. data/test/integration/nested_query_test.rb +135 -0
  80. data/test/integration/percolator_test.rb +111 -0
  81. data/test/integration/persistent_model_test.rb +205 -0
  82. data/test/integration/prefix_query_test.rb +43 -0
  83. data/test/integration/query_return_version_test.rb +70 -0
  84. data/test/integration/query_string_test.rb +52 -0
  85. data/test/integration/range_queries_test.rb +36 -0
  86. data/test/integration/reindex_test.rb +56 -0
  87. data/test/integration/results_test.rb +58 -0
  88. data/test/integration/scan_test.rb +56 -0
  89. data/test/integration/script_fields_test.rb +38 -0
  90. data/test/integration/sort_test.rb +52 -0
  91. data/test/integration/text_query_test.rb +39 -0
  92. data/test/models/active_model_article.rb +31 -0
  93. data/test/models/active_model_article_with_callbacks.rb +49 -0
  94. data/test/models/active_model_article_with_custom_document_type.rb +7 -0
  95. data/test/models/active_model_article_with_custom_index_name.rb +7 -0
  96. data/test/models/active_record_models.rb +131 -0
  97. data/test/models/article.rb +15 -0
  98. data/test/models/mongoid_models.rb +85 -0
  99. data/test/models/persistent_article.rb +11 -0
  100. data/test/models/persistent_article_in_index.rb +16 -0
  101. data/test/models/persistent_article_in_namespace.rb +12 -0
  102. data/test/models/persistent_article_with_casting.rb +28 -0
  103. data/test/models/persistent_article_with_defaults.rb +12 -0
  104. data/test/models/persistent_article_with_percolation.rb +5 -0
  105. data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
  106. data/test/models/supermodel_article.rb +17 -0
  107. data/test/models/validated_model.rb +11 -0
  108. data/test/test_helper.rb +118 -0
  109. data/test/unit/active_model_lint_test.rb +17 -0
  110. data/test/unit/configuration_test.rb +84 -0
  111. data/test/unit/count_test.rb +67 -0
  112. data/test/unit/http_client_test.rb +79 -0
  113. data/test/unit/http_response_test.rb +49 -0
  114. data/test/unit/index_alias_test.rb +335 -0
  115. data/test/unit/index_test.rb +1098 -0
  116. data/test/unit/logger_test.rb +125 -0
  117. data/test/unit/model_callbacks_test.rb +116 -0
  118. data/test/unit/model_import_test.rb +75 -0
  119. data/test/unit/model_initialization_test.rb +31 -0
  120. data/test/unit/model_persistence_test.rb +548 -0
  121. data/test/unit/model_search_test.rb +964 -0
  122. data/test/unit/multi_search_test.rb +304 -0
  123. data/test/unit/results_collection_test.rb +372 -0
  124. data/test/unit/results_item_test.rb +173 -0
  125. data/test/unit/rubyext_test.rb +66 -0
  126. data/test/unit/search_facet_test.rb +186 -0
  127. data/test/unit/search_filter_test.rb +42 -0
  128. data/test/unit/search_highlight_test.rb +46 -0
  129. data/test/unit/search_query_test.rb +419 -0
  130. data/test/unit/search_scan_test.rb +113 -0
  131. data/test/unit/search_script_field_test.rb +26 -0
  132. data/test/unit/search_sort_test.rb +50 -0
  133. data/test/unit/search_test.rb +556 -0
  134. data/test/unit/tire_test.rb +144 -0
  135. data/tire.gemspec +83 -0
  136. metadata +586 -0
@@ -0,0 +1,28 @@
1
+ module Tire
2
+ module Search
3
+
4
+ # http://www.elasticsearch.org/guide/reference/api/search/filter.html
5
+ # http://www.elasticsearch.org/guide/reference/query-dsl/
6
+ #
7
+ class Filter
8
+
9
+ def initialize(type, *options)
10
+ value = if options.size < 2
11
+ options.first || {}
12
+ else
13
+ options # An +or+ filter encodes multiple filters as an array
14
+ end
15
+ @hash = { type => value }
16
+ end
17
+
18
+ def to_json(options={})
19
+ to_hash.to_json
20
+ end
21
+
22
+ def to_hash
23
+ @hash
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ module Tire
2
+ module Search
3
+
4
+ # http://www.elasticsearch.org/guide/reference/api/search/highlighting.html
5
+ #
6
+ class Highlight
7
+
8
+ def initialize(*args)
9
+ @options = (args.last.is_a?(Hash) && args.last.delete(:options)) || {}
10
+ extract_highlight_tags
11
+ @fields = args.inject({}) do |result, field|
12
+ field.is_a?(Hash) ? result.update(field) : result[field.to_sym] = {}; result
13
+ end
14
+ end
15
+
16
+ def to_json(options={})
17
+ to_hash.to_json
18
+ end
19
+
20
+ def to_hash
21
+ { :fields => @fields }.update @options
22
+ end
23
+
24
+ private
25
+
26
+ def extract_highlight_tags
27
+ if tag = @options.delete(:tag)
28
+ @options.update \
29
+ :pre_tags => [tag],
30
+ :post_tags => [tag.to_s.gsub(/^<([a-z]+).*/, '</\1>')]
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ module Tire
2
+ module Search
3
+ class Query
4
+
5
+ def match(field, value, options={})
6
+ if @value.empty?
7
+ @value = MatchQuery.new(field, value, options).to_hash
8
+ else
9
+ MatchQuery.add(self, field, value, options)
10
+ end
11
+ @value
12
+ end
13
+ end
14
+
15
+ class MatchQuery
16
+ def initialize(field, value, options={})
17
+ query_options = { :query => value }.merge(options)
18
+
19
+ if field.is_a?(Array)
20
+ @value = { :multi_match => query_options.merge( :fields => field ) }
21
+ else
22
+ @value = { :match => { field => query_options } }
23
+ end
24
+ end
25
+
26
+ def self.add(query, field, value, options={})
27
+ unless query.value[:bool]
28
+ original_value = query.value.dup
29
+ query.value = { :bool => {} }
30
+ (query.value[:bool][:must] ||= []) << original_value
31
+ end
32
+ query.value[:bool][:must] << MatchQuery.new(field, value, options).to_hash
33
+ end
34
+
35
+ def to_hash
36
+ @value
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,250 @@
1
+ module Tire
2
+ module Search
3
+
4
+ class Query
5
+ attr_accessor :value
6
+
7
+ def initialize(&block)
8
+ @value = {}
9
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
10
+ end
11
+
12
+ def term(field, value, options={})
13
+ query = if value.is_a?(Hash)
14
+ { field => value.to_hash }
15
+ else
16
+ { field => { :term => value }.update(options) }
17
+ end
18
+ @value = { :term => query }
19
+ end
20
+
21
+ def terms(field, value, options={})
22
+ @value = { :terms => { field => value } }
23
+ @value[:terms].update( { :minimum_match => options[:minimum_match] } ) if options[:minimum_match]
24
+ @value
25
+ end
26
+
27
+ def range(field, value)
28
+ @value = { :range => { field => value } }
29
+ end
30
+
31
+ def text(field, value, options={})
32
+ Tire.warn "The 'text' query has been deprecated, please use a 'match' query."
33
+ query_options = { :query => value }.update(options)
34
+ @value = { :text => { field => query_options } }
35
+ @value
36
+ end
37
+
38
+ def string(value, options={})
39
+ @value = { :query_string => { :query => value } }
40
+ @value[:query_string].update(options)
41
+ @value
42
+ end
43
+
44
+ def prefix(field, value, options={})
45
+ if options[:boost]
46
+ @value = { :prefix => { field => { :prefix => value, :boost => options[:boost] } } }
47
+ else
48
+ @value = { :prefix => { field => value } }
49
+ end
50
+ end
51
+
52
+ def custom_score(options={}, &block)
53
+ @custom_score ||= Query.new(&block)
54
+ @value[:custom_score] = options
55
+ @value[:custom_score].update({:query => @custom_score.to_hash})
56
+ @value
57
+ end
58
+
59
+ def fuzzy(field, value, options={})
60
+ query = { field => { :term => value }.update(options) }
61
+ @value = { :fuzzy => query }
62
+ end
63
+
64
+ def boolean(options={}, &block)
65
+ @boolean ||= BooleanQuery.new(options)
66
+ block.arity < 1 ? @boolean.instance_eval(&block) : block.call(@boolean) if block_given?
67
+ @value[:bool] = @boolean.to_hash
68
+ @value
69
+ end
70
+
71
+ def filtered(&block)
72
+ @filtered = FilteredQuery.new
73
+ block.arity < 1 ? @filtered.instance_eval(&block) : block.call(@filtered) if block_given?
74
+ @value[:filtered] = @filtered.to_hash
75
+ @value
76
+ end
77
+
78
+ def dis_max(options={}, &block)
79
+ @dis_max ||= DisMaxQuery.new(options)
80
+ block.arity < 1 ? @dis_max.instance_eval(&block) : block.call(@dis_max) if block_given?
81
+ @value[:dis_max] = @dis_max.to_hash
82
+ @value
83
+ end
84
+
85
+ def nested(options={}, &block)
86
+ @nested = NestedQuery.new(options)
87
+ block.arity < 1 ? @nested.instance_eval(&block) : block.call(@nested) if block_given?
88
+ @value[:nested] = @nested.to_hash
89
+ @value
90
+ end
91
+
92
+ def all(options = {})
93
+ @value = { :match_all => options }
94
+ @value
95
+ end
96
+
97
+ def ids(values, type)
98
+ @value = { :ids => { :values => values, :type => type } }
99
+ end
100
+
101
+ def boosting(options={}, &block)
102
+ @boosting ||= BoostingQuery.new(options)
103
+ block.arity < 1 ? @boosting.instance_eval(&block) : block.call(@boosting) if block_given?
104
+ @value[:boosting] = @boosting.to_hash
105
+ @value
106
+ end
107
+
108
+ def to_hash
109
+ @value
110
+ end
111
+
112
+ def to_json(options={})
113
+ to_hash.to_json
114
+ end
115
+
116
+ end
117
+
118
+ class BooleanQuery
119
+
120
+ # TODO: Try to get rid of multiple `should`, `must`, etc invocations, and wrap queries directly:
121
+ #
122
+ # boolean do
123
+ # should do
124
+ # string 'foo'
125
+ # string 'bar'
126
+ # end
127
+ # end
128
+ #
129
+ # Inherit from Query, implement `encode` method there, and overload it here, so it puts
130
+ # queries in an Array instead of hash.
131
+
132
+ def initialize(options={}, &block)
133
+ @options = options
134
+ @value = {}
135
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
136
+ end
137
+
138
+ def must(&block)
139
+ (@value[:must] ||= []) << Query.new(&block).to_hash
140
+ @value
141
+ end
142
+
143
+ def must_not(&block)
144
+ (@value[:must_not] ||= []) << Query.new(&block).to_hash
145
+ @value
146
+ end
147
+
148
+ def should(&block)
149
+ (@value[:should] ||= []) << Query.new(&block).to_hash
150
+ @value
151
+ end
152
+
153
+ def to_hash
154
+ @value.update(@options)
155
+ end
156
+ end
157
+
158
+ class FilteredQuery
159
+ def initialize(&block)
160
+ @value = {}
161
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
162
+ end
163
+
164
+ def query(options={}, &block)
165
+ @value[:query] = Query.new(&block).to_hash
166
+ @value
167
+ end
168
+
169
+ def filter(type, *options)
170
+ @value[:filter] ||= {}
171
+ @value[:filter][:and] ||= []
172
+ @value[:filter][:and] << Filter.new(type, *options).to_hash
173
+ @value
174
+ end
175
+
176
+ def to_hash
177
+ @value
178
+ end
179
+
180
+ def to_json(options={})
181
+ to_hash.to_json
182
+ end
183
+ end
184
+
185
+ class DisMaxQuery
186
+ def initialize(options={}, &block)
187
+ @options = options
188
+ @value = {}
189
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
190
+ end
191
+
192
+ def query(&block)
193
+ (@value[:queries] ||= []) << Query.new(&block).to_hash
194
+ @value
195
+ end
196
+
197
+ def to_hash
198
+ @value.update(@options)
199
+ end
200
+
201
+ def to_json(options={})
202
+ to_hash.to_json
203
+ end
204
+ end
205
+
206
+ class NestedQuery
207
+ def initialize(options={}, &block)
208
+ @options = options
209
+ @value = {}
210
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
211
+ end
212
+
213
+ def query(&block)
214
+ @value[:query] = Query.new(&block).to_hash
215
+ @value
216
+ end
217
+
218
+ def to_hash
219
+ @value.update(@options)
220
+ end
221
+
222
+ def to_json
223
+ to_hash.to_json
224
+ end
225
+ end
226
+
227
+ class BoostingQuery
228
+ def initialize(options={}, &block)
229
+ @options = options
230
+ @value = {}
231
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
232
+ end
233
+
234
+ def positive(&block)
235
+ (@value[:positive] ||= []) << Query.new(&block).to_hash
236
+ @value
237
+ end
238
+
239
+ def negative(&block)
240
+ (@value[:negative] ||= []) << Query.new(&block).to_hash
241
+ @value
242
+ end
243
+
244
+ def to_hash
245
+ @value.update(@options)
246
+ end
247
+ end
248
+
249
+ end
250
+ end
@@ -0,0 +1,114 @@
1
+ module Tire
2
+ module Search
3
+
4
+
5
+ # Performs a "scan/scroll" search request, which obtains a `scroll_id`
6
+ # and keeps returning documents matching the passed query (or all documents) in batches.
7
+ #
8
+ # You may want to iterate over the batches being returned:
9
+ #
10
+ # search = Tire::Search::Scan.new('articles')
11
+ # search.each do |results|
12
+ # puts results.map(&:title)
13
+ # end
14
+ #
15
+ # The scan object has a fully Enumerable-compatible interface, so you may
16
+ # call methods like `map` or `each_with_index` on it.
17
+ #
18
+ # To iterate over individual documents, use the `each_document` method:
19
+ #
20
+ # search.each_document do |document|
21
+ # puts document.title
22
+ # end
23
+ #
24
+ # You may limit the result set being returned by a regular Tire DSL query
25
+ # (or a hash, if you prefer), passed as a second argument:
26
+ #
27
+ # search = Tire::Search::Scan.new('articles') do
28
+ # query { term 'author.exact', 'John Smith' }
29
+ # end
30
+ #
31
+ # The feature is also exposed in the Tire top-level DSL:
32
+ #
33
+ # search = Tire.scan 'articles' do
34
+ # query { term 'author.exact', 'John Smith' }
35
+ # end
36
+ #
37
+ # See Elasticsearch documentation for further reference:
38
+ #
39
+ # * http://www.elasticsearch.org/guide/reference/api/search/search-type.html
40
+ # * http://www.elasticsearch.org/guide/reference/api/search/scroll.html
41
+ #
42
+ class Scan
43
+ include Enumerable
44
+
45
+ attr_reader :indices, :options, :search
46
+
47
+ def initialize(indices=nil, options={}, &block)
48
+ @indices = Array(indices)
49
+ @options = options.update(:search_type => 'scan', :scroll => '10m')
50
+ @seen = 0
51
+ @search = Search.new(@indices, @options, &block)
52
+ end
53
+
54
+ def url; Configuration.url + "/_search/scroll"; end
55
+ def params; @options.empty? ? '' : '?' + @options.to_param; end
56
+ def results; @results || (__perform; @results); end
57
+ def response; @response || (__perform; @response); end
58
+ def json; @json || (__perform; @json); end
59
+ def total; @total || (__perform; @total); end
60
+ def seen; @seen || (__perform; @seen); end
61
+
62
+ def scroll_id
63
+ @scroll_id ||= @search.perform.json['_scroll_id']
64
+ end
65
+
66
+ def each
67
+ until results.empty?
68
+ yield results.results
69
+ __perform
70
+ end
71
+ end
72
+
73
+ def each_document
74
+ until results.empty?
75
+ results.each { |item| yield item }
76
+ __perform
77
+ end
78
+ end
79
+
80
+ def size
81
+ results.size
82
+ end
83
+
84
+ def __perform
85
+ @response = Configuration.client.get [url, params].join, scroll_id
86
+ @json = MultiJson.decode @response.body
87
+ @results = Results::Collection.new @json, @options
88
+ @total = @json['hits']['total'].to_i
89
+ @seen += @results.size
90
+ @scroll_id = @json['_scroll_id']
91
+ return self
92
+ ensure
93
+ __logged
94
+ end
95
+
96
+ def to_a; results; end; alias :to_ary :to_a
97
+ def to_curl; %Q|curl -X GET '#{url}?pretty' -d '#{@scroll_id}'|; end
98
+
99
+ def __logged(error=nil)
100
+ if Configuration.logger
101
+ Configuration.logger.log_request 'scroll', nil, to_curl
102
+
103
+ took = @json['took'] rescue nil
104
+ code = @response.code rescue nil
105
+ body = "#{@seen}/#{@total} (#{@seen/@total.to_f*100}%)" rescue nil
106
+
107
+ Configuration.logger.log_response code || 'N/A', took || 'N/A', body
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+ end