riq 0.8.3
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 +7 -0
- data/.gitignore +41 -0
- data/.yardopts +2 -0
- data/Gemfile +8 -0
- data/lib/relateiq.rb +3 -0
- data/lib/relateiq/account.rb +79 -0
- data/lib/relateiq/batch_manager.rb +187 -0
- data/lib/relateiq/client.rb +152 -0
- data/lib/relateiq/contact.rb +139 -0
- data/lib/relateiq/error.rb +53 -0
- data/lib/relateiq/events.rb +104 -0
- data/lib/relateiq/list.rb +87 -0
- data/lib/relateiq/list_item_manager.rb +13 -0
- data/lib/relateiq/listitem.rb +115 -0
- data/lib/relateiq/riq_obj.rb +142 -0
- data/lib/relateiq/user.rb +36 -0
- data/test/test.rb +4 -0
- data/test/test_account.rb +39 -0
- data/test/test_batch_manager.rb +45 -0
- data/test/test_contact.rb +50 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 34a510c0f7cad37904ababa9279e498c71e0bb9c
|
4
|
+
data.tar.gz: 661d369470ad7fa0be4588036ab57206f41a4e55
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 71590ee05218590ebb4adc49aebeb462958bf843448d025c466f0b8a53b20ce4b0893d8bf85509790321e8deafc996eda3700ad9a5ad5aa1d8025625d956f386
|
7
|
+
data.tar.gz: c3c788129202330ad448cff886a0981b1a594d4808e806f4f444815aefd5929d990aee56936c4b03b143c7f955b03779f49aace5f6645544ce9a6f2e1aec5e48
|
data/.gitignore
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Created by https://www.gitignore.io
|
2
|
+
|
3
|
+
### Ruby ###
|
4
|
+
*.gem
|
5
|
+
*.rbc
|
6
|
+
/.config
|
7
|
+
/coverage/
|
8
|
+
/InstalledFiles
|
9
|
+
/pkg/
|
10
|
+
/spec/reports/
|
11
|
+
/test/tmp/
|
12
|
+
/test/version_tmp/
|
13
|
+
/tmp/
|
14
|
+
|
15
|
+
## Specific to RubyMotion:
|
16
|
+
.dat*
|
17
|
+
.repl_history
|
18
|
+
build/
|
19
|
+
|
20
|
+
## Documentation cache and generated files:
|
21
|
+
/.yardoc/
|
22
|
+
/_yardoc/
|
23
|
+
/doc/
|
24
|
+
/rdoc/
|
25
|
+
|
26
|
+
## Environment normalisation:
|
27
|
+
/.bundle/
|
28
|
+
/vendor/bundle
|
29
|
+
/lib/bundler/man/
|
30
|
+
|
31
|
+
# for a library or gem, you might want to ignore these files since the code is
|
32
|
+
# intended to run in multiple environments; otherwise, check them in:
|
33
|
+
# Gemfile.lock
|
34
|
+
# .ruby-version
|
35
|
+
# .ruby-gemset
|
36
|
+
|
37
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
38
|
+
.rvmrc
|
39
|
+
|
40
|
+
# test file
|
41
|
+
t.rb
|
data/.yardopts
ADDED
data/Gemfile
ADDED
data/lib/relateiq.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require_relative 'riq_obj'
|
2
|
+
|
3
|
+
module RIQ
|
4
|
+
class Account < RIQObject
|
5
|
+
attr_accessor :name
|
6
|
+
attr_accessor :field_values
|
7
|
+
|
8
|
+
# (see RIQObject#node)
|
9
|
+
def node
|
10
|
+
"accounts/#{@id}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# (see #node)
|
14
|
+
def self.node(arg = nil)
|
15
|
+
"accounts"
|
16
|
+
end
|
17
|
+
|
18
|
+
# (see RIQObject#data)
|
19
|
+
def data
|
20
|
+
{
|
21
|
+
id: @id,
|
22
|
+
name: @name,
|
23
|
+
field_values: @field_values
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# (see RIQObject#payload)
|
28
|
+
def payload
|
29
|
+
# TODO: find more elegant way to do this
|
30
|
+
pld = data
|
31
|
+
pld['fieldValues'] = to_raw(@field_values)
|
32
|
+
pld.delete(:field_values)
|
33
|
+
pld.to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
# @overload field_value(key)
|
37
|
+
# @param key [String, Integer]
|
38
|
+
# @return [Array] Value of key
|
39
|
+
# @overload field_value(key, value)
|
40
|
+
# Sets key to value
|
41
|
+
# @param key [String, Integer] Key to set
|
42
|
+
# @param value [#to_s] Sets key to value
|
43
|
+
def field_value(key, value = nil)
|
44
|
+
# TODO: double check that this works with arrays of stuff
|
45
|
+
# or, have a format function that casts ints to string on save
|
46
|
+
if value.nil?
|
47
|
+
@field_values.fetch(key.to_s, nil)
|
48
|
+
else
|
49
|
+
@field_values[key.to_s] = value.to_s
|
50
|
+
{key.to_s => value.to_s}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def init(obj = nil)
|
56
|
+
unless obj.nil?
|
57
|
+
@id = obj['id']
|
58
|
+
@name = obj['name']
|
59
|
+
@field_values = from_raw(obj['fieldValues'])
|
60
|
+
else
|
61
|
+
@id = nil
|
62
|
+
@name = nil
|
63
|
+
@field_values = {}
|
64
|
+
end
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
class << self
|
71
|
+
# Convenince method to create new Accounts
|
72
|
+
# @param id [String, nil] create a blank Account object or
|
73
|
+
# fetch an existing one by id.
|
74
|
+
# @return [RIQ::Account]
|
75
|
+
def account(id = nil)
|
76
|
+
Account.new(id)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module RIQ
|
2
|
+
# Manages caching and fetching for a certain type of child
|
3
|
+
class BatchManager
|
4
|
+
attr_reader :fetch_options
|
5
|
+
|
6
|
+
# @param klass [RIQObject] The child class that's being fetched, such as {Account} or {ListItem}
|
7
|
+
# @param page_length [Integer] The number of items per page
|
8
|
+
# @!macro [new] krass
|
9
|
+
# is a $1
|
10
|
+
def initialize(klass, page_length = 200, fetch_options: {})
|
11
|
+
@klass = klass
|
12
|
+
begin
|
13
|
+
raise unless @klass.ancestors.include? RIQ::RIQObject
|
14
|
+
rescue
|
15
|
+
raise RIQError, 'Must pass a RIQ Class'
|
16
|
+
end
|
17
|
+
|
18
|
+
reset_cache
|
19
|
+
# ruby says that nil is an argument and page_size was getting set to 0
|
20
|
+
@page_length = page_length.nil? ? 200 : page_length
|
21
|
+
@fetch_options = fetch_options
|
22
|
+
@client = RIQ.client
|
23
|
+
end
|
24
|
+
|
25
|
+
# Iterator for each item in the manager. Pass a block to it!
|
26
|
+
def each(&blok)
|
27
|
+
reset_cache
|
28
|
+
loop do
|
29
|
+
x = next_item
|
30
|
+
if x
|
31
|
+
blok.call(x)
|
32
|
+
else
|
33
|
+
return
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [@macro klass]
|
39
|
+
def first
|
40
|
+
reset_cache
|
41
|
+
fetch_page(0, 1).first
|
42
|
+
end
|
43
|
+
|
44
|
+
def fetch_options=(opts = {})
|
45
|
+
reset_cache
|
46
|
+
options = {}
|
47
|
+
opts.each do |k, v|
|
48
|
+
# some args are comma-separated arrays, some are just args
|
49
|
+
if v.is_a? Array
|
50
|
+
options[k] = v.join(',')
|
51
|
+
else
|
52
|
+
options[k] = v
|
53
|
+
end
|
54
|
+
end
|
55
|
+
@fetch_options = options
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def next_item
|
61
|
+
if @cache_index == @cache.size
|
62
|
+
return nil if ![0, @page_length].include? @cache.size
|
63
|
+
# puts "\n=== fetching page! (#{@page_index}) (#{@page_length})"
|
64
|
+
@cache = fetch_page(@page_index, @page_length)
|
65
|
+
@page_index += @cache.size
|
66
|
+
@cache_index = 0
|
67
|
+
end
|
68
|
+
|
69
|
+
if @cache_index < @cache.size
|
70
|
+
obj = @cache[@cache_index]
|
71
|
+
@cache_index += 1
|
72
|
+
obj
|
73
|
+
else
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# def fetch_batch(param, values, max_size)
|
79
|
+
# objects = []
|
80
|
+
# chunks = []
|
81
|
+
# (0...values.size).step(max_size) do |i|
|
82
|
+
# chunks << values[i...i+max_size]
|
83
|
+
# end
|
84
|
+
# chunks.each_with_index do |obj, i|
|
85
|
+
# begin
|
86
|
+
# set_fetch_options({param: chunk.join(',')})
|
87
|
+
# objects << fetch_page
|
88
|
+
# rescue HTTPError => e
|
89
|
+
# # max url length is 8192 (2^13). If longer, will give a 414 error.
|
90
|
+
# if [413, 414].include? e.code
|
91
|
+
# objects << fetch_batch(param, chunk, (max_size/2.0).ceil)
|
92
|
+
# else
|
93
|
+
# # raise a certain error?
|
94
|
+
# raise e
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
# objects
|
99
|
+
# end
|
100
|
+
|
101
|
+
def fetch_page(index = 0, limit = nil)
|
102
|
+
limit = @page_length if limit.nil?
|
103
|
+
|
104
|
+
@fetch_options[:_start] = index
|
105
|
+
@fetch_options[:_limit] = limit
|
106
|
+
|
107
|
+
# all class#node functions can take an arg, so far, only list items need it.
|
108
|
+
# having list_id usually be nil (that is, undefined) is fine. probably.
|
109
|
+
data = @client.get(@klass.node(@list_id), options: @fetch_options)
|
110
|
+
|
111
|
+
objects = []
|
112
|
+
data.fetch('objects', []).each do |obj|
|
113
|
+
objects << @klass.new(obj)
|
114
|
+
end
|
115
|
+
objects
|
116
|
+
end
|
117
|
+
|
118
|
+
def reset_cache
|
119
|
+
# shouldn't reset options, that's silly
|
120
|
+
# @fetch_options = {}
|
121
|
+
@cache = []
|
122
|
+
@cache_index = 0
|
123
|
+
@page_index = 0
|
124
|
+
end
|
125
|
+
|
126
|
+
# these used to be 2 methods, now they're one
|
127
|
+
def batch(objs, mode)
|
128
|
+
return_objs = {}
|
129
|
+
while objs.size > 0
|
130
|
+
chunk_size = objs.size > @page_length ? @page_length : objs.size
|
131
|
+
chunk = objs[0...chunk_size]
|
132
|
+
objs = chunk_size < objs.size ? objs[chunk_size..-1] : []
|
133
|
+
|
134
|
+
batch_objs = batch_helper(mode, chunk)
|
135
|
+
return_objs['success_objects'] = batch_objs.fetch('success_objects', [])
|
136
|
+
return_objs['error_objects'] = batch_objs.fetch('error_objects', [])
|
137
|
+
end
|
138
|
+
return_objs
|
139
|
+
end
|
140
|
+
|
141
|
+
def batch_helper(mode, objs)
|
142
|
+
payloads = []
|
143
|
+
objs.each {|o| payloads << o.payload}
|
144
|
+
|
145
|
+
if mode == :create
|
146
|
+
batch_results = @client.post('/createBatch', {objects: payloads})
|
147
|
+
elsif mode == :update
|
148
|
+
batch_results = @client.put('/updateBatch', {objects: payloads})
|
149
|
+
else
|
150
|
+
raise RIQError, 'Invalid Batch Mode'
|
151
|
+
end
|
152
|
+
|
153
|
+
batch_results['success_objects'] = []
|
154
|
+
batch_results['successObjects'].each do |o|
|
155
|
+
batch_results['success_objects'] << self.class(o)
|
156
|
+
end
|
157
|
+
|
158
|
+
batch_results['error_objects'] = []
|
159
|
+
batch_results['errorObjects'].each do |o|
|
160
|
+
batch_results['error_objects'] << self.class(o)
|
161
|
+
end
|
162
|
+
|
163
|
+
batch_results
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class << self
|
168
|
+
# @!macro [new] conv
|
169
|
+
# Returns an iterator for all $0
|
170
|
+
# @return [BatchManager]
|
171
|
+
|
172
|
+
# @macro conv
|
173
|
+
def lists(page_size = nil)
|
174
|
+
BatchManager.new(RIQ::List, page_size)
|
175
|
+
end
|
176
|
+
|
177
|
+
# @macro conv
|
178
|
+
def contacts(page_size = nil)
|
179
|
+
BatchManager.new(RIQ::Contact, page_size)
|
180
|
+
end
|
181
|
+
|
182
|
+
# @macro conv
|
183
|
+
def accounts(page_size = nil)
|
184
|
+
BatchManager.new(RIQ::Account, page_size)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'pry'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require_relative 'error'
|
6
|
+
|
7
|
+
module RIQ
|
8
|
+
# HTTP client responsible for actually handling the HTTP requests.
|
9
|
+
# @note Utility class to perform http requests. This shouldn't be
|
10
|
+
# instantiated directy but used by children of RIQObject instead.
|
11
|
+
class Client
|
12
|
+
attr_accessor :cache
|
13
|
+
|
14
|
+
def initialize(key, secret)
|
15
|
+
raise 'Missing credentials' if key.nil? || secret.nil?
|
16
|
+
|
17
|
+
@root_url = 'https://api.relateiq.com/v2'
|
18
|
+
|
19
|
+
@auth = {username: key, password: secret}
|
20
|
+
@headers = {
|
21
|
+
'Content-type' => 'application/json',
|
22
|
+
'Accept' => 'application/json'
|
23
|
+
}
|
24
|
+
@cache = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
# for caching, used with #next ?
|
28
|
+
# def store(endpoint, objects)
|
29
|
+
# @cache[endpoint] = objects unless objects.nil?
|
30
|
+
# end
|
31
|
+
|
32
|
+
# Makes a GET request, used for fetching existing objects.
|
33
|
+
# @return (see #request)
|
34
|
+
def get(endpoint, options: nil)
|
35
|
+
request(endpoint, :get, nil, options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Makes a POST request, used for creating new objects.
|
39
|
+
# @return (see #request)
|
40
|
+
def post(endpoint, body, options: nil)
|
41
|
+
request(endpoint, :post, body, options)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Makes a PUT request, used for updating existing objects.
|
45
|
+
# @return (see #request)
|
46
|
+
def put(endpoint, body, options: nil)
|
47
|
+
request(endpoint, :put, body, options)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Makes a DELETE request, used for deleting existing objects.
|
51
|
+
# @return (see #request)
|
52
|
+
def delete(endpoint, options: nil)
|
53
|
+
request(endpoint, :delete, nil, options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch(endpoint = nil, options: {})
|
57
|
+
# i'm not sure why this would return an empty object instead of raising a notfound
|
58
|
+
# or really, why it exists at all
|
59
|
+
# puts "fetching #{endpoint} with options #{options}"
|
60
|
+
# begin
|
61
|
+
get(endpoint, options: options)
|
62
|
+
# rescue NotFoundError
|
63
|
+
# {}
|
64
|
+
# end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# actually does the requesting
|
70
|
+
# @return (see #process_response)
|
71
|
+
def request(endpoint, method = :get, body = nil, options = nil)
|
72
|
+
# there may be a better way to do this, but this is close
|
73
|
+
url = "#{@root_url}/#{endpoint}"
|
74
|
+
# pp "#{method} request to #{url} with body: #{body} and options: #{options}"
|
75
|
+
if [:get, :delete].include? method
|
76
|
+
resp = HTTParty.method(method).call(
|
77
|
+
url,
|
78
|
+
headers: @headers,
|
79
|
+
basic_auth: @auth,
|
80
|
+
query: options
|
81
|
+
)
|
82
|
+
elsif [:post, :put].include? method
|
83
|
+
resp = HTTParty.method(method).call(
|
84
|
+
url,
|
85
|
+
headers: @headers,
|
86
|
+
basic_auth: @auth,
|
87
|
+
query: options,
|
88
|
+
body: body
|
89
|
+
)
|
90
|
+
else
|
91
|
+
# this shouldn't ever get hit?
|
92
|
+
raise RIQError, 'Invalid method'
|
93
|
+
end
|
94
|
+
|
95
|
+
# HTTParty response object
|
96
|
+
process_response(resp)
|
97
|
+
# resp
|
98
|
+
end
|
99
|
+
|
100
|
+
# Checks for errors and returns the parsed object
|
101
|
+
# @return [RIQObject] The result of the request
|
102
|
+
def process_response(resp)
|
103
|
+
# pp "processing #{resp}, code: #{resp.code}"
|
104
|
+
# binding.pry
|
105
|
+
if resp.code == 503
|
106
|
+
raise NotImplementedError, 'This function is not currently supported by RelateIQ'
|
107
|
+
elsif resp.code == 404
|
108
|
+
raise NotFoundError, 'Object Not Found'
|
109
|
+
elsif resp.code >= 400
|
110
|
+
# Record Response Data
|
111
|
+
raise HTTPError, resp
|
112
|
+
end
|
113
|
+
# 404 Object not found
|
114
|
+
# if List, have them check if it is shared
|
115
|
+
# 500
|
116
|
+
# 422
|
117
|
+
# 400 Bad Request - pass on internal message
|
118
|
+
# 502 Bad Gateway - Reattempt
|
119
|
+
|
120
|
+
# if resp.include? 'objects'
|
121
|
+
# resp['objects']
|
122
|
+
# else
|
123
|
+
resp.parsed_response
|
124
|
+
# end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Configuration Methods
|
128
|
+
# def fetchConfig
|
129
|
+
# return get('configs')
|
130
|
+
# end
|
131
|
+
end
|
132
|
+
|
133
|
+
class << self
|
134
|
+
# A singleton client for use by methods in Object.
|
135
|
+
# Always use RIQ.client to retrieve the client object.
|
136
|
+
# @param key [String] RelateIQ API key
|
137
|
+
# @param secret [String] RelateIQ API secret
|
138
|
+
# @return [RIQ] The module, in case you want it.
|
139
|
+
def init(key = nil, secret = nil)
|
140
|
+
key ||= ENV['RIQ_TEST_API_KEY']
|
141
|
+
secret ||= ENV['RIQ_TEST_API_SECRET']
|
142
|
+
|
143
|
+
@@client = Client.new(key, secret)
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
def client
|
148
|
+
raise RIQError, 'Client not initialized' unless @@client
|
149
|
+
@@client
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|