load_balanced_tire 0.1

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 (121) hide show
  1. data/.gitignore +14 -0
  2. data/.travis.yml +29 -0
  3. data/Gemfile +4 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.markdown +760 -0
  6. data/Rakefile +78 -0
  7. data/examples/rails-application-template.rb +249 -0
  8. data/examples/tire-dsl.rb +876 -0
  9. data/lib/tire.rb +55 -0
  10. data/lib/tire/alias.rb +296 -0
  11. data/lib/tire/configuration.rb +30 -0
  12. data/lib/tire/dsl.rb +43 -0
  13. data/lib/tire/http/client.rb +62 -0
  14. data/lib/tire/http/clients/curb.rb +61 -0
  15. data/lib/tire/http/clients/faraday.rb +71 -0
  16. data/lib/tire/http/response.rb +27 -0
  17. data/lib/tire/index.rb +361 -0
  18. data/lib/tire/logger.rb +60 -0
  19. data/lib/tire/model/callbacks.rb +40 -0
  20. data/lib/tire/model/import.rb +26 -0
  21. data/lib/tire/model/indexing.rb +128 -0
  22. data/lib/tire/model/naming.rb +100 -0
  23. data/lib/tire/model/percolate.rb +99 -0
  24. data/lib/tire/model/persistence.rb +71 -0
  25. data/lib/tire/model/persistence/attributes.rb +143 -0
  26. data/lib/tire/model/persistence/finders.rb +66 -0
  27. data/lib/tire/model/persistence/storage.rb +69 -0
  28. data/lib/tire/model/search.rb +307 -0
  29. data/lib/tire/results/collection.rb +114 -0
  30. data/lib/tire/results/item.rb +86 -0
  31. data/lib/tire/results/pagination.rb +54 -0
  32. data/lib/tire/rubyext/hash.rb +8 -0
  33. data/lib/tire/rubyext/ruby_1_8.rb +7 -0
  34. data/lib/tire/rubyext/symbol.rb +11 -0
  35. data/lib/tire/search.rb +188 -0
  36. data/lib/tire/search/facet.rb +74 -0
  37. data/lib/tire/search/filter.rb +28 -0
  38. data/lib/tire/search/highlight.rb +37 -0
  39. data/lib/tire/search/query.rb +186 -0
  40. data/lib/tire/search/scan.rb +114 -0
  41. data/lib/tire/search/script_field.rb +23 -0
  42. data/lib/tire/search/sort.rb +25 -0
  43. data/lib/tire/tasks.rb +135 -0
  44. data/lib/tire/utils.rb +17 -0
  45. data/lib/tire/version.rb +22 -0
  46. data/test/fixtures/articles/1.json +1 -0
  47. data/test/fixtures/articles/2.json +1 -0
  48. data/test/fixtures/articles/3.json +1 -0
  49. data/test/fixtures/articles/4.json +1 -0
  50. data/test/fixtures/articles/5.json +1 -0
  51. data/test/integration/active_model_indexing_test.rb +51 -0
  52. data/test/integration/active_model_searchable_test.rb +114 -0
  53. data/test/integration/active_record_searchable_test.rb +446 -0
  54. data/test/integration/boolean_queries_test.rb +43 -0
  55. data/test/integration/count_test.rb +34 -0
  56. data/test/integration/custom_score_queries_test.rb +88 -0
  57. data/test/integration/dis_max_queries_test.rb +68 -0
  58. data/test/integration/dsl_search_test.rb +22 -0
  59. data/test/integration/explanation_test.rb +44 -0
  60. data/test/integration/facets_test.rb +259 -0
  61. data/test/integration/filtered_queries_test.rb +66 -0
  62. data/test/integration/filters_test.rb +63 -0
  63. data/test/integration/fuzzy_queries_test.rb +20 -0
  64. data/test/integration/highlight_test.rb +64 -0
  65. data/test/integration/index_aliases_test.rb +122 -0
  66. data/test/integration/index_mapping_test.rb +43 -0
  67. data/test/integration/index_store_test.rb +96 -0
  68. data/test/integration/index_update_document_test.rb +111 -0
  69. data/test/integration/mongoid_searchable_test.rb +309 -0
  70. data/test/integration/percolator_test.rb +111 -0
  71. data/test/integration/persistent_model_test.rb +130 -0
  72. data/test/integration/prefix_query_test.rb +43 -0
  73. data/test/integration/query_return_version_test.rb +70 -0
  74. data/test/integration/query_string_test.rb +52 -0
  75. data/test/integration/range_queries_test.rb +36 -0
  76. data/test/integration/reindex_test.rb +46 -0
  77. data/test/integration/results_test.rb +39 -0
  78. data/test/integration/scan_test.rb +56 -0
  79. data/test/integration/script_fields_test.rb +38 -0
  80. data/test/integration/sort_test.rb +36 -0
  81. data/test/integration/text_query_test.rb +39 -0
  82. data/test/models/active_model_article.rb +31 -0
  83. data/test/models/active_model_article_with_callbacks.rb +49 -0
  84. data/test/models/active_model_article_with_custom_document_type.rb +7 -0
  85. data/test/models/active_model_article_with_custom_index_name.rb +7 -0
  86. data/test/models/active_record_models.rb +122 -0
  87. data/test/models/article.rb +15 -0
  88. data/test/models/mongoid_models.rb +97 -0
  89. data/test/models/persistent_article.rb +11 -0
  90. data/test/models/persistent_article_in_namespace.rb +12 -0
  91. data/test/models/persistent_article_with_casting.rb +28 -0
  92. data/test/models/persistent_article_with_defaults.rb +11 -0
  93. data/test/models/persistent_articles_with_custom_index_name.rb +10 -0
  94. data/test/models/supermodel_article.rb +17 -0
  95. data/test/models/validated_model.rb +11 -0
  96. data/test/test_helper.rb +93 -0
  97. data/test/unit/active_model_lint_test.rb +17 -0
  98. data/test/unit/configuration_test.rb +74 -0
  99. data/test/unit/http_client_test.rb +76 -0
  100. data/test/unit/http_response_test.rb +49 -0
  101. data/test/unit/index_alias_test.rb +275 -0
  102. data/test/unit/index_test.rb +894 -0
  103. data/test/unit/logger_test.rb +125 -0
  104. data/test/unit/model_callbacks_test.rb +116 -0
  105. data/test/unit/model_import_test.rb +71 -0
  106. data/test/unit/model_persistence_test.rb +528 -0
  107. data/test/unit/model_search_test.rb +913 -0
  108. data/test/unit/results_collection_test.rb +281 -0
  109. data/test/unit/results_item_test.rb +162 -0
  110. data/test/unit/rubyext_test.rb +66 -0
  111. data/test/unit/search_facet_test.rb +153 -0
  112. data/test/unit/search_filter_test.rb +42 -0
  113. data/test/unit/search_highlight_test.rb +46 -0
  114. data/test/unit/search_query_test.rb +301 -0
  115. data/test/unit/search_scan_test.rb +113 -0
  116. data/test/unit/search_script_field_test.rb +26 -0
  117. data/test/unit/search_sort_test.rb +50 -0
  118. data/test/unit/search_test.rb +499 -0
  119. data/test/unit/tire_test.rb +126 -0
  120. data/tire.gemspec +90 -0
  121. metadata +549 -0
