tire 0.5.4 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.travis.yml +3 -0
  2. data/README.markdown +49 -29
  3. data/examples/rails-application-template.rb +15 -15
  4. data/examples/tire-dsl.rb +24 -24
  5. data/lib/tire.rb +2 -1
  6. data/lib/tire/alias.rb +3 -3
  7. data/lib/tire/dsl.rb +17 -19
  8. data/lib/tire/index.rb +67 -10
  9. data/lib/tire/model/callbacks.rb +1 -1
  10. data/lib/tire/model/indexing.rb +2 -2
  11. data/lib/tire/model/naming.rb +1 -1
  12. data/lib/tire/model/percolate.rb +3 -3
  13. data/lib/tire/model/persistence.rb +1 -1
  14. data/lib/tire/model/persistence/attributes.rb +2 -2
  15. data/lib/tire/model/persistence/storage.rb +2 -2
  16. data/lib/tire/model/search.rb +8 -8
  17. data/lib/tire/multi_search.rb +4 -4
  18. data/lib/tire/rubyext/ruby_1_8.rb +1 -1
  19. data/lib/tire/search.rb +1 -1
  20. data/lib/tire/search/scan.rb +1 -1
  21. data/lib/tire/tasks.rb +1 -1
  22. data/lib/tire/version.rb +7 -11
  23. data/test/integration/active_record_searchable_test.rb +42 -0
  24. data/test/integration/dis_max_queries_test.rb +1 -1
  25. data/test/integration/dsl_search_test.rb +9 -0
  26. data/test/integration/index_mapping_test.rb +82 -7
  27. data/test/integration/persistent_model_test.rb +17 -0
  28. data/test/integration/sort_test.rb +16 -0
  29. data/test/models/active_record_models.rb +9 -0
  30. data/test/models/persistent_article.rb +1 -1
  31. data/test/models/persistent_article_in_index.rb +1 -1
  32. data/test/models/persistent_article_in_namespace.rb +1 -1
  33. data/test/models/persistent_article_with_percolation.rb +5 -0
  34. data/test/models/persistent_articles_with_custom_index_name.rb +1 -1
  35. data/test/test_helper.rb +1 -1
  36. data/test/unit/index_alias_test.rb +1 -1
  37. data/test/unit/index_test.rb +79 -1
  38. data/test/unit/model_persistence_test.rb +11 -1
  39. data/test/unit/model_search_test.rb +2 -2
  40. data/test/unit/tire_test.rb +11 -7
  41. data/tire.gemspec +4 -3
  42. metadata +61 -51
@@ -6,7 +6,8 @@ require 'cgi'
6
6
 
7
7
  require 'active_support/core_ext/object/to_param'
8
8
  require 'active_support/core_ext/object/to_query'
9
- require 'active_support/core_ext/hash/except.rb'
9
+ require 'active_support/core_ext/hash/except'
10
+ require 'active_support/json'
10
11
 
11
12
  # Ruby 1.8 compatibility
12
13
  require 'tire/rubyext/ruby_1_8' if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
@@ -1,6 +1,6 @@
1
1
  module Tire
2
2
 
3
- # Represents an *alias* in _ElasticSearch_. An alias may point to one or multiple
3
+ # Represents an *alias* in _Elasticsearch_. An alias may point to one or multiple
4
4
  # indices, for instance to separate physical indices into logical entities, where
5
5
  # each user has a "virtual index" or for setting up "sliding window" scenarios.
6
6
  #
@@ -156,7 +156,7 @@ module Tire
156
156
  type ? (@attributes[:filter] = Search::Filter.new(type, *options).to_hash and return self ) : @attributes[:filter]
157
157
  end
158
158
 
159
- # Save the alias in _ElasticSearch_
159
+ # Save the alias in _Elasticsearch_
160
160
  #
161
161
  def save
162
162
  @response = Configuration.client.post "#{Configuration.url}/_aliases", to_json
@@ -184,7 +184,7 @@ module Tire
184
184
  { :actions => actions }
185
185
  end
186
186
 
187
- # Return alias serialized in JSON for _ElasticSearch_
187
+ # Return alias serialized in JSON for _Elasticsearch_
188
188
  #
189
189
  def to_json(options=nil)
190
190
  as_json.to_json
@@ -5,29 +5,27 @@ module Tire
5
5
  Configuration.class_eval(&block)
6
6
  end
7
7
 
8
- def search(indices=nil, options={}, &block)
8
+ def search(indices=nil, payload={}, &block)
9
9
  if block_given?
