load_balanced_tire 0.1

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