tire 0.5.4 → 0.5.5

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 (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