10
- Search::Search.new(indices, options, &block)
10
+ Search::Search.new(indices, payload, &block)
11
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
- unless options.empty?
22
- Search::Search.new(indices, :payload => payload)
23
- else
24
- Search::Search.new(indices)
12
+ raise ArgumentError, "Please pass a Ruby Hash or an object with `to_hash` method, not #{payload.class}" \
13
+ unless payload.respond_to?(:to_hash)
14
+
15
+ # Extract URL parameters from payload
16
+ #
17
+ search_params = %w| search_type routing scroll from size timeout |
18
+
19
+ options = search_params.inject({}) do |sum,item|
20
+ if param = (payload.delete(item) || payload.delete(item.to_sym))
21
+ sum[item.to_sym] = param
22
+ end
23
+ sum
25
24
  end
25
+
26
+ options.update(:payload => payload) unless payload.empty?
27
+ Search::Search.new(indices, options)
26
28
  end
27
- rescue Exception => error
28
- STDERR.puts "[REQUEST FAILED] #{error.class} #{error.message rescue nil}\n"
29
- raise
30
- ensure
31
29
  end
32
30
 
33
31
  # Build and perform a [multi-search](http://elasticsearch.org/guide/reference/api/multi-search.html)
@@ -52,9 +52,64 @@ module Tire
52
52
  alias_name ? Alias.all(@name).select { |a| a.name == alias_name }.first : Alias.all(@name)
53
53
  end
54
54
 
55
- def mapping
55
+ # Get or update the index mapping
56
+ #
57
+ # Without arguments, returns the index mapping as a Hash
58
+ #
59
+ # When passed arguments, attempts to update the index mapping:
60
+ #
61
+ # index.mapping 'article', properties: { body: { type: "string" } }
62
+ #
63
+ # You can pass the `ignore_conflicts` option as a part of the Hash:
64
+ #
65
+ # index.mapping 'article', properties: { body: { type: "string" } }, ignore_conflicts: true
66
+ #
67
+ def mapping(*args)
68
+ args.empty? ? get_mapping : put_mapping(*args)
69
+ end
70
+
71
+ # Raises an exception for unsuccessful responses
72
+ #
73
+ def mapping!(*args)
74
+ mapping(*args)
75
+ raise RuntimeError, response.body unless response.success?
76
+ end
77
+
78
+ def get_mapping
56
79
  @response = Configuration.client.get("#{url}/_mapping")
57
- MultiJson.decode(@response.body)[@name]
80
+ result = MultiJson.decode(@response.body)[@name]
81
+ @response.success? ? result : false
82
+ ensure
83
+ curl = %Q|curl -X GET "#{url}/_mapping?pretty"|
84
+ logged("GET MAPPING", curl)
85
+ end
86
+
87
+ def put_mapping(type, mapping)
88
+ params = {}
89
+ if ignore_conflicts = mapping.delete(:ignore_conflicts) || mapping.delete("ignore_conflicts")
90
+ params[:ignore_conflicts] = ignore_conflicts
91
+ end
92
+
93
+ url = "#{self.url}/#{type}/_mapping"
94
+ url += "?#{params.to_param}" unless params.empty?
95
+
96
+ payload = { type => mapping }.to_json
97
+
98
+ @response = Configuration.client.put url, payload
99
+ result = MultiJson.decode(@response.body)
100
+ @response.success? ? result : false
101
+ ensure
102
+ curl = %Q|curl -X PUT "#{url}" -d '#{payload}'|
103
+ logged("PUT MAPPING #{type}", curl)
104
+ end
105
+
106
+ def delete_mapping(type)
107
+ url = "#{self.url}/#{type}"
108
+ @response = Configuration.client.delete(url)
109
+ @response.success?
110
+ ensure
111
+ curl = %Q|curl -X DELETE "#{url}"|
112
+ logged("DELETE MAPPING #{type}", curl)
58
113
  end
59
114
 
60
115
  def settings
@@ -79,10 +134,11 @@ module Tire
79
134
 
80
135
  params[:parent] = options[:parent] if options[:parent]
81
136
  params[:routing] = options[:routing] if options[:routing]
137
+ params[:replication] = options[:replication] if options[:replication]
82
138
 
83
139
  params_encoded = params.empty? ? '' : "?#{params.to_param}"
84
140
 
