slingshot-rb 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -3,15 +3,15 @@ Slingshot
3
3
 
4
4
  ![Slingshot](https://github.com/karmi/slingshot/raw/master/slingshot.png)
5
5
 
6
- _Slingshot_ aims to provide a rich Ruby API and DSL for the
6
+ _Slingshot_ aims to provide rich and comfortable Ruby API and DSL for the
7
7
  [ElasticSearch](http://www.elasticsearch.org/) search engine/database.
8
8
 
9
9
  _ElasticSearch_ is a scalable, distributed, highly-available,
10
10
  RESTful database communicating by JSON over HTTP, based on [Lucene](http://lucene.apache.org/),
11
- written in Java. It manages to very simple and very powerful at the same time.
11
+ written in Java. It manages to be very simple and very powerful at the same time.
12
12
  You should seriously consider it to power search in your Ruby applications:
13
13
  it will deliver all the features you want — and many more you may have not
14
- imagined yet (native geo search? histogram facets for dates?)
14
+ imagined yet (native geo search? date histogram facets? _percolator_?)
15
15
 
16
16
  _Slingshot_ currently allows basic operation with the index and searching. More is planned.
17
17
 
@@ -44,7 +44,7 @@ Plans for full ActiveModel integration (and other convenience layers) are in pro
44
44
  (see the [`activemodel`](https://github.com/karmi/slingshot/compare/activemodel) branch).
45
45
 
46
46
  To kick the tires, require the gem in an IRB session or a Ruby script
47
- (note that you can run the full example from [`examples/dsl.rb`](https://github.com/karmi/slingshot/blob/master/examples/dsl.rb)):
47
+ (note that you can just run the full example from [`examples/dsl.rb`](https://github.com/karmi/slingshot/blob/master/examples/dsl.rb)):
48
48
 
49
49
  require 'rubygems'
50
50
  require 'slingshot'
@@ -63,20 +63,36 @@ First, let's create an index named `articles` and store/index some documents:
63
63
  refresh
64
64
  end
65
65
 
66
- Now, let's query the database:
66
+ You can also create the
67
+ index with specific [mappings](http://www.elasticsearch.org/guide/reference/api/admin-indices-create-index.html), such as:
67
68
 
68
- We are searching for articles tagged _ruby_, sorted by `title` in `descending` order,
69
- and also retrieving some [_facets_](http://www.lucidimagination.com/Community/Hear-from-the-Experts/Articles/Faceted-Search-Solr)
69
+ Slingshot.index 'articles' do
70
+ create :mappings => {
71
+ :article => {
72
+ :properties => {
73
+ :id => { :type => 'string', :index => 'not_analyzed', :include_in_all => false },
74
+ :title => { :type => 'string', :boost => 2.0, :analyzer => 'snowball' },
75
+ :tags => { :type => 'string', :analyzer => 'keyword' },
76
+ :content => { :type => 'string', :analyzer => 'snowball' }
77
+ }
78
+ }
79
+ }
80
+ end
81
+
82
+ Now, let's query the database.
83
+
84
+ We are searching for articles whose `title` begins with letter “T”, sorted by `title` in `descending` order,
85
+ filtering them for ones tagged “ruby”, and also retrieving some [_facets_](http://www.lucidimagination.com/Community/Hear-from-the-Experts/Articles/Faceted-Search-Solr)
70
86
  from the database:
71
87
 
72
88
  s = Slingshot.search 'articles' do
73
89
  query do
74
- terms :tags, ['ruby']
90
+ string 'title:T*'
75
91
  end
76
92
 
77
- sort do
78
- title 'desc'
79
- end
93
+ filter :terms, :tags => ['ruby']
94
+
95
+ sort { title 'desc' }
80
96
 
81
97
  facet 'global-tags' do
82
98
  terms :tags, :global => true
@@ -90,14 +106,12 @@ from the database:
90
106
  Let's display the results:
91
107
 
92
108
  s.results.each do |document|
93
- puts "* #{ document.title }"
109
+ puts "* #{ document.title } [tags: #{document.tags.join(', ')}]"
94
110
  end
95
111
 
96
- # * Two
97
- # * One
98
- # * Four
112
+ # * Two [tags: ruby, python]
99
113
 
100
- Let's display the facets (distribution of tags across the whole database):
114
+ Let's display the global facets (distribution of tags across the whole database):
101
115
 
102
116
  s.results.facets['global-tags']['terms'].each do |f|
103
117
  puts "#{f['term'].ljust(10)} #{f['count']}"
@@ -107,18 +121,28 @@ Let's display the facets (distribution of tags across the whole database):
107
121
  # python 1
108
122
  # php 1
109
123
  # java 1
110
-
124
+
125
+ Now, let's display the facets based on current query (notice that count for articles
126
+ tagged with 'java' is included, even though it's not returned by our query;
127
+ count for articles tagged 'php' is excluded, since they don't match the current query):
128
+
129
+ s.results.facets['current-tags']['terms'].each do |f|
130
+ puts "#{f['term'].ljust(10)} #{f['count']}"
131
+ end
132
+
133
+ # ruby 1
134
+ # python 1
135
+ # java 1
136
+
111
137
  We can display the full query JSON:
112
138
 
113
139
  puts s.to_json
114
- # {"facets":{"current-tags":{"terms":{"field":"tags"}},"global-tags":{"global":true,"terms":{"field":"tags"}}},"sort":[{"title":"desc"}],"query":{"terms":{"tags":["ruby"]}}}
115
-
116
- See, a Ruby DSL for this thing is kinda handy?
140
+ # {"facets":{"current-tags":{"terms":{"field":"tags"}},"global-tags":{"global":true,"terms":{"field":"tags"}}},"query":{"query_string":{"query":"title:T*"}},"filter":{"terms":{"tags":["ruby"]}},"sort":[{"title":"desc"}]}
117
141
 
118
- You can display the corresponding `curl` command easily:
142
+ Or, we can display the corresponding `curl` command for easy debugging:
119
143
 
120
144
  puts s.to_curl
121
- # 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"]}}}'
145
+ # curl -X POST "http://localhost:9200/articles/_search?pretty=true" -d '{"facets":{"current-tags":{"terms":{"field":"tags"}},"global-tags":{"global":true,"terms":{"field":"tags"}}},"query":{"query_string":{"query":"title:T*"}},"filter":{"terms":{"tags":["ruby"]}},"sort":[{"title":"desc"}]}'
122
146
 
123
147
 
124
148
  Features
@@ -127,9 +151,11 @@ Features
127
151
  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/):
128
152
 
129
153
  * Creating, deleting and refreshing the index
154
+ * Creating the index with specific [mapping](http://www.elasticsearch.org/guide/reference/api/admin-indices-create-index.html)
130
155
  * Storing a document in the index
131
156
  * [Querying](https://github.com/karmi/slingshot/blob/master/examples/dsl.rb) the index with the `query_string`, `term` and `terms` types of queries
132
- * Sorting the results by `fields`
157
+ * [Sorting](http://elasticsearch.org/guide/reference/api/search/sort.html) the results by `fields`
158
+ * [Filtering](http://elasticsearch.org/guide/reference/query-dsl/) the results
133
159
  * Retrieving a _terms_ type of [facets](http://www.elasticsearch.org/guide/reference/api/search/facets/index.html) -- other types are high priority
134
160
  * Returning just specific `fields` from documents
135
161
  * Paging with `from` and `size` query options
@@ -155,9 +181,6 @@ In order of importance:
155
181
  * Dual interface: allow to simply pass queries/options for _ElasticSearch_ as a Hash in any method
156
182
  * [Histogram](http://www.elasticsearch.org/guide/reference/api/search/facets/histogram-facet.html) facets
157
183
  * Seamless support for [auto-updating _river_ index](http://www.elasticsearch.org/guide/reference/river/couchdb.html) for _CouchDB_ `_changes` feed
158
- * Infrastructure for query filters
159
- * [Range](http://www.elasticsearch.org/guide/reference/query-dsl/range-filter.html) filters and queries
160
- * [Geo Filters](http://www.elasticsearch.org/blog/2010/08/16/geo_location_and_search.html) for queries
161
184
  * [Statistical](http://www.elasticsearch.org/guide/reference/api/search/facets/statistical-facet.html) facets
162
185
  * [Geo Distance](http://www.elasticsearch.org/guide/reference/api/search/facets/geo-distance-facet.html) facets
163
186
  * [Index aliases](http://www.elasticsearch.org/guide/reference/api/admin-indices-aliases.html) management
data/examples/dsl.rb CHANGED
@@ -29,9 +29,11 @@ end
29
29
 
30
30
  s = search 'articles' do
31
31
  query do
32
- terms :tags, ['ruby']
32
+ string 'T*'
33
33
  end
34
34
 
35
+ filter :terms, :tags => ['ruby']
36
+
35
37
  sort do
36
38
  title 'desc'
37
39
  end
@@ -56,15 +58,16 @@ puts s.to_curl
56
58
 
57
59
  puts "", "Results:", "-"*80
58
60
  s.results.each_with_index do |document, i|
59
- puts "#{i+1}. #{ document.title.ljust(20) } [id] #{document._id}"
61
+ puts "#{i+1}. #{ document.title.ljust(10) } [id] #{document._id}"
60
62
  end
61
63
 
62
64
  puts "", "Facets: tags distribution across the whole database:", "-"*80
63
65
  s.results.facets['global-tags']['terms'].each do |f|
64
- puts "#{f['term'].ljust(10)} #{f['count']}"
66
+ puts "#{f['term'].ljust(13)} #{f['count']}"
65
67
  end
66
68
 
67
- puts "", "Facets: tags distribution for the current query", "-"*80
69
+ puts "", "Facets: tags distribution for the current query ",
70
+ "(Notice that 'java' is included, because of the filter)", "-"*80
68
71
  s.results.facets['current-tags']['terms'].each do |f|
69
- puts "#{f['term'].ljust(10)} #{f['count']}"
72
+ puts "#{f['term'].ljust(13)} #{f['count']}"
70
73
  end
data/lib/slingshot.rb CHANGED
@@ -9,6 +9,7 @@ require 'slingshot/search'
9
9
  require 'slingshot/search/query'
10
10
  require 'slingshot/search/sort'
11
11
  require 'slingshot/search/facet'
12
+ require 'slingshot/search/filter'
12
13
  require 'slingshot/results/collection'
13
14
  require 'slingshot/results/item'
14
15
  require 'slingshot/index'
@@ -3,7 +3,7 @@ module Slingshot
3
3
 
4
4
  class Search
5
5
 
6
- attr_reader :indices, :url, :results, :response, :query, :facets
6
+ attr_reader :indices, :url, :results, :response, :query, :facets, :filters
7
7
 
8
8
  def initialize(*indices, &block)
9
9
  @options = indices.pop if indices.last.is_a?(Hash)
@@ -31,6 +31,12 @@ module Slingshot
31
31
  self
32
32
  end
33
33
 
34
+ def filter(type, *options)
35
+ @filters ||= []
36
+ @filters << Filter.new(type, *options).to_hash
37
+ self
38
+ end
39
+
34
40
  def from(value)
35
41
  @from = value
36
42
  self
@@ -51,6 +57,9 @@ module Slingshot
51
57
  @response = JSON.parse( Configuration.client.post(@url, self.to_json) )
52
58
  @results = Results::Collection.new(@response)
53
59
  self
60
+ rescue Exception
61
+ STDERR.puts "Request failed: \n#{self.to_curl}"
62
+ raise
54
63
  end
55
64
 
56
65
  def to_curl
@@ -60,12 +69,13 @@ module Slingshot
60
69
  def to_json
61
70
  request = {}
62
71
  request.update( { :query => @query } )
63
- request.update( { :sort => @sort } ) if @sort
64
- request.update( { :facets => @facets } ) if @facets
65
- request.update( { :size => @size } ) if @size
66
- request.update( { :from => @from } ) if @from
67
- request.update( { :fields => @fields } ) if @fields
68
- request.to_json
72
+ request.update( { :sort => @sort } ) if @sort
73
+ request.update( { :facets => @facets } ) if @facets
74
+ @filters.each { |filter| request.update( { :filter => filter } ) } if @filters
75
+ request.update( { :size => @size } ) if @size
76
+ request.update( { :from => @from } ) if @from
77
+ request.update( { :fields => @fields } ) if @fields
78
+ Yajl::Encoder.encode(request)
69
79
  end
70
80
 
71
81
  end
@@ -0,0 +1,29 @@
1
+ module Slingshot
2
+ module Search
3
+
4
+ # http://www.elasticsearch.org/guide/reference/api/search/filter.html
5
+ # http://www.elasticsearch.org/guide/reference/query-dsl/
6
+ #
7
+ class Filter
8
+
9
+ def initialize(type, *options)
10
+ @type = type
11
+ @options = options || []
12
+ end
13
+
14
+ def to_json
15
+ to_hash.to_json
16
+ end
17
+
18
+ def to_hash
19
+ initial = @options.size > 1 ? { @type => [] } : { @type => {} }
20
+ method = initial[@type].is_a?(Hash) ? :update : :push
21
+ @options.inject(initial) do |hash, option|
22
+ hash[@type].send(method, option)
23
+ hash
24
+ end
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -1,3 +1,3 @@
1
1
  module Slingshot
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -0,0 +1,46 @@
1
+ require 'test_helper'
2
+
3
+ module Slingshot
4
+
5
+ class FiltersIntegrationTest < Test::Unit::TestCase
6
+ include Test::Integration
7
+
8
+ context "Filters" do
9
+
10
+ should "filter the results" do
11
+ s = Slingshot.search('articles-test') do
12
+ query { string 'title:T*' }
13
+ filter :terms, :tags => ['ruby']
14
+ end
15
+
16
+ assert_equal 1, s.results.count
17
+ assert_equal 'Two', s.results.first.title
18
+ end
19
+
20
+ should "filter the results with multiple filters" do
21
+ s = Slingshot.search('articles-test') do
22
+ query { string 'title:F*' }
23
+ filter :or, {:terms => {:tags => ['ruby']}},
24
+ {:terms => {:tags => ['erlang']}}
25
+ end
26
+
27
+ assert_equal 1, s.results.count
28
+ assert_equal 'Four', s.results.first.title
29
+ end
30
+
31
+ should "not influence facets" do
32
+ s = Slingshot.search('articles-test') do
33
+ query { string 'title:T*' }
34
+ filter :terms, :tags => ['ruby']
35
+ facet('tags') { terms :tags }
36
+ end
37
+
38
+ assert_equal 1, s.results.count
39
+ assert_equal 3, s.results.facets['tags'].size
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -7,7 +7,7 @@ module Slingshot::Search
7
7
  context "Facet" do
8
8
 
9
9
  should "be serialized to JSON" do
10
- assert_respond_to Sort.new, :to_json
10
+ assert_respond_to Facet.new('foo'), :to_json
11
11
  end
12
12
 
13
13
  context "generally" do
@@ -0,0 +1,33 @@
1
+ require 'test_helper'
2
+
3
+ module Slingshot::Search
4
+
5
+ class FilterTest < Test::Unit::TestCase
6
+
7
+ context "Filter" do
8
+
9
+ should "be serialized to JSON" do
10
+ assert_respond_to Filter.new(:terms, {}), :to_json
11
+ end
12
+
13
+ should "encode simple filter declarations as JSON" do
14
+ assert_equal( { :terms => { :tags => ['foo'] } }.to_json,
15
+ Filter.new('terms', :tags => ['foo']).to_json )
16
+
17
+ assert_equal( { :range => { :age => { :from => 10, :to => 20 } } }.to_json,
18
+ Filter.new('range', { :age => { :from => 10, :to => 20 } }).to_json )
19
+
20
+ assert_equal( { :geo_distance => { :distance => '12km', :location => [40, -70] } }.to_json,
21
+ Filter.new('geo_distance', { :distance => '12km', :location => [40, -70] }).to_json )
22
+ end
23
+
24
+ should "encode 'or' filter declarations as JSON" do
25
+ # See http://www.elasticsearch.org/guide/reference/query-dsl/or-filter.html
26
+ assert_equal( { :or => [ {:terms => {:tags => ['foo']}}, {:terms => {:tags => ['bar']}} ] }.to_json,
27
+ Filter.new('or', {:terms => {:tags => ['foo']}}, {:terms => {:tags => ['bar']}}).to_json )
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
@@ -46,6 +46,14 @@ module Slingshot
46
46
  assert_not_nil s.response
47
47
  end
48
48
 
49
+ should "print debugging information on exception and re-raise it" do
50
+ Configuration.client.expects(:post).raises(RestClient::InternalServerError)
51
+ STDERR.expects(:puts)
52
+
53
+ s = Search::Search.new('index')
54
+ assert_raise(RestClient::InternalServerError) { s.perform }
55
+ end
56
+
49
57
  context "sort" do
50
58
 
51
59
  should "allow sorting by multiple fields" do
@@ -77,6 +85,20 @@ module Slingshot
77
85
 
78
86
  end
79
87
 
88
+ context "filter" do
89
+
90
+ should "allow to specify filter" do
91
+ s = Search::Search.new('index') do
92
+ filter :terms, :tags => ['foo']
93
+ end
94
+
95
+ assert_equal 1, s.filters.size
96
+ assert_not_nil s.filters.first
97
+ assert_not_nil s.filters.first[:terms]
98
+ end
99
+
100
+ end
101
+
80
102
  context "with from/size" do
81
103
 
82
104
  should "set the values in request" do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slingshot-rb
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 5
10
- version: 0.0.5
9
+ - 6
10
+ version: 0.0.6
11
11
  platform: ruby
12
12
  authors:
13
13
  - Karel Minarik
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-02 00:00:00 +01:00
18
+ date: 2011-03-13 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -163,6 +163,7 @@ files:
163
163
  - lib/slingshot/rubyext/hash.rb
164
164
  - lib/slingshot/search.rb
165
165
  - lib/slingshot/search/facet.rb
166
+ - lib/slingshot/search/filter.rb
166
167
  - lib/slingshot/search/query.rb
167
168
  - lib/slingshot/search/sort.rb
168
169
  - lib/slingshot/version.rb
@@ -174,6 +175,7 @@ files:
174
175
  - test/fixtures/articles/4.json
175
176
  - test/fixtures/articles/5.json
176
177
  - test/integration/facets_test.rb
178
+ - test/integration/filters_test.rb
177
179
  - test/integration/index_mapping_test.rb
178
180
  - test/integration/index_store_test.rb
179
181
  - test/integration/query_string_test.rb
@@ -187,6 +189,7 @@ files:
187
189
  - test/unit/results_collection_test.rb
188
190
  - test/unit/results_item_test.rb
189
191
  - test/unit/search_facet_test.rb
192
+ - test/unit/search_filter_test.rb
190
193
  - test/unit/search_query_test.rb
191
194
  - test/unit/search_sort_test.rb
192
195
  - test/unit/search_test.rb
@@ -234,6 +237,7 @@ test_files:
234
237
  - test/fixtures/articles/4.json
235
238
  - test/fixtures/articles/5.json
236
239
  - test/integration/facets_test.rb
240
+ - test/integration/filters_test.rb
237
241
  - test/integration/index_mapping_test.rb
238
242
  - test/integration/index_store_test.rb
239
243
  - test/integration/query_string_test.rb
@@ -247,6 +251,7 @@ test_files:
247
251
  - test/unit/results_collection_test.rb
248
252
  - test/unit/results_item_test.rb
249
253
  - test/unit/search_facet_test.rb
254
+ - test/unit/search_filter_test.rb
250
255
  - test/unit/search_query_test.rb
251
256
  - test/unit/search_sort_test.rb
252
257
  - test/unit/search_test.rb