attune 0.0.1
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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.yardopts +2 -0
- data/Gemfile +8 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +97 -0
- data/Rakefile +1 -0
- data/attune.gemspec +28 -0
- data/lib/attune/client.rb +158 -0
- data/lib/attune/configurable.rb +31 -0
- data/lib/attune/default.rb +33 -0
- data/lib/attune/json_logger.rb +38 -0
- data/lib/attune/param_flattener.rb +17 -0
- data/lib/attune/version.rb +3 -0
- data/lib/attune.rb +12 -0
- data/spec/attune/client_spec.rb +87 -0
- data/spec/attune/json_logger_spec.rb +29 -0
- data/spec/attune_spec.rb +12 -0
- data/spec/remote_spec.rb +43 -0
- data/spec/spec_helper.rb +19 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YmI0ZDQ2MjAxYWIyYzNiNDE0Y2E5YWI1YWFmMTUxMDcyYTNjZTc3Mw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YmRkYWIzMTRhOGNlOTVlYzBhZDFjY2FkMGFkNDY2MGVlNWIzMGY2ZA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
M2UyY2E5ZTE5MzAyNGY2NGIzNjQyNDEwYWU0ZTRiN2FkYWEwNDA5MjY3Yjg2
|
10
|
+
ZTQ3N2Q3ZGU3MzE0OWUzZWNiNmM2ZjUyMTQxMjU4NGJhMTA2MGUzYTI4ODk2
|
11
|
+
ODhhYmQ1MWQ0ZjM0MDA3ZDU4OWEyNDJjMDExYjY4ODk2MWE1MDk=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NWI4YmYxMDMxNWZkMWQxYTZjZTIyODQ1MjdiYjkxMWIwZjYzM2E1MDM3MDk1
|
14
|
+
M2Y5ZTU5YWQ4ODcwNTk4ZDQ0NWE0ZjA1NzMzOTI1MTQyMGQ0NzQ5MDE3MzFi
|
15
|
+
YjJmYzVlNTQxNmQ5ZjUyMWQyNzZkZjU5NDMxNjQxZTIzMjA5YTc=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 John Hawthorn
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Attune
|
2
|
+
|
3
|
+
A client for the [Attune ranking API](http://attune.co/). Build using the excellent [faraday](https://github.com/lostisland/faraday) library.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'attune'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
### Example rails usage
|
18
|
+
|
19
|
+
Requests are performed through a client object
|
20
|
+
|
21
|
+
``` ruby
|
22
|
+
client = Attune::Client.new
|
23
|
+
```
|
24
|
+
|
25
|
+
Visitors to the application should be tagged with an anonymous user id
|
26
|
+
|
27
|
+
``` ruby
|
28
|
+
class ApplicationController
|
29
|
+
before_filter do
|
30
|
+
session[:attune_id] ||= attune_client.create_anonymous(user_agent: request.env["HTTP_USER_AGENT"])
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def attune_client
|
35
|
+
@attune_client ||= Attune.client
|
36
|
+
end
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
The user id can be bound to a customer id at login
|
41
|
+
|
42
|
+
``` ruby
|
43
|
+
class SessionsController
|
44
|
+
# ...
|
45
|
+
|
46
|
+
def create
|
47
|
+
# ...
|
48
|
+
attune_client.bind(session[:attune_id], current_user.id)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
The client can then perform rankings
|
54
|
+
|
55
|
+
``` ruby
|
56
|
+
class ProductsController
|
57
|
+
def index
|
58
|
+
@products = Product.all
|
59
|
+
|
60
|
+
ranking = attune_client.get_ranking(
|
61
|
+
id: session[:attune_id],
|
62
|
+
view: request.fullpath,
|
63
|
+
collection: 'products',
|
64
|
+
entities: @products.map(&:id)
|
65
|
+
)
|
66
|
+
@products.sort_by do |product|
|
67
|
+
ranking.index(product.id.to_s)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
|
74
|
+
### Configuration
|
75
|
+
|
76
|
+
Attune can be configured globally
|
77
|
+
|
78
|
+
``` ruby
|
79
|
+
Attune.configure do |c|
|
80
|
+
c.endpoint = "http://example.com/"
|
81
|
+
c.timeout = 5
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
Settings can also be overridden on a client object
|
86
|
+
|
87
|
+
``` ruby
|
88
|
+
client = Attune::Client.new(timeout: 2)
|
89
|
+
```
|
90
|
+
|
91
|
+
## Contributing
|
92
|
+
|
93
|
+
1. Fork it ( http://github.com/freerunningtech/attune/fork )
|
94
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
95
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
96
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
97
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/attune.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'attune/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "attune"
|
8
|
+
spec.version = Attune::VERSION
|
9
|
+
spec.authors = ["John Hawthorn"]
|
10
|
+
spec.email = ["john@freerunningtechnologies.com"]
|
11
|
+
spec.summary = %q{Client for the Attune product ranking API.}
|
12
|
+
spec.description = %q{Client for the Attune product ranking API.}
|
13
|
+
spec.homepage = "https://github.com/freerunningtech/attune-ruby"
|
14
|
+
spec.license = "MIT"
|
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"]
|
20
|
+
|
21
|
+
spec.add_dependency "faraday"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.2"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "yard"
|
27
|
+
spec.add_development_dependency "redcarpet"
|
28
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Attune
|
4
|
+
|
5
|
+
# Client for the attune
|
6
|
+
class Client
|
7
|
+
include Attune::Configurable
|
8
|
+
|
9
|
+
# Initializes a new Client
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# client = Attune::Client.new(
|
13
|
+
# endpoint: "http://example.com:8080/",
|
14
|
+
# timeout: 10
|
15
|
+
# )
|
16
|
+
#
|
17
|
+
# @param [Hash] options Options for connection (see Attune::Configurable)
|
18
|
+
# @returns A new client object
|
19
|
+
def initialize(options={})
|
20
|
+
Attune::Configurable::KEYS.each do |key|
|
21
|
+
send("#{key}=", options[key] || Attune::Default.send(key))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Create an anonymous tracked user
|
26
|
+
#
|
27
|
+
# @example Generate a new id (preferred)
|
28
|
+
# anonymous_id = client.create_anonymous(
|
29
|
+
# user_agent: 'Mozilla/5.0'
|
30
|
+
# )
|
31
|
+
# @example Create using an existing id
|
32
|
+
# client.create_anonymous(
|
33
|
+
# id: '0cddbc0-6114-11e3-949a-0800200c9a66',
|
34
|
+
# user_agent: 'Mozilla/5.0'
|
35
|
+
# )
|
36
|
+
# @param [Hash] options
|
37
|
+
# @option options [String] :id optional. An id will be generated if this is not provided
|
38
|
+
# @option options [String] :user_agent The user agent for the application used by the anonymous users
|
39
|
+
# @return id [String]
|
40
|
+
# @raise [ArgumentError] if user_agent is not provided
|
41
|
+
def create_anonymous(options)
|
42
|
+
raise ArgumentError, "user_agent required" unless options[:user_agent]
|
43
|
+
if id = options[:id]
|
44
|
+
response = put("anonymous/#{id}", {user_agent: options[:user_agent]})
|
45
|
+
id
|
46
|
+
else
|
47
|
+
response = post("anonymous", {user_agent: options[:user_agent]})
|
48
|
+
response[:location][/\Aurn:id:([a-z0-9\-]+)\Z/, 1]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns all entities from the specified collection in order of the user's preference
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# rankings = client.get_rankings(
|
56
|
+
# id: '0cddbc0-6114-11e3-949a-0800200c9a66',
|
57
|
+
# view: 'b/mens-pants',
|
58
|
+
# collection: 'products',
|
59
|
+
# entities: %w[1001, 1002, 1003, 1004]
|
60
|
+
# )
|
61
|
+
# @param [Hash] options
|
62
|
+
# @option options [String] :id The anonymous user id for whom to grab rankings
|
63
|
+
# @option options [String] :view The page or app URN on which the entities will be displayed
|
64
|
+
# @option options [String] :collection name of the collection of entities
|
65
|
+
# @option options [Array<String>] :entities entities to be ranked. These should be numeric strings or integers.
|
66
|
+
# @option options [String] :ip ip address of remote user. Used for geolocation (optional)
|
67
|
+
# @option options [String] :customer id of customer (optional)
|
68
|
+
# @return ranking [Array<String>] The entities in their ranked order
|
69
|
+
# @raise [ArgumentError] if required parameters are missing
|
70
|
+
def get_rankings(options)
|
71
|
+
qs = encoded_ranking_params(options)
|
72
|
+
response = get("rankings/#{qs}", customer: options.fetch(:customer, 'none'))
|
73
|
+
JSON.parse(response.body)['ranking']
|
74
|
+
end
|
75
|
+
|
76
|
+
# Get multiple rankings in one call
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# rankings = client.get_rankings([
|
80
|
+
# {
|
81
|
+
# id: '0cddbc0-6114-11e3-949a-0800200c9a66',
|
82
|
+
# view: 'b/mens-pants',
|
83
|
+
# collection: 'products',
|
84
|
+
# entities: %w[1001, 1002, 1003, 1004]
|
85
|
+
# },
|
86
|
+
# {
|
87
|
+
# id: '0cddbc0-6114-11e3-949a-0800200c9a66',
|
88
|
+
# view: 'b/mens-pants',
|
89
|
+
# collection: 'products',
|
90
|
+
# entities: %w[2001, 2002, 2003, 2004]
|
91
|
+
# }
|
92
|
+
# ])
|
93
|
+
# @param [Array<Hash>] multi_options An array of options (see #get_rankings)
|
94
|
+
# @return [Array<Array<String>>] rankings
|
95
|
+
def multi_get_rankings(multi_options)
|
96
|
+
requests = multi_options.map do |options|
|
97
|
+
encoded_ranking_params(options)
|
98
|
+
end
|
99
|
+
response = get("rankings", ids: requests)
|
100
|
+
results = JSON.parse(response.body)['results']
|
101
|
+
results.values.map do |result|
|
102
|
+
result['ranking']
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Binds an anonymous user to a customer id
|
107
|
+
#
|
108
|
+
# @param [String] id The anonymous visitor to bind
|
109
|
+
# @param [String] customer_id The customer id to bind
|
110
|
+
# @example
|
111
|
+
# rankings = client.bind(
|
112
|
+
# '25892e17-80f6-415f-9c65-7395632f022',
|
113
|
+
# 'cd171f7c-560d-4a62-8d65-16b87419a58'
|
114
|
+
# )
|
115
|
+
def bind(id, customer_id)
|
116
|
+
put("bindings/anonymous=#{id}&customer=#{customer_id}")
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
def encoded_ranking_params(options)
|
122
|
+
params = {
|
123
|
+
anonymous: options.fetch(:id),
|
124
|
+
view: options.fetch(:view),
|
125
|
+
entity_collection: options.fetch(:collection),
|
126
|
+
entities: options.fetch(:entities).join(','),
|
127
|
+
ip: options.fetch(:ip, 'none')
|
128
|
+
}
|
129
|
+
Faraday::Utils::ParamsHash[params].to_query
|
130
|
+
end
|
131
|
+
|
132
|
+
def get(path, params={})
|
133
|
+
adapter.get(path, params)
|
134
|
+
rescue Faraday::ClientError => e
|
135
|
+
handle_exception(e)
|
136
|
+
end
|
137
|
+
|
138
|
+
def put(path, params={})
|
139
|
+
adapter.put(path, ::JSON.dump(params))
|
140
|
+
rescue Faraday::ClientError => e
|
141
|
+
handle_exception(e)
|
142
|
+
end
|
143
|
+
|
144
|
+
def post(path, params={})
|
145
|
+
adapter.post(path, ::JSON.dump(params))
|
146
|
+
rescue Faraday::ClientError => e
|
147
|
+
handle_exception(e)
|
148
|
+
end
|
149
|
+
|
150
|
+
def handle_exception e
|
151
|
+
raise e
|
152
|
+
end
|
153
|
+
|
154
|
+
def adapter
|
155
|
+
Faraday.new(url: endpoint, builder: middleware, request: {timeout: timeout})
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Attune
|
2
|
+
module Configurable
|
3
|
+
KEYS = [
|
4
|
+
:endpoint,
|
5
|
+
:middleware,
|
6
|
+
:disabled,
|
7
|
+
:timeout
|
8
|
+
]
|
9
|
+
|
10
|
+
# The HTTP endpoint to connect to
|
11
|
+
attr_accessor :endpoint
|
12
|
+
|
13
|
+
# Middleware used by faraday
|
14
|
+
attr_accessor :middleware
|
15
|
+
|
16
|
+
# FIXME
|
17
|
+
attr_accessor :disabled
|
18
|
+
|
19
|
+
# Time (in seconds) to wait for requests to finish
|
20
|
+
attr_accessor :timeout
|
21
|
+
|
22
|
+
# @example configure
|
23
|
+
# Attune.configure do |c|
|
24
|
+
# c.endpoint = "http://example.com:8080/"
|
25
|
+
# c.timeout = 5
|
26
|
+
# end
|
27
|
+
def configure
|
28
|
+
yield self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'attune/param_flattener'
|
2
|
+
require "attune/json_logger"
|
3
|
+
|
4
|
+
module Attune
|
5
|
+
# Default options
|
6
|
+
module Default
|
7
|
+
extend Configurable
|
8
|
+
|
9
|
+
ENDPOINT = "http://localhost/".freeze
|
10
|
+
|
11
|
+
MIDDLEWARE = Faraday::Builder.new do |builder|
|
12
|
+
# Needed for encoding of BATCH GET requests
|
13
|
+
builder.use Attune::ParamFlattener
|
14
|
+
|
15
|
+
# Allow one retry per request
|
16
|
+
builder.request :retry, 1
|
17
|
+
|
18
|
+
# Log all requests
|
19
|
+
builder.use Attune::JsonLogger
|
20
|
+
|
21
|
+
# Raise exceptions for HTTP 4xx/5xx
|
22
|
+
builder.response :raise_error
|
23
|
+
builder.adapter Faraday.default_adapter
|
24
|
+
end
|
25
|
+
|
26
|
+
configure do |c|
|
27
|
+
c.endpoint = ENDPOINT
|
28
|
+
c.middleware = MIDDLEWARE
|
29
|
+
c.disabled = false
|
30
|
+
c.timeout = 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Attune
|
6
|
+
class JsonLogger < Faraday::Middleware
|
7
|
+
def initialize app, logger=nil
|
8
|
+
super(app)
|
9
|
+
@logger = logger || Logger.new(STDERR)
|
10
|
+
end
|
11
|
+
def call(env)
|
12
|
+
time = (Time.now.to_f * 1000).to_i
|
13
|
+
response = nil
|
14
|
+
elapsed_time = Benchmark.realtime do
|
15
|
+
response = @app.call(env)
|
16
|
+
end
|
17
|
+
log(
|
18
|
+
ref: nil,
|
19
|
+
v: 1,
|
20
|
+
protocol: env[:url].scheme,
|
21
|
+
host: env[:url].host,
|
22
|
+
path: env[:url].path,
|
23
|
+
t: time,
|
24
|
+
r_id: SecureRandom.uuid,
|
25
|
+
status: response.status,
|
26
|
+
ua: env[:request_headers]['User-Agent'],
|
27
|
+
method: env[:method],
|
28
|
+
perf: {
|
29
|
+
total: elapsed_time * 1000
|
30
|
+
}
|
31
|
+
)
|
32
|
+
response
|
33
|
+
end
|
34
|
+
def log(data)
|
35
|
+
@logger.info JSON.dump(data)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Attune
|
2
|
+
# Faraday 0.8 can't make resuests like /?ids=123&ids=456 and forces the form
|
3
|
+
# /?ids[]=123&ids[]=456 (this is fixed in faraday 0.9)
|
4
|
+
#
|
5
|
+
# Fortunately faraday's middleware is quite powerful. This just strips the
|
6
|
+
# array syntax from the request.
|
7
|
+
class ParamFlattener < Faraday::Middleware
|
8
|
+
def call(env)
|
9
|
+
url = env[:url]
|
10
|
+
|
11
|
+
# replaces ?foo[]=123 with ?foo=123
|
12
|
+
url.query = url.query.gsub('%5B%5D', '') if url.query
|
13
|
+
|
14
|
+
@app.call(env)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/attune.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Attune::Client do
|
4
|
+
let(:client){ described_class.new(options) }
|
5
|
+
subject { client }
|
6
|
+
|
7
|
+
context "with defaults" do
|
8
|
+
let(:options){ {} }
|
9
|
+
specify { expect(subject.endpoint).to eq(Attune::Default::ENDPOINT) }
|
10
|
+
specify { expect(subject.middleware).to eq(Attune::Default::MIDDLEWARE) }
|
11
|
+
end
|
12
|
+
context "with custom endpoint" do
|
13
|
+
let(:endpoint){ 'http://example.com/' }
|
14
|
+
let(:options){ {endpoint: endpoint} }
|
15
|
+
specify { expect(subject.endpoint).to eq(endpoint) }
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:options){ {endpoint: 'http://example.com:8080/', middleware: middleware} }
|
19
|
+
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
20
|
+
let(:middleware) do
|
21
|
+
Faraday::Builder.new do |builder|
|
22
|
+
builder.use Attune::ParamFlattener
|
23
|
+
builder.adapter :test, stubs
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can create_anonymous generating an id" do
|
28
|
+
stubs.post("anonymous", %[{"user_agent":"Mozilla/5.0"}]){ [200, {location: 'urn:id:abcd123'}, nil] }
|
29
|
+
id = client.create_anonymous(user_agent: 'Mozilla/5.0')
|
30
|
+
stubs.verify_stubbed_calls
|
31
|
+
|
32
|
+
expect(id).to eq('abcd123')
|
33
|
+
end
|
34
|
+
|
35
|
+
it "can bind" do
|
36
|
+
stubs.put("bindings/anonymous=abcd123&customer=foobar"){ [200, {}, nil] }
|
37
|
+
client.bind('abcd123', 'foobar')
|
38
|
+
stubs.verify_stubbed_calls
|
39
|
+
end
|
40
|
+
|
41
|
+
it "can create_anonymous using existing id" do
|
42
|
+
stubs.put("anonymous/abcd123", %[{"user_agent":"Mozilla/5.0"}]){ [200, {}, nil] }
|
43
|
+
id = client.create_anonymous(id: 'abcd123', user_agent: 'Mozilla/5.0')
|
44
|
+
stubs.verify_stubbed_calls
|
45
|
+
|
46
|
+
expect(id).to eq('abcd123')
|
47
|
+
end
|
48
|
+
|
49
|
+
it "can get_rankings" do
|
50
|
+
stubs.get("rankings/anonymous=abcd123&view=b%2Fmens-pants&entity_collection=products&entities=1001%2C%2C1002%2C%2C1003%2C%2C1004&ip=none"){ [200, {}, %[{"ranking":["1004","1003","1002","1001"]}]] }
|
51
|
+
rankings = client.get_rankings(
|
52
|
+
id: 'abcd123',
|
53
|
+
view: 'b/mens-pants',
|
54
|
+
collection: 'products',
|
55
|
+
entities: %w[1001, 1002, 1003, 1004]
|
56
|
+
)
|
57
|
+
stubs.verify_stubbed_calls
|
58
|
+
|
59
|
+
expect(rankings).to eq(%W[1004 1003 1002 1001])
|
60
|
+
end
|
61
|
+
|
62
|
+
it "can multi_get_rankings" do
|
63
|
+
stubs.get("/rankings?ids=anonymous%3D0cddbc0-6114-11e3-949a-0800200c9a66%26view%3Db%252Fmens-pants%26entity_collection%3Dproducts%26entities%3D1001%252C%252C1002%252C%252C1003%252C%252C1004%26ip%3Dnone&ids=anonymous%3D0cddbc0-6114-11e3-949a-0800200c9a66%26view%3Db%252Fmens-pants%26entity_collection%3Dproducts%26entities%3D2001%252C%252C2002%252C%252C2003%252C%252C2004%26ip%3Dnone") do
|
64
|
+
[200, {}, %[{"results":{"fake0":{"ranking":["1004","1003","1002","1001"]},"fake1":{"ranking":["2004","2003","2002","2001"]}}}]]
|
65
|
+
end
|
66
|
+
rankings = client.multi_get_rankings([
|
67
|
+
{
|
68
|
+
id: '0cddbc0-6114-11e3-949a-0800200c9a66',
|
69
|
+
view: 'b/mens-pants',
|
70
|
+
collection: 'products',
|
71
|
+
entities: %w[1001, 1002, 1003, 1004]
|
72
|
+
},
|
73
|
+
{
|
74
|
+
id: '0cddbc0-6114-11e3-949a-0800200c9a66',
|
75
|
+
view: 'b/mens-pants',
|
76
|
+
collection: 'products',
|
77
|
+
entities: %w[2001, 2002, 2003, 2004]
|
78
|
+
}
|
79
|
+
])
|
80
|
+
stubs.verify_stubbed_calls
|
81
|
+
|
82
|
+
expect(rankings).to eq [
|
83
|
+
%W[1004 1003 1002 1001],
|
84
|
+
%W[2004 2003 2002 2001]
|
85
|
+
]
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Attune::JsonLogger do
|
5
|
+
let(:logger){ double(:logger) }
|
6
|
+
let(:connection) do
|
7
|
+
Faraday.new(url: 'http://example.com/') do |builder|
|
8
|
+
builder.use described_class, logger
|
9
|
+
builder.adapter :test do |stubs|
|
10
|
+
stubs.get("/test"){ [200, {}, "foobar"] }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "logs as expected" do
|
16
|
+
logged = ""
|
17
|
+
logger.stub(:info){|s| logged << s }
|
18
|
+
|
19
|
+
Benchmark.stub(:realtime).and_yield.and_return(0.12)
|
20
|
+
SecureRandom.stub(:uuid).and_return("eaa45af2-efc3-45ef-90da-9bcb56758e77")
|
21
|
+
Time.stub(:now).and_return(12345)
|
22
|
+
|
23
|
+
response = connection.get("/test")
|
24
|
+
response.status.should == 200
|
25
|
+
response.body.should == "foobar"
|
26
|
+
|
27
|
+
expect(logged).to eq %[{"ref":null,"v":1,"protocol":"http","host":"example.com","path":"/test","t":12345000,"r_id":"eaa45af2-efc3-45ef-90da-9bcb56758e77","status":200,"ua":"Faraday v0.8.8","method":"get","perf":{"total":120.0}}]
|
28
|
+
end
|
29
|
+
end
|
data/spec/attune_spec.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Attune do
|
3
|
+
describe 'client' do
|
4
|
+
subject { Attune.client }
|
5
|
+
it{ should be_a Attune::Client }
|
6
|
+
end
|
7
|
+
describe 'defaults' do
|
8
|
+
subject { Attune::Default }
|
9
|
+
specify { expect(subject.endpoint).to eq 'http://localhost/' }
|
10
|
+
specify { expect(subject.disabled).to eq false }
|
11
|
+
end
|
12
|
+
end
|
data/spec/remote_spec.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "remote requests" do
|
4
|
+
let(:endpoint){ ENV['REMOTE_ENDPOINT'] }
|
5
|
+
before { pending "REMOTE_ENDPOINT required for remote spec" unless endpoint }
|
6
|
+
let!(:client){ Attune::Client.new(endpoint: endpoint) }
|
7
|
+
|
8
|
+
it "can create an anonymous user" do
|
9
|
+
id = client.create_anonymous(user_agent: 'Mozilla/5.0')
|
10
|
+
id.should =~ /[a-z0-9\-]+/
|
11
|
+
end
|
12
|
+
|
13
|
+
it "can create an anonymous user with an id" do
|
14
|
+
id = client.create_anonymous(id: '123456', user_agent: 'Mozilla/5.0')
|
15
|
+
id.should == '123456'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can bind an anonymous user" do
|
19
|
+
id = client.create_anonymous(id: '123456', user_agent: 'Mozilla/5.0')
|
20
|
+
client.bind(id, '654321')
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "get_rankings" do
|
24
|
+
let(:entities){ [202875,202876,202874,202900,202902,202898,202905,200182,200181,185940,188447,185932,190589,1238689589] }
|
25
|
+
it "can get rankings" do
|
26
|
+
id = client.create_anonymous(id: '123456', user_agent: 'Mozilla/5.0')
|
27
|
+
client.bind(id, '654321')
|
28
|
+
result = client.get_rankings(id: '123456', view: 'b/mens-pants', collection: 'products', entities: entities)
|
29
|
+
result.should be_an Array
|
30
|
+
result.sort.should == entities.map(&:to_s).sort
|
31
|
+
end
|
32
|
+
|
33
|
+
it "can batch get rankings" do
|
34
|
+
id = client.create_anonymous(id: '123456', user_agent: 'Mozilla/5.0')
|
35
|
+
client.bind(id, '654321')
|
36
|
+
results = client.multi_get_rankings([id: '123456', view: 'b/mens-pants', collection: 'products', entities: entities])
|
37
|
+
results.should be_an Array
|
38
|
+
|
39
|
+
result, = *results
|
40
|
+
result.sort.should == entities.map(&:to_s).sort
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'attune'
|
2
|
+
|
3
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
4
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
5
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
6
|
+
# loaded once.
|
7
|
+
#
|
8
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
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
|
+
|
14
|
+
# Run specs in random order to surface order dependencies. If you find an
|
15
|
+
# order dependency and want to debug it, you can fix the order by providing
|
16
|
+
# the seed, which is printed after each run.
|
17
|
+
# --seed 1234
|
18
|
+
config.order = 'random'
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: attune
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Hawthorn
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: redcarpet
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Client for the Attune product ranking API.
|
98
|
+
email:
|
99
|
+
- john@freerunningtechnologies.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- .gitignore
|
105
|
+
- .rspec
|
106
|
+
- .yardopts
|
107
|
+
- Gemfile
|
108
|
+
- Guardfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- attune.gemspec
|
113
|
+
- lib/attune.rb
|
114
|
+
- lib/attune/client.rb
|
115
|
+
- lib/attune/configurable.rb
|
116
|
+
- lib/attune/default.rb
|
117
|
+
- lib/attune/json_logger.rb
|
118
|
+
- lib/attune/param_flattener.rb
|
119
|
+
- lib/attune/version.rb
|
120
|
+
- spec/attune/client_spec.rb
|
121
|
+
- spec/attune/json_logger_spec.rb
|
122
|
+
- spec/attune_spec.rb
|
123
|
+
- spec/remote_spec.rb
|
124
|
+
- spec/spec_helper.rb
|
125
|
+
homepage: https://github.com/freerunningtech/attune-ruby
|
126
|
+
licenses:
|
127
|
+
- MIT
|
128
|
+
metadata: {}
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ! '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
requirements: []
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 2.1.9
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: Client for the Attune product ranking API.
|
149
|
+
test_files:
|
150
|
+
- spec/attune/client_spec.rb
|
151
|
+
- spec/attune/json_logger_spec.rb
|
152
|
+
- spec/attune_spec.rb
|
153
|
+
- spec/remote_spec.rb
|
154
|
+
- spec/spec_helper.rb
|
155
|
+
has_rdoc:
|