Permalink
Browse files

First commit.

  • Loading branch information...
0 parents commit a4b8a631c90317b91f3744db370b8d3de2f4da7c @jonmagic committed Mar 6, 2012
Showing with 373 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +4 −0 Gemfile
  3. +19 −0 LICENSE
  4. +53 −0 README.md
  5. +10 −0 Rakefile
  6. +26 −0 elasticsearch-client.gemspec
  7. +188 −0 lib/elasticsearch.rb
  8. +3 −0 lib/elasticsearch/version.rb
  9. +64 −0 spec/elasticsearch_spec.rb
  10. +2 −0 spec/spec_helper.rb
@@ -0,0 +1,4 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in elasticsearch-client.gemspec
+gemspec
19 LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Jonathan Hoyt
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
@@ -0,0 +1,53 @@
+# ElasticSearch ruby client
+
+ElasticSearch ruby module.
+
+## Usage
+
+Add to Gemfile
+
+ gem 'elasticsearch-client', :require => 'elasticsearch'
+
+Create connection:
+
+ index = 'twitter'
+ url = 'http://localhost:9200'
+ es = ElasticSearch::Index.new(index, url)
+
+Index a document:
+
+ doc = {:id => 'abcd', :foo => 'bar'}
+ es.add(index, doc[:id], doc)
+
+Get a document:
+
+ id = '1234'
+ es.mget(id)
+
+Search:
+
+ query = {
+ :query => {
+ :bool => {
+ :must => {
+ :query_string => {
+ :default_field => '_all',
+ :query => 'foobar!',
+ }
+ }
+ }
+ }
+ }
+ es.search(index, query)
+
+Remove record:
+
+ es.remove_by_query(index, :term => {:id => id})
+
+## Note on Patches/Pull Requests
+
+* Fork the project.
+* Make your feature addition or bug fix.
+* Add tests for it. This is important so we don't break it in a future version unintentionally.
+* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine, but bump version in a commit by itself so we can ignore when we pull)
+* Send us a pull request. Bonus points for topic branches.
@@ -0,0 +1,10 @@
+require "bundler/gem_tasks"
+
+require "rspec/core/rake_task"
+desc "Run the specs"
+RSpec::Core::RakeTask.new do |t|
+ t.rspec_opts = %w(-fs --color)
+end
+
+task :default => :spec
+task :test => :spec
@@ -0,0 +1,26 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "elasticsearch/version"
+
+Gem::Specification.new do |s|
+ s.name = "elasticsearch-client"
+ s.version = ElasticSearch::VERSION
+ s.authors = ["Jonathan Hoyt"]
+ s.email = ["hoyt@github.com"]
+ s.homepage = ""
+ s.summary = %q{ElasticSearch ruby client.}
+ s.description = %q{ElasticSearch ruby client.}
+
+ s.rubyforge_project = "elasticsearch-client"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ s.add_dependency 'faraday', '~> 0.7.6'
+ s.add_dependency 'yajl-ruby', '~> 1.1.0'
+
+ s.add_development_dependency 'rake'
+ s.add_development_dependency 'rspec', '~> 2.8.0'
+end
@@ -0,0 +1,188 @@
+require "elasticsearch/version"
+require 'faraday'
+require 'yajl'
+require 'time'
+
+module ElasticSearch
+ class JSONResponse < Faraday::Response::Middleware
+ def parse(body)
+ Yajl.load body
+ end
+ end
+
+ class Error < StandardError
+ end
+
+ def self.get_connection(server)
+ return unless server
+
+ Faraday.new(:url => server) do |builder|
+ # TODO: add timeout middleware
+ builder.request :json
+ # builder.response :logger
+ builder.use JSONResponse
+ builder.adapter :excon
+ end
+ end
+
+ def self.available?
+ conn = get_connection
+ resp = conn.get '/'
+ resp.status == 200
+ end
+
+ # Object to represent an index in elasticsearch
+ class Index
+ def initialize(name, server)
+ @name = name
+ @conn = ElasticSearch.get_connection(server)
+ end
+
+ # Some helpers for making REST calls to elasticsearch
+ %w[ get post put delete ].each do |method|
+ class_eval <<-EOC, __FILE__, __LINE__
+ def #{method}(*args, &blk)
+ raise Error, "no connection" unless @conn
+ resp = @conn.#{method}(*args, &blk)
+ raise Error, "elasticsearch server is offline or not accepting requests" if resp.status == 0
+ raise Error, resp.body['error'] if resp.body['error']
+ @last_resp = resp
+ resp.body
+ end
+ EOC
+ end
+
+ # Force a refresh of this index
+ #
+ # This basically tells elasticsearch to flush it's buffers
+ # but not clear caches (unlike a commit in Solr)
+ # "Commits" happen automatically and are managed by elasticsearch
+ #
+ # Returns a hash, the parsed response body from elasticsearch
+ def refresh
+ post "/#{@name}/_refresh"
+ end
+
+ def bulk(data)
+ return if data.empty?
+ body = post "/#{@name}/_bulk", data
+ raise Error, "bulk import got HTTP #{@last_resp.status} response" if @last_resp.status != 200
+ end
+
+ # Grab a bunch of items from this index
+ #
+ # type - the type to pull from
+ # ids - an Array of ids to fetch
+ #
+ # Returns a hash, the parsed response body from elasticsearch
+ def mget(type, ids)
+ get do |req|
+ req.url "#{@name}/#{type}/_mget"
+ req.body = {'ids' => ids}
+ end
+ end
+
+ # Search this index using a post body
+ #
+ # types - the type or types (comma seperated) to search
+ # options - options hash for this search request
+ #
+ # Returns a hash, the parsed response body from elasticsearch
+ def search(types, options)
+ get do |req|
+ req.url "#{@name}/#{types}/_search"
+ req.body = options
+ end
+ end
+
+ # Search this index using a query string
+ #
+ # types - the type or types (comma seperated) to search
+ # query - the search query string
+ # options - options hash for this search request (optional)
+ #
+ # Returns a hash, the parsed response body from elasticsearch
+ def query(types, query, options=nil)
+ query = {'q' => query} if query.is_a?(String)
+ get do |req|
+ req.url "#{@name}/#{types}/_search", query
+ req.body = options if options
+ end
+ end
+
+ # Count results using a query string
+ #
+ # types - the type or types (comma seperated) to search
+ # query - the search query string
+ # options - options hash for this search request (optional)
+ #
+ # Returns a hash, the parsed response body from elasticsearch
+ def count(types, query, options=nil)
+ query = {'q' => query} if query.is_a?(String)
+ get do |req|
+ req.url "#{@name}/#{types}/_count", query
+ req.body = options if options
+ end
+ end
+
+ # Add a document to this index
+ #
+ # type - the type of this document
+ # id - the unique identifier for this document
+ # doc - the document to be indexed
+ #
+ # Returns a hash, the parsed response body from elasticsearch
+ def add(type, id, doc, params={})
+ doc.each do |key, val|
+ # make sure dates are in a consistent format for indexing
+ doc[key] = val.iso8601 if val.respond_to?(:iso8601)
+ end
+
+ put do |req|
+ req.url "/#{@name}/#{type}/#{id}", params
+ req.body = doc
+ end
+ end
+
+ # Remove a document from this index
+ #
+ # type - the type of document to be removed
+ # id - the unique identifier of the document to be removed
+ #
+ # Returns a hash, the parsed response body from elasticsearch
+ def remove(type, id)
+ delete do |req|
+ req.url "#{@name}/#{type}/#{id}"
+ end
+ end
+
+ # Remove a collection of documents matched by a query
+ #
+ # types - the type or types to query
+ # options - the search options hash
+ #
+ # Returns a hash, the parsed response body from elasticsearch
+ def remove_by_query(types, options)
+ delete do |req|
+ req.url "#{@name}/#{types}/_query"
+ req.body = options
+ end
+ end
+
+ # Create a new index in elasticsearch
+ #
+ # name - the name of the index to be created
+ # create_options - a hash of index creation options
+ #
+ # Returns a new ElasticSearch::Index instance
+ def self.create(name, create_options={})
+ conn = ElasticSearch.get_connection
+ conn.put do |req|
+ req.url "/#{name}"
+ req.body = create_options
+ end
+
+ new(name)
+ end
+ end
+end
@@ -0,0 +1,3 @@
+module ElasticSearch
+ VERSION = "0.0.1"
+end
@@ -0,0 +1,64 @@
+require 'spec_helper'
+require 'elasticsearch'
+
+describe ElasticSearch::JSONResponse do
+ describe "#parse" do
+ subject { ElasticSearch::JSONResponse.new }
+
+ it "parses json" do
+ subject.parse('{"good":"bad"}').should == {"good" => "bad"}
+ end
+ end
+end
+
+describe ElasticSearch::Error do
+ subject { ElasticSearch::Error.new }
+
+ it "subclasses StandardError" do
+ subject.methods.should include("backtrace")
+ subject.methods.should include("exception")
+ subject.methods.should include("message")
+ subject.methods.should include("set_backtrace")
+ subject.methods.should include("to_str")
+ end
+end
+
+describe ElasticSearch do
+ let(:server) { '127.0.0.1' }
+
+ describe ".get_connection" do
+ let(:subject) { ElasticSearch.get_connection(server) }
+
+ it "returns immediately without server" do
+ Faraday.should_not_receive(:new)
+ ElasticSearch.get_connection(nil)
+ end
+
+ it "creates faraday instance with server" do
+ Faraday.should_receive(:new).with(:url => server)
+ subject
+ end
+
+ it "returns faraday instance" do
+ subject.should be_instance_of(Faraday::Connection)
+ end
+ end
+
+ describe ".available?" do
+ context "connection up" do
+ it "returns true" do
+ conn = stub(:get => stub(:status => 200))
+ ElasticSearch.stub(:get_connection => conn)
+ ElasticSearch.available?.should be_true
+ end
+ end
+
+ context "connection down" do
+ it "returns false" do
+ conn = stub(:get => stub(:status => 500))
+ ElasticSearch.stub(:get_connection => conn)
+ ElasticSearch.available?.should be_false
+ end
+ end
+ end
+end
@@ -0,0 +1,2 @@
+require 'bundler'
+Bundler.require :default, :test

0 comments on commit a4b8a63

Please sign in to comment.