asari 0.8.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ *.swp
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
- If you require 'asari/active\_record' in your project, you have access to the
58
- ActiveRecord module for Asari. You can take advantage of that module like so:
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], :aws_regon => "us-west-1")
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
 
@@ -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?q=#{CGI.escape(term)}&size=#{page_size}"
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
@@ -126,7 +126,7 @@ class Asari
126
126
  def asari_should_index?(object)
127
127
  when_test = self.asari_when
128
128
  if when_test.is_a? Proc
129
- return Proc.call(object)
129
+ return when_test.call(object)
130
130
  else
131
131
  return object.send(when_test)
132
132
  end
@@ -41,8 +41,12 @@ class Asari
41
41
 
42
42
  start = resp["hits"]["start"]
43
43
  @current_page = (start / page_size) + 1
44
-
45
- @data = resp["hits"]["hit"].map { |h| h["id"] }
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)
@@ -1,3 +1,3 @@
1
1
  class Asari
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.2"
3
3
  end
@@ -1,13 +1,13 @@
1
1
  require_relative "../spec_helper"
2
2
 
3
3
  describe Asari do
4
- describe "searching" do
5
- before :each do
6
- @asari = Asari.new("testdomain")
7
- stub_const("HTTParty", double())
8
- HTTParty.stub(:get).and_return(fake_response)
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.8.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-04-17 00:00:00.000000000 Z
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.23
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.