def bulk(action, documents, options={})
      return false if documents.empty?

      # TODO: A more Ruby-like DSL notation should be supported:
      #
      #     Tire.index('myindex').bulk do
      #       create id: 1, title: 'bar', _routing: 'abc'
      #       delete id: 1
      #       # ...
      #     end

      payload = documents.map do |document|
        type = get_type_from_document(document, :escape => false) # Do not URL-escape the _type
        id   = get_id_from_document(document)

        if ENV['DEBUG']
          STDERR.puts "[ERROR] Document #{document.inspect} does not have ID" unless id
        end

        if action.to_sym == :update
          raise ArgumentError, "Cannot update without document type" unless type
          raise ArgumentError, "Cannot update without document ID" unless id
          raise ArgumentError, "Update requires a hash document" unless document.respond_to?(:to_hash)
          document = document.to_hash
          raise ArgumentError, "Update requires either a script or a partial document" unless document[:script] || document[:doc]
        end

        header = { action.to_sym => { :_index => name, :_type => type, :_id => id } }
        header[action.to_sym].update({:_retry_on_conflict => options[:retry_on_conflict]}) if options[:retry_on_conflict]

        if document.respond_to?(:to_hash) && doc_hash = document.to_hash
          meta = doc_hash.select do |k,v|
            [ :_parent,
              :_percolate,
              :_retry_on_conflict,
              :_routing,
              :_timestamp,
              :_ttl,
              :_version,
              :_version_type].include?(k)
          end
          # Normalize Ruby 1.8 and Ruby 1.9 Hash#select behaviour
          meta = Hash[meta] unless meta.is_a?(Hash)

          # meta = SUPPORTED_META_PARAMS_FOR_BULK.inject({}) { |hash, param|
          #   value = doc_hash.delete(param)
          #   hash[param] = value unless !value || value.empty?
          #   hash
          # }
          header[action.to_sym].update(meta)
        end

        if action.to_sym == :update
          document.keep_if do |k,_|
            [
              :doc,
              :upsert,
              :doc_as_upsert,
              :script,
              :params,
              :lang
            ].include?(k)
          end
        end

        output = []
        output << MultiJson.encode(header)
        output << convert_document_to_json(document) unless action.to_s == 'delete'
        output.join("\n")
      end
      payload << ""

      tries = 5
      count = 0

      begin
        params = {}
        params[:consistency] = options.delete(:consistency)
        params[:refresh]     = options.delete(:refresh)
        params               = params.reject { |name,value| !value }
        params_encoded       = params.empty? ? '' : "?#{params.to_param}"

        @response = Configuration.client.post("#{url}/_bulk#{params_encoded}", payload.join("\n"))
        raise RuntimeError, "#{@response.code} > #{@response.body}" if @response && @response.failure?
        @response
      rescue StandardError => error
        if count < tries
          count += 1
          STDERR.puts "[ERROR] #{error.message}, retrying (#{count})..."
          retry
        else
          STDERR.puts "[ERROR] Too many exceptions occured, giving up. The HTTP response was: #{error.message}"
          raise if options[:raise]
        end

      ensure
        data = Configuration.logger && Configuration.logger.level.to_s == 'verbose' ? payload.join("\n") : '... data omitted ...'
        curl = %Q|curl -X POST "#{url}/_bulk" --data-binary '#{data}'|
        logged('_bulk', curl)
      end

    end
def bulk(action, documents, options={})
      return false if documents.empty?

      # TODO: A more Ruby-like DSL notation should be supported:
      #
      #     Tire.index('myindex').bulk do
      #       create id: 1, title: 'bar', _routing: 'abc'
      #       delete id: 1
      #       # ...
      #     end

      payload = documents.map do |document|
        type = get_type_from_document(document, :escape => false) # Do not URL-escape the _type
        id   = get_id_from_document(document)

        if ENV['DEBUG']
          STDERR.puts "[ERROR] Document #{document.inspect} does not have ID" unless id
        end

        if action.to_sym == :update
          raise ArgumentError, "Cannot update without document type" unless type
          raise ArgumentError, "Cannot update without document ID" unless id
          raise ArgumentError, "Update requires a hash document" unless document.respond_to?(:to_hash)
          document = document.to_hash
          raise ArgumentError, "Update requires either a script or a partial document" unless document[:script] || document[:doc]
        end

        header = { action.to_sym => { :_index => name, :_type => type, :_id => id } }
        header[action.to_sym].update({:_retry_on_conflict => options[:retry_on_conflict]}) if options[:retry_on_conflict]

        if document.respond_to?(:to_hash) && doc_hash = document.to_hash
          meta = doc_hash.select do |k,v|
            [ :_parent,
              :_percolate,
              :_retry_on_conflict,
              :_routing,
              :_timestamp,
              :_ttl,
              :_version,
              :_version_type].include?(k)
          end
          # Normalize Ruby 1.8 and Ruby 1.9 Hash#select behaviour
          meta = Hash[meta] unless meta.is_a?(Hash)

          # meta = SUPPORTED_META_PARAMS_FOR_BULK.inject({}) { |hash, param|
          #   value = doc_hash.delete(param)
          #   hash[param] = value unless !value || value.empty?
          #   hash
          # }
          header[action.to_sym].update(meta)
        end

        if action.to_sym == :update
          document.keep_if do |k,_|
            [
              :doc,
              :upsert,
              :doc_as_upsert,
              :script,
              :params,
              :lang
            ].include?(k)
          end
        end

        output = []
        output << MultiJson.encode(header)
        output << convert_document_to_json(document) unless action.to_s == 'delete'
        output.join("\n")
      end
      payload << ""

      tries = 5
      count = 0

      begin
        params = {}
        params[:consistency] = options.delete(:consistency)
        params[:refresh]     = options.delete(:refresh)
        params               = params.reject { |name,value| !value }
        params_encoded       = params.empty? ? '' : "?#{params.to_param}"

        @response = Configuration.client.post("#{url}/_bulk#{params_encoded}", payload.join("\n"))
        raise RuntimeError, "#{@response.code} > #{@response.body}" if @response && @response.failure?
        @response
      rescue StandardError => error
        if count < tries
          count += 1
          STDERR.puts "[ERROR] #{error.message}, retrying (#{count})..."
          retry
        else
          STDERR.puts "[ERROR] Too many exceptions occured, giving up. The HTTP response was: #{error.message}"
          raise if options[:raise]
        end

      ensure
        data = Configuration.logger && Configuration.logger.level.to_s == 'verbose' ? payload.join("\n") : '... data omitted ...'
        curl = %Q|curl -X POST "#{url}/_bulk" --data-binary '#{data}'|
        logged('_bulk', curl)
      end

    end
Performs a [bulk](http://www.elasticsearch.org/guide/reference/api/bulk.html) request

     @myindex.bulk :index, [ {id: 1, title: 'One'}, { id: 2, title: 'Two', _version: 3 } ], refresh: true

 Pass the action (`index`, `create`, `delete`, `update`) as the first argument, the collection of documents as
 the second argument, and URL parameters as the last option.

 Any _meta_ information contained in documents (such as `_routing` or `_parent`) is extracted
 and added to the "header" line.

 Shortcut methods `bulk_store`, `bulk_delete`, `bulk_create`, and `bulk_update` are available.