slingshot-rb 0.0.1
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.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +164 -0
- data/Rakefile +52 -0
- data/examples/dsl.rb +70 -0
- data/lib/slingshot/client.rb +25 -0
- data/lib/slingshot/configuration.rb +15 -0
- data/lib/slingshot/dsl.rb +17 -0
- data/lib/slingshot/index.rb +42 -0
- data/lib/slingshot/results/collection.rb +22 -0
- data/lib/slingshot/rubyext/hash.rb +3 -0
- data/lib/slingshot/search/facet.rb +34 -0
- data/lib/slingshot/search/query.rb +33 -0
- data/lib/slingshot/search/sort.rb +24 -0
- data/lib/slingshot/search.rb +70 -0
- data/lib/slingshot/slingshot-rb.rb +1 -0
- data/lib/slingshot/version.rb +3 -0
- data/lib/slingshot.rb +18 -0
- data/slingshot.gemspec +41 -0
- data/test/fixtures/articles/1.json +1 -0
- data/test/fixtures/articles/2.json +1 -0
- data/test/fixtures/articles/3.json +1 -0
- data/test/fixtures/articles/4.json +1 -0
- data/test/fixtures/articles/5.json +1 -0
- data/test/integration/facets_test.rb +47 -0
- data/test/integration/query_string_test.rb +43 -0
- data/test/integration/sort_test.rb +36 -0
- data/test/test_helper.rb +46 -0
- data/test/unit/client_test.rb +35 -0
- data/test/unit/configuration_test.rb +34 -0
- data/test/unit/index_test.rb +71 -0
- data/test/unit/results_collection_test.rb +31 -0
- data/test/unit/search_facet_test.rb +44 -0
- data/test/unit/search_query_test.rb +40 -0
- data/test/unit/search_sort_test.rb +42 -0
- data/test/unit/search_test.rb +106 -0
- data/test/unit/slingshot_test.rb +17 -0
- metadata +241 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2011 Karel Minarik
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
Slingshot
|
2
|
+
=========
|
3
|
+
|
4
|
+
_Slingshot_ aims to provide a rich Ruby API and DSL for the
|
5
|
+
[ElasticSearch](http://www.elasticsearch.org/) search engine/database.
|
6
|
+
|
7
|
+
_ElasticSearch_ is a scalable, distributed, highly-available,
|
8
|
+
RESTful database communicating by JSON over HTTP, based on [Lucene](http://lucene.apache.org/),
|
9
|
+
written in Java. It manages to very simple and very powerful at the same time.
|
10
|
+
You should seriously consider it to power search in your Ruby applications:
|
11
|
+
it will deliver all the features you want — and many more you may have not
|
12
|
+
imagined yet (native geo search? histogram facets?)
|
13
|
+
|
14
|
+
_Slingshot_ currently allow basic operation with the index and searching. See chapters below.
|
15
|
+
|
16
|
+
|
17
|
+
Installation
|
18
|
+
------------
|
19
|
+
|
20
|
+
First, you need a running _ElasticSearch_ server. Thankfully, it's easy. Let's define easy:
|
21
|
+
|
22
|
+
$ curl -L -o elasticsearch-0.14.4.tar.gz http://github.com/downloads/elasticsearch/elasticsearch/elasticsearch-0.14.4.tar.gz
|
23
|
+
$ tar -zxvf elasticsearch-0.14.4.tar.gz
|
24
|
+
$ ./elasticsearch-0.14.4/bin/elasticsearch -f
|
25
|
+
|
26
|
+
OK, easy. Now, install the gem via Rubygems:
|
27
|
+
|
28
|
+
$ gem install slingshot
|
29
|
+
|
30
|
+
or from source:
|
31
|
+
|
32
|
+
$ git clone git://github.com/karmi/slingshot.git
|
33
|
+
$ rake install
|
34
|
+
|
35
|
+
|
36
|
+
Usage
|
37
|
+
-----
|
38
|
+
|
39
|
+
Currently, you can use _Slingshot_ via the DSL (eg. by extending your class with it).
|
40
|
+
Plans for full ActiveModel integration (and other convenience layers) are in progress.
|
41
|
+
|
42
|
+
To kick the tiers, require the gem in an IRB session or a Ruby script
|
43
|
+
(note that you can run the full example from [`examples/dsl.rb`](https://github.com/karmi/slingshot/blob/master/examples/dsl.rb)):
|
44
|
+
|
45
|
+
require 'rubygems'
|
46
|
+
require 'slingshot'
|
47
|
+
|
48
|
+
First, let's create an index named `articles` and store/index some documents:
|
49
|
+
|
50
|
+
Slingshot.index 'articles' do
|
51
|
+
delete
|
52
|
+
create
|
53
|
+
|
54
|
+
store :title => 'One', :tags => ['ruby']
|
55
|
+
store :title => 'Two', :tags => ['ruby', 'python']
|
56
|
+
store :title => 'Three', :tags => ['java']
|
57
|
+
store :title => 'Four', :tags => ['ruby', 'php']
|
58
|
+
|
59
|
+
refresh
|
60
|
+
end
|
61
|
+
|
62
|
+
Now, let's query the database:
|
63
|
+
|
64
|
+
We are searching for articles tagged _ruby_, sorted by `title` in `descending` order,
|
65
|
+
and also retrieving some [_facets_](http://www.lucidimagination.com/Community/Hear-from-the-Experts/Articles/Faceted-Search-Solr)
|
66
|
+
from the database:
|
67
|
+
|
68
|
+
s = Slingshot.search 'articles' do
|
69
|
+
query do
|
70
|
+
terms :tags, ['ruby']
|
71
|
+
end
|
72
|
+
|
73
|
+
sort do
|
74
|
+
title 'desc'
|
75
|
+
end
|
76
|
+
|
77
|
+
facet 'global-tags' do
|
78
|
+
terms :tags, :global => true
|
79
|
+
end
|
80
|
+
|
81
|
+
facet 'current-tags' do
|
82
|
+
terms :tags
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Let's display the results:
|
87
|
+
|
88
|
+
s.results.each do |document|
|
89
|
+
puts "* #{ document['_source']['title'] }"
|
90
|
+
end
|
91
|
+
|
92
|
+
# * Two
|
93
|
+
# * One
|
94
|
+
# * Four
|
95
|
+
|
96
|
+
Let's display the facets (distribution of tags across the whole database):
|
97
|
+
|
98
|
+
s.results.facets['global-tags']['terms'].each do |f|
|
99
|
+
puts "#{f['term'].ljust(10)} #{f['count']}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# ruby 3
|
103
|
+
# python 1
|
104
|
+
# php 1
|
105
|
+
# java 1
|
106
|
+
|
107
|
+
We can display the full query JSON:
|
108
|
+
|
109
|
+
puts s.to_json
|
110
|
+
# {"facets":{"current-tags":{"terms":{"field":"tags"}},"global-tags":{"global":true,"terms":{"field":"tags"}}},"sort":[{"title":"desc"}],"query":{"terms":{"tags":["ruby"]}}}
|
111
|
+
|
112
|
+
See, a Ruby DSL for this thing is kinda handy? We can query _ElasticSearch_ manually with `curl`, simply:
|
113
|
+
|
114
|
+
puts s.to_curl
|
115
|
+
# curl -X POST "http://localhost:9200/articles/_search?pretty=true" -d '{"facets":{"current-tags":{"terms":{"field":"tags"}},"global-tags":{"global":true,"terms":{"field":"tags"}}},"sort":[{"title":"desc"}],"query":{"terms":{"tags":["ruby"]}}}'
|
116
|
+
|
117
|
+
|
118
|
+
Features
|
119
|
+
--------
|
120
|
+
|
121
|
+
Currently, _Slingshot_ supports only a limited subset of vast _ElasticSearch_ [Search API](http://www.elasticsearch.org/guide/reference/api/search/request-body.html) and it's [Query DSL](http://www.elasticsearch.org/guide/reference/query-dsl/):
|
122
|
+
|
123
|
+
* Creating, deleting and refreshing the index
|
124
|
+
* Storing a document in the index
|
125
|
+
* [Querying](https://github.com/karmi/slingshot/blob/master/examples/dsl.rb) the index with the `query_string`, `term` and `terms` types of queries
|
126
|
+
* Sorting the results by `fields`
|
127
|
+
* Retrieving a _terms_ type of [facets](http://www.elasticsearch.org/guide/reference/api/search/facets/index.html) -- other types are high priority
|
128
|
+
* Returning just specific fields from documents
|
129
|
+
* Paging with `from` and `size` query options
|
130
|
+
|
131
|
+
See the [`examples/dsl.rb`](blob/master/examples/dsl.rb).
|
132
|
+
|
133
|
+
Todo & Plans
|
134
|
+
------------
|
135
|
+
|
136
|
+
In order of importance:
|
137
|
+
|
138
|
+
* Basic wrapper class for _hits_ in results, so we could write `results.first.document.title` instead of using the raw Hash
|
139
|
+
* Getting document [by ID](http://www.elasticsearch.org/guide/reference/api/get.html)
|
140
|
+
* Seamless _ActiveModel_ compatibility for easy usage in _Rails_ applications (this also means nearly full _ActiveRecord_ compatibility)
|
141
|
+
* Allowing to set custom non-ActiveModel wrapper class (your own)
|
142
|
+
* Seamless [will_paginate](https://github.com/mislav/will_paginate) compatibility for easy pagination
|
143
|
+
* [Histogram](http://www.elasticsearch.org/guide/reference/api/search/facets/histogram-facet.html) facets
|
144
|
+
* Seamless support for [auto-updating _river_ index](http://www.elasticsearch.org/guide/reference/river/couchdb.html) for _CouchDB_ `_changes` feed
|
145
|
+
* [Mapping](http://www.elasticsearch.org/guide/reference/mapping/) management
|
146
|
+
* Infrastructure for query filters
|
147
|
+
* [Range](http://www.elasticsearch.org/guide/reference/query-dsl/range-filter.html) filters and queries
|
148
|
+
* [Geo Filters](http://www.elasticsearch.org/blog/2010/08/16/geo_location_and_search.html) for queries
|
149
|
+
* [Statistical](http://www.elasticsearch.org/guide/reference/api/search/facets/statistical-facet.html) facets
|
150
|
+
* [Geo Distance](http://www.elasticsearch.org/guide/reference/api/search/facets/geo-distance-facet.html) facets
|
151
|
+
* [Index aliases](http://www.elasticsearch.org/guide/reference/api/admin-indices-aliases.html) management
|
152
|
+
* [Analyze](http://www.elasticsearch.org/guide/reference/api/admin-indices-analyze.html) API support
|
153
|
+
* [Highligting](http://www.elasticsearch.org/guide/reference/api/search/highlighting.html) support
|
154
|
+
* [Bulk](http://www.elasticsearch.org/guide/reference/api/bulk.html) API
|
155
|
+
* Embedded webserver to display cluster statistics and allow easy searches
|
156
|
+
|
157
|
+
Feedback
|
158
|
+
--------
|
159
|
+
|
160
|
+
You can send feedback via [e-mail](mailto:karmi@karmi.cz) or via [Github Issues](https://github.com/karmi/slingshot/issues).
|
161
|
+
|
162
|
+
-----
|
163
|
+
|
164
|
+
[Karel Minarik](http://karmi.cz)
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
task :default => :test
|
5
|
+
|
6
|
+
require 'rake/testtask'
|
7
|
+
Rake::TestTask.new(:test) do |test|
|
8
|
+
test.libs << 'lib' << 'test'
|
9
|
+
test.pattern = 'test/**/*_test.rb'
|
10
|
+
test.verbose = true
|
11
|
+
end
|
12
|
+
|
13
|
+
namespace :test do
|
14
|
+
Rake::TestTask.new(:unit) do |test|
|
15
|
+
test.libs << 'lib' << 'test'
|
16
|
+
test.pattern = 'test/unit/*_test.rb'
|
17
|
+
test.verbose = true
|
18
|
+
end
|
19
|
+
Rake::TestTask.new(:integration) do |test|
|
20
|
+
test.libs << 'lib' << 'test'
|
21
|
+
test.pattern = 'test/integration/*_test.rb'
|
22
|
+
test.verbose = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Generate documentation
|
27
|
+
begin
|
28
|
+
require 'sdoc'
|
29
|
+
rescue LoadError
|
30
|
+
end
|
31
|
+
require 'rake/rdoctask'
|
32
|
+
Rake::RDocTask.new do |rdoc|
|
33
|
+
rdoc.rdoc_dir = 'rdoc'
|
34
|
+
rdoc.title = "Slingshot"
|
35
|
+
rdoc.rdoc_files.include('README.rdoc')
|
36
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Generate coverage reports
|
40
|
+
begin
|
41
|
+
require 'rcov/rcovtask'
|
42
|
+
Rcov::RcovTask.new do |test|
|
43
|
+
test.libs << 'test'
|
44
|
+
test.rcov_opts = ['--exclude', 'gems/*']
|
45
|
+
test.pattern = 'test/**/*_test.rb'
|
46
|
+
test.verbose = true
|
47
|
+
end
|
48
|
+
rescue LoadError
|
49
|
+
task :rcov do
|
50
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install rcov"
|
51
|
+
end
|
52
|
+
end
|
data/examples/dsl.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'slingshot'
|
5
|
+
|
6
|
+
extend Slingshot::DSL
|
7
|
+
|
8
|
+
configure do
|
9
|
+
url "http://localhost:9200"
|
10
|
+
end
|
11
|
+
|
12
|
+
index 'articles' do
|
13
|
+
delete
|
14
|
+
create
|
15
|
+
|
16
|
+
puts "Documents:", "-"*80
|
17
|
+
[
|
18
|
+
{ :title => 'One', :tags => ['ruby'] },
|
19
|
+
{ :title => 'Two', :tags => ['ruby', 'python'] },
|
20
|
+
{ :title => 'Three', :tags => ['java'] },
|
21
|
+
{ :title => 'Four', :tags => ['ruby', 'php'] }
|
22
|
+
].each do |article|
|
23
|
+
puts "Indexing article: #{article.to_json}"
|
24
|
+
store article
|
25
|
+
end
|
26
|
+
|
27
|
+
refresh
|
28
|
+
end
|
29
|
+
|
30
|
+
s = search 'articles' do
|
31
|
+
query do
|
32
|
+
terms :tags, ['ruby']
|
33
|
+
end
|
34
|
+
|
35
|
+
sort do
|
36
|
+
title 'desc'
|
37
|
+
end
|
38
|
+
|
39
|
+
facet 'global-tags' do
|
40
|
+
terms :tags, :global => true
|
41
|
+
end
|
42
|
+
|
43
|
+
facet 'current-tags' do
|
44
|
+
terms :tags
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
puts "", "Query:", "-"*80
|
49
|
+
puts s.to_json
|
50
|
+
|
51
|
+
puts "", "Raw JSON result:", "-"*80
|
52
|
+
puts JSON.pretty_generate(s.response)
|
53
|
+
|
54
|
+
puts "", "Try the query in Curl:", "-"*80
|
55
|
+
puts s.to_curl
|
56
|
+
|
57
|
+
puts "", "Results:", "-"*80
|
58
|
+
s.results.each_with_index do |document, i|
|
59
|
+
puts "#{i+1}. #{ document['_source']['title'].ljust(20) } [id] #{document['_id']}"
|
60
|
+
end
|
61
|
+
|
62
|
+
puts "", "Facets: tags distribution across the whole database:", "-"*80
|
63
|
+
s.results.facets['global-tags']['terms'].each do |f|
|
64
|
+
puts "#{f['term'].ljust(10)} #{f['count']}"
|
65
|
+
end
|
66
|
+
|
67
|
+
puts "", "Facets: tags distribution for the current query", "-"*80
|
68
|
+
s.results.facets['current-tags']['terms'].each do |f|
|
69
|
+
puts "#{f['term'].ljust(10)} #{f['count']}"
|
70
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Slingshot
|
2
|
+
|
3
|
+
module Client
|
4
|
+
|
5
|
+
class Base
|
6
|
+
def post(url, data)
|
7
|
+
raise NoMethodError, "Implement this method in your client class"
|
8
|
+
end
|
9
|
+
def delete(url)
|
10
|
+
raise NoMethodError, "Implement this method in your client class"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class RestClient < Base
|
15
|
+
def self.post(url, data)
|
16
|
+
::RestClient.post url, data
|
17
|
+
end
|
18
|
+
def self.delete(url)
|
19
|
+
::RestClient.delete url rescue nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Slingshot
|
2
|
+
module DSL
|
3
|
+
|
4
|
+
def configure(&block)
|
5
|
+
Configuration.class_eval(&block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def search(indices, &block)
|
9
|
+
Search::Search.new(indices, &block).perform
|
10
|
+
end
|
11
|
+
|
12
|
+
def index(name, &block)
|
13
|
+
Index.new(name, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Slingshot
|
2
|
+
class Index
|
3
|
+
|
4
|
+
def initialize(name, &block)
|
5
|
+
@name = name
|
6
|
+
instance_eval(&block) if block_given?
|
7
|
+
end
|
8
|
+
|
9
|
+
def delete
|
10
|
+
response = Configuration.client.delete "#{Configuration.url}/#{@name}"
|
11
|
+
return response =~ /error/ ? false : true
|
12
|
+
rescue
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def create
|
17
|
+
Configuration.client.post "#{Configuration.url}/#{@name}", ''
|
18
|
+
rescue
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def store(*args)
|
23
|
+
if args.size > 1
|
24
|
+
(type, document = args)
|
25
|
+
else
|
26
|
+
(document = args.pop; type = :document)
|
27
|
+
end
|
28
|
+
document = case true
|
29
|
+
when document.is_a?(String) then document
|
30
|
+
when document.respond_to?(:to_indexed_json) then document.to_indexed_json
|
31
|
+
else raise ArgumentError, "Please pass a JSON string or object with a 'to_indexed_json' method"
|
32
|
+
end
|
33
|
+
result = Configuration.client.post "#{Configuration.url}/#{@name}/#{type}/", document
|
34
|
+
JSON.parse(result)
|
35
|
+
end
|
36
|
+
|
37
|
+
def refresh
|
38
|
+
Configuration.client.post "#{Configuration.url}/#{@name}/_refresh", ''
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Slingshot
|
2
|
+
module Results
|
3
|
+
|
4
|
+
class Collection
|
5
|
+
include Enumerable
|
6
|
+
attr_reader :time, :total, :results, :facets
|
7
|
+
|
8
|
+
def initialize(response)
|
9
|
+
@time = response['took']
|
10
|
+
@total = response['hits']['total']
|
11
|
+
@results = response['hits']['hits']
|
12
|
+
@facets = response['facets']
|
13
|
+
end
|
14
|
+
|
15
|
+
def each(&block)
|
16
|
+
@results.each(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Slingshot
|
2
|
+
module Search
|
3
|
+
|
4
|
+
#--
|
5
|
+
# TODO: Implement all elastic search facets (geo, histogram, range, etc)
|
6
|
+
# https://github.com/elasticsearch/elasticsearch/wiki/Search-API-Facets
|
7
|
+
#++
|
8
|
+
|
9
|
+
class Facet
|
10
|
+
|
11
|
+
def initialize(name, options={}, &block)
|
12
|
+
@name = name
|
13
|
+
@options = options
|
14
|
+
self.instance_eval(&block) if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
def terms(field, options={})
|
18
|
+
@value = { :terms => { :field => field } }.update(options)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_json
|
23
|
+
to_hash.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_hash
|
27
|
+
h = { @name => @value }
|
28
|
+
h[@name].update @options
|
29
|
+
return h
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Slingshot
|
2
|
+
module Search
|
3
|
+
|
4
|
+
class Query
|
5
|
+
def initialize(&block)
|
6
|
+
self.instance_eval(&block) if block_given?
|
7
|
+
end
|
8
|
+
|
9
|
+
def term(field, value)
|
10
|
+
@value = { :term => { field => value } }
|
11
|
+
end
|
12
|
+
|
13
|
+
def terms(field, value, options={})
|
14
|
+
@value = { :terms => { field => value } }
|
15
|
+
@value[:terms].update( { :minimum_match => options[:minimum_match] } ) if options[:minimum_match]
|
16
|
+
@value
|
17
|
+
end
|
18
|
+
|
19
|
+
def string(value, options={})
|
20
|
+
@value = { :query_string => { :query => value } }
|
21
|
+
@value[:query_string].update( { :default_field => options[:default_field] } ) if options[:default_field]
|
22
|
+
# TODO: https://github.com/elasticsearch/elasticsearch/wiki/Query-String-Query
|
23
|
+
@value
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_json
|
27
|
+
@value.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Slingshot
|
2
|
+
module Search
|
3
|
+
|
4
|
+
class Sort
|
5
|
+
def initialize(&block)
|
6
|
+
@value = []
|
7
|
+
self.instance_eval(&block) if block_given?
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(id, *args, &block)
|
11
|
+
case arg = args.shift
|
12
|
+
when String, Symbol, Hash then @value << { id => arg }
|
13
|
+
else @value << id
|
14
|
+
end
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_json
|
19
|
+
@value.to_json
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Slingshot
|
2
|
+
module Search
|
3
|
+
|
4
|
+
class Search
|
5
|
+
|
6
|
+
attr_reader :indices, :url, :results, :response, :query, :facets
|
7
|
+
|
8
|
+
def initialize(*indices, &block)
|
9
|
+
raise ArgumentError, 'Please pass index or indices to search' if indices.empty?
|
10
|
+
@indices = indices
|
11
|
+
instance_eval(&block) if block_given?
|
12
|
+
end
|
13
|
+
|
14
|
+
def query(&block)
|
15
|
+
@query = Query.new(&block)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def sort(&block)
|
20
|
+
@sort = Sort.new(&block)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def facet(name, options={}, &block)
|
25
|
+
@facets ||= {}
|
26
|
+
@facets.update Facet.new(name, options, &block).to_hash
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def from(value)
|
31
|
+
@from = value
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def size(value)
|
36
|
+
@size = value
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def fields(fields=[])
|
41
|
+
@fields = fields
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def perform
|
46
|
+
@url = "#{Configuration.url}/#{indices.join(',')}/_search"
|
47
|
+
@response = JSON.parse( Configuration.client.post(@url, self.to_json) )
|
48
|
+
@results = Results::Collection.new(@response)
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_curl
|
53
|
+
%Q|curl -X POST "http://localhost:9200/#{indices}/_search?pretty=true" -d '#{self.to_json}'|
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_json
|
57
|
+
request = {}
|
58
|
+
request.update( { :query => @query } )
|
59
|
+
request.update( { :sort => @sort } ) if @sort
|
60
|
+
request.update( { :facets => @facets } ) if @facets
|
61
|
+
request.update( { :size => @size } ) if @size
|
62
|
+
request.update( { :from => @from } ) if @from
|
63
|
+
request.update( { :fields => @fields } ) if @fields
|
64
|
+
request.to_json
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'slingshot'
|
data/lib/slingshot.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'yajl/json_gem'
|
3
|
+
|
4
|
+
require 'slingshot/rubyext/hash'
|
5
|
+
require 'slingshot/configuration'
|
6
|
+
require 'slingshot/client'
|
7
|
+
require 'slingshot/client'
|
8
|
+
require 'slingshot/search'
|
9
|
+
require 'slingshot/search/query'
|
10
|
+
require 'slingshot/search/sort'
|
11
|
+
require 'slingshot/search/facet'
|
12
|
+
require 'slingshot/results/collection'
|
13
|
+
require 'slingshot/index'
|
14
|
+
require 'slingshot/dsl'
|
15
|
+
|
16
|
+
module Slingshot
|
17
|
+
extend DSL
|
18
|
+
end
|
data/slingshot.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "slingshot/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "slingshot-rb"
|
7
|
+
s.version = Slingshot::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.summary = "Ruby API for ElasticSearch"
|
10
|
+
s.homepage = "http://github.com/karmi/slingshot"
|
11
|
+
s.authors = [ 'Karel Minarik' ]
|
12
|
+
s.email = 'karmi@karmi.cz'
|
13
|
+
|
14
|
+
s.rubyforge_project = "slingshot"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.extra_rdoc_files = [ "README.markdown", "MIT-LICENSE" ]
|
23
|
+
s.rdoc_options = [ "--charset=UTF-8" ]
|
24
|
+
|
25
|
+
s.required_rubygems_version = ">= 1.3.6"
|
26
|
+
|
27
|
+
s.add_dependency "bundler", "~> 1.0.0"
|
28
|
+
s.add_dependency "rest-client", "~> 1.6.0"
|
29
|
+
s.add_dependency "yajl-ruby", "> 0.7.9"
|
30
|
+
|
31
|
+
s.add_development_dependency "turn"
|
32
|
+
s.add_development_dependency "shoulda"
|
33
|
+
s.add_development_dependency "mocha"
|
34
|
+
s.add_development_dependency "sdoc"
|
35
|
+
s.add_development_dependency "rcov"
|
36
|
+
|
37
|
+
s.description = <<-DESC
|
38
|
+
Ruby API for the ElasticSearch search engine/database.
|
39
|
+
A work in progress, currently.
|
40
|
+
DESC
|
41
|
+
end
|