@@ -0,0 +1,74 @@
1
+ module Tire
2
+ module Search
3
+
4
+ #--
5
+ # TODO: Implement all elastic search facets (geo, histogram, range, etc)
6
+ # http://elasticsearch.org/guide/reference/api/search/facets/
7
+ #++
8
+
9
+ class Facet
10
+
11
+ def initialize(name, options={}, &block)
12
+ @name = name
13
+ @options = options
14
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
15
+ end
16
+
17
+ def terms(field, options={})
18
+ size = options.delete(:size) || 10
19
+ all_terms = options.delete(:all_terms) || false
20
+ @value = if field.is_a?(Enumerable) and not field.is_a?(String)
21
+ { :terms => { :fields => field }.update({ :size => size, :all_terms => all_terms }).update(options) }
22
+ else
23
+ { :terms => { :field => field }.update({ :size => size, :all_terms => all_terms }).update(options) }
24
+ end
25
+ self
26
+ end
27
+
28
+ def date(field, options={})
29
+ interval = options.delete(:interval) || 'day'
30
+ @value = { :date_histogram => { :field => field, :interval => interval }.update(options) }
31
+ self
32
+ end
33
+
34
+ def range(field, ranges=[], options={})
35
+ @value = { :range => { :field => field, :ranges => ranges }.update(options) }
36
+ self
37
+ end
38
+
39
+ def histogram(field, options={})
40
+ @value = { :histogram => (options.delete(:histogram) || {:field => field}.update(options)) }
41
+ self
42
+ end
43
+
44
+ def statistical(field, options={})
45
+ @value = { :statistical => (options.delete(:statistical) || {:field => field}.update(options)) }
46
+ self
47
+ end
48
+
49
+ def terms_stats(key_field, value_field, options={})
50
+ @value = { :terms_stats => {:key_field => key_field, :value_field => value_field}.update(options) }
51
+ self
52
+ end
53
+
54
+ def query(&block)
55
+ @value = { :query => Query.new(&block).to_hash }
56
+ end
57
+
58
+ def filter(field, value, options={})
59
+ @value = { :filter => { :term => { field => value }}.update(options) }
60
+ self
61
+ end
62
+
63
+ def to_json
64
+ to_hash.to_json
65
+ end
66
+
67
+ def to_hash
68
+ @value.update @options
69
+ { @name => @value }
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -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
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
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,186 @@
1
+ module Tire
2
+ module Search
3
+
4
+ class Query
5
+ def initialize(&block)
6
+ @value = {}
7
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
8
+ end
9
+
10
+ def term(field, value, options={})
11
+ query = { field => { :term => value }.update(options) }
12
+ @value = { :term => query }
13
+ end
14
+
15
+ def terms(field, value, options={})
16
+ @value = { :terms => { field => value } }
17
+ @value[:terms].update( { :minimum_match => options[:minimum_match] } ) if options[:minimum_match]
18
+ @value
19
+ end
20
+
21
+ def range(field, value)
22
+ @value = { :range => { field => value } }
23
+ end
24
+
25
+ def text(field, value, options={})
26
+ query_options = { :query => value }.update(options)
27
+ @value = { :text => { field => query_options } }
28
+ @value
29
+ end
30
+
31
+ def string(value, options={})
32
+ @value = { :query_string => { :query => value } }
33
+ @value[:query_string].update(options)
34
+ @value
35
+ end
36
+
37
+ def prefix(field, value, options={})
38
+ if options[:boost]
39
+ @value = { :prefix => { field => { :prefix => value, :boost => options[:boost] } } }
40
+ else
41
+ @value = { :prefix => { field => value } }
42
+ end
43
+ end
44
+
45
+ def custom_score(options={}, &block)
46
+ @custom_score ||= Query.new(&block)
47
+ @value[:custom_score] = options
48
+ @value[:custom_score].update({:query => @custom_score.to_hash})
49
+ @value
50
+ end
51
+
52
+ def fuzzy(field, value, options={})
53
+ query = { field => { :term => value }.update(options) }
54
+ @value = { :fuzzy => query }
55
+ end
56
+
57
+ def boolean(options={}, &block)
58
+ @boolean ||= BooleanQuery.new(options)
59
+ block.arity < 1 ? @boolean.instance_eval(&block) : block.call(@boolean) if block_given?
60
+ @value[:bool] = @boolean.to_hash
61
+ @value
62
+ end
63
+
64
+ def filtered(&block)
65
+ @filtered = FilteredQuery.new
66
+ block.arity < 1 ? @filtered.instance_eval(&block) : block.call(@filtered) if block_given?
67
+ @value[:filtered] = @filtered.to_hash
68
+ @value
69
+ end
70
+
71
+ def dis_max(options={}, &block)
72
+ @dis_max ||= DisMaxQuery.new(options)
73
+ block.arity < 1 ? @dis_max.instance_eval(&block) : block.call(@dis_max) if block_given?
74
+ @value[:dis_max] = @dis_max.to_hash
75
+ @value
76
+ end
77
+
78
+ def all
79
+ @value = { :match_all => {} }
80
+ @value
81
+ end
82
+
83
+ def ids(values, type)
84
+ @value = { :ids => { :values => values, :type => type } }
85
+ end
86
+
87
+ def to_hash
88
+ @value
89
+ end
90
+
91
+ def to_json
92
+ to_hash.to_json
93
+ end
94
+
95
+ end
96
+
97
+ class BooleanQuery
98
+
99
+ # TODO: Try to get rid of multiple `should`, `must`, etc invocations, and wrap queries directly:
100
+ #
101
+ # boolean do
102
+ # should do
103
+ # string 'foo'
104
+ # string 'bar'
105
+ # end
106
+ # end
107
+ #
108
+ # Inherit from Query, implement `encode` method there, and overload it here, so it puts
109
+ # queries in an Array instead of hash.
110
+
111
+ def initialize(options={}, &block)
112
+ @options = options
113
+ @value = {}
114
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
115
+ end
116
+
117
+ def must(&block)
118
+ (@value[:must] ||= []) << Query.new(&block).to_hash
119
+ @value
120
+ end
121
+
122
+ def must_not(&block)
123
+ (@value[:must_not] ||= []) << Query.new(&block).to_hash
124
+ @value
125
+ end
126
+
127
+ def should(&block)
128
+ (@value[:should] ||= []) << Query.new(&block).to_hash
129
+ @value
130
+ end
131
+
132
+ def to_hash
133
+ @value.update(@options)
134
+ end
135
+ end
136
+
137
+ class FilteredQuery
138
+ def initialize(&block)
139
+ @value = {}
140
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
141
+ end
142
+
143
+ def query(options={}, &block)
144
+ @value[:query] = Query.new(&block).to_hash
145
+ @value
146
+ end
147
+
148
+ def filter(type, *options)
149
+ @value[:filter] ||= {}
150
+ @value[:filter][:and] ||= []
151
+ @value[:filter][:and] << Filter.new(type, *options).to_hash
152
+ @value
153
+ end
154
+
155
+ def to_hash
156
+ @value
157
+ end
158
+
159
+ def to_json
160
+ to_hash.to_json
161
+ end
162
+ end
163
+
164
+ class DisMaxQuery
165
+ def initialize(options={}, &block)
166
+ @options = options
167
+ @value = {}
168
+ block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
169
+ end
170
+
171
+ def query(&block)
172
+ (@value[:queries] ||= []) << Query.new(&block).to_hash
173
+ @value
174
+ end
175
+
176
+ def to_hash
177
+ @value.update(@options)
178
+ end
179
+
180
+ def to_json
181
+ to_hash.to_json
182
+ end
183
+ end
184
+
185
+ end
186
+ 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=true" -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
@@ -0,0 +1,23 @@
1
+ module Tire
2
+ module Search
3
+
4
+ # http://www.elasticsearch.org/guide/reference/api/search/script-fields.html
5
+ # http://www.elasticsearch.org/guide/reference/modules/scripting.html
6
+
7
+ class ScriptField
8
+
9
+ def initialize(name, options)
10
+ @hash = { name => options }
11
+ end
12
+
13
+ def to_json
14
+ to_hash.to_json
15
+ end
16
+
17
+ def to_hash
18
+ @hash
19
+ end
20
+ end
21
+
22
+ end
23
+ end