asari 0.3.0
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 +4 -0
- data/Gemfile +4 -0
- data/README.md +4 -0
- data/Rakefile +1 -0
- data/asari.gemspec +28 -0
- data/lib/asari.rb +151 -0
- data/lib/asari/active_record.rb +114 -0
- data/lib/asari/exceptions.rb +10 -0
- data/lib/asari/version.rb +3 -0
- data/spec/active_record_spec.rb +105 -0
- data/spec/asari_spec.rb +28 -0
- data/spec/documents_spec.rb +66 -0
- data/spec/search_spec.rb +40 -0
- data/spec_helper.rb +78 -0
- metadata +86 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/asari.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "asari/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "asari"
|
7
|
+
s.version = Asari::VERSION
|
8
|
+
s.authors = ["Tommy Morgan"]
|
9
|
+
s.email = ["tommy@wellbredgrapefruit.com"]
|
10
|
+
s.homepage = "http://github.com/duwanis/asari"
|
11
|
+
s.summary = %q{Asari is a Ruby interface for AWS CloudSearch.}
|
12
|
+
s.description = %q{Asari s a Ruby interface for AWS CloudSearch}
|
13
|
+
|
14
|
+
s.rubyforge_project = "asari"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
|
25
|
+
s.add_runtime_dependency "httparty"
|
26
|
+
|
27
|
+
s.add_development_dependency "rspec"
|
28
|
+
end
|
data/lib/asari.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require "asari/version"
|
2
|
+
|
3
|
+
require "asari/exceptions"
|
4
|
+
|
5
|
+
require "httparty"
|
6
|
+
|
7
|
+
require "json"
|
8
|
+
require "cgi"
|
9
|
+
|
10
|
+
class Asari
|
11
|
+
class << self
|
12
|
+
attr_accessor :mode
|
13
|
+
@mode = :sandbox
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_writer :api_version
|
17
|
+
attr_writer :search_domain
|
18
|
+
|
19
|
+
def initialize(search_domain=nil)
|
20
|
+
@search_domain = search_domain
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: returns the current search_domain, or raises a
|
24
|
+
# MissingSearchDomainException.
|
25
|
+
#
|
26
|
+
def search_domain
|
27
|
+
@search_domain || raise(MissingSearchDomainException.new)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: returns the current api_version, or the sensible default of
|
31
|
+
# "2011-02-01" (at the time of writing, the current version of the
|
32
|
+
# CloudSearch API).
|
33
|
+
#
|
34
|
+
def api_version
|
35
|
+
@api_version || "2011-02-01"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Search for the specified term.
|
39
|
+
#
|
40
|
+
# Examples:
|
41
|
+
#
|
42
|
+
# @asari.search("fritters") #=> ["13","28"]
|
43
|
+
#
|
44
|
+
# Returns: An Array of all document IDs in the system that match the
|
45
|
+
# specified search term. If no results are found, an empty Array is
|
46
|
+
# returned.
|
47
|
+
#
|
48
|
+
# Raises: SearchException if there's an issue communicating the request to
|
49
|
+
# the server.
|
50
|
+
def search(term)
|
51
|
+
return [] if self.class.mode == :sandbox
|
52
|
+
|
53
|
+
url = "http://search-#{search_domain}.us-east-1.cloudsearch.amazonaws.com/#{api_version}/search?q=#{CGI.escape(term)}"
|
54
|
+
begin
|
55
|
+
response = HTTParty.get(url)
|
56
|
+
rescue Exception => e
|
57
|
+
ae = Asari::SearchException.new("#{e.class}: #{e.message}")
|
58
|
+
ae.set_backtrace e.backtrace
|
59
|
+
raise ae
|
60
|
+
end
|
61
|
+
|
62
|
+
unless response.response.code == "200"
|
63
|
+
raise Asari::SearchException.new("#{response.response.code}: #{response.response.msg}")
|
64
|
+
end
|
65
|
+
|
66
|
+
response.parsed_response["hits"]["hit"].map { |h| h["id"] }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public: Add an item to the index with the given ID.
|
70
|
+
#
|
71
|
+
# id - the ID to associate with this document
|
72
|
+
# fields - a hash of the data to associate with this document. This
|
73
|
+
# needs to match the search fields defined in your CloudSearch domain.
|
74
|
+
#
|
75
|
+
# Examples:
|
76
|
+
#
|
77
|
+
# @asari.update_item("4", { :name => "Party Pooper", :email => ..., ... }) #=> nil
|
78
|
+
#
|
79
|
+
# Returns: nil if the request is successful.
|
80
|
+
#
|
81
|
+
# Raises: DocumentUpdateException if there's an issue communicating the
|
82
|
+
# request to the server.
|
83
|
+
#
|
84
|
+
def add_item(id, fields)
|
85
|
+
return nil if self.class.mode == :sandbox
|
86
|
+
query = { "type" => "add", "id" => id, "version" => 1, "lang" => "en" }
|
87
|
+
query["fields"] = fields
|
88
|
+
doc_request(query)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Public: Update an item in the index based on its document ID.
|
92
|
+
# Note: As of right now, this is the same method call in CloudSearch
|
93
|
+
# that's utilized for adding items. This method is here to provide a
|
94
|
+
# consistent interface in case that changes.
|
95
|
+
#
|
96
|
+
# Examples:
|
97
|
+
#
|
98
|
+
# @asari.update_item("4", { :name => "Party Pooper", :email => ..., ... }) #=> nil
|
99
|
+
#
|
100
|
+
# Returns: nil if the request is successful.
|
101
|
+
#
|
102
|
+
# Raises: DocumentUpdateException if there's an issue communicating the
|
103
|
+
# request to the server.
|
104
|
+
#
|
105
|
+
def update_item(id, fields)
|
106
|
+
add_item(id, fields)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Public: Remove an item from the index based on its document ID.
|
110
|
+
#
|
111
|
+
# Examples:
|
112
|
+
#
|
113
|
+
# @asari.search("fritters") #=> ["13","28"]
|
114
|
+
# @asari.remove_item("13") #=> nil
|
115
|
+
# @asari.search("fritters") #=> ["28"]
|
116
|
+
# @asari.remove_item("13") #=> nil
|
117
|
+
#
|
118
|
+
# Returns: nil if the request is successful (note that asking the index to
|
119
|
+
# delete an item that's not present in the index is still a successful
|
120
|
+
# request).
|
121
|
+
# Raises: DocumentUpdateException if there's an issue communicating the
|
122
|
+
# request to the server.
|
123
|
+
def remove_item(id)
|
124
|
+
return nil if self.class.mode == :sandbox
|
125
|
+
|
126
|
+
query = { "type" => "delete", "id" => id, "version" => 2 }
|
127
|
+
doc_request query
|
128
|
+
end
|
129
|
+
|
130
|
+
# Internal: helper method: common logic for queries against the doc endpoint.
|
131
|
+
#
|
132
|
+
def doc_request(query)
|
133
|
+
endpoint = "http://doc-#{search_domain}.us-east-1.cloudsearch.amazonaws.com/#{api_version}/documents/batch"
|
134
|
+
|
135
|
+
options = { :body => [query].to_json, :headers => { "Content-Type" => "application/json"} }
|
136
|
+
|
137
|
+
begin
|
138
|
+
response = HTTParty.post(endpoint, options)
|
139
|
+
rescue Exception => e
|
140
|
+
ae = Asari::DocumentUpdateException.new("#{e.class}: #{e.message}")
|
141
|
+
ae.set_backtrace e.backtrace
|
142
|
+
raise ae
|
143
|
+
end
|
144
|
+
|
145
|
+
unless response.response.code == "200"
|
146
|
+
raise Asari::DocumentUpdateException.new("#{response.response.code}: #{response.response.msg}")
|
147
|
+
end
|
148
|
+
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
class Asari
|
2
|
+
# Public: This module should be included in any class inheriting from
|
3
|
+
# ActiveRecord::Base that needs to be indexed. Every time this module is
|
4
|
+
# included, asari_index *must* be called (see below). Including this module
|
5
|
+
# will automatically create before_delete, after_create, and after_update AR
|
6
|
+
# callbacks to remove, add, and update items in the CloudSearch index
|
7
|
+
# (respectively).
|
8
|
+
#
|
9
|
+
module ActiveRecord
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
|
13
|
+
base.before_delete :asari_remove_from_index
|
14
|
+
base.after_create :asari_add_to_index
|
15
|
+
base.after_update :asari_update_in_index
|
16
|
+
end
|
17
|
+
|
18
|
+
def asari_remove_from_index
|
19
|
+
self.class.asari_remove_item(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
def asari_add_to_index
|
23
|
+
self.class.asari_add_item(self)
|
24
|
+
end
|
25
|
+
|
26
|
+
def asari_update_in_index
|
27
|
+
self.class.asari_update_item(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
|
32
|
+
# Public: DSL method for adding this model object to the asari search
|
33
|
+
# index.
|
34
|
+
#
|
35
|
+
# This method *must* be called in any object that includes
|
36
|
+
# Asari::ActiveRecord, or your methods will be very sad.
|
37
|
+
#
|
38
|
+
# search_domain - the CloudSearch domain to use for indexing this model.
|
39
|
+
# fields - an array of Symbols representing the list of fields that
|
40
|
+
# should be included in this index.
|
41
|
+
#
|
42
|
+
# Examples:
|
43
|
+
# class User < ActiveRecord::Base
|
44
|
+
# include Asari::ActiveRecord
|
45
|
+
#
|
46
|
+
# asari_index("my-companies-users-asglkj4rsagkjlh34", [:name, :email])
|
47
|
+
#
|
48
|
+
def asari_index(search_domain, fields)
|
49
|
+
@asari = Asari.new(search_domain)
|
50
|
+
@fields = fields
|
51
|
+
end
|
52
|
+
|
53
|
+
# Internal: method for adding a newly created item to the CloudSearch
|
54
|
+
# index. Should probably only be called from asari_add_to_index above.
|
55
|
+
def asari_add_item(obj)
|
56
|
+
data = {}
|
57
|
+
@fields.each do |field|
|
58
|
+
data[field] = obj.send(field)
|
59
|
+
end
|
60
|
+
@asari.add_item(obj.send(:id), data)
|
61
|
+
rescue Asari::DocumentUpdateException => e
|
62
|
+
asari_on_error(e)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Internal: method for updating a freshly edited item to the CloudSearch
|
66
|
+
# index. Should probably only be called from asari_update_in_index above.
|
67
|
+
def asari_update_item(obj)
|
68
|
+
data = {}
|
69
|
+
@fields.each do |field|
|
70
|
+
data[field] = obj.send(field)
|
71
|
+
end
|
72
|
+
@asari.update_item(obj.send(:id), data)
|
73
|
+
rescue Asari::DocumentUpdateException => e
|
74
|
+
asari_on_error(e)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Internal: method for removing a soon-to-be deleted item from the CloudSearch
|
78
|
+
# index. Should probably only be called from asari_remove_from_index above.
|
79
|
+
def asari_remove_item(obj)
|
80
|
+
@asari.remove_item(obj.send(:id))
|
81
|
+
rescue Asari::DocumentUpdateException => e
|
82
|
+
asari_on_error(e)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Public: method for searching the index for the specified term and
|
86
|
+
# returning all model objects that match.
|
87
|
+
#
|
88
|
+
# Returns: a list of all matching AR model objects, or an empty list if no
|
89
|
+
# records are found that match.
|
90
|
+
#
|
91
|
+
# Raises: an Asari::SearchException error if there are issues
|
92
|
+
# communicating with the CloudSearch server.
|
93
|
+
def asari_find(term)
|
94
|
+
ids = @asari.search(term).map { |id| id.to_i }
|
95
|
+
begin
|
96
|
+
self.find(*ids)
|
97
|
+
rescue ::ActiveRecord::RecordNotFound
|
98
|
+
[]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Public: method for handling errors from Asari document updates. By
|
103
|
+
# default, this method causes all such exceptions (generated by issues
|
104
|
+
# from updates, creates, or deletes to the index) to be raised immediately
|
105
|
+
# to the caller; override this method on your activerecord object to
|
106
|
+
# handle the errors in a custom fashion. Be sure to return true if you
|
107
|
+
# don't want the AR callbacks to halt execution.
|
108
|
+
#
|
109
|
+
def asari_on_error(exception)
|
110
|
+
raise exception
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Asari do
|
4
|
+
describe Asari::ActiveRecord do
|
5
|
+
describe "when CloudSearch is responding without error" do
|
6
|
+
before :each do
|
7
|
+
@asari = double()
|
8
|
+
ActiveRecordFake.instance_variable_set(:@asari, @asari)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "correctly sets up a before_delete listener" do
|
12
|
+
expect(ActiveRecordFake.instance_variable_get(:@before_delete)).to eq(:asari_remove_from_index)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "correctly sets up an after_create listener" do
|
16
|
+
expect(ActiveRecordFake.instance_variable_get(:@after_create)).to eq(:asari_add_to_index)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "correctly sets up an after_update listener" do
|
20
|
+
expect(ActiveRecordFake.instance_variable_get(:@after_update)).to eq(:asari_update_in_index)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "will automatically attempt to remove itself from the index" do
|
24
|
+
@asari.should_receive(:remove_item).with(1)
|
25
|
+
ActiveRecordFake.new.asari_remove_from_index
|
26
|
+
end
|
27
|
+
|
28
|
+
it "will automatically add itself to the index" do
|
29
|
+
@asari.should_receive(:add_item).with(1, {:name => "Fritters", :email => "fritters@aredelicious.com"})
|
30
|
+
ActiveRecordFake.new.asari_add_to_index
|
31
|
+
end
|
32
|
+
|
33
|
+
it "will automatically update itself in the index" do
|
34
|
+
@asari.should_receive(:update_item).with(1, {:name => "Fritters", :email => "fritters@aredelicious.com"})
|
35
|
+
ActiveRecordFake.new.asari_update_in_index
|
36
|
+
end
|
37
|
+
|
38
|
+
it "will allow you to search for items with the index" do
|
39
|
+
@asari.should_receive(:search).with("fritters").and_return(["1"])
|
40
|
+
|
41
|
+
ActiveRecordFake.asari_find("fritters")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "will return a list of model objects when you search" do
|
45
|
+
@asari.should_receive(:search).with("fritters").and_return(["1"])
|
46
|
+
|
47
|
+
results = ActiveRecordFake.asari_find("fritters")
|
48
|
+
expect(results.class).to eq(Array)
|
49
|
+
expect(results[0].class).to eq(ActiveRecordFake)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "will return an empty list when you search for a term that isn't in the index" do
|
53
|
+
@asari.should_receive(:search).with("veggie burgers").and_return([])
|
54
|
+
|
55
|
+
results = ActiveRecordFake.asari_find("veggie burgers")
|
56
|
+
expect(results.class).to eq(Array)
|
57
|
+
expect(results.size).to eq(0)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "When CloudSearch is being a problem" do
|
62
|
+
before :each do
|
63
|
+
ActiveRecordFake.instance_variable_set(:@asari, Asari.new("test-domain"))
|
64
|
+
stub_const("HTTParty", double())
|
65
|
+
HTTParty.stub(:post).and_return(fake_error_response)
|
66
|
+
HTTParty.stub(:get).and_return(fake_error_response)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "will raise the Asari exception by default when adding to the index." do
|
70
|
+
expect { ActiveRecordFake.new.asari_add_to_index }.to raise_error(Asari::DocumentUpdateException)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "will raise the Asari exception by default when updating the index." do
|
74
|
+
expect { ActiveRecordFake.new.asari_update_in_index }.to raise_error(Asari::DocumentUpdateException)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "will raise the Asari exception by default when removing from index." do
|
78
|
+
expect { ActiveRecordFake.new.asari_remove_from_index }.to raise_error(Asari::DocumentUpdateException)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "will always raise the Asari exception when searching in the index." do
|
82
|
+
expect { ActiveRecordFake.asari_find("fritters") }.to raise_error(Asari::SearchException)
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "when we've overridden asari_on_error" do
|
86
|
+
it "honors asari_on_error when adding to the index." do
|
87
|
+
expect(ActiveRecordFakeWithErrorOverride.new.asari_add_to_index).to eq(false)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "honors asari_on_error when updating in the index." do
|
91
|
+
expect(ActiveRecordFakeWithErrorOverride.new.asari_update_in_index).to eq(false)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "honors asari_on_error when removing from the index." do
|
95
|
+
expect(ActiveRecordFakeWithErrorOverride.new.asari_remove_from_index).to eq(false)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "still raises the Asari exception when searching." do
|
99
|
+
expect { ActiveRecordFakeWithErrorOverride.asari_find("fritters") }.to raise_error(Asari::SearchException)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/spec/asari_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe "Asari" do
|
4
|
+
before :each do
|
5
|
+
@asari = Asari.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "configuration" do
|
9
|
+
it "defaults to the first CloudSearch API version." do
|
10
|
+
expect(@asari.api_version).to eq "2011-02-01"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "allows you to set a specific API version." do
|
14
|
+
@asari.api_version = "2015-10-21" # WE'VE GOT TO GO BACK
|
15
|
+
expect(@asari.api_version).to eq "2015-10-21"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "raises an exeception if no search domain is provided." do
|
19
|
+
expect { @asari.search_domain }.to raise_error Asari::MissingSearchDomainException
|
20
|
+
end
|
21
|
+
|
22
|
+
it "allows you to set a search domain." do
|
23
|
+
@asari.search_domain = "theroyaldomainofawesome"
|
24
|
+
expect(@asari.search_domain).to eq "theroyaldomainofawesome"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
|
3
|
+
describe Asari do
|
4
|
+
describe "updating the index" do
|
5
|
+
before :each do
|
6
|
+
@asari = Asari.new("testdomain")
|
7
|
+
stub_const("HTTParty", double())
|
8
|
+
HTTParty.stub(:post).and_return(fake_post_success)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "allows you to add an item to the index." do
|
12
|
+
HTTParty.should_receive(:post).with("http://doc-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/documents/batch", { :body => [{ "type" => "add", "id" => "1", "version" => 1, "lang" => "en", "fields" => { :name => "fritters"}}].to_json, :headers => { "Content-Type" => "application/json"}})
|
13
|
+
|
14
|
+
expect(@asari.add_item("1", {:name => "fritters"})).to eq(nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "allows you to update an item to the index." do
|
18
|
+
HTTParty.should_receive(:post).with("http://doc-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/documents/batch", { :body => [{ "type" => "add", "id" => "1", "version" => 1, "lang" => "en", "fields" => { :name => "fritters"}}].to_json, :headers => { "Content-Type" => "application/json"}})
|
19
|
+
|
20
|
+
expect(@asari.update_item("1", {:name => "fritters"})).to eq(nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "allows you to delete an item from the index." do
|
24
|
+
HTTParty.should_receive(:post).with("http://doc-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/documents/batch", { :body => [{ "type" => "delete", "id" => "1", "version" => 2}].to_json, :headers => { "Content-Type" => "application/json"}})
|
25
|
+
|
26
|
+
expect(@asari.remove_item("1")).to eq(nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "when there are internet issues" do
|
30
|
+
before :each do
|
31
|
+
HTTParty.stub(:post).and_raise(SocketError.new)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "raises an exception when you try to add an item to the index" do
|
35
|
+
expect { @asari.add_item("1", {})}.to raise_error(Asari::DocumentUpdateException)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "raises an exception when you try to update an item in the index" do
|
39
|
+
expect { @asari.update_item("1", {})}.to raise_error(Asari::DocumentUpdateException)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "raises an exception when you try to remove an item from the index" do
|
43
|
+
expect { @asari.remove_item("1")}.to raise_error(Asari::DocumentUpdateException)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "when there are CloudSearch issues" do
|
48
|
+
before :each do
|
49
|
+
HTTParty.stub(:post).and_return(fake_error_response)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "raises an exception when you try to add an item to the index" do
|
53
|
+
expect { @asari.add_item("1", {})}.to raise_error(Asari::DocumentUpdateException)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "raises an exception when you try to update an item in the index" do
|
57
|
+
expect { @asari.update_item("1", {})}.to raise_error(Asari::DocumentUpdateException)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises an exception when you try to remove an item from the index" do
|
61
|
+
expect { @asari.remove_item("1")}.to raise_error(Asari::DocumentUpdateException)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/spec/search_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
|
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
|
10
|
+
|
11
|
+
it "allows you to search." do
|
12
|
+
HTTParty.should_receive(:get).with("http://search-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/search?q=testsearch")
|
13
|
+
@asari.search("testsearch")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "escapes dangerous characters in search terms." do
|
17
|
+
HTTParty.should_receive(:get).with("http://search-testdomain.us-east-1.cloudsearch.amazonaws.com/2011-02-01/search?q=testsearch%21")
|
18
|
+
@asari.search("testsearch!")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns a list of document IDs for search results." do
|
22
|
+
expect(@asari.search("testsearch")).to eq(["123","456"])
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns an empty list when no search results are found." do
|
26
|
+
HTTParty.stub(:get).and_return(fake_empty_response)
|
27
|
+
expect(@asari.search("testsearch")).to eq([])
|
28
|
+
end
|
29
|
+
|
30
|
+
it "raises an exception if the service errors out." do
|
31
|
+
HTTParty.stub(:get).and_return(fake_error_response)
|
32
|
+
expect { @asari.search("testsearch)") }.to raise_error Asari::SearchException
|
33
|
+
end
|
34
|
+
|
35
|
+
it "raises an exception if there are internet issues." do
|
36
|
+
HTTParty.stub(:get).and_raise(SocketError.new)
|
37
|
+
expect { @asari.search("testsearch)") }.to raise_error Asari::SearchException
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec_helper.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'asari'
|
2
|
+
require 'asari/active_record'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
RSpec.configuration.expect_with(:rspec) { |c| c.syntax = :expect }
|
6
|
+
|
7
|
+
def fake_response
|
8
|
+
OpenStruct.new(:parsed_response => { "hits" => {"hit" => [{"id" => "123"}, {"id" => "456"}]}},
|
9
|
+
:response => OpenStruct.new(:code => "200"))
|
10
|
+
end
|
11
|
+
|
12
|
+
def fake_empty_response
|
13
|
+
OpenStruct.new(:parsed_response => { "hits" => {"hit" => []}},
|
14
|
+
:response => OpenStruct.new(:code => "200"))
|
15
|
+
end
|
16
|
+
|
17
|
+
def fake_error_response
|
18
|
+
OpenStruct.new(:response => OpenStruct.new(:code => "404"))
|
19
|
+
end
|
20
|
+
|
21
|
+
def fake_post_success
|
22
|
+
OpenStruct.new(:response => OpenStruct.new(:code => "200"))
|
23
|
+
end
|
24
|
+
|
25
|
+
module ActiveRecord
|
26
|
+
class RecordNotFound < StandardError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ActiveRecordFake
|
31
|
+
class << self
|
32
|
+
def before_delete(sym)
|
33
|
+
@before_delete = sym
|
34
|
+
end
|
35
|
+
|
36
|
+
def after_create(sym)
|
37
|
+
@after_create = sym
|
38
|
+
end
|
39
|
+
|
40
|
+
def after_update(sym)
|
41
|
+
@after_update = sym
|
42
|
+
end
|
43
|
+
|
44
|
+
def find(*args)
|
45
|
+
if args.size > 0
|
46
|
+
return [ActiveRecordFake.new]
|
47
|
+
else
|
48
|
+
raise ActiveRecord::RecordNotFound
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
include Asari::ActiveRecord
|
54
|
+
|
55
|
+
asari_index("test-domain", [:name, :email])
|
56
|
+
|
57
|
+
def id
|
58
|
+
1
|
59
|
+
end
|
60
|
+
|
61
|
+
def name
|
62
|
+
"Fritters"
|
63
|
+
end
|
64
|
+
|
65
|
+
def email
|
66
|
+
"fritters@aredelicious.com"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class ActiveRecordFakeWithErrorOverride < ActiveRecordFake
|
71
|
+
include Asari::ActiveRecord
|
72
|
+
|
73
|
+
asari_index("test-domain", [:name, :email])
|
74
|
+
|
75
|
+
def self.asari_on_error(exception)
|
76
|
+
false
|
77
|
+
end
|
78
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: asari
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tommy Morgan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: &70106563512800 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70106563512800
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70106563528480 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70106563528480
|
36
|
+
description: Asari s a Ruby interface for AWS CloudSearch
|
37
|
+
email:
|
38
|
+
- tommy@wellbredgrapefruit.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- Gemfile
|
45
|
+
- README.md
|
46
|
+
- Rakefile
|
47
|
+
- asari.gemspec
|
48
|
+
- lib/asari.rb
|
49
|
+
- lib/asari/active_record.rb
|
50
|
+
- lib/asari/exceptions.rb
|
51
|
+
- lib/asari/version.rb
|
52
|
+
- spec/active_record_spec.rb
|
53
|
+
- spec/asari_spec.rb
|
54
|
+
- spec/documents_spec.rb
|
55
|
+
- spec/search_spec.rb
|
56
|
+
- spec_helper.rb
|
57
|
+
homepage: http://github.com/duwanis/asari
|
58
|
+
licenses: []
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project: asari
|
77
|
+
rubygems_version: 1.8.15
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Asari is a Ruby interface for AWS CloudSearch.
|
81
|
+
test_files:
|
82
|
+
- spec/active_record_spec.rb
|
83
|
+
- spec/asari_spec.rb
|
84
|
+
- spec/documents_spec.rb
|
85
|
+
- spec/search_spec.rb
|
86
|
+
has_rdoc:
|