s3search 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f2f409bf1d5943a4bba14de2d68380fd42de29e5
4
+ data.tar.gz: fa9aae7844c80c61be5fca34bfbf1d18be16b3f3
5
+ SHA512:
6
+ metadata.gz: 17497e746c81e2e54ab2690b64e4a27f8b98cca4f24a79772bbe81044cb70acec9d0c1ad95dc2c5b6e80e9e28917f46d61c0a236bd81588120dd544e5ce75672
7
+ data.tar.gz: 0d79ac7927201e5a74a407d865c83622b0e072f2ca3f966c0c79a4c24a04f163b6afaba16ab211d636401666e575c97166ec1f9ee940b8d0de3ee0606625f764
data/.gitignore CHANGED
@@ -15,3 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .powenv
19
+ .rbenv-vars
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile CHANGED
@@ -2,3 +2,6 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in s3search.gemspec
4
4
  gemspec
5
+
6
+ gem 'rspec'
7
+ gem 'webmock'
data/README.md CHANGED
@@ -1,6 +1,12 @@
1
- # S3search
1
+ # S3Search
2
2
 
3
- TODO: Write a gem description
3
+ [S3Search](http://addons.heroku.com/s3search) is an [add-on](http://addons.heroku.com) for providing powerful document-based full-text indexing and search to your application.
4
+
5
+ Adding S3Search to your application will allow you search your documents based upon the actual text within the documents, as well as any metadata fields you assign to them. Yes, that's right! S3Search will index the text inside your documents.
6
+
7
+ Do you already have documents stored and want to index them and make them searchable? No worries. S3Search works by you sending it a URL to fetch the document content to index, along with a hash of metadata attributes to record against it. You can them perform powerful queries against that indexed data, based on the rich features of [elasticsearch](http://www.elasticsearch.org).
8
+
9
+ S3Search is a Heroku add-on.
4
10
 
5
11
  ## Installation
6
12
 
@@ -12,13 +18,155 @@ And then execute:
12
18
 
13
19
  $ bundle
14
20
 
15
- Or install it yourself as:
21
+ ## Provisioning the add-on
22
+
23
+ S3Search can be attached to a Heroku application via the CLI:
24
+
25
+ <div class="callout" markdown="1">
26
+ A list of all plans available can be found [here](http://addons.heroku.com/s3search).
27
+ </div>
28
+
29
+ ```term
30
+ $ heroku addons:add s3search
31
+ -----> Adding s3search to sharp-mountain-4005... done, v18 (free)
32
+ ```
33
+
34
+ Once S3Search has been added a `S3SEARCH_URL` setting will be available in the app configuration and will contain your custom URL to access the newly provisioned S3Search service instance. This can be confirmed using the `heroku config:get` command.
35
+
36
+ ```term
37
+ $ heroku config:get S3SEARCH_URL
38
+ https://user:pass@api.s3searchapp.com
39
+ ```
40
+
41
+ After installing S3Search the application should be configured to fully integrate with the add-on.
42
+
43
+ ## Using with Rails 3.x
44
+
45
+ Ruby on Rails applications will need to add the following entry into their `Gemfile` specifying the S3Search client library.
46
+
47
+ ```ruby
48
+ gem 's3search'
49
+ ```
50
+
51
+ Update application dependencies with bundler.
52
+
53
+ ```term
54
+ $ bundle install
55
+ ```
56
+
57
+ Write some application code to index some documents. Use the special field, `_content_url`, if you want to specify a location to download content and make it searchable. S3Search will download the content and make it searchable via the special field `_document_content`.
58
+
59
+ ```ruby
60
+ S3Search.create title: 'MyDocument', _content_url: 'https://s3-us-east-1.amazonaws.com/my_bucket/my_document.pdf'
61
+ S3Search.create name: 'Bob Lob Law', resume_id: 25, _content_url: 'https://s3-us-east-1.amazonaws.com/resumes.mycompany.com/bob.pdf'
62
+ ```
63
+
64
+ The documents don't even really need to be in S3.
65
+
66
+ ```ruby
67
+ S3Search.create name: 'Bitcoin Pirate', resume_id: 42, _content_url: 'https://user:pass@authenticatedlocation.com/docs/jenny.pdf'
68
+ S3Search.create title: 'Bitcoin', author: 'santoshin@gmx.com', tags: ['bitcoin', 'manifesto'], _content_url: 'http://bitcoin.org/bitcoin.pdf'
69
+ ```
70
+
71
+ The documents don't even really need to be documents! You can use S3Search to use its powerful search capability over just your custom metadata.
72
+
73
+ ```ruby
74
+ S3Search.create customer_id: 32, first_name: 'His Holiness', last_name: 'The Dalia Lama', religion: 'Buddhist', twitter_handle: '@DalaiLama'
75
+ S3Search.create customer_id: 99, first_name: 'George', middle_name: 'R. R.', last_name: 'Martin', job_title: 'Author'
76
+ ```
77
+
78
+ Now retrieve some documents via the powerful query API.
79
+
80
+ Search by a single metadata field
81
+
82
+ ```ruby
83
+ results = S3Search.search('title:MyDocument')
84
+ ```
85
+
86
+ Search all metadata fields AND the content of the documents.
87
+
88
+ ```ruby
89
+ results = S3Search.search('bitcoin')
90
+ ```
91
+
92
+ Search only the content of the documents.
93
+
94
+ ```ruby
95
+ results = S3Search.search('_document_content:bitcoin')
96
+ ```
97
+
98
+ Boost the search ranking of a certain field.
99
+
100
+ ```ruby
101
+ results = S3Search.search('bitcoin', boost: { title: 2.5 })
102
+ ```
103
+ Find a single document based on its unique id.
104
+
105
+ ```ruby
106
+ document = S3Search.get '833FCA4EEEF2943AC2D8E0'
107
+ ```
108
+ ## Monitoring & Logging
109
+
110
+ Stats and the current state of S3Search can be displayed via the CLI.
111
+
112
+ ```term
113
+ $ heroku s3search:status
114
+ documents_indexed: 32842
115
+ index_size: 640MB
116
+ ```
117
+
118
+ S3Search activity can be observed within the Heroku log-stream.
119
+
120
+ ```term
121
+ $ heroku logs -t | grep 's3search'
122
+ ```
123
+
124
+ ## Dashboard
125
+
126
+ <div class="callout" markdown="1">
127
+ For more information on the features available within the S3Search dashboard please see the docs at [heroku.s3searchapp.com/docs](heroku.s3searchapp.com/docs).
128
+ </div>
129
+
130
+ The S3Search dashboard allows you to view the current status of your S3Search cluster.
131
+
132
+ The dashboard can be accessed via the CLI:
133
+
134
+ ```term
135
+ $ heroku addons:open s3search
136
+ Opening s3search for sharp-mountain-4005…
137
+ ```
138
+
139
+ or by visiting the [Heroku apps web interface](http://heroku.com/myapps) and selecting the application in question. Select S3Search from the Add-ons menu.
140
+
141
+ ## Migrating between plans
142
+
143
+ <div class="note" markdown="1">Application owners should carefully manage the migration timing to ensure proper application function during the migration process.</div>
144
+
145
+ Use the `heroku addons:upgrade` command to migrate to a new plan.
146
+
147
+ ```term
148
+ $ heroku addons:upgrade s3search:newplan
149
+ -----> Upgrading s3search:newplan to sharp-mountain-4005... done, v18 ($49/mo)
150
+ Your plan has been updated to: s3search:newplan
151
+ ```
152
+
153
+ ## Removing the add-on
154
+
155
+ S3Search can be removed via the CLI.
156
+
157
+ <div class="warning" markdown="1">This will destroy all metadata and indexes stored in S3Search and cannot be undone! Of course, documents indexed in S3Search but stored elsewhere will remain untouched.</div>
158
+
159
+ ```term
160
+ $ heroku addons:remove s3search
161
+ -----> Removing s3search from sharp-mountain-4005... done, v20 (free)
162
+ ```
163
+
164
+ Before removing S3Search a data export can be performed by contacting support@s3searchapp.com directly.
16
165
 
17
- $ gem install s3search
166
+ ## Support
18
167
 
19
- ## Usage
168
+ All S3Search support and runtime issues should be submitted via on of the [Heroku Support channels](support-channels). Any non-support related issues or product feedback is welcome at feedback@s3searchapp.com.
20
169
 
21
- TODO: Write usage instructions here
22
170
 
23
171
  ## Contributing
24
172
 
@@ -0,0 +1,13 @@
1
+ require 'integration_helper'
2
+
3
+ describe '/document' do
4
+
5
+ it 'is implemented correctly' do
6
+ document = S3Search.create title: 'blah'
7
+ expect(document.title).to eq('blah')
8
+
9
+ id = document.id
10
+
11
+ expect(S3Search.get(id).title).to eq('blah')
12
+ end
13
+ end
@@ -0,0 +1,70 @@
1
+ require 'integration_helper'
2
+
3
+ describe '/searches' do
4
+
5
+ it 'is implemented correctly' do
6
+
7
+ S3Search.get_all(per_page: 9999).map do |doc|
8
+ S3Search.delete doc.id
9
+ end
10
+
11
+ title_a = SecureRandom.hex
12
+ title_b = SecureRandom.hex
13
+
14
+ a_docs = 10.times.map do
15
+ {title: title_a}
16
+ end
17
+
18
+ b_docs = 10.times.map do
19
+ {title: title_b}
20
+ end
21
+
22
+ a_doc_ids = S3Search.create_many(a_docs).map(&:id)
23
+ b_doc_ids = S3Search.create_many(b_docs).map(&:id)
24
+ all_doc_ids = a_doc_ids + b_doc_ids
25
+
26
+ S3Search.get_all(per_page: 20).each do |doc|
27
+ expect(all_doc_ids).to include(doc.id)
28
+ end
29
+
30
+ results = S3Search.simple_search "title:#{title_a}", per_page: 4
31
+ expect(results.size).to eq(4)
32
+ results.each do |doc|
33
+ expect(a_doc_ids).to include(doc.id)
34
+ end
35
+
36
+ results.next
37
+ expect(results.size).to eq(4)
38
+ results.each do |doc|
39
+ expect(a_doc_ids).to include(doc.id)
40
+ end
41
+
42
+ results.next
43
+ expect(results.size).to eq(2)
44
+ results.each do |doc|
45
+ expect(a_doc_ids).to include(doc.id)
46
+ end
47
+ expect(results.next?).to be_false
48
+
49
+ results = S3Search.simple_search "title:#{title_b}", per_page: 999
50
+ expect(results.size).to eq(10)
51
+ results.each do |doc|
52
+ expect(b_doc_ids).to include(doc.id)
53
+ end
54
+
55
+ # begin
56
+ # results.each do |result|
57
+ # S3Search.delete result.id
58
+ # end
59
+ # end while results.next
60
+
61
+ # document = S3Search.create title: 'blah'
62
+ # expect(document.title).to eq('blah')
63
+
64
+ # id = document.id
65
+
66
+ # expect(S3Search.get(id).title).to eq('blah')
67
+
68
+ # puts S3Search.search 'blah'
69
+ end
70
+ end
@@ -0,0 +1,37 @@
1
+ require 's3search/result_page'
2
+
3
+ module S3Search
4
+ module API
5
+ module Documents
6
+
7
+ def create data
8
+ raise 'use create_many for multiple documents' if data.is_a?(Array)
9
+ _post('/v1/documents.json', {data: data})
10
+ end
11
+
12
+ def create_many data
13
+ response = _post('/v1/documents.json', {data: [data].flatten})
14
+ response.results
15
+ end
16
+
17
+ def delete id
18
+ _delete "/v1/documents/#{id}.json"
19
+ end
20
+
21
+ def get id
22
+ _get "/v1/documents/#{id}.json"
23
+ end
24
+
25
+ def update id, attributes
26
+ _put("/v1/documents/#{id}.json", {document: attributes})
27
+ end
28
+
29
+ def get_all options={}
30
+ options[:page] ||= 1
31
+ options[:per_page] ||= 25
32
+ response = _get("/v1/documents.json", options)
33
+ S3Search::ResultPage.new(response, self)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,49 @@
1
+ require 's3search/result_page'
2
+
3
+ module S3Search
4
+ module API
5
+ module Searches
6
+
7
+ def simple_search search_term, options={}
8
+ if search_term.include?(':')
9
+ field, value = search_term.split(':')[0..1]
10
+ options.merge!({where: { field => value }})
11
+ else
12
+ options.merge!({where: {_all: search_term }})
13
+ end
14
+ search options
15
+ end
16
+
17
+ def search options
18
+ raise 'where: {field: "value"} is a required option' unless options[:where]
19
+ query_terms = options.delete(:where).map do |field, value|
20
+ {
21
+ query_string: {
22
+ default_field: field,
23
+ query: value,
24
+ default_operator: 'AND'
25
+ }
26
+ }
27
+ end
28
+ query = {
29
+ query: {
30
+ bool: {
31
+ must: query_terms
32
+ }
33
+ }
34
+ }
35
+
36
+ page = options.delete(:page) || 1
37
+ per_page = options.delete(:per_page) || 25
38
+
39
+ query.merge!(options)
40
+ response = _post '/v1/searches.json', {
41
+ search: query,
42
+ page: page,
43
+ per_page: per_page
44
+ }
45
+ S3Search::ResultPage.new(response, self)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,77 @@
1
+ require 'active_support/core_ext/object/try'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+ require 'hashie'
5
+ require 'logger'
6
+ require 's3search/api/documents'
7
+ require 's3search/api/searches'
8
+ require 's3search/request_error'
9
+ require 's3search/version'
10
+
11
+ module S3Search
12
+ class Client
13
+ include S3Search::API::Documents
14
+ include S3Search::API::Searches
15
+
16
+ attr_reader :url, :http, :logger
17
+
18
+ def initialize
19
+ @url = ENV['S3SEARCH_URL'] || 'https://api.s3searchapp.com'
20
+ @http = Faraday.new(:url => @url) do |builder|
21
+ builder.response :mashify
22
+ builder.response :json, :content_type => /\bjson$/
23
+ builder.request :json
24
+ builder.request :basic_auth, ENV['S3SEARCH_API_KEY'], ENV['S3SEARCH_API_SECRET']
25
+ builder.options[:read_timeout] = 4
26
+ builder.options[:open_timeout] = 2
27
+ builder.adapter :excon
28
+ end
29
+
30
+ @http.headers = {
31
+ accept: 'application/json',
32
+ user_agent: "S3Search Ruby Gem #{S3Search::VERSION}",
33
+ "Content-Type" => "application/json"
34
+ }
35
+
36
+ @logger = Logger.new(STDOUT)
37
+
38
+ # 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR, 4=FATAL
39
+ @logger.level = ENV['S3SEARCH_LOG_LEVEL'].try(:to_i) || 2
40
+
41
+ @logger.formatter = proc do |severity, datetime, progname, msg|
42
+ "[S3Search][#{severity}]: #{msg}\n"
43
+ end
44
+ end
45
+
46
+ def _delete path, params={}
47
+ request :delete, path, params
48
+ end
49
+
50
+ def _get path, params={}
51
+ request :get, path, params
52
+ end
53
+
54
+ def _post path, params={}
55
+ request :post, path, params
56
+ end
57
+
58
+ def _put path, params={}
59
+ request :put, path, params
60
+ end
61
+
62
+ def request method, path, params
63
+ response = http.send(method, path, params)
64
+
65
+ case response.status
66
+ when 200..299
67
+ response.body
68
+ when 404, 410
69
+ raise RequestError::NotFound.new(response)
70
+ when 401
71
+ raise RequestError::Unauthorized.new(response)
72
+ else
73
+ raise RequestError.new(response)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,19 @@
1
+ module S3Search
2
+ class RequestError < StandardError
3
+ attr_reader :response
4
+
5
+ def initialize(response)
6
+ @response = response
7
+ msg = "Error processing request: (#{response.status})! #{response.env[:method]} URL: #{response.env[:url]}"
8
+ msg << "\n Resp Body: #{response.body}"
9
+ super msg
10
+ end
11
+
12
+ class NotFound < RequestError
13
+ end
14
+
15
+ class Unauthorized < RequestError
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ module S3Search
2
+ class ResultPage
3
+
4
+ attr_reader :response, :results
5
+
6
+ def initialize response, client
7
+ @response = response
8
+ @results = response.results
9
+ @client = client
10
+ end
11
+
12
+ def next
13
+ return unless next?
14
+ change_page(links.next)
15
+ end
16
+
17
+ def next?
18
+ links.next
19
+ end
20
+
21
+ def prev
22
+ return unless prev?
23
+ change_page(links.prev)
24
+ end
25
+
26
+ def prev?
27
+ links.prev
28
+ end
29
+
30
+ def respond_to_missing? method_name, include_private=false
31
+ results.respond_to?(method_name, include_private) || response.respond_to?(method_name, include_private)
32
+ end
33
+
34
+ private
35
+
36
+ def change_page link
37
+ @response = @client._get(link)
38
+ @results = @response.results
39
+ S3Search::ResultPage.new(@response, @client)
40
+ end
41
+
42
+ def method_missing method_name, *args, &block
43
+ if results.respond_to?(method_name)
44
+ results.send method_name, *args, &block
45
+ elsif response.respond_to?(method_name)
46
+ response.send method_name, *args, &block
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
- module S3search
2
- VERSION = "0.0.1"
1
+ module S3Search
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/s3search.rb CHANGED
@@ -1,5 +1,21 @@
1
- require "s3search/version"
1
+ require 'json'
2
+ require 's3search/client'
2
3
 
3
- module S3search
4
- # Your code goes here...
4
+ module S3Search
5
+ class << self
6
+ def client
7
+ @client ||= S3Search::Client.new
8
+ end
9
+
10
+ def respond_to_missing? method_name, include_private=false
11
+ client.respond_to?(method_name, include_private)
12
+ end
13
+
14
+ private
15
+
16
+ def method_missing method_name, *args, &block
17
+ return super unless client.respond_to?(method_name)
18
+ client.send(method_name, *args, &block)
19
+ end
20
+ end
5
21
  end
data/s3search.gemspec CHANGED
@@ -3,21 +3,27 @@ lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 's3search/version'
5
5
 
6
- Gem::Specification.new do |spec|
7
- spec.name = "s3search"
8
- spec.version = S3search::VERSION
9
- spec.authors = ["Chris Aitchison"]
10
- spec.email = ["chris.aitchison@sodalis.com.au"]
11
- spec.description = %q{Ruby implementation of the S3Search API}
12
- spec.summary = %q{S3Search Ruby API}
13
- spec.homepage = "https://github.com/sodalis/s3search-ruby"
14
- spec.license = "MIT"
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "s3search"
8
+ gem.version = S3Search::VERSION
9
+ gem.authors = ["Chris Aitchison"]
10
+ gem.email = ["chris.aitchison@sodalis.com.au"]
11
+ gem.description = %q{Ruby implementation of the S3Search API}
12
+ gem.summary = %q{S3Search Ruby API}
13
+ gem.homepage = "https://github.com/sodalis/s3search-ruby"
14
+ gem.license = "MIT"
15
15
 
16
- spec.files = `git ls-files`.split($/)
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(spec|integration)/})
19
+ gem.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake"
21
+ gem.add_dependency 'activesupport', ">= 3.0.0"
22
+ gem.add_dependency('faraday', '~> 0.8')
23
+ gem.add_dependency('faraday_middleware', '~> 0.9.0')
24
+ gem.add_dependency('excon', '>= 0.16')
25
+ gem.add_dependency('hashie', '~> 1.2.0')
26
+
27
+ gem.add_development_dependency "bundler", "~> 1.3"
28
+ gem.add_development_dependency "rake"
23
29
  end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'creating many documents at once' do
4
+
5
+ let(:docs){ (0..9).map {|i| {title: "Doc#{i}"} } }
6
+
7
+ before do
8
+ stub_api_http :post, '/v1/documents.json', {data: docs} do
9
+ {
10
+ status: 201,
11
+ body: {results: docs.map{|doc| Hashie::Mash.new(doc.merge(id: SecureRandom.hex))} }
12
+ }
13
+ end
14
+ @result = S3Search.create_many docs
15
+ end
16
+
17
+ it 'returns the created documents' do
18
+ expect(@result.size).to eq(10)
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'creating a simple document' do
4
+
5
+ let(:create_params){ {title: 'Simple Document'} }
6
+ let(:created_id) { 'zMitmokAR5O4utuyz7qXRw' }
7
+ before do
8
+ stub_api_http :post, '/v1/documents.json', {data: create_params} do
9
+ {
10
+ status: 201,
11
+ body: Hashie::Mash.new(create_params.merge(id: created_id))
12
+ }
13
+ end
14
+ @document = S3Search.create create_params
15
+ end
16
+
17
+ it 'returns the created document' do
18
+ expect(@document.title).to eq('Simple Document')
19
+ expect(@document.id).to eq(created_id)
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'deleting a document' do
4
+
5
+ let(:document_id) { '3cbv4PUcRcOGsHnvIX9lJQ' }
6
+ before do
7
+ stub_api_http :delete, "/v1/documents/#{document_id}.json" do
8
+ {
9
+ status: 204
10
+ }
11
+ end
12
+ end
13
+
14
+ it 'deletes the document' do
15
+ S3Search.delete document_id
16
+ end
17
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'getting all documents' do
4
+ let(:document_jsons) { (1..27).map{ |i| Hashie::Mash.new(id: "id#{i}", _score: 1.0, name: "doc#{i}") } }
5
+
6
+ def link page, per_page
7
+ "https://api.s3searchapp.test/v1/documents.json?page=#{page}&per_page=#{per_page}"
8
+ end
9
+
10
+ it 'takes per_page and page params' do
11
+ stub_get_all 2, 3
12
+ results = S3Search.get_all page: 2, per_page: 3
13
+ expect_page(results, 2, 3)
14
+
15
+ expect(results.map(&:name)).to eq(['doc4', 'doc5', 'doc6'])
16
+ end
17
+
18
+ it 'allows paging with next and prev' do
19
+ stub_get_all 4, 2
20
+ page = S3Search.get_all page: 4, per_page: 2
21
+ expect_page(page, 4, 2)
22
+
23
+ stub_get_all 5, 2
24
+ page.next
25
+ expect_page(page, 5, 2)
26
+
27
+ page.prev
28
+ expect_page(page, 4, 2)
29
+
30
+ stub_get_all 3, 2
31
+ page.prev
32
+ expect_page(page, 3, 2)
33
+
34
+ stub_get_all 2, 2
35
+ page.prev
36
+ expect_page(page, 2, 2)
37
+
38
+ stub_get_all 1, 2
39
+ page.prev
40
+ expect_page(page, 1, 2)
41
+
42
+ page.prev
43
+ expect_page(page, 1, 2)
44
+ end
45
+
46
+ it 'defaults to page: 1, per_page: 25' do
47
+ stub_get_all 1, 25
48
+ page = S3Search.get_all
49
+ expect_page(page, 1, 25)
50
+
51
+ stub_get_all 2, 25
52
+ page.next
53
+ expect_page(page, 2, 25)
54
+
55
+ page.next
56
+ expect_page(page, 2, 25)
57
+ end
58
+
59
+ def expect_page result, page, per_page
60
+ from = from(page, per_page)
61
+ to = to(page, per_page)
62
+
63
+ expect(result.size).to eq((to - from) + 1)
64
+ expect(result.from).to eq(from)
65
+ expect(result.to).to eq(to)
66
+ expect(result.first).to eq(document_jsons[from - 1])
67
+ expect(result.last).to eq(document_jsons[to - 1])
68
+ end
69
+
70
+
71
+ def from page, per_page
72
+ [(page - 1) * per_page + 1, 1].max
73
+ end
74
+
75
+ def to page, per_page, total=27
76
+ [page * per_page, total].min
77
+ end
78
+
79
+ def stub_get_all page, per_page
80
+ total = 27
81
+ from = from(page, per_page)
82
+ to = to(page, per_page)
83
+ links = {
84
+ self: link(page, per_page)
85
+ }
86
+ links[:next] = link(page + 1, per_page) if to < total
87
+ links[:prev] = link(page - 1, per_page) if page > 1
88
+
89
+ stub_api_http :get, "/v1/documents.json?page=#{page}&per_page=#{per_page}" do
90
+ {
91
+ body: Hashie::Mash.new(
92
+ total: total,
93
+ from: from,
94
+ to: to,
95
+ results: document_jsons[(from - 1)..(to - 1)],
96
+ links: links
97
+ ),
98
+ status: 200
99
+ }
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'deleting a document' do
4
+
5
+ let(:document_id) { 'ZenfyafgRRGWAR6pqF0DeA' }
6
+ before do
7
+ stub_api_http :get, "/v1/documents/#{document_id}.json" do
8
+ {
9
+ body: Hashie::Mash.new({id: document_id, name: 'Some Document', some_property: 42}),
10
+ status: 200
11
+ }
12
+ end
13
+ @document = S3Search.get document_id
14
+ end
15
+
16
+
17
+ it 'gets the document' do
18
+ expect(@document.id).to eq(document_id)
19
+ expect(@document.name).to eq('Some Document')
20
+ expect(@document.some_property).to eq(42)
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'updating a document' do
4
+
5
+ it 'updates the document and returns the updated version'
6
+ end
@@ -0,0 +1,154 @@
1
+ require 'active_support/core_ext/hash/deep_merge'
2
+
3
+ describe 'search' do
4
+
5
+ def link id, page, per_page
6
+ "https://api.s3searchapp.test/v1/searches/abcde.json?page=#{page}&per_page=#{per_page}"
7
+ end
8
+
9
+ let(:found_documents) { (1..70).map{|i| {id: SecureRandom.hex, name: "doc#{i}", _score: 1.0 / i}}}
10
+
11
+ let(:simple_request_body) {
12
+ {
13
+ search: {
14
+ query: {
15
+ bool: {
16
+ must: [
17
+ {
18
+ query_string:{
19
+ default_field: '_all',
20
+ query: "searchterm",
21
+ default_operator:"AND"
22
+ }
23
+ }
24
+ ]
25
+ }
26
+ }
27
+ },
28
+ page: 1,
29
+ per_page: 25
30
+ }
31
+ }
32
+ it 'finds by simple query' do
33
+ stub_api_http :post, "/v1/searches.json", simple_request_body do
34
+ {
35
+ body: Hashie::Mash.new(
36
+ total: 70,
37
+ from: 1,
38
+ to: 25,
39
+ results: found_documents[0..24],
40
+ links: {
41
+ self: link('abcde', 1, 25),
42
+ next: link('abcde', 2, 25)
43
+ }
44
+ ),
45
+ status: 200
46
+ }
47
+ end
48
+
49
+ results = S3Search.simple_search 'searchterm'
50
+ expect(results.first).to eq(
51
+ {
52
+ "_score" => 1.0,
53
+ "id" => results[0].id,
54
+ "name" => "doc1"
55
+ }
56
+ )
57
+ expect(results.last).to eq(
58
+ {
59
+ "_score" => 0.04,
60
+ "id" => results[24].id,
61
+ "name" => "doc25"
62
+ }
63
+ )
64
+
65
+ stub_api_http :get, "/v1/searches/abcde.json?page=2&per_page=25" do
66
+ {
67
+ body: Hashie::Mash.new(
68
+ total: 70,
69
+ from: 26,
70
+ to: 50,
71
+ results: found_documents[25..49],
72
+ links: {
73
+ self: link('abcde', 2, 25),
74
+ next: link('abcde', 3, 25),
75
+ prev: link('abcde', 1, 25)
76
+ }
77
+ ),
78
+ status: 200
79
+ }
80
+ end
81
+
82
+ results.next
83
+ expect(results.first).to eq(
84
+ {
85
+ "_score" => results[0]._score,
86
+ "id" => results[0].id,
87
+ "name" => "doc26"
88
+ }
89
+ )
90
+ expect(results.last).to eq(
91
+ {
92
+ "_score" => results[24]._score,
93
+ "id" => results[24].id,
94
+ "name" => "doc50"
95
+ }
96
+ )
97
+
98
+ end
99
+
100
+ let(:highlight_options) {
101
+ {
102
+ highlight: {
103
+ fields: [:name],
104
+ tag: '<strong class="highlight">',
105
+ number_of_fragments: 3,
106
+ fragment_size: 150
107
+ }
108
+ }
109
+ }
110
+ let(:highlight_request_body) {
111
+ simple_request_body.deep_merge(search: highlight_options)
112
+ }
113
+
114
+ let(:highlight_documents) {
115
+ (1..70).map do |i|
116
+ {
117
+ id: SecureRandom.hex,
118
+ name: "doc#{i}",
119
+ description: 'a searchterm here',
120
+ _score: 1.0 / i,
121
+ _highlight: {
122
+ _description: ['a <strong class="highlight">searchterm</strong> here']
123
+ }
124
+ }
125
+ end
126
+ }
127
+
128
+ it 'allows highlighting of fields' do
129
+ stub_api_http :post, "/v1/searches.json", highlight_request_body do
130
+ {
131
+ body: Hashie::Mash.new(
132
+ total: 70,
133
+ from: 1,
134
+ to: 25,
135
+ results: highlight_documents[0..24],
136
+ links: {
137
+ self: link('abcde', 1, 25),
138
+ next: link('abcde', 2, 25)
139
+ }
140
+ ),
141
+ status: 200
142
+ }
143
+ end
144
+ results = S3Search.simple_search 'searchterm', highlight_options
145
+ expect(results.first).to eq({
146
+ "_highlight" => {"_description"=>['a <strong class="highlight">searchterm</strong> here']},
147
+ "_score" => 1.0,
148
+ "description" => "a searchterm here",
149
+ "id" => results[0].id,
150
+ "name" => "doc1"
151
+ })
152
+ end
153
+
154
+ end
@@ -0,0 +1,10 @@
1
+ require 's3search'
2
+
3
+ ENV['S3SEARCH_URL'] = 'http://s3search.dev'
4
+
5
+ RSpec.configure do |config|
6
+ config.treat_symbols_as_metadata_keys_with_true_values = true
7
+ config.run_all_when_everything_filtered = true
8
+ config.filter_run :focus
9
+ config.order = 'random'
10
+ end
@@ -0,0 +1,30 @@
1
+ require 'webmock/rspec'
2
+ require 's3search'
3
+ require 'active_support/core_ext/object/try'
4
+
5
+ ENV['S3SEARCH_API_KEY'] = 'api_key'
6
+ ENV['S3SEARCH_API_SECRET'] = 'api_secret'
7
+ ENV['S3SEARCH_URL'] = 'https://@api.s3searchapp.test'
8
+
9
+ RSpec.configure do |config|
10
+ config.treat_symbols_as_metadata_keys_with_true_values = true
11
+ config.run_all_when_everything_filtered = true
12
+ config.filter_run :focus
13
+ config.order = 'random'
14
+ end
15
+
16
+ def stub_api_http method, resource, body=nil, &block
17
+ stub_request(
18
+ method,
19
+ "https://api.s3searchapp.test#{resource}"
20
+ ).with(
21
+ body: body.try(:to_json),
22
+ headers: {
23
+ 'Accept' => 'application/json',
24
+ 'Content-Type' => 'application/json',
25
+ 'Authorization' => 'Basic YXBpX2tleTphcGlfc2VjcmV0'
26
+ }
27
+ ).to_return(
28
+ yield
29
+ )
30
+ end
metadata CHANGED
@@ -1,20 +1,88 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: s3search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
5
- prerelease:
4
+ version: 0.0.2
6
5
  platform: ruby
7
6
  authors:
8
7
  - Chris Aitchison
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-06-05 00:00:00.000000000 Z
11
+ date: 2013-06-17 00:00:00.000000000 Z
13
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0.8'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday_middleware
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 0.9.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.9.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: excon
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0.16'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0.16'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hashie
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: 1.2.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: 1.2.0
14
83
  - !ruby/object:Gem::Dependency
15
84
  name: bundler
16
85
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
86
  requirements:
19
87
  - - ~>
20
88
  - !ruby/object:Gem::Version
@@ -22,7 +90,6 @@ dependencies:
22
90
  type: :development
23
91
  prerelease: false
24
92
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
93
  requirements:
27
94
  - - ~>
28
95
  - !ruby/object:Gem::Version
@@ -30,17 +97,15 @@ dependencies:
30
97
  - !ruby/object:Gem::Dependency
31
98
  name: rake
32
99
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
100
  requirements:
35
- - - ! '>='
101
+ - - '>='
36
102
  - !ruby/object:Gem::Version
37
103
  version: '0'
38
104
  type: :development
39
105
  prerelease: false
40
106
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
107
  requirements:
43
- - - ! '>='
108
+ - - '>='
44
109
  - !ruby/object:Gem::Version
45
110
  version: '0'
46
111
  description: Ruby implementation of the S3Search API
@@ -51,42 +116,63 @@ extensions: []
51
116
  extra_rdoc_files: []
52
117
  files:
53
118
  - .gitignore
119
+ - .rspec
54
120
  - Gemfile
55
121
  - LICENSE.txt
56
122
  - README.md
57
123
  - Rakefile
124
+ - integration/document_spec.rb
125
+ - integration/search_spec.rb
58
126
  - lib/s3search.rb
127
+ - lib/s3search/api/documents.rb
128
+ - lib/s3search/api/searches.rb
129
+ - lib/s3search/client.rb
130
+ - lib/s3search/request_error.rb
131
+ - lib/s3search/result_page.rb
59
132
  - lib/s3search/version.rb
60
133
  - s3search.gemspec
134
+ - spec/acceptance/document_create_many_spec.rb
135
+ - spec/acceptance/document_create_spec.rb
136
+ - spec/acceptance/document_delete_spec.rb
137
+ - spec/acceptance/document_get_all_spec.rb
138
+ - spec/acceptance/document_get_spec.rb
139
+ - spec/acceptance/document_update_spec.rb
140
+ - spec/acceptance/search_simple_spec.rb
141
+ - spec/integration_helper.rb
142
+ - spec/spec_helper.rb
61
143
  homepage: https://github.com/sodalis/s3search-ruby
62
144
  licenses:
63
145
  - MIT
146
+ metadata: {}
64
147
  post_install_message:
65
148
  rdoc_options: []
66
149
  require_paths:
67
150
  - lib
68
151
  required_ruby_version: !ruby/object:Gem::Requirement
69
- none: false
70
152
  requirements:
71
- - - ! '>='
153
+ - - '>='
72
154
  - !ruby/object:Gem::Version
73
155
  version: '0'
74
- segments:
75
- - 0
76
- hash: -1043739705409496661
77
156
  required_rubygems_version: !ruby/object:Gem::Requirement
78
- none: false
79
157
  requirements:
80
- - - ! '>='
158
+ - - '>='
81
159
  - !ruby/object:Gem::Version
82
160
  version: '0'
83
- segments:
84
- - 0
85
- hash: -1043739705409496661
86
161
  requirements: []
87
162
  rubyforge_project:
88
- rubygems_version: 1.8.23
163
+ rubygems_version: 2.0.2
89
164
  signing_key:
90
- specification_version: 3
165
+ specification_version: 4
91
166
  summary: S3Search Ruby API
92
- test_files: []
167
+ test_files:
168
+ - integration/document_spec.rb
169
+ - integration/search_spec.rb
170
+ - spec/acceptance/document_create_many_spec.rb
171
+ - spec/acceptance/document_create_spec.rb
172
+ - spec/acceptance/document_delete_spec.rb
173
+ - spec/acceptance/document_get_all_spec.rb
174
+ - spec/acceptance/document_get_spec.rb
175
+ - spec/acceptance/document_update_spec.rb
176
+ - spec/acceptance/search_simple_spec.rb
177
+ - spec/integration_helper.rb
178
+ - spec/spec_helper.rb