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.
@@ -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
@@ -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
@@ -0,0 +1,2 @@
1
+ lib/**/*.rb
2
+ --title RelateIQ
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ group :development do
5
+ gem 'pry'
6
+ end
7
+
8
+ gem 'httparty'
@@ -0,0 +1,3 @@
1
+ # Base file from which everything else is included
2
+
3
+ Dir[__dir__ + '/relateiq/*.rb'].each {|file| require file }
@@ -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