mode-sdk 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+ #
3
+ module Mode
4
+ module Sdk
5
+ # Represents the status of a Mode table import job
6
+ #
7
+ class TableImport
8
+ # Construct a new TableImport instance
9
+ #
10
+ # @param resource_path [String] Mode API resource path
11
+ #
12
+ # @return [Mode::Sdk::TableImport] the instance
13
+ #
14
+ def initialize(resource_path)
15
+ @resource_path = resource_path
16
+ end
17
+
18
+ # Poll the API representation of the table import until the job is no
19
+ # longer running
20
+ #
21
+ # @param interval [optional, Integer, Float] polling interval in seconds
22
+ #
23
+ # @yield [Hash] the API representation of the table import job
24
+ #
25
+ def poll(interval = 0.5)
26
+ loop do
27
+ repr = fetch_repr
28
+
29
+ yield repr
30
+
31
+ if %w(new enqueued running).include?(repr.fetch('state'))
32
+ sleep interval
33
+ else
34
+ break
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :resource_path
42
+
43
+ # Get the API representation of the resource from the Mode API
44
+ #
45
+ # @return [Hash] the API representation
46
+ #
47
+ def fetch_repr
48
+ Mode::Sdk::Client.get("#{resource_path}?embed[table]=1").body
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+ #
3
+ module Mode
4
+ module Sdk
5
+ # Represents a Mode Upload containing raw CSV with no header
6
+ #
7
+ class Upload
8
+ attr_reader :content, :options
9
+
10
+ # Construct a new Upload instance
11
+ #
12
+ # @param content [String, #read] the content
13
+ # @param options [optional, Hash] hash of options
14
+ #
15
+ # @option options [Integer] :content_size the size of the provided
16
+ # content
17
+ #
18
+ # @return [Mode::Sdk::Upload] the instance
19
+ #
20
+ def initialize(content, options = {})
21
+ @content = content
22
+ @options = options
23
+ end
24
+
25
+ # Create the Upload through the Mode API
26
+ #
27
+ # @return [Mode::Sdk::Client::Response] the response
28
+ #
29
+ def create
30
+ @create ||= Mode::Sdk::Client.post(
31
+ collection_path,
32
+ content,
33
+ content_type: 'application/csv',
34
+ content_length: content_length,
35
+ expect: [201])
36
+ end
37
+
38
+ # The unique token assigned to the upload
39
+ #
40
+ # @return [String] the token
41
+ #
42
+ def token
43
+ create.body.fetch('token')
44
+ end
45
+
46
+ private
47
+
48
+ def content_length
49
+ options.fetch(:content_length, nil) || content.size
50
+ end
51
+
52
+ def collection_path
53
+ '/api/uploads'
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ #
3
+ module Mode
4
+ # Official [Mode Analytics](https://modeanalytics.com) Ruby SDK
5
+ #
6
+ module Sdk
7
+ VERSION = '0.0.1'
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ #
3
+ module Mode
4
+ module Sdk
5
+ # Provides simple utility methods for use with the Mode public data
6
+ # warehouse
7
+ #
8
+ module WarehouseUtil
9
+ # Normalize and truncate a string for use as a Mode warehouse table or
10
+ # column name
11
+ #
12
+ # @param name [String] the original name
13
+ #
14
+ # @return [String] the normalized name
15
+ #
16
+ def normalize_name(name)
17
+ normalize_string(name)[0..63]
18
+ end
19
+
20
+ protected
21
+
22
+ def normalize_string(name)
23
+ name
24
+ .downcase # no uppercase characters
25
+ .strip # no leading or trailing whitespace
26
+ .gsub(/\s+/, ' ') # no multiple consecutive spaces
27
+ .gsub(/-/, '_') # no hyphens
28
+ .gsub(/[^\w\s_]/, '') # no unexpected characters
29
+ .gsub(/\s/, '_') # no spaces
30
+ .gsub(/_+/, '_') # no multiple consecutive underscores
31
+ .gsub(/(^_)|(_$)/, '') # no leading or trailing underscores
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ #
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'mode/sdk/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'mode-sdk'
10
+ spec.version = Mode::Sdk::VERSION
11
+ spec.authors = ['Heather Rivers']
12
+ spec.email = ['heather@modeanalytics.com']
13
+ spec.description = 'Mode Ruby SDK'
14
+ spec.summary = 'Ruby SDK for interacting with the Mode Analytics API'
15
+ spec.homepage = 'https://github.com/mode/mode-ruby-sdk'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
19
+ spec.executables = spec.files.grep(/^bin/) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(/^(test|spec|features)/)
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.3'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+ spec.add_development_dependency 'simplecov'
27
+ end
@@ -0,0 +1,105 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'spec_helper'
4
+
5
+ describe Mode::Sdk::Client::Request do
6
+ describe '#response' do
7
+ let :request do
8
+ Mode::Sdk::Client::Request.new(Net::HTTP::Head.new('some/path'))
9
+ end
10
+
11
+ it 'performs http request' do
12
+ expect(Mode::Sdk::Client::Request.http).to receive(:request)
13
+
14
+ expect(request.response).to(
15
+ be_an_instance_of(Mode::Sdk::Client::Response))
16
+ end
17
+ end
18
+
19
+ describe '#build_http_request' do
20
+ let :head do
21
+ Net::HTTP::Head.new('some/path')
22
+ end
23
+
24
+ it 'sets default headers' do
25
+ request = Mode::Sdk::Client::Request.new(head)
26
+
27
+ http_request = request.send(:build_http_request)
28
+
29
+ expect(http_request['accept']).to match(/application\/json\Z/)
30
+ expect(http_request['user-agent']).to match(/mode\-sdk/)
31
+ expect(http_request['authorization']).to match(/Basic/)
32
+ end
33
+
34
+ describe 'content' do
35
+ it 'does not assign nil body' do
36
+ request = Mode::Sdk::Client::Request.new(head)
37
+
38
+ expect(head).to receive(:body=).never
39
+ expect(head).to receive(:body_stream=).never
40
+
41
+ request.send(:build_http_request)
42
+ end
43
+
44
+ it 'handles string body' do
45
+ request = Mode::Sdk::Client::Request.new(head, 'content')
46
+
47
+ expect(head).to receive(:body=).once
48
+ expect(head).to receive(:body_stream=).never
49
+
50
+ request.send(:build_http_request)
51
+ end
52
+
53
+ it 'handles io body' do
54
+ request = Mode::Sdk::Client::Request.new(head, StringIO.new)
55
+
56
+ expect(head).to receive(:body=).never
57
+ expect(head).to receive(:body_stream=).once
58
+
59
+ request.send(:build_http_request)
60
+ end
61
+ end
62
+
63
+ describe 'content_type' do
64
+ it 'has default value' do
65
+ request = Mode::Sdk::Client::Request.new(head)
66
+
67
+ http_request = request.send(:build_http_request)
68
+
69
+ expect(http_request.content_type).to eq('application/json')
70
+ end
71
+
72
+ it 'allows override' do
73
+ request = Mode::Sdk::Client::Request.new(
74
+ head,
75
+ nil,
76
+ content_type: 'application/lol')
77
+
78
+ http_request = request.send(:build_http_request)
79
+
80
+ expect(http_request.content_type).to eq('application/lol')
81
+ end
82
+ end
83
+
84
+ describe 'content_length' do
85
+ it 'is not set by default' do
86
+ request = Mode::Sdk::Client::Request.new(head)
87
+
88
+ http_request = request.send(:build_http_request)
89
+
90
+ expect(http_request.content_length).to be_nil
91
+ end
92
+
93
+ it 'is set if provided' do
94
+ request = Mode::Sdk::Client::Request.new(
95
+ head,
96
+ nil,
97
+ content_length: 42)
98
+
99
+ http_request = request.send(:build_http_request)
100
+
101
+ expect(http_request.content_length).to eq(42)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'spec_helper'
4
+
5
+ describe Mode::Sdk::Client::Response do
6
+ describe '#code' do
7
+ it 'returns http response code as an integer' do
8
+ http_response = double(:http_response, code: '200')
9
+
10
+ response = Mode::Sdk::Client::Response.new(http_response)
11
+
12
+ expect(response.code).to eq(200)
13
+ end
14
+ end
15
+
16
+ describe '#body' do
17
+ it 'parses response body' do
18
+ http_response = double(:http_response, body: '{"foo":"bar"}')
19
+
20
+ response = Mode::Sdk::Client::Response.new(http_response)
21
+
22
+ expect(response.body).to eq('foo' => 'bar')
23
+ end
24
+ end
25
+
26
+ describe '#validate!' do
27
+ it 'raises exception if response is 401' do
28
+ http_response = double(:http_response, code: '401')
29
+
30
+ response = Mode::Sdk::Client::Response.new(http_response, [200])
31
+
32
+ expect {
33
+ response.validate!
34
+ }.to raise_error(Mode::Sdk::Client::AuthenticationError)
35
+ end
36
+
37
+ it 'raises exception if response is 403' do
38
+ http_response = double(:http_response, code: '403')
39
+
40
+ response = Mode::Sdk::Client::Response.new(http_response, [200])
41
+
42
+ expect {
43
+ response.validate!
44
+ }.to raise_error(Mode::Sdk::Client::AuthorizationError)
45
+ end
46
+
47
+ it 'raises exception if response is unexpected' do
48
+ http_response = double(:http_response, code: '200')
49
+
50
+ response = Mode::Sdk::Client::Response.new(http_response, [201])
51
+
52
+ expect {
53
+ response.validate!
54
+ }.to raise_error(Mode::Sdk::Client::ResponseError)
55
+ end
56
+
57
+ it 'returns true if response is expected' do
58
+ http_response = double(:http_response, code: '201')
59
+
60
+ response = Mode::Sdk::Client::Response.new(http_response, [201])
61
+
62
+ expect(response.validate!).to eq(true)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'spec_helper'
4
+
5
+ describe Mode::Sdk::Client do
6
+ describe '.head' do
7
+ it 'performs a head request' do
8
+ expect(Mode::Sdk::Client).to receive(:request) do |request|
9
+ expect(request).to be_an_instance_of(Net::HTTP::Head)
10
+ end
11
+
12
+ Mode::Sdk::Client.head('some/path')
13
+ end
14
+ end
15
+
16
+ describe '.get' do
17
+ it 'performs a get request' do
18
+ expect(Mode::Sdk::Client).to receive(:request) do |request|
19
+ expect(request).to be_an_instance_of(Net::HTTP::Get)
20
+ end
21
+
22
+ Mode::Sdk::Client.get('some/path')
23
+ end
24
+ end
25
+
26
+ describe '.post' do
27
+ it 'performs a post request' do
28
+ expect(Mode::Sdk::Client).to receive(:request) do |request|
29
+ expect(request).to be_an_instance_of(Net::HTTP::Post)
30
+ end
31
+
32
+ Mode::Sdk::Client.post('some/path', nil)
33
+ end
34
+ end
35
+
36
+ describe '.put' do
37
+ it 'performs a put request' do
38
+ expect(Mode::Sdk::Client).to receive(:request) do |request|
39
+ expect(request).to be_an_instance_of(Net::HTTP::Put)
40
+ end
41
+
42
+ Mode::Sdk::Client.put('some/path', nil)
43
+ end
44
+ end
45
+
46
+ describe '.request' do
47
+ it 'returns response' do
48
+ mock_response = Mode::Sdk::Client::Response.new(nil)
49
+
50
+ expect_any_instance_of(Mode::Sdk::Client::Request).to(
51
+ receive(:response).and_return(mock_response))
52
+
53
+ response = Mode::Sdk::Client.send(:request, nil, nil)
54
+
55
+ expect(response).to be_an_instance_of(Mode::Sdk::Client::Response)
56
+ end
57
+ end
58
+
59
+ describe '.account' do
60
+ it 'returns account representation' do
61
+ mock_response = double(:response, body: { 'username' => 'someone' })
62
+
63
+ expect_any_instance_of(Mode::Sdk::Client::Request).to(
64
+ receive(:response).and_return(mock_response))
65
+
66
+ response = Mode::Sdk::Client.account
67
+
68
+ expect(response).to eq('username' => 'someone')
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'spec_helper'
4
+
5
+ describe Mode::Sdk::ColumnSet do
6
+ describe '#columns' do
7
+ it 'builds an array of columns from hashes' do
8
+ set = Mode::Sdk::ColumnSet.new([{}, {}])
9
+
10
+ expect(set.columns.map(&:class)).to eq([
11
+ Mode::Sdk::Column, Mode::Sdk::Column])
12
+ end
13
+ end
14
+
15
+ describe '#validate!' do
16
+ it 'raises error if no columns are provided' do
17
+ set = Mode::Sdk::ColumnSet.new(nil)
18
+
19
+ expect {
20
+ set.validate!
21
+ }.to raise_error(Mode::Sdk::ColumnSet::InvalidError) do |exception|
22
+ expect(exception.message).to match(/no columns provided/i)
23
+ end
24
+ end
25
+
26
+ it 'raises error if column is invalid' do
27
+ expect_any_instance_of(Mode::Sdk::Column).to receive(
28
+ :validate!).and_raise(Mode::Sdk::Column::InvalidError)
29
+
30
+ set = Mode::Sdk::ColumnSet.new([{}])
31
+
32
+ expect {
33
+ set.validate!
34
+ }.to raise_error(Mode::Sdk::Column::InvalidError)
35
+ end
36
+
37
+ it 'returns true if columns are valid' do
38
+ expect_any_instance_of(Mode::Sdk::Column).to receive(:validate!)
39
+
40
+ set = Mode::Sdk::ColumnSet.new([{}])
41
+
42
+ expect(set.validate!).to eq(true)
43
+ end
44
+ end
45
+
46
+ describe '#to_array' do
47
+ it 'converts columns to attributes' do
48
+ set = Mode::Sdk::ColumnSet.new([{ foo: 'bar' }])
49
+
50
+ expect(set.to_array).to eq([{ 'foo' => 'bar' }])
51
+ end
52
+ end
53
+ end