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 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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --markup-provider=redcarpet
2
+ --markup=markdown
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in attune.gemspec
4
+ gemspec
5
+ gem 'guard-rspec'
6
+ gem 'pry'
7
+ gem 'pry-rescue'
8
+ gem 'pry-stack_explorer'
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
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
@@ -0,0 +1,3 @@
1
+ module Attune
2
+ VERSION = "0.0.1"
3
+ end
data/lib/attune.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'faraday'
2
+
3
+ require "attune/version"
4
+ require "attune/configurable"
5
+ require "attune/default"
6
+ require "attune/client"
7
+
8
+ module Attune
9
+ def self.client
10
+ Client.new
11
+ end
12
+ end
@@ -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
@@ -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
@@ -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
@@ -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: