asari 0.8.0 → 0.9.2
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 +1 -0
- data/README.md +35 -3
- data/lib/asari.rb +33 -6
- data/lib/asari/active_record.rb +1 -1
- data/lib/asari/collection.rb +7 -3
- data/lib/asari/version.rb +1 -1
- data/spec/search_spec.rb +60 -6
- metadata +3 -3
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -11,6 +11,10 @@ for easy integration with your Rails apps.
|
|
11
11
|
|
12
12
|
## Usage
|
13
13
|
|
14
|
+
#### Your Search Domain
|
15
|
+
|
16
|
+
Amazon Cloud Search will give you a Search Endpoint and Document Endpoint. When specifying your search domain in Asari omit the search- for your search domain. For example if your search endpoint is "search-beavis-er432w3er.us-east-1.cloudsearch.amazonaws.com" the search domain you use in Asari would be "beavis-er432w3er". Your region is the second item. In this example it would be "us-east-1".
|
17
|
+
|
14
18
|
#### Basic Usage
|
15
19
|
|
16
20
|
asari = Asari.new("my-search-domain-asdfkljwe4") # CloudSearch search domain
|
@@ -20,6 +24,16 @@ for easy integration with your Rails apps.
|
|
20
24
|
asari.search("tommy", :rank => ["name", :desc]) # Sort the search descending
|
21
25
|
asari.search("tommy", :rank => "-name") # Another way to sort the search descending
|
22
26
|
|
27
|
+
|
28
|
+
#### Boolean Query Usage
|
29
|
+
|
30
|
+
asari.search(filter: { and: { title: "donut", type: "cruller" }})
|
31
|
+
asari.search("boston creme", filter: { and: { title: "donut", or: { type: "cruller",
|
32
|
+
type: "twist" }}}) # Full text search and nested boolean logic
|
33
|
+
|
34
|
+
For more information on how to use Cloudsearch boolean queries, [see the
|
35
|
+
documentation.](http://docs.aws.amazon.com/cloudsearch/latest/developerguide/booleansearch.html)
|
36
|
+
|
23
37
|
#### Sandbox Mode
|
24
38
|
|
25
39
|
Because there is no "local" version of CloudSearch, and search instances can be
|
@@ -52,10 +66,27 @@ with will\_paginate:
|
|
52
66
|
results.offset #=> 300
|
53
67
|
results.page_size #=> 30
|
54
68
|
|
69
|
+
#### Retrieving Data From Index Fields
|
70
|
+
|
71
|
+
By default Asari only returns the document id's for any hits returned from a search.
|
72
|
+
If you have result_enabled a index field you can have asari resturn that field in the
|
73
|
+
result set without having to hit a database to get the results. Simply pass the
|
74
|
+
:return_fields option with an array of fields
|
75
|
+
|
76
|
+
results = asari.search "Beavis", :return_fields => ["name", "address"]
|
77
|
+
|
78
|
+
The result will look like this
|
79
|
+
|
80
|
+
{"23" => {"name" => "Beavis", "address" => "One CNN Center, Atlanta"},
|
81
|
+
"54" => {"name" => "Beavis C", "address" => "Cornholio Way, USA"}}
|
82
|
+
|
55
83
|
#### ActiveRecord
|
56
84
|
|
57
|
-
|
58
|
-
|
85
|
+
By default the ActiveRecord module for Asari is not included in your project. To use it you will need to require it via
|
86
|
+
|
87
|
+
require 'asari/active_record'
|
88
|
+
|
89
|
+
You can take advantage of that module like so:
|
59
90
|
|
60
91
|
class User < ActiveRecord::Base
|
61
92
|
include Asari::ActiveRecord
|
@@ -138,7 +169,7 @@ ActiveRecord model:
|
|
138
169
|
class User < ActiveRecord::Base
|
139
170
|
include Asari::ActiveRecord
|
140
171
|
|
141
|
-
asari_index("my-search-domain",[field1,field2], :
|
172
|
+
asari_index("my-search-domain",[field1,field2], :aws_region => "us-west-1")
|
142
173
|
|
143
174
|
...
|
144
175
|
end
|
@@ -162,6 +193,7 @@ Gem requirements/etc. should be handled by Bundler.
|
|
162
193
|
|
163
194
|
### Contributors
|
164
195
|
|
196
|
+
* [Lance Gleason](https://github.com/lgleasain "lgleasain on Github")
|
165
197
|
* [Emil Soman](https://github.com/emilsoman "emilsoman on Github")
|
166
198
|
* [Chris Vincent](https://github.com/cvincent "cvincent on Github")
|
167
199
|
|
data/lib/asari.rb
CHANGED
@@ -23,7 +23,8 @@ class Asari
|
|
23
23
|
attr_writer :aws_region
|
24
24
|
|
25
25
|
def initialize(search_domain=nil, aws_region=nil)
|
26
|
-
@search_domain = search_domain
|
26
|
+
@search_domain = search_domain
|
27
|
+
@aws_region = aws_region
|
27
28
|
end
|
28
29
|
|
29
30
|
# Public: returns the current search_domain, or raises a
|
@@ -52,6 +53,8 @@ class Asari
|
|
52
53
|
# Examples:
|
53
54
|
#
|
54
55
|
# @asari.search("fritters") #=> ["13","28"]
|
56
|
+
# @asari.search(filter: { and: { type: 'donuts' }}) #=> ["13,"28","35","50"]
|
57
|
+
# @asari.search("fritters", filter: { and: { type: 'donuts' }}) #=> ["13"]
|
55
58
|
#
|
56
59
|
# Returns: An Asari::Collection containing all document IDs in the system that match the
|
57
60
|
# specified search term. If no results are found, an empty Asari::Collection is
|
@@ -61,10 +64,16 @@ class Asari
|
|
61
64
|
# the server.
|
62
65
|
def search(term, options = {})
|
63
66
|
return Asari::Collection.sandbox_fake if self.class.mode == :sandbox
|
67
|
+
term,options = "",term if term.is_a?(Hash) and options.empty?
|
64
68
|
|
69
|
+
bq = boolean_query(options[:filter]) if options[:filter]
|
65
70
|
page_size = options[:page_size].nil? ? 10 : options[:page_size].to_i
|
66
71
|
|
67
|
-
url = "http://search-#{search_domain}.#{aws_region}.cloudsearch.amazonaws.com/#{api_version}/search
|
72
|
+
url = "http://search-#{search_domain}.#{aws_region}.cloudsearch.amazonaws.com/#{api_version}/search"
|
73
|
+
url += "?q=#{CGI.escape(term)}"
|
74
|
+
url += "&bq=#{CGI.escape(bq)}" if options[:filter]
|
75
|
+
url += "&size=#{page_size}"
|
76
|
+
url += "&return-fields=#{options[:return_fields].join ','}" if options[:return_fields]
|
68
77
|
|
69
78
|
if options[:page]
|
70
79
|
start = (options[:page].to_i - 1) * page_size
|
@@ -92,7 +101,7 @@ class Asari
|
|
92
101
|
end
|
93
102
|
|
94
103
|
# Public: Add an item to the index with the given ID.
|
95
|
-
#
|
104
|
+
#
|
96
105
|
# id - the ID to associate with this document
|
97
106
|
# fields - a hash of the data to associate with this document. This
|
98
107
|
# needs to match the search fields defined in your CloudSearch domain.
|
@@ -122,9 +131,9 @@ class Asari
|
|
122
131
|
# Note: As of right now, this is the same method call in CloudSearch
|
123
132
|
# that's utilized for adding items. This method is here to provide a
|
124
133
|
# consistent interface in case that changes.
|
125
|
-
#
|
134
|
+
#
|
126
135
|
# Examples:
|
127
|
-
#
|
136
|
+
#
|
128
137
|
# @asari.update_item("4", { :name => "Party Pooper", :email => ..., ... }) #=> nil
|
129
138
|
#
|
130
139
|
# Returns: nil if the request is successful.
|
@@ -139,7 +148,7 @@ class Asari
|
|
139
148
|
# Public: Remove an item from the index based on its document ID.
|
140
149
|
#
|
141
150
|
# Examples:
|
142
|
-
#
|
151
|
+
#
|
143
152
|
# @asari.search("fritters") #=> ["13","28"]
|
144
153
|
# @asari.remove_item("13") #=> nil
|
145
154
|
# @asari.search("fritters") #=> ["28"]
|
@@ -181,6 +190,24 @@ class Asari
|
|
181
190
|
|
182
191
|
protected
|
183
192
|
|
193
|
+
# Private: Builds the query from a passed hash
|
194
|
+
#
|
195
|
+
# terms - a hash of the search query. %w(and or not) are reserved hash keys
|
196
|
+
# that build the logic of the query
|
197
|
+
def boolean_query(terms = {}, options = {})
|
198
|
+
reduce = lambda { |hash|
|
199
|
+
hash.reduce("") do |memo, (key, value)|
|
200
|
+
if %w(and or not).include?(key.to_s) && value.is_a?(Hash)
|
201
|
+
memo += "(#{key}#{reduce.call(value)})"
|
202
|
+
else
|
203
|
+
memo += " #{key}:'#{value}'" unless value.to_s.nil? || value.to_s.empty?
|
204
|
+
end
|
205
|
+
memo
|
206
|
+
end
|
207
|
+
}
|
208
|
+
reduce.call(terms)
|
209
|
+
end
|
210
|
+
|
184
211
|
def normalize_rank(rank)
|
185
212
|
rank = Array(rank)
|
186
213
|
rank << :asc if rank.size < 2
|
data/lib/asari/active_record.rb
CHANGED
data/lib/asari/collection.rb
CHANGED
@@ -41,8 +41,12 @@ class Asari
|
|
41
41
|
|
42
42
|
start = resp["hits"]["start"]
|
43
43
|
@current_page = (start / page_size) + 1
|
44
|
-
|
45
|
-
|
44
|
+
if resp["hits"]["hit"].first && resp["hits"]["hit"].first["data"]
|
45
|
+
@data = {}
|
46
|
+
resp["hits"]["hit"].each { |hit| @data[hit["id"]] = hit["data"]}
|
47
|
+
else
|
48
|
+
@data = resp["hits"]["hit"].map { |hit| hit["id"] }
|
49
|
+
end
|
46
50
|
end
|
47
51
|
|
48
52
|
def offset
|
@@ -66,7 +70,7 @@ class Asari
|
|
66
70
|
end
|
67
71
|
|
68
72
|
def class
|
69
|
-
Asari::Collection
|
73
|
+
::Asari::Collection
|
70
74
|
end
|
71
75
|
|
72
76
|
def method_missing(method, *args, &block)
|
data/lib/asari/version.rb
CHANGED
data/spec/search_spec.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
require_relative "../spec_helper"
|
2
2
|
|
3
3
|
describe Asari do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
4
|
+
before :each do
|
5
|
+
@asari = Asari.new("testdomain")
|
6
|
+
stub_const("HTTParty", double())
|
7
|
+
HTTParty.stub(:get).and_return(fake_response)
|
8
|
+
end
|
10
9
|
|
10
|
+
describe "searching" do
|
11
11
|
context "when region is not specified" do
|
12
12
|
it "allows you to search using default region." do
|
13
13
|
HTTParty.should_receive(:get).with("http://search-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/search?q=testsearch&size=10")
|
@@ -78,6 +78,27 @@ describe Asari do
|
|
78
78
|
expect(result.total_entries).to eq(0)
|
79
79
|
end
|
80
80
|
|
81
|
+
context 'return_fields option' do
|
82
|
+
let(:response_with_field_data) { OpenStruct.new(:parsed_response => { "hits" => {
|
83
|
+
"found" => 2,
|
84
|
+
"start" => 0,
|
85
|
+
"hit" => [{"id" => "123",
|
86
|
+
"data" => {"name" => "Beavis", "address" => "arizona"}},
|
87
|
+
{"id" => "456",
|
88
|
+
"data" => {"name" => "Honey Badger", "address" => "africa"}}]}},
|
89
|
+
:response => OpenStruct.new(:code => "200"))
|
90
|
+
}
|
91
|
+
let(:return_struct) {{"123" => {"name" => "Beavis", "address" => "arizona"},
|
92
|
+
"456" => {"name" => "Honey Badger", "address" => "africa"}}}
|
93
|
+
|
94
|
+
before :each do
|
95
|
+
HTTParty.should_receive(:get).with("http://search-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/search?q=testsearch&size=10&return-fields=name,address").and_return response_with_field_data
|
96
|
+
end
|
97
|
+
|
98
|
+
subject { @asari.search("testsearch", :return_fields => [:name, :address])}
|
99
|
+
it {should eql return_struct}
|
100
|
+
end
|
101
|
+
|
81
102
|
it "raises an exception if the service errors out." do
|
82
103
|
HTTParty.stub(:get).and_return(fake_error_response)
|
83
104
|
expect { @asari.search("testsearch)") }.to raise_error Asari::SearchException
|
@@ -89,4 +110,37 @@ describe Asari do
|
|
89
110
|
end
|
90
111
|
|
91
112
|
end
|
113
|
+
|
114
|
+
describe "boolean searching" do
|
115
|
+
it "builds a query string from a passed hash" do
|
116
|
+
HTTParty.should_receive(:get).with("http://search-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/search?q=&bq=%28and+foo%3A%27bar%27+baz%3A%27bug%27%29&size=10")
|
117
|
+
@asari.search(filter: { and: { foo: "bar", baz: "bug" }})
|
118
|
+
end
|
119
|
+
|
120
|
+
it "honors the logic types" do
|
121
|
+
HTTParty.should_receive(:get).with("http://search-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/search?q=&bq=%28or+foo%3A%27bar%27+baz%3A%27bug%27%29&size=10")
|
122
|
+
@asari.search(filter: { or: { foo: "bar", baz: "bug" }})
|
123
|
+
end
|
124
|
+
|
125
|
+
it "supports nested logic" do
|
126
|
+
HTTParty.should_receive(:get).with("http://search-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/search?q=&bq=%28or+is_donut%3A%27true%27%28and+round%3A%27true%27+frosting%3A%27true%27+fried%3A%27true%27%29%29&size=10")
|
127
|
+
@asari.search(filter: { or: { is_donut: true, and:
|
128
|
+
{ round: true, frosting: true, fried: true }}
|
129
|
+
})
|
130
|
+
end
|
131
|
+
|
132
|
+
it "fails gracefully with empty params" do
|
133
|
+
HTTParty.should_receive(:get).with("http://search-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/search?q=&bq=%28or+is_donut%3A%27true%27%28and+fried%3A%27true%27%29%29&size=10")
|
134
|
+
@asari.search(filter: { or: { is_donut: true, and:
|
135
|
+
{ round: "", frosting: nil, fried: true }}
|
136
|
+
})
|
137
|
+
end
|
138
|
+
|
139
|
+
it "supports full text search and boolean searching" do
|
140
|
+
HTTParty.should_receive(:get).with("http://search-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/search?q=nom&bq=%28or+is_donut%3A%27true%27%28and+fried%3A%27true%27%29%29&size=10")
|
141
|
+
@asari.search("nom", filter: { or: { is_donut: true, and:
|
142
|
+
{ round: "", frosting: nil, fried: true }}
|
143
|
+
})
|
144
|
+
end
|
145
|
+
end
|
92
146
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: asari
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-09-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: httparty
|
@@ -87,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
87
|
version: '0'
|
88
88
|
requirements: []
|
89
89
|
rubyforge_project: asari
|
90
|
-
rubygems_version: 1.8.
|
90
|
+
rubygems_version: 1.8.25
|
91
91
|
signing_key:
|
92
92
|
specification_version: 3
|
93
93
|
summary: Asari is a Ruby interface for AWS CloudSearch.
|