85
- url = id ? "#{self.url}/#{type}/#{id}#{params_encoded}" : "#{self.url}/#{type}/#{params_encoded}"
141
+ url = id ? "#{self.url}/#{type}/#{Utils.escape(id)}#{params_encoded}" : "#{self.url}/#{type}/#{params_encoded}"
86
142
 
87
143
  @response = Configuration.client.post url, document
88
144
  MultiJson.decode(@response.body)
@@ -104,6 +160,8 @@ module Tire
104
160
  # Shortcut methods `bulk_store`, `bulk_delete` and `bulk_create` are available.
105
161
  #
106
162
  def bulk(action, documents, options={})
163
+ return false if documents.empty?
164
+
107
165
  # TODO: A more Ruby-like DSL notation should be supported:
108
166
  #
109
167
  # Tire.index('myindex').bulk do
@@ -111,7 +169,7 @@ module Tire
111
169
  # delete id: 1
112
170
  # # ...
113
171
  # end
114
- #
172
+
115
173
  payload = documents.map do |document|
116
174
  type = get_type_from_document(document, :escape => false) # Do not URL-escape the _type
117
175
  id = get_id_from_document(document)
@@ -236,20 +294,19 @@ module Tire
236
294
  end
237
295
  raise ArgumentError, "Please pass a document ID" unless id
238
296
 
239
- url = "#{self.url}/#{type}/#{id}"
297
+ url = "#{self.url}/#{type}/#{Utils.escape(id)}"
240
298
  result = Configuration.client.delete url
241
299
  MultiJson.decode(result.body) if result.success?
242
300
 
243
301
  ensure
244
302
  curl = %Q|curl -X DELETE "#{url}"|
245
- logged(id, curl)
303
+ logged("#{type}/#{id}", curl)
246
304
  end
247
305
 
248
306
  def retrieve(type, id, options={})
249
307
  raise ArgumentError, "Please pass a document ID" unless id
250
308
 
251
- type = Utils.escape(type)
252
- url = "#{self.url}/#{type}/#{id}"
309
+ url = "#{self.url}/#{Utils.escape(type)}/#{Utils.escape(id)}"
253
310
 
254
311
  params = {}
255
312
  params[:routing] = options[:routing] if options[:routing]
@@ -271,7 +328,7 @@ module Tire
271
328
 
272
329
  ensure
273
330
  curl = %Q|curl -X GET "#{url}"|
274
- logged(id, curl)
331
+ logged("#{type}/#{id}", curl)
275
332
  end
276
333
 
277
334
  def update(type, id, payload={}, options={})
@@ -280,7 +337,7 @@ module Tire
280
337
  raise ArgumentError, "Please pass a script or partial document in the payload hash" unless payload[:script] || payload[:doc]
281
338
 
282
339
  type = Utils.escape(type)
283
- url = "#{self.url}/#{type}/#{id}/_update"
340
+ url = "#{self.url}/#{type}/#{Utils.escape(id)}/_update"
284
341
  url += "?#{options.to_param}" unless options.keys.empty?
285
342
  @response = Configuration.client.post url, MultiJson.encode(payload)
286
343
  MultiJson.decode(@response.body)
@@ -2,7 +2,7 @@ module Tire
2
2
  module Model
3
3
 
4
4
  # Main module containing the infrastructure for automatic updating
5
- # of the _ElasticSearch_ index on model instance create, update or delete.
5
+ # of the _Elasticsearch_ index on model instance create, update or delete.
6
6
  #
7
7
  # Include it in your model: `include Tire::Model::Callbacks`
8
8
  #
@@ -28,7 +28,7 @@ module Tire
28
28
  end
29
29
 
30
30
  # Define the [_mapping_](http://www.elasticsearch.org/guide/reference/mapping/index.html)
31
- # for the corresponding index, telling _ElasticSearch_ how to understand your documents:
31
+ # for the corresponding index, telling _Elasticsearch_ how to understand your documents:
32
32
  # what type is which property, whether it is analyzed or no, which analyzer to use, etc.
33
33
  #
34
34
  # You may pass the top level mapping properties (such as `_source` or `_all`) as a Hash.
@@ -113,7 +113,7 @@ module Tire
113
113
  end
114
114
  end
115
115
  rescue Errno::ECONNREFUSED => e
116
- STDERR.puts "Skipping index creation, cannot connect to ElasticSearch",
116
+ STDERR.puts "Skipping index creation, cannot connect to Elasticsearch",
117
117
  "(The original exception was: #{e.inspect})"
118
118
  false
119
119
  end
@@ -75,7 +75,7 @@ module Tire
75
75
  #
76
76
  def document_type name=nil
