tire 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,17 @@
1
1
  Tire
2
2
  =========
3
3
 
4
+ ---------------------------------------------------------------------------------------------------
5
+
6
+ NOTICE: This library has been renamed and retired in September 2013
7
+ ([read the explanation](https://github.com/karmi/retire/wiki/Tire-Retire)).
8
+
9
+ Have a look at the **<http://github.com/elasticsearch/elasticsearch-ruby>**
10
+ suite of gems, which will contain similar set of features for
11
+ ActiveRecord and Rails integration as Tire.
12
+
13
+ ---------------------------------------------------------------------------------------------------
14
+
4
15
  _Tire_ is a Ruby (1.8 or 1.9) client for the [Elasticsearch](http://www.elasticsearch.org/)
5
16
  search engine/database.
6
17
 
@@ -9,7 +20,7 @@ full-text search engine and database with
9
20
  [powerful aggregation features](http://www.elasticsearch.org/guide/reference/api/search/facets/),
10
21
  communicating by JSON over RESTful HTTP, based on [Lucene](http://lucene.apache.org/), written in Java.
11
22
 
12
- This Readme provides a brief overview of _Tire's_ features. The more detailed documentation is at <http://karmi.github.com/tire/>.
23
+ This Readme provides a brief overview of _Tire's_ features. The more detailed documentation is at <http://karmi.github.com/retire/>.
13
24
 
14
25
  Both of these documents contain a lot of information. Please set aside some time to read them thoroughly, before you blindly dive into „somehow making it work“. Just skimming through it **won't work** for you. For more information, please see the project [Wiki](https://github.com/karmi/tire/wiki/_pages), search the [issues](https://github.com/karmi/tire/issues), and refer to the [integration test suite](https://github.com/karmi/tire/tree/master/test/integration).
15
26
 
@@ -52,7 +63,7 @@ To test-drive the core _Elasticsearch_ functionality, let's require the gem:
52
63
  ```
53
64
 
54
65
  Please note that you can copy these snippets from the much more extensive and heavily annotated file
55
- in [examples/tire-dsl.rb](http://karmi.github.com/tire/).
66
+ in [examples/tire-dsl.rb](http://karmi.github.com/retire/).
56
67
 
57
68
  Also, note that we're doing some heavy JSON lifting here. _Tire_ uses the
58
69
  [_multi_json_](https://github.com/intridea/multi_json) gem as a generic JSON wrapper,
@@ -595,6 +606,20 @@ just drop down one layer and use the `Tire::Index#store` and `Tire::Index#remove
595
606
  end
596
607
  ```
597
608
 
609
+ Of course, in this way, you're still performing an HTTP request during your database transaction,
610
+ which is not optimal for large-scale applications. In these situations, a better option would be processing
611
+ the index operations in background, with something like [Resque](https://github.com/resque/resque) or
612
+ [Sidekiq](https://github.com/mperham/sidekiq):
613
+
614
+ ```ruby
615
+ class Article < ActiveRecord::Base
616
+ include Tire::Model::Search
617
+
618
+ after_save { Indexer::Index.perform_async(document) }
619
+ after_destroy { Indexer::Remove.perform_async(document) }
620
+ end
621
+ ```
622
+
598
623
  When you're integrating _Tire_ with ActiveRecord models, you should use the `after_commit`
599
624
  and `after_rollback` hooks to keep the index in sync with your database.
600
625
 
@@ -13,8 +13,8 @@
13
13
  # * Git
14
14
  # * Ruby >= 1.8.7
15
15
  # * Rubygems
16
- # * Rails >= 3.0.7
17
- # * Sun Java 6 (for Elasticsearch)
16
+ # * Rails >= 3
17
+ # * Java 6 or 7 (for Elasticsearch)
18
18
  #
19
19
  #
20
20
  # Usage
@@ -181,14 +181,13 @@ file 'app/models/article.rb', <<-CODE
181
181
  class Article < ActiveRecord::Base
182
182
  include Tire::Model::Search
183
183
  include Tire::Model::Callbacks
184
-
185
- attr_accessible :title, :content, :published_on
186
184
  end
187
185
  CODE
188
186
 
189
187
  initializer 'tire.rb', <<-CODE
190
188
  Tire.configure do
191
- logger STDERR
189
+ # url 'http://localhost:9200'
190
+ # logger STDERR
192
191
  end
193
192
  CODE
194
193
 
@@ -201,7 +200,7 @@ puts '-'*80, ''; sleep 1
201
200
  gsub_file 'app/controllers/articles_controller.rb', %r{# GET /articles/1$}, <<-CODE
202
201
  # GET /articles/search
203
202
  def search
204
- @articles = Article.search params[:q]
203
+ @articles = Article.tire.search params[:q]
205
204
 
206
205
  render :action => "index"
207
206
  end
@@ -238,7 +237,7 @@ puts
238
237
  say_status "Index", "Indexing the database...", :yellow
239
238
  puts '-'*80, ''; sleep 0.5
240
239
 
241
- rake "environment tire:import CLASS='Article' FORCE=true"
240
+ rake "environment tire:import:model CLASS='Article' FORCE=true"
242
241
 
243
242
  puts
244
243
  say_status "Git", "Details about the application:", :yellow
@@ -30,6 +30,7 @@ require 'tire/search/filter'
30
30
  require 'tire/search/highlight'
31
31
  require 'tire/search/scan'
32
32
  require 'tire/search/script_field'
33
+ require 'tire/delete_by_query'
33
34
  require 'tire/multi_search'
34
35
  require 'tire/count'
35
36
  require 'tire/results/pagination'
@@ -0,0 +1,76 @@
1
+ module Tire
2
+ class DeleteByQuery
3
+ class DeleteByQueryRequestFailed < StandardError; end
4
+
5
+ attr_reader :indices, :types, :query, :response, :json
6
+
7
+ def initialize(indices=nil, options={}, &block)
8
+ @indices = Array(indices)
9
+ @types = Array(options[:type]).flatten
10
+ @options = options
11
+
12
+ if block_given?
13
+ @query = Search::Query.new
14
+ block.arity < 1 ? @query.instance_eval(&block) : block.call(@query)
15
+ else
16
+ raise "no query given for #{self.class}"
17
+ end
18
+ end
19
+
20
+ def perform
21
+ @response = Configuration.client.delete url
22
+ if @response.failure?
23
+ STDERR.puts "[REQUEST FAILED] #{self.to_curl}\n"
24
+ raise DeleteByQueryRequestFailed, @response.to_s
25
+ end
26
+ @json = MultiJson.decode(@response.body)
27
+ true
28
+ ensure
29
+ logged
30
+ end
31
+
32
+ private
33
+
34
+ def path
35
+ [
36
+ '/',
37
+ indices.join(','),
38
+ types.map { |type| Utils.escape(type) }.join(','),
39
+ '_query',
40
+ ].compact.join('/')
41
+ end
42
+
43
+ def url
44
+ "#{Configuration.url}#{path}/?source=#{Utils.escape(to_json)}"
45
+ end
46
+
47
+ def to_json(options={})
48
+ query.to_json
49
+ end
50
+
51
+ def to_curl
52
+ %Q|curl -X DELETE '#{url}'|
53
+ end
54
+
55
+ def logged(endpoint='_query')
56
+ if Configuration.logger
57
+
58
+ Configuration.logger.log_request endpoint, indices, to_curl
59
+
60
+ code = response.code rescue nil
61
+
62
+ if Configuration.logger.level.to_s == 'debug'
63
+ body = if json
64
+ MultiJson.encode(json, :pretty => Configuration.pretty)
65
+ else
66
+ MultiJson.encode(MultiJson.load(response.body), :pretty => Configuration.pretty) rescue ''
67
+ end
68
+ else
69
+ body = ''
70
+ end
71
+
72
+ Configuration.logger.log_response code || 'N/A', 'N/A', body || 'N/A'
73
+ end
74
+ end
75
+ end
76
+ end
@@ -105,6 +105,10 @@ module Tire
105
105
  Search::Count.new(indices, options, &block).value
106
106
  end
107
107
 
108
+ def delete(indices=nil, options={}, &block)
109
+ DeleteByQuery.new(indices, options, &block).perform
110
+ end
111
+
108
112
  def index(name, &block)
109
113
  Index.new(name, &block)
110
114
  end
@@ -48,7 +48,7 @@ module Tire
48
48
  end
49
49
 
50
50
  def self.__host_unreachable_exceptions
51
- [Errno::ECONNREFUSED, ::RestClient::ServerBrokeConnection, ::RestClient::RequestTimeout]
51
+ [Errno::ECONNREFUSED, Errno::ETIMEDOUT, ::RestClient::ServerBrokeConnection, ::RestClient::RequestTimeout, SocketError]
52
52
  end
53
53
 
54
54
  private
@@ -38,9 +38,13 @@ module Tire
38
38
  Response.new client.body_str, client.response_code
39
39
  end
40
40
 
41
+ # NOTE: newrelic_rpm breaks Curl::Easy#http_put
42
+ # https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/curb.rb#L49
43
+ #
41
44
  def self.put(url, data)
45
+ method = client.respond_to?(:http_put_without_newrelic) ? :http_put_without_newrelic : :http_put
42
46
  client.url = url
43
- client.http_put data
47
+ client.send method, data
44
48
  Response.new client.body_str, client.response_code
45
49
  end
46
50
 
@@ -154,13 +154,13 @@ module Tire
154
154
  #
155
155
  # @myindex.bulk :index, [ {id: 1, title: 'One'}, { id: 2, title: 'Two', _version: 3 } ], refresh: true
156
156
  #
157
- # Pass the action (`index`, `create`, `delete`) as the first argument, the collection of documents as
157
+ # Pass the action (`index`, `create`, `delete`, `update`) as the first argument, the collection of documents as
158
158
  # the second argument, and URL parameters as the last option.
159
159
  #
160
160
  # Any _meta_ information contained in documents (such as `_routing` or `_parent`) is extracted
161
161
  # and added to the "header" line.
162
162
  #
163
- # Shortcut methods `bulk_store`, `bulk_delete` and `bulk_create` are available.
163
+ # Shortcut methods `bulk_store`, `bulk_delete`, `bulk_create`, and `bulk_update` are available.
164
164
  #
165
165
  def bulk(action, documents, options={})
166
166
  return false if documents.empty?
@@ -181,12 +181,22 @@ module Tire
181
181
  STDERR.puts "[ERROR] Document #{document.inspect} does not have ID" unless id
182
182
  end
183
183
 
184
+ if action.to_sym == :update
185
+ raise ArgumentError, "Cannot update without document type" unless type
186
+ raise ArgumentError, "Cannot update without document ID" unless id
187
+ raise ArgumentError, "Update requires a hash document" unless document.respond_to?(:to_hash)
188
+ document = document.to_hash
189
+ raise ArgumentError, "Update requires either a script or a partial document" unless document[:script] || document[:doc]
190
+ end
191
+
184
192
  header = { action.to_sym => { :_index => name, :_type => type, :_id => id } }
193
+ header[action.to_sym].update({:_retry_on_conflict => options[:retry_on_conflict]}) if options[:retry_on_conflict]
185
194
 
186
195
  if document.respond_to?(:to_hash) && doc_hash = document.to_hash
187
196
  meta = doc_hash.select do |k,v|
188
197
  [ :_parent,
189
198
  :_percolate,
199
+ :_retry_on_conflict,
190
200
  :_routing,
191
201
  :_timestamp,
192
202
  :_ttl,
@@ -204,6 +214,19 @@ module Tire
204
214
  header[action.to_sym].update(meta)
205
215
  end
206
216
 
217
+ if action.to_sym == :update
218
+ document.keep_if do |k,_|
219
+ [
220
+ :doc,
221
+ :upsert,
222
+ :doc_as_upsert,
223
+ :script,
224
+ :params,
225
+ :lang
226
+ ].include?(k)
227
+ end
228
+ end
229
+
207
230
  output = []
208
231
  output << MultiJson.encode(header)
209
232
  output << convert_document_to_json(document) unless action.to_s == 'delete'
@@ -254,6 +277,10 @@ module Tire
254
277
  bulk :delete, documents, options
255
278
  end
256
279
 
280
+ def bulk_update(documents, options={})
281
+ bulk :update, documents, options
282
+ end
283
+
257
284
  def import(klass_or_collection, options={})
258
285
  case
259
286
  when method = options.delete(:method)
@@ -487,7 +514,7 @@ module Tire
487
514
  when document.is_a?(Hash)
488
515
  document[:_id] || document['_id'] || document[:id] || document['id']
489
516
  when document.respond_to?(:id) && document.id != document.object_id
490
- document.id.as_json
517
+ document.id.to_s
491
518
  end
492
519
  $VERBOSE = old_verbose
493
520
  id
@@ -46,7 +46,7 @@ module Tire
46
46
 
47
47
  # Save property default value (when relevant):
48
48
  unless (default_value = options.delete(:default)).nil?
49
- property_defaults[name.to_sym] = default_value.respond_to?(:call) ? default_value.call : default_value
49
+ property_defaults[name.to_sym] = default_value
50
50
  end
51
51
 
52
52
  # Save property casting (when relevant):
@@ -92,7 +92,15 @@ module Tire
92
92
  #
93
93
  property_defaults = self.class.property_defaults.inject({}) do |hash, item|
94
94
  key, value = item
95
- hash[key.to_sym] = value.class.respond_to?(:new) ? value.clone : value
95
+
96
+ hash[key.to_sym] = if value.respond_to?(:call)
97
+ value.call
98
+ elsif value.class.respond_to?(:new)
99
+ value.clone
100
+ else
101
+ value
102
+ end
103
+
96
104
  hash
97
105
  end
98
106
  __update_attributes(property_defaults.merge(attributes))
@@ -23,6 +23,10 @@ module Tire
23
23
  result
24
24
  end
25
25
  end
26
+
27
+ def delete(&block)
28
+ DeleteByQuery.new(index_name, {:type => document_type}, &block).perform
29
+ end
26
30
  end
27
31
 
28
32
  module InstanceMethods
@@ -52,6 +52,7 @@ module Tire
52
52
  alias :total_count :total_entries
53
53
  alias :num_pages :total_pages
54
54
  alias :offset_value :offset
55
+ alias :out_of_range? :out_of_bounds?
55
56
 
56
57
  def first_page?
57
58
  current_page == 1
@@ -48,7 +48,7 @@ module Tire
48
48
  end
49
49
 
50
50
  def params
51
- options = @options.except(:wrapper, :payload)
51
+ options = @options.except(:wrapper, :payload, :load)
52
52
  options.empty? ? '' : '?' + options.to_param
53
53
  end
54
54
 
@@ -121,11 +121,18 @@ namespace :tire do
121
121
 
122
122
  puts "[IMPORT] Loading models from: #{dir}"
123
123
  Dir.glob(File.join("#{dir}/**/*.rb")).each do |path|
124
- require path
125
-
126
124
  model_filename = path[/#{Regexp.escape(dir.to_s)}\/([^\.]+).rb/, 1]
125
+
126
+ next if model_filename.match(/^concerns\//i) # Skip concerns/ folder
127
+
127
128
  klass = model_filename.camelize.constantize
128
129
 
130
+ begin
131
+ klass = model_filename.camelize.constantize
132
+ rescue NameError
133
+ require(path) ? retry : raise
134
+ end
135
+
129
136
  # Skip if the class doesn't have Tire integration
130
137
  next unless klass.respond_to?(:tire)
131
138
 
@@ -1,20 +1,15 @@
1
1
  module Tire
2
- VERSION = "0.6.0"
2
+ VERSION = "0.6.1"
3
3
 
4
4
  CHANGELOG =<<-END
5
5
  IMPORTANT CHANGES LATELY:
6
6
 
7
- * Fixed incorrect inflection in the Rake import tasks
8
- * Added support for `geo_distance` facets
9
- * Added support for the `custom_filters_score` query
10
- * Added a custom strategy option to <Model.import>
11
- * Allow the `:wrapper` option to be passed to Tire.search consistently
12
- * Improved the Mongoid importing strategy
13
- * Merge returned `fields` with `_source` if both are returned
14
- * Removed the deprecated `text` query
15
- * [FIX] Rescue HTTP client specific connection errors in MyModel#create_elasticsearch_index
16
- * Added support for passing `version` in Tire::Index#store
17
- * Added support for `_version_type` in Tire::Index#bulk
18
- * Added ActiveModel::Serializers compatibility
7
+ * Added support for bulk update
8
+ * Improved Kaminari support
9
+ * Improved the behaviour of `default` properties in Tire::Persistence
10
+ * Added the information about the gem "retirement" and other documentation improvements
11
+ * Fixed errors due to NewRelic's patching of Curl
12
+ * [ACTIVEMODEL] Use Object#id#to_s in `get_id_from_document`
13
+ * Added support for "Delete By Query" API
19
14
  END
20
15
  end
@@ -86,6 +86,21 @@ module Tire
86
86
  assert_equal 'abc123', results.first.id
87
87
  end
88
88
 
89
+ should "return facets" do
90
+ a = SupermodelArticle.new :title => 'Test'
91
+ a.save
92
+ a.index.refresh
93
+
94
+ s = SupermodelArticle.search do
95
+ query { match :title, 'test' }
96
+ facet 'title' do
97
+ terms :title
98
+ end
99
+ end
100
+
101
+ assert_equal 1, s.facets['title']['terms'][0]['count']
102
+ end
103
+
89
104
  context "within Rails" do
90
105
 
91
106
  setup do
@@ -49,6 +49,24 @@ module Tire
49
49
  assert_equal 0, Tire.search('bulk-test') { query {all} }.results.size
50
50
  end
51
51
 
52
+ should 'update documents in bulk' do
53
+ @index.bulk_store @articles, refresh: true
54
+
55
+ documents = @articles.map do |a|
56
+ {
57
+ id: a[:id],
58
+ type: a[:type],
59
+ doc: { title: "#{a[:title]}-updated" }
60
+ }
61
+ end
62
+ @index.bulk_update documents, refresh: true
63
+
64
+ documents = Tire.search('bulk-test') { query {all} }.results.to_a
65
+ assert_equal 'one-updated', documents[0][:title]
66
+ assert_equal 'two-updated', documents[1][:title]
67
+ assert_equal 'three-updated', documents[2][:title]
68
+ end
69
+
52
70
  should "allow to feed search results to bulk API" do
53
71
  (1..10).to_a.each { |i| @index.store id: i }
54
72
  @index.refresh
@@ -68,7 +68,7 @@ module Tire
68
68
  query do
69
69
  # Replace documents score with parameterized computation
70
70
  #
71
- custom_score :script => "doc['words'].doubleValue / max(a, b)",
71
+ custom_score :script => "doc['words'].value.doubleValue() / max(a, b)",
72
72
  :params => { :a => 1, :b => 2 } do
73
73
  string "title:T*"
74
74
  end
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ module Tire
4
+ class DeleteByQueryIntegrationTest < Test::Unit::TestCase
5
+ include Test::Integration
6
+
7
+ should "delete documents matching a query" do
8
+ assert_python_size(1)
9
+ delete_by_query
10
+ assert_python_size(0)
11
+ end
12
+
13
+ should "leave documents not matching a query" do
14
+ assert_python_size(1)
15
+ delete_by_query('article', 'go')
16
+ assert_python_size(1)
17
+ end
18
+
19
+ should "not delete documents with different types" do
20
+ assert_python_size(1)
21
+ delete_by_query('different_type')
22
+ assert_python_size(1)
23
+ end
24
+
25
+ context "DSL" do
26
+ should "delete documents matching a query" do
27
+ assert_python_size(1)
28
+ Tire.delete('articles-test') { term :tags, 'python' }
29
+ assert_python_size(0)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def delete_by_query(type='article', token='python')
36
+ Tire::DeleteByQuery.new('articles-test', :type => type) do
37
+ term :tags, token
38
+ end.perform
39
+ end
40
+
41
+ def assert_python_size(size)
42
+ Tire.index('articles-test').refresh
43
+ search = Tire.search('articles-test') { query { term :tags, 'python' } }
44
+ assert_equal size, search.results.size
45
+ end
46
+ end
47
+ end
@@ -20,7 +20,6 @@ module Tire
20
20
  end
21
21
 
22
22
  should "add '_explanation' field to the result item" do
23
- # Tire::Configuration.logger STDERR, :level => 'debug'
24
23
  s = Tire.search 'explanation-test', :explain => true do
25
24
  query do
26
25
  boolean do
@@ -30,15 +29,11 @@ module Tire
30
29
  end
31
30
 
32
31
  doc = s.results.first
32
+ d = doc._explanation.details.first
33
33
 
34
- explanation = doc._explanation
35
-
36
- assert explanation.description.include?("product of:")
37
- assert explanation.value < 0.6
38
- assert_not_nil explanation.details
39
- end
40
-
34
+ assert d.description.include?("product of:")
35
+ assert_not_nil d.details
41
36
  end
42
-
43
37
  end
44
38
  end
39
+ end
@@ -23,12 +23,12 @@ module Tire
23
23
  teardown { Tire.index('articles-with-tags').delete }
24
24
 
25
25
  should "increment a counter" do
26
- Tire.index('articles-with-tags') { update( 'article', '1', :script => "ctx._source.views += 1" ) and refresh }
26
+ Tire.index('articles-with-tags') { update( 'article', '1', {:script => "ctx._source.views += 1"}, :refresh => true) }
27
27
 
28
28
  document = Tire.search('articles-with-tags') { query { string 'title:one' } }.results.first
29
29
  assert_equal 1, document.views, document.inspect
30
30
 
31
- Tire.index('articles-with-tags') { update( 'article', '2', :script => "ctx._source.views += 1" ) and refresh }
31
+ Tire.index('articles-with-tags') { update( 'article', '2', {:script => "ctx._source.views += 1"}, :refresh => true) }
32
32
 
33
33
  document = Tire.search('articles-with-tags') { query { string 'title:two' } }.results.first
34
34
  assert_equal 11, document.views, document.inspect
@@ -36,9 +36,13 @@ module Tire
36
36
 
37
37
  should "add a tag to document" do
38
38
  Tire.index('articles-with-tags') do
39
- update 'article', '1', :script => "ctx._source.tags += tag",
40
- :params => { :tag => 'new' }
41
- refresh
39
+ update 'article', '1', {
40
+ :script => "ctx._source.tags += tag",
41
+ :params => { :tag => 'new' }
42
+ },
43
+ {
44
+ :refresh => true
45
+ }
42
46
  end
43
47
 
44
48
  document = Tire.search('articles-with-tags') { query { string 'title:one' } }.results.first
@@ -47,9 +51,12 @@ module Tire
47
51
 
48
52
  should "remove a tag from document" do
49
53
  Tire.index('articles-with-tags') do
50
- update 'article', '1', :script => "ctx._source.tags = tags",
51
- :params => { :tags => [] }
52
- refresh
54
+ update 'article', '1', {
55
+ :script => "ctx._source.tags = tags",
56
+ :params => { :tags => [] }
57
+ }, {
58
+ :refresh => true
59
+ }
53
60
  end
54
61
 
55
62
  document = Tire.index('articles-with-tags').retrieve 'article', '1'
@@ -59,9 +66,12 @@ module Tire
59
66
  should "remove the entire document if specific condition is met" do
60
67
  Tire.index('articles-with-tags') do
61
68
  # Remove document when it contains tag 'foobar'
62
- update 'article', '3', :script => "ctx._source.tags.contains(tag) ? ctx.op = 'delete' : 'none'",
63
- :params => { :tag => 'foobar' }
64
- refresh
69
+ update 'article', '3', {
70
+ :script => "ctx._source.tags.contains(tag) ? ctx.op = 'delete' : 'none'",
71
+ :params => { :tag => 'foobar' }
72
+ }, {
73
+ :refresh => true
74
+ }
65
75
  end
66
76
 
67
77
  assert_nil Tire.index('articles-with-tags').retrieve 'article', '3'
@@ -81,8 +91,7 @@ module Tire
81
91
 
82
92
  should "update the document with a partial one" do
83
93
  Tire.index('articles-with-tags') do
84
- update( 'article', '1', :doc => { :title => 'One UPDATED' } )
85
- refresh
94
+ update( 'article', '1', {:doc => { :title => 'One UPDATED' }}, :refresh => true )
86
95
  end
87
96
 
88
97
  document = Tire.search('articles-with-tags') { query { string 'title:one' } }.results.first
@@ -101,9 +110,12 @@ module Tire
101
110
  Tire.index('articles-with-tags') do |index|
102
111
  $t.assert_not_nil @tags
103
112
 
104
- index.update 'article', '3', :script => "ctx._source.tags = tags",
105
- :params => { :tags => @tags }
106
- index.refresh
113
+ index.update 'article', '3', {
114
+ :script => "ctx._source.tags = tags",
115
+ :params => { :tags => @tags }
116
+ }, {
117
+ :refresh => true
118
+ }
107
119
  end
108
120
  end
109
121
  end
@@ -115,7 +127,5 @@ module Tire
115
127
  end
116
128
 
117
129
  end
118
-
119
130
  end
120
-
121
131
  end
@@ -8,13 +8,13 @@ module Tire
8
8
  context "Percolator" do
9
9
  setup do
10
10
  delete_registered_queries
11
- delete_percolator_index if ENV['TRAVIS']
11
+ delete_percolator_index
12
12
  @index = Tire.index('percolator-test')
13
13
  @index.create
14
14
  end
15
15
  teardown do
16
16
  delete_registered_queries
17
- delete_percolator_index if ENV['TRAVIS']
17
+ delete_percolator_index
18
18
  @index.delete
19
19
  end
20
20
 
@@ -98,7 +98,7 @@ module Tire
98
98
  end
99
99
  end
100
100
 
101
- end
101
+ end if ENV['TRAVIS']
102
102
 
103
103
  private
104
104
 
@@ -78,6 +78,23 @@ module Tire
78
78
  assert_equal [], results.first.tags
79
79
  end
80
80
 
81
+ context "with deleting" do
82
+ should "search with simple query" do
83
+ PersistentArticle.create :id => 1, :title => 'One'
84
+ PersistentArticle.index.refresh
85
+
86
+ results = PersistentArticle.search 'one'
87
+ assert_equal 1, results.size
88
+
89
+ PersistentArticle.delete do
90
+ term :title, 'one'
91
+ end
92
+
93
+ results = PersistentArticle.search 'one'
94
+ assert_equal 0, results.size
95
+ end
96
+ end
97
+
81
98
  context "with pagination" do
82
99
 
83
100
  setup do
@@ -188,7 +205,7 @@ module Tire
188
205
  context "percolated search" do
189
206
  setup do
190
207
  delete_registered_queries
191
- delete_percolator_index if ENV['TRAVIS']
208
+ delete_percolator_index
192
209
  PersistentArticleWithPercolation.index.register_percolator_query('alert') { string 'warning' }
193
210
  Tire.index('_percolator').refresh
194
211
  end
@@ -206,7 +223,7 @@ module Tire
206
223
  a = PersistentArticleWithPercolation.create :title => 'Warning!'
207
224
  assert_contains a.matches, 'alert'
208
225
  end
209
- end
226
+ end if ENV['TRAVIS']
210
227
 
211
228
  context "with strict mapping" do
212
229
  should "successfuly save valid model" do
@@ -363,13 +363,11 @@ module Tire
363
363
  @index.store Article.new(:id => 123, :title => 'Test', :body => 'Lorem')
364
364
  end
365
365
 
366
- should "convert document ID to string or number" do
367
- # This is related to issues #529, #535:
368
- # When using Mongoid and the Yajl gem, document IDs from Mongo (Moped::BSON::ObjectId)
369
- # are incorrectly serialized to JSON, and documents are stored with incorrect, auto-generated IDs.
366
+ should "convert document ID to string" do
367
+ # This is related to issues #529, #535, #775
370
368
  class Document1; def id; "one"; end; end
371
369
  class Document2; def id; 1; end; end
372
- class Document3; class ID; def as_json; 'c'; end; end
370
+ class Document3; class ID; def to_s; 'c'; end; end
373
371
  def id; ID.new; end
374
372
  end
375
373
 
@@ -378,7 +376,7 @@ module Tire
378
376
  document_3 = Document3.new
379
377
 
380
378
  assert_equal 'one', @index.get_id_from_document(document_1)
381
- assert_equal 1, @index.get_id_from_document(document_2)
379
+ assert_equal '1', @index.get_id_from_document(document_2)
382
380
  assert_equal 'c', @index.get_id_from_document(document_3)
383
381
  end
384
382
 
@@ -616,7 +614,7 @@ module Tire
616
614
  end
617
615
 
618
616
  context "when performing a bulk api action" do
619
- # Possible Bulk API actions are `index`, `create`, `delete`
617
+ # Possible Bulk API actions are `index`, `create`, `delete`, `update`
620
618
  #
621
619
  # The expected JSON looks like this:
622
620
  #
@@ -625,6 +623,8 @@ module Tire
625
623
  # {"create":{"_index":"dummy","_type":"document","_id":"2"}}
626
624
  # {"id":"2","title":"Two"}
627
625
  # {"delete":{"_index":"dummy","_type":"document","_id":"2"}}
626
+ # {"update":{"_index":"dummy","_type":"document","_id":"1","_retry_on_conflict": 3}}
627
+ # {"doc":{"title":"Updated One"}}
628
628
  #
629
629
  # See http://www.elasticsearch.org/guide/reference/api/bulk.html
630
630
 
@@ -681,6 +681,39 @@ module Tire
681
681
  @index.bulk :delete, [ {:id => '1', :title => 'One'}, {:id => '2', :title => 'Two'} ]
682
682
  end
683
683
 
684
+ should "serialize payload for update action" do
685
+ Configuration.client.
686
+ expects(:post).
687
+ with do |url, payload|
688
+ assert_equal "#{@index.url}/_bulk", url
689
+ assert_match /"update"/, payload
690
+ assert_match /"_index":"dummy"/, payload
691
+ assert_match /"_type":"document"/, payload
692
+ assert_match /"_id":"1"/, payload
693
+ assert_match /"_id":"2"/, payload
694
+ lines = payload.split("\n")
695
+ assert_equal 'One', MultiJson.decode(lines[1])['doc']['title']
696
+ assert_equal 'Two', MultiJson.decode(lines[3])['doc']['title']
697
+ assert_equal true, MultiJson.decode(lines[3])['doc_as_upsert']
698
+ end.
699
+ returns(mock_response('{}'), 200)
700
+
701
+ @index.bulk :update, [ {:id => '1', :doc => {:title => 'One'}}, {:id => '2', :doc => {:title => 'Two'}, :doc_as_upsert => true} ]
702
+ end
703
+
704
+ should 'serialize _retry_on_conflict parameter into payload header' do
705
+ Configuration.client.
706
+ expects(:post).
707
+ with do |url, payload|
708
+ lines = payload.split("\n")
709
+ assert_equal 3, MultiJson.decode(lines[0])['update']['_retry_on_conflict']
710
+ assert_equal 3, MultiJson.decode(lines[2])['update']['_retry_on_conflict']
711
+ end.
712
+ returns(mock_response('{}'), 200)
713
+
714
+ @index.bulk :update, [ {:id => '1', :doc => {:title => 'One'}}, {:id => '2', :doc => {:title => 'Two'}} ], :retry_on_conflict => 3
715
+ end
716
+
684
717
  should "serialize meta parameters into payload header" do
685
718
  Configuration.client.
686
719
  expects(:post).
@@ -214,9 +214,11 @@ module Tire
214
214
  assert_equal false, article.hidden
215
215
  end
216
216
 
217
- should "evaluate lambdas as default values" do
217
+ should "evaluate lambdas as default values at time of initialization" do
218
+ now = Time.now
219
+ Time.stubs(:now).returns(now)
218
220
  article = PersistentArticleWithDefaults.new
219
- assert_equal Time.now.year, article.created_at.year
221
+ assert_equal now, article.created_at
220
222
  end
221
223
 
222
224
  should "not affect default value" do
@@ -69,7 +69,7 @@ module Tire
69
69
 
70
70
  should "be kaminari compatible" do
71
71
  collection = Results::Collection.new(@default_response)
72
- %w(limit_value total_count num_pages offset_value first_page? last_page?).each do |method|
72
+ %w(limit_value total_count num_pages offset_value first_page? last_page? out_of_range?).each do |method|
73
73
  assert_respond_to collection, method
74
74
  end
75
75
  end
@@ -128,6 +128,14 @@ module Tire
128
128
  assert_match /index_1,index_2/, s.to_curl
129
129
  end
130
130
 
131
+ should 'not include the load option in queries' do
132
+ s = Search::Search.new(:load => { :includes => [:author, {:nested => :relation}] }) do
133
+ query { string 'title:foo' }
134
+ end
135
+
136
+ assert_nil s.to_hash[:load], 'Make sure to ignore load in URL params'
137
+ end
138
+
131
139
  should "return itself as a Hash" do
132
140
  s = Search::Search.new('index') do
133
141
  query { string 'title:foo' }
@@ -68,13 +68,13 @@ Gem::Specification.new do |s|
68
68
  your models (incrementally upon saving, or in bulk), searching and
69
69
  paginating the results.
70
70
 
71
- Please check the documentation at <http://karmi.github.com/tire/>.
71
+ Please check the documentation at <http://karmi.github.com/retire/>.
72
72
  DESC
73
73
 
74
74
  s.post_install_message =<<-CHANGELOG.gsub(/^ /, '')
75
75
  ================================================================================
76
76
 
77
- Please check the documentation at <http://karmi.github.com/tire/>.
77
+ Please check the documentation at <http://karmi.github.com/retire/>.
78
78
 
79
79
  --------------------------------------------------------------------------------
80
80
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tire
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-06-08 00:00:00.000000000 Z
12
+ date: 2013-10-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -370,7 +370,7 @@ description: ! " Tire is a Ruby client for the Elasticsearch search engine/dat
370
370
  bulk API, and presents an easy-to-use DSL for constructing your queries.\n\n It
371
371
  has full ActiveRecord/ActiveModel compatibility, allowing you to index\n your
372
372
  models (incrementally upon saving, or in bulk), searching and\n paginating the
373
- results.\n\n Please check the documentation at <http://karmi.github.com/tire/>.\n"
373
+ results.\n\n Please check the documentation at <http://karmi.github.com/retire/>.\n"
374
374
  email: karmi@karmi.cz
375
375
  executables: []
376
376
  extensions: []
@@ -391,6 +391,7 @@ files:
391
391
  - lib/tire/alias.rb
392
392
  - lib/tire/configuration.rb
393
393
  - lib/tire/count.rb
394
+ - lib/tire/delete_by_query.rb
394
395
  - lib/tire/dsl.rb
395
396
  - lib/tire/http/client.rb
396
397
  - lib/tire/http/clients/curb.rb
@@ -444,6 +445,7 @@ files:
444
445
  - test/integration/count_test.rb
445
446
  - test/integration/custom_filters_score_queries_test.rb
446
447
  - test/integration/custom_score_queries_test.rb
448
+ - test/integration/delete_by_query_test.rb
447
449
  - test/integration/dis_max_queries_test.rb
448
450
  - test/integration/dsl_search_test.rb
449
451
  - test/integration/explanation_test.rb
@@ -519,16 +521,13 @@ files:
519
521
  homepage: http://github.com/karmi/tire
520
522
  licenses: []
521
523
  post_install_message: ! "================================================================================\n\n
522
- \ Please check the documentation at <http://karmi.github.com/tire/>.\n\n--------------------------------------------------------------------------------\n\n
523
- \ IMPORTANT CHANGES LATELY:\n\n * Fixed incorrect inflection in the Rake import
524
- tasks\n * Added support for `geo_distance` facets\n * Added support for the `custom_filters_score`
525
- query\n * Added a custom strategy option to <Model.import>\n * Allow the `:wrapper`
526
- option to be passed to Tire.search consistently\n * Improved the Mongoid importing
527
- strategy\n * Merge returned `fields` with `_source` if both are returned\n * Removed
528
- the deprecated `text` query\n * [FIX] Rescue HTTP client specific connection errors
529
- in MyModel#create_elasticsearch_index\n * Added support for passing `version` in
530
- Tire::Index#store\n * Added support for `_version_type` in Tire::Index#bulk\n *
531
- Added ActiveModel::Serializers compatibility\n\n See the full changelog at <http://github.com/karmi/tire/commits/v0.6.0>.\n\n--------------------------------------------------------------------------------\n"
524
+ \ Please check the documentation at <http://karmi.github.com/retire/>.\n\n--------------------------------------------------------------------------------\n\n
525
+ \ IMPORTANT CHANGES LATELY:\n\n * Added support for bulk update\n * Improved Kaminari
526
+ support\n * Improved the behaviour of `default` properties in Tire::Persistence\n
527
+ \ * Added the information about the gem \"retirement\" and other documentation improvements\n
528
+ \ * Fixed errors due to NewRelic's patching of Curl\n * [ACTIVEMODEL] Use Object#id#to_s
529
+ in `get_id_from_document`\n * Added support for \"Delete By Query\" API\n\n See
530
+ the full changelog at <http://github.com/karmi/tire/commits/v0.6.1>.\n\n--------------------------------------------------------------------------------\n"
532
531
  rdoc_options:
533
532
  - --charset=UTF-8
534
533
  require_paths:
@@ -567,6 +566,7 @@ test_files:
567
566
  - test/integration/count_test.rb
568
567
  - test/integration/custom_filters_score_queries_test.rb
569
568
  - test/integration/custom_score_queries_test.rb
569
+ - test/integration/delete_by_query_test.rb
570
570
  - test/integration/dis_max_queries_test.rb
571
571
  - test/integration/dsl_search_test.rb
572
572
  - test/integration/explanation_test.rb