Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[#366] Cleaned up the Update API support and added an integration tes…

…t suite

This patch expands karmi/retire@f620b36.

* Polished the `curl` output from the API call
* Added the ability to pass URL parameters (timeout, refresh, etc)
* Cleaned up the unit tests
* Added a separate integration test for resilience and documentation

Example usage
-------------

Update a counter:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ruby
  Tire.index('articles-with-tags') { update( 'article', '1', :script => "ctx._source.views += 1" ) and refresh }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Add a tag to the document:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ruby
    Tire.index('articles-with-tags') do
      update 'article', '1', :script => "ctx._source.tags += tag",
                             :params => { :tag => 'new-tag' }
      refresh
    end
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Set a title of the document:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ruby
  @title = 'Foo Is Now Bar!'

  Tire.index('articles-with-tags') do |index|

    index.update 'article', '1', :script => "ctx._source.title = title",
                                 :params => { :title => @title }
    index.refresh
  end
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

See <http://www.elasticsearch.org/guide/reference/api/update.html> for more documentation.

---

Closes #366.
  • Loading branch information...
commit ff00015244230f796304c39fff3985804af9b636 1 parent f620b36
Karel Minarik authored
29 lib/tire/index.rb
View
@@ -198,6 +198,22 @@ def retrieve(type, id)
logged(id, curl)
end
+ def update(type, id, payload={}, options={})
+ raise ArgumentError, "Please pass a document type" unless type
+ raise ArgumentError, "Please pass a document ID" unless id
+ raise ArgumentError, "Please pass a script in the payload hash" unless payload[:script]
+
+ type = Utils.escape(type)
+ url = "#{self.url}/#{type}/#{id}/_update"
+ url += "?#{options.to_param}" unless options.keys.empty?
+ @response = Configuration.client.post url, MultiJson.encode(payload)
+ MultiJson.decode(@response.body)
+
+ ensure
+ curl = %Q|curl -X POST "#{url}" -d '#{MultiJson.encode(payload)}'|
+ logged(id, curl)
+ end
+
def refresh
@response = Configuration.client.post "#{url}/_refresh", ''
@@ -275,19 +291,6 @@ def percolate(*args, &block)
logged('_percolate', curl)
end
- def update(type, id, options)
- raise ArgumentError, "Please pass a document ID" unless id
- raise ArgumentError, "Missing script in options hash" unless options[:script]
-
- type = Utils.escape(type)
- url = "#{self.url}/#{type}/#{id}/_update"
- @response = Configuration.client.post url, MultiJson.encode(options)
- MultiJson.decode(@response.body)['ok']
- ensure
- curl = %Q|curl -X POST "#{url}"|
- logged(id, curl)
- end
-
def logged(endpoint='/', curl='')
if Configuration.logger
error = $!
2  lib/tire/logger.rb
View
@@ -9,7 +9,7 @@ def initialize(device, options={})
end
@device.sync = true if @device.respond_to?(:sync)
@options = options
- at_exit { @device.close unless @device.closed? } if @device.respond_to?(:closed?) && @device.respond_to?(:close)
+ # at_exit { @device.close unless @device.closed? } if @device.respond_to?(:closed?) && @device.respond_to?(:close)
end
def level
111 test/integration/index_update_document_test.rb
View
@@ -0,0 +1,111 @@
+require 'test_helper'
+
+module Tire
+
+ class IndexUpdateDocumentIntegrationTest < Test::Unit::TestCase
+ include Test::Integration
+
+ context "Updating a document" do
+
+ setup do
+ Tire.index 'articles-with-tags' do
+ delete
+ create
+
+ store :type => 'article', :id => 1, :title => 'One', :tags => ['foo'], :views => 0
+ store :type => 'article', :id => 2, :title => 'Two', :tags => ['foo', 'bar'], :views => 10
+ store :type => 'article', :id => 3, :title => 'Three', :tags => ['foobar']
+
+ refresh
+ end
+ end
+
+ teardown { Tire.index('articles-with-tags').delete }
+
+ should "increment a counter" do
+ Tire.index('articles-with-tags') { update( 'article', '1', :script => "ctx._source.views += 1" ) and refresh }
+
+ document = Tire.search('articles-with-tags') { query { string 'title:one' } }.results.first
+ assert_equal 1, document.views, document.inspect
+
+ Tire.index('articles-with-tags') { update( 'article', '2', :script => "ctx._source.views += 1" ) and refresh }
+
+ document = Tire.search('articles-with-tags') { query { string 'title:two' } }.results.first
+ assert_equal 11, document.views, document.inspect
+ end
+
+ should "add a tag to document" do
+ Tire.index('articles-with-tags') do
+ update 'article', '1', :script => "ctx._source.tags += tag",
+ :params => { :tag => 'new' }
+ refresh
+ end
+
+ document = Tire.search('articles-with-tags') { query { string 'title:one' } }.results.first
+ assert_equal ['foo', 'new'], document.tags, document.inspect
+ end
+
+ should "remove a tag from document" do
+ Tire.index('articles-with-tags') do
+ update 'article', '1', :script => "ctx._source.tags = tags",
+ :params => { :tags => [] }
+ refresh
+ end
+
+ document = Tire.index('articles-with-tags').retrieve 'article', '1'
+ assert_equal [], document.tags, document.inspect
+ end
+
+ should "remove the entire document if specific condition is met" do
+ Tire.index('articles-with-tags') do
+ # Remove document when it contains tag 'foobar'
+ update 'article', '3', :script => "ctx._source.tags.contains(tag) ? ctx.op = 'delete' : 'none'",
+ :params => { :tag => 'foobar' }
+ refresh
+ end
+
+ assert_nil Tire.index('articles-with-tags').retrieve 'article', '3'
+ end
+
+ should "pass the operation parameters to the API" do
+ Tire.index('articles-with-tags').update 'article', '2', { :script => "ctx._source.tags += tag",
+ :params => { :tag => 'new' }
+ },
+ {
+ :refresh => true
+ }
+
+ document = Tire.search('articles-with-tags') { query { string 'title:two' } }.results.first
+ assert_equal 3, document.tags.size, document.inspect
+ end
+
+ should "access variables from the outer scope" do
+ $t = self
+
+ class Updater
+ @tags = ['foo', 'bar', 'baz']
+
+ def self.perform_update!
+ $t.assert_not_nil @tags
+
+ Tire.index('articles-with-tags') do |index|
+ $t.assert_not_nil @tags
+
+ index.update 'article', '3', :script => "ctx._source.tags = tags",
+ :params => { :tags => @tags }
+ index.refresh
+ end
+ end
+ end
+
+ Updater.perform_update!
+
+ document = Tire.search('articles-with-tags') { query { string 'title:three' } }.results.first
+ assert_equal 3, document.tags.size, document.inspect
+ end
+
+ end
+
+ end
+
+end
61 test/unit/index_test.rb
View
@@ -420,6 +420,48 @@ class MyDocument;end; document = MyDocument.new
end
+ context "when updating" do
+
+ should "send payload" do
+ Configuration.client.expects(:post).with do |url,payload|
+ payload = MultiJson.decode(payload)
+ # p [url, payload]
+ assert_equal( "#{@index.url}/document/42/_update", url ) &&
+ assert_not_nil( payload['script'] ) &&
+ assert_not_nil( payload['params'] ) &&
+ assert_equal( '21', payload['params']['bar'] )
+ end.
+ returns(
+ mock_response('{"ok":"true","_index":"dummy","_type":"document","_id":"42","_version":"2"}'))
+
+ assert @index.update('document', '42', {:script => "ctx._source.foo = bar;", :params => { :bar => '21' }})
+ end
+
+ should "send options" do
+ Configuration.client.expects(:post).with do |url,payload|
+ payload = MultiJson.decode(payload)
+ # p [url, payload]
+ assert_equal( "#{@index.url}/document/42/_update?timeout=1000", url ) &&
+ assert_nil( payload['timeout'] )
+ end.
+ returns(
+ mock_response('{"ok":"true","_index":"dummy","_type":"document","_id":"42","_version":"2"}'))
+ assert @index.update('document', '42', {:script => "ctx._source.foo = 'bar'"}, {:timeout => 1000})
+ end
+
+ should "raise error when no type or ID is passed" do
+ assert_raise(ArgumentError) { @index.update('article', nil, :script => 'foobar') }
+ assert_raise(ArgumentError) { @index.update(nil, '123', :script => 'foobar') }
+ end
+
+ should "raise an error when no script is passed" do
+ assert_raise ArgumentError do
+ @index.update('article', "42", {:params => {"foo" => "bar"}})
+ end
+ end
+
+ end
+
context "when storing in bulk" do
# The expected JSON looks like this:
#
@@ -750,24 +792,6 @@ def self.count; DATA.size; end
assert_equal ["alerts"], matches
end
- should "run update script against a given document" do
- Configuration.client.expects(:post).with do |url,payload|
- payload = MultiJson.decode(payload)
- # p [url, payload]
- url == "#{@index.url}/document/42/_update" &&
- payload['script'] != nil &&
- payload['params'] != nil &&
- payload['params']['x'] == '21' &&
- payload['params']['y'] == [2,4,6]
- end.returns(mock_response('{"ok":"true","_index":"dummy","_type":"document","_id":"42","_version":"2"}'))
- assert @index.update('document', '42', {:script => "ctx._source.test = 'youpi';", :params => { :x => '21', :y => [2,4,6] }})
- end
-
- should "raise error when running update without a script or an id" do
- assert_raise(ArgumentError) { @index.update('lol', "42", {:params => {"foo" => "bar"}}) }
- assert_raise(ArgumentError) { @index.update('lol', nil, {:script => "ctx._source.test = 'test';"}) }
- end
-
context "while storing document" do
should "percolate document against all registered queries" do
@@ -862,6 +886,7 @@ def index_something
end
end
+
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.