77
77
  @document_type = name if name
78
- @document_type || klass.model_name.underscore
78
+ @document_type || klass.model_name.to_s.underscore
79
79
  end
80
80
  end
81
81
 
@@ -2,7 +2,7 @@ module Tire
2
2
  module Model
3
3
 
4
4
  # Contains support for the [percolation](http://www.elasticsearch.org/guide/reference/api/percolate.html)
5
- # feature of _ElasticSearch_.
5
+ # feature of _Elasticsearch_.
6
6
  #
7
7
  module Percolate
8
8
 
@@ -31,7 +31,7 @@ module Tire
31
31
  # See <http://www.elasticsearch.org/guide/reference/api/index_.html> for more information.
32
32
  #
33
33
  def percolate!(pattern=true)
34
- @@_percolator = pattern
34
+ @_percolator = pattern
35
35
  self
36
36
  end
37
37
 
@@ -57,7 +57,7 @@ module Tire
57
57
  # Returns the status or pattern of percolator for this class.
58
58
  #
59
59
  def percolator
60
- defined?(@@_percolator) ? @@_percolator : nil
60
+ @_percolator
61
61
  end
62
62
  end
63
63
 
@@ -1,7 +1,7 @@
1
1
  module Tire
2
2
  module Model
3
3
 
4
- # Allows to use _ElasticSearch_ as a primary database (storage).
4
+ # Allows to use _Elasticsearch_ as a primary database (storage).
5
5
  #
6
6
  # Contains all the `Tire::Model::Search` features and provides
7
7
  # an [_ActiveModel_](http://rubygems.org/gems/activemodel)-compatible
@@ -19,7 +19,7 @@ module Tire
19
19
  # property :tags, :analyzer => 'keywords', :default => []
20
20
  # end
21
21
  #
22
- # You can pass mapping definition for ElasticSearch in the options Hash.
22
+ # You can pass mapping definition for Elasticsearch in the options Hash.
23
23
  #
24
24
  # You can define default property values.
25
25
  #
@@ -134,7 +134,7 @@ module Tire
134
134
 
135
135
  else
136
136
  # Strings formatted as <http://en.wikipedia.org/wiki/ISO8601> are automatically converted to Time
137
- value = Time.parse(value) if value.is_a?(String) && value =~ /^\d{4}[\/\-]\d{2}[\/\-]\d{2}T\d{2}\:\d{2}\:\d{2}Z$/
137
+ value = Time.parse(value).utc if value.is_a?(String) && value =~ /^\d{4}[\/\-]\d{2}[\/\-]\d{2}T\d{2}\:\d{2}\:\d{2}/
138
138
  value
139
139
  end
140
140
  end
@@ -3,7 +3,7 @@ module Tire
3
3
 
4
4
  module Persistence
5
5
 
6
- # Provides infrastructure for storing records in _ElasticSearch_.
6
+ # Provides infrastructure for storing records in _Elasticsearch_.
7
7
  #
8
8
  module Storage
9
9
  def self.included(base)
@@ -34,7 +34,7 @@ module Tire
34
34
  end
35
35
 
36
36
  def update_index
37
- send :_run_update_elasticsearch_index_callbacks do
37
+ run_callbacks :update_elasticsearch_index do
38
38
  if destroyed?
39
39
  index.remove self
40
40
  else
@@ -52,7 +52,7 @@ module Tire
52
52
  # end
53
53
  #
54
54
  # This methods returns a Tire::Results::Collection instance, containing instances
55
- # of Tire::Results::Item, populated by the data available in _ElasticSearch, by default.
55
+ # of Tire::Results::Item, populated by the data available in _Elasticsearch, by default.
56
56
  #
57
57
  # If you'd like to load the "real" models from the database, you may use the `:load` option:
58
58
  #
@@ -132,7 +132,7 @@ module Tire
132
132
  instance.class.tire.index
133
133
  end
134
134
 
135
- # Updates the index in _ElasticSearch_.
135
+ # Updates the index in _Elasticsearch_.
136
136
  #
137
137
  # On model instance create or update, it will store its serialized representation in the index.
138
138
  #
@@ -141,7 +141,7 @@ module Tire
141
141
  # It will also execute any `<after|before>_update_elasticsearch_index` callback hooks.
142
142
  #
143
143
  def update_index
144
- instance.send :_run_update_elasticsearch_index_callbacks do
144
+ instance.run_callbacks :update_elasticsearch_index do
145
145
  if instance.destroyed?
