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