slingshot-rb 0.0.5 → 0.0.6

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/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