146
146
  index.remove instance
147
147
  else
@@ -158,7 +158,7 @@ module Tire
158
158
  #
159
159
  # If you don't define any mapping, the model is serialized as-is.
160
160
  #
161
- # If you do define the mapping for _ElasticSearch_, only attributes
161
+ # If you do define the mapping for _Elasticsearch_, only attributes
162
162
  # declared in the mapping are serialized.
163
163
  #
164
164
  # For properties declared with the `:as` option, the passed String or Proc
@@ -191,15 +191,15 @@ module Tire
191
191
 
192
192
  def matches
193
193
  instance.instance_eval do
194
- @attributes ||= {}
195
- @attributes['tire__matches']
194
+ @tire__attributes ||= {}
195
+ @tire__attributes['matches']
196
196
  end
197
197
  end
198
198
 
199
199
  def matches=(value)
200
200
  instance.instance_eval do
201
- @attributes ||= {}
202
- @attributes['tire__matches'] = value
201
+ @tire__attributes ||= {}
202
+ @tire__attributes['matches'] = value
203
203
  end
204
204
  end
205
205
 
@@ -218,12 +218,12 @@ module Tire
218
218
  end
219
219
 
220
220
  def perform
221
- @responses = Configuration.client.get(url + params, to_payload)
222
- if @responses.failure?
221
+ @response = Configuration.client.get(url + params, to_payload)
222
+ if @response.failure?
223
223
  STDERR.puts "[REQUEST FAILED] #{to_curl}\n"
224
- raise SearchRequestFailed, @responses.to_s
224
+ raise SearchRequestFailed, @response.to_s
225
225
  end
226
- @json = MultiJson.decode(@responses.body)
226
+ @json = MultiJson.decode(@response.body)
227
227
  @results = Tire::Search::Multi::Results.new @searches, @json['responses']
228
228
  return self
229
229
  ensure
@@ -1 +1 @@
1
- require 'tire/rubyext/uri_escape'
1
+ require 'tire/rubyext/uri_escape' unless defined?(URI.encode_www_form_component) && defined?(URI.decode_www_form_component)
@@ -48,7 +48,7 @@ module Tire
48
48
  end
49
49
 
50
50
  def params
51
- options = @options.except(:wrapper)
51
+ options = @options.except(:wrapper, :payload)
52
52
  options.empty? ? '' : '?' + options.to_param
53
53
  end
54
54
 
@@ -34,7 +34,7 @@ module Tire
34
34
  # query { term 'author.exact', 'John Smith' }
35
35
  # end
36
36
  #
37
- # See ElasticSearch documentation for further reference:
37
+ # See Elasticsearch documentation for further reference:
38
38
  #
39
39
  # * http://www.elasticsearch.org/guide/reference/api/search/search-type.html
40
40
  # * http://www.elasticsearch.org/guide/reference/api/search/scroll.html
@@ -76,7 +76,7 @@ namespace :tire do
76
76
  if defined?(Kaminari) && klass.respond_to?(:page)
77
77
  klass.instance_eval do
78
78
  def paginate(options = {})
79
- page(options[:page]).per(options[:per_page]).to_a
79
+ page(options[:page]).per(options[:per_page])
80
80
  end
81
81
  end
82
82
  end unless klass.respond_to?(:paginate)
@@ -1,18 +1,14 @@
1
1
  module Tire
2
- VERSION = "0.5.4"
2
+ VERSION = "0.5.5"
3
3
 
4
4
  CHANGELOG =<<-END
5
5
  IMPORTANT CHANGES LATELY:
6
6
 
7
- * Added the support for the Count API
8
- * Escape single quotes in `to_curl` serialization
9
- * Added JRuby compatibility
10
- * Added proper `as_json` support for `Results::Collection` and `Results::Item` classes
11
- * Added extracting the `routing` information in the `Index#store` method
12
- * Refactored the `update_index` method for search and persistence integration
13
- * Cast collection properties in Model::Persistence as empty Array by default
14
- * Allow passing `:index` option to `MyModel.import`
15
- * Update to Mocha ~> 0.13
16
- * Update to MultiJson ~> 1.3
7
+ * Improved documentation
8
+ * Improved isolation of Tire methods in model integrations
9
+ * Improved handling of times/dates in `Model::Persistence`
10
+ * Added support for "Put Mapping" and "Delete mapping" APIs
11
+ * Added escaping document IDs in URLs
12
+ * Allowed passing URL options when passing search definition as a Hash
17
13
  END
18
14
  end