mode-sdk 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.
@@ -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