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
data/lib/tire.rb ADDED
@@ -0,0 +1,55 @@
1
+ require 'rest_client'
2
+ require 'multi_json'
3
+ require 'active_model'
4
+ require 'hashr'
5
+ require 'cgi'
6
+
7
+ require 'active_support/core_ext/object/to_param'
8
+ require 'active_support/core_ext/object/to_query'
9
+ require 'active_support/core_ext/hash/except.rb'
10
+
11
+ # Ruby 1.8 compatibility
12
+ require 'tire/rubyext/ruby_1_8' if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
13
+
14
+ require 'tire/rubyext/hash'
15
+ require 'tire/rubyext/symbol'
16
+ require 'tire/utils'
17
+ require 'tire/logger'
18
+ require 'tire/configuration'
19
+ require 'tire/http/response'
20
+ require 'tire/http/client'
21
+ require 'tire/search'
22
+ require 'tire/search/query'
23
+ require 'tire/search/sort'
24
+ require 'tire/search/facet'
25
+ require 'tire/search/filter'
26
+ require 'tire/search/highlight'
27
+ require 'tire/search/scan'
28
+ require 'tire/search/script_field'
29
+ require 'tire/results/pagination'
30
+ require 'tire/results/collection'
31
+ require 'tire/results/item'
32
+ require 'tire/index'
33
+ require 'tire/alias'
34
+ require 'tire/dsl'
35
+ require 'tire/model/naming'
36
+ require 'tire/model/callbacks'
37
+ require 'tire/model/percolate'
38
+ require 'tire/model/indexing'
39
+ require 'tire/model/import'
40
+ require 'tire/model/search'
41
+ require 'tire/model/persistence/finders'
42
+ require 'tire/model/persistence/attributes'
43
+ require 'tire/model/persistence/storage'
44
+ require 'tire/model/persistence'
45
+ require 'tire/tasks'
46
+
47
+ module Tire
48
+ extend DSL
49
+
50
+ def warn(message)
51
+ line = caller.detect { |line| line !~ %r|lib\/tire\/| }.sub(/:in .*/, '')
52
+ STDERR.puts "", "\e[31m[DEPRECATION WARNING] #{message}", "(Called from #{line})", "\e[0m"
53
+ end
54
+ module_function :warn
55
+ end
data/lib/tire/alias.rb ADDED
@@ -0,0 +1,296 @@
1
+ module Tire
2
+
3
+ # Represents an *alias* in _ElasticSearch_. An alias may point to one or multiple
4
+ # indices, for instance to separate physical indices into logical entities, where
5
+ # each user has a "virtual index" or for setting up "sliding window" scenarios.
6
+ #
7
+ # See: http://www.elasticsearch.org/guide/reference/api/admin-indices-aliases.html
8
+ #
9
+ class Alias
10
+
11
+ # Create an alias pointing to multiple indices:
12
+ #
13
+ # Tire::Alias.create name: 'my_alias', indices: ['index_1', 'index_2']
14
+ #
15
+ # Pass the routing and/or filtering configuration in the options Hash:
16
+ #
17
+ # a = Tire::Alias.new name: 'index_anne',
18
+ # indices: ['index_2012_04', 'index_2012_03', 'index_2012_02'],
19
+ # routing: 1,
20
+ # filter: { :terms => { :user => 'anne' } }
21
+ # a.save
22
+ #
23
+ # You may configure the alias in an imperative manner:
24
+ #
25
+ # a = Tire::Alias.new
26
+ # a.name('index_anne')
27
+ # a.index('index_2012_04')
28
+ # a.index('index_2012_03')
29
+ # # ...
30
+ # a.save
31
+ #
32
+ # or more declaratively, with a block:
33
+ #
34
+ # Tire::Alias.new name: 'my_alias' do
35
+ # index 'index_A'
36
+ # index 'index_B'
37
+ # filter :terms, username: 'mary'
38
+ # end
39
+ #
40
+ # To update an existing alias, find it by name, configure it and save it:
41
+ #
42
+ # a = Tire::Alias.find('my_alias')
43
+ # a.indices.delete 'index_A'
44
+ # a.indices.add 'index_B'
45
+ # a.indices.add 'index_C'
46
+ # a.save
47
+ #
48
+ # Or do it with a block:
49
+ #
50
+ # Tire::Alias.find('articles_aliased') do |a|
51
+ # a.indices.remove 'articles_2'
52
+ # puts '---', "#{a.name} says: /me as JSON >", a.as_json, '---'
53
+ # a.save
54
+ # end
55
+ #
56
+ # To remove indices from alias, you may want to use the `delete_all` method:
57
+ #
58
+ #
59
+ # require 'active_support/core_ext/numeric'
60
+ # require 'active_support/core_ext/date/calculations'
61
+ #
62
+ # a = Tire::Alias.find('articles_aliased')
63
+ # a.indices.delete_if do |i|
64
+ # Time.parse( i.gsub(/articles_/, '') ) < 4.weeks.ago rescue false
65
+ # end
66
+ # a.save
67
+ #
68
+ # To get all aliases, use the `Tire::Alias.all` method:
69
+ #
70
+ # Tire::Alias.all.each do |a|
71
+ # puts "#{a.name.rjust(30)} points to: #{a.indices}"
72
+ # end
73
+ #
74
+ def initialize(attributes={}, &block)
75
+ raise ArgumentError, "Please pass a Hash-like object" unless attributes.respond_to?(:each_pair)
76
+
77
+ @attributes = { :indices => IndexCollection.new([]) }
78
+
79
+ attributes.each_pair do |key, value|
80
+ if key.to_s =~ /index|indices/
81
+ @attributes[:indices] = IndexCollection.new(value)
82
+ else
83
+ @attributes[key.to_sym] = value
84
+ end
85
+ end
86
+
87
+ block.arity < 1 ? instance_eval(&block) : block.call(self) if block_given?
88
+ end
89
+
90
+ # Returns a collection of Tire::Alias objects for all aliases defined in the cluster, or for a specific index.
91
+ #
92
+ def self.all(index=nil)
93
+ @response = Configuration.client.get [Configuration.url, index, '_aliases'].compact.join('/')
94
+
95
+ aliases = MultiJson.decode(@response.body).inject({}) do |result, (index, value)|
96
+ # 1] Skip indices without aliases
97
+ next result if value['aliases'].empty?
98
+
99
+ # 2] Build a reverse map of hashes (alias => indices, config)
100
+ value['aliases'].each do |key, value| (result[key] ||= { 'indices' => [] }).update(value)['indices'].push(index) end
101
+ result
102
+ end
103
+
104
+ # 3] Build a collection of Alias objects from hashes
105
+ aliases.map do |key, value|
106
+ self.new(value.update('name' => key))
107
+ end
108
+
109
+ ensure
110
+ # FIXME: Extract the `logged` method
111
+ Alias.new.logged '_aliases', %Q|curl "#{Configuration.url}/_aliases"|
112
+ end
113
+
114
+ # Returns an alias by name
115
+ #
116
+ def self.find(name, &block)
117
+ a = all.select { |a| a.name == name }.first
118
+ block.call(a) if block_given?
119
+ return a
120
+ end
121
+
122
+ # Create new alias
123
+ #
124
+ def self.create(attributes={}, &block)
125
+ new(attributes, &block).save
126
+ end
127
+
128
+ # Delegate to the `@attributes` Hash
129
+ #
130
+ def method_missing(method_name, *arguments)
131
+ @attributes.has_key?(method_name.to_sym) ? @attributes[method_name.to_sym] : super
132
+ end
133
+
134
+ # Get or set the alias name
135
+ #
136
+ def name(value=nil)
137
+ value ? (@attributes[:name] = value and return self) : @attributes[:name]
138
+ end
139
+
140
+ # Get or set the alias indices
141
+ #
142
+ def indices(*names)
143
+ names = Array(names).flatten
144
+ names.compact.empty? ? @attributes[:indices] : (names.each { |n| @attributes[:indices].push(n) } and return self)
145
+ end
146
+ alias_method :index, :indices
147
+
148
+ # Get or set the alias routing
149
+ #
150
+ def routing(value=nil)
151
+ value ? (@attributes[:routing] = value and return self) : @attributes[:routing]
152
+ end
153
+
154
+ # Get or set the alias routing
155
+ def filter(type=nil, *options)
156
+ type ? (@attributes[:filter] = Search::Filter.new(type, *options).to_hash and return self ) : @attributes[:filter]
157
+ end
158
+
159
+ # Save the alias in _ElasticSearch_
160
+ #
161
+ def save
162
+ @response = Configuration.client.post "#{Configuration.url}/_aliases", to_json
163
+
164
+ ensure
165
+ logged '_aliases', %Q|curl -X POST "#{Configuration.url}/_aliases" -d '#{to_json}'|
166
+ end
167
+
168
+ # Return a Hash suitable for JSON serialization
169
+ #
170
+ def as_json(options=nil)
171
+ actions = []
172
+ indices.add_indices.each do |index|
173
+ operation = { :index => index, :alias => name }
174
+ operation.update( { :routing => routing } ) if respond_to?(:routing) and routing
175
+ operation.update( { :filter => filter } ) if respond_to?(:filter) and filter
176
+ actions.push( { :add => operation } )
177
+ end
178
+
179
+ indices.remove_indices.each do |index|
180
+ operation = { :index => index, :alias => name }
181
+ actions.push( { :remove => operation } )
182
+ end
183
+
184
+ { :actions => actions }
185
+ end
186
+
187
+ # Return alias serialized in JSON for _ElasticSearch_
188
+ #
189
+ def to_json(options=nil)
190
+ as_json.to_json
191
+ end
192
+
193
+ def inspect
194
+ %Q|<#{self.class} #{@attributes.inspect}>|
195
+ end
196
+
197
+ def to_s
198
+ name
199
+ end
200
+
201
+ def logged(endpoint='/', curl='')
202
+ # FIXME: Extract the `logged` method into module and mix it into classes
203
+ if Configuration.logger
204
+ error = $!
205
+
206
+ Configuration.logger.log_request endpoint, @name, curl
207
+
208
+ code = @response ? @response.code : error.class rescue 200
209
+
210
+ if Configuration.logger.level.to_s == 'debug'
211
+ body = if @response
212
+ defined?(Yajl) ? Yajl::Encoder.encode(@response.body, :pretty => true) : MultiJson.encode(@response.body)
213
+ else
214
+ error.message rescue ''
215
+ end
216
+ else
217
+ body = ''
218
+ end
219
+
220
+ Configuration.logger.log_response code, nil, body
221
+ end
222
+ end
223
+
224
+ # Thin wrapper around array representing a collection of indices for a specific alias,
225
+ # which allows hooking into adding/removing indices.
226
+ #
227
+ # It keeps track of which aliases to add and which to remove in two separate collections,
228
+ # `add_indices` and `remove_indices`.
229
+ #
230
+ # It delegates Enumerable-like methods to the `add_indices` collection.
231
+ #
232
+ class IndexCollection
233
+ include Enumerable
234
+ attr_reader :add_indices, :remove_indices
235
+
236
+ def initialize(*values)
237
+ @add_indices = Array.new(values).flatten.compact
238
+ @remove_indices = []
239
+ end
240
+
241
+ def push(value)
242
+ @add_indices |= [value]
243
+ @remove_indices.delete value
244
+ end
245
+ alias_method :add, :push
246
+
247
+ def delete(value)
248
+ @add_indices.delete value
249
+ @remove_indices |= [value]
250
+ end
251
+ alias_method :remove, :delete
252
+
253
+ def delete_if(&block)
254
+ @add_indices.clone.each do |name|
255
+ delete(name) if block.call(name)
256
+ end
257
+ end
258
+
259
+ def each(&block)
260
+ @add_indices.each(&block)
261
+ end
262
+
263
+ def empty?
264
+ @add_indices.empty?
265
+ end
266
+
267
+ def clear
268
+ @remove_indices = @add_indices.clone
269
+ @add_indices.clear
270
+ end
271
+
272
+ def [](index)
273
+ @add_indices[index]
274
+ end
275
+
276
+ def size
277
+ @add_indices.size
278
+ end
279
+
280
+ def to_ary
281
+ @add_indices
282
+ end
283
+
284
+ def to_s
285
+ @add_indices.join(', ')
286
+ end
287
+
288
+ def inspect
289
+ %Q|<#{self.class} #{@add_indices.map{|i| "\"#{i}\""}.join(', ')}>|
290
+ end
291
+
292
+ end
293
+
294
+ end
295
+
296
+ end
@@ -0,0 +1,30 @@
1
+ module Tire
2
+
3
+ class Configuration
4
+
5
+ def self.url(value=nil)
6
+ @url = (value ? value.to_s.gsub(%r|/*$|, '') : nil) || @url || ENV['ELASTICSEARCH_URL'] || "http://localhost:9200"
7
+ end
8
+
9
+ def self.client(klass=nil)
10
+ @client = klass || @client || HTTP::Client::RestClient
11
+ end
12
+
13
+ def self.wrapper(klass=nil)
14
+ @wrapper = klass || @wrapper || Results::Item
15
+ end
16
+
17
+ def self.logger(device=nil, options={})
18
+ return @logger = Logger.new(device, options) if device
19
+ @logger || nil
20
+ end
21
+
22
+ def self.reset(*properties)
23
+ reset_variables = properties.empty? ? instance_variables : instance_variables.map { |p| p.to_s} & \
24
+ properties.map { |p| "@#{p}" }
25
+ reset_variables.each { |v| instance_variable_set(v.to_sym, nil) }
26
+ end
27
+
28
+ end
29
+
30
+ end
data/lib/tire/dsl.rb ADDED
@@ -0,0 +1,43 @@
1
+ module Tire
2
+ module DSL
3
+
4
+ def configure(&block)
5
+ Configuration.class_eval(&block)
6
+ end
7
+
8
+ def search(indices=nil, options={}, &block)
9
+ if block_given?
10
+ Search::Search.new(indices, options, &block)
11
+ else
12
+ payload = case options
13
+ when Hash then
14
+ options
15
+ when String then
16
+ Tire.warn "Passing the payload as a JSON string in Tire.search has been deprecated, " +
17
+ "please use the block syntax or pass a plain Hash."
18
+ options
19
+ else raise ArgumentError, "Please pass a Ruby Hash or String with JSON"
20
+ end
21
+
22
+ Search::Search.new(indices, :payload => payload)
23
+ end
24
+ rescue Exception => error
25
+ STDERR.puts "[REQUEST FAILED] #{error.class} #{error.message rescue nil}\n"
26
+ raise
27
+ ensure
28
+ end
29
+
30
+ def index(name, &block)
31
+ Index.new(name, &block)
32
+ end
33
+
34
+ def scan(names, options={}, &block)
35
+ Search::Scan.new(names, options, &block)
36
+ end
37
+
38
+ def aliases
39
+ Alias.all
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+ module Tire
2
+
3
+ module HTTP
4
+
5
+ module Client
6
+
7
+ class RestClient
8
+ ConnectionExceptions = [::RestClient::ServerBrokeConnection, ::RestClient::RequestTimeout]
9
+
10
+ def self.get(url, data=nil)
11
+ perform ::RestClient::Request.new(:method => :get, :url => url, :payload => data).execute
12
+ rescue *ConnectionExceptions
13
+ raise
14
+ rescue ::RestClient::Exception => e
15
+ Response.new e.http_body, e.http_code
16
+ end
17
+
18
+ def self.post(url, data)
19
+ perform ::RestClient.post(url, data)
20
+ rescue *ConnectionExceptions
21
+ raise
22
+ rescue ::RestClient::Exception => e
23
+ Response.new e.http_body, e.http_code
24
+ end
25
+
26
+ def self.put(url, data)
27
+ perform ::RestClient.put(url, data)
28
+ rescue *ConnectionExceptions
29
+ raise
30
+ rescue ::RestClient::Exception => e
31
+ Response.new e.http_body, e.http_code
32
+ end
33
+
34
+ def self.delete(url)
35
+ perform ::RestClient.delete(url)
36
+ rescue *ConnectionExceptions
37
+ raise
38
+ rescue ::RestClient::Exception => e
39
+ Response.new e.http_body, e.http_code
40
+ end
41
+
42
+ def self.head(url)
43
+ perform ::RestClient.head(url)
44
+ rescue *ConnectionExceptions
45
+ raise
46
+ rescue ::RestClient::Exception => e
47
+ Response.new e.http_body, e.http_code
48
+ end
49
+
50
+ private
51
+
52
+ def self.perform(response)
53
+ Response.new response.body, response.code, response.headers
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
62
+ end