checkr-official 1.0.0
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 +5 -0
- data/.travis.yml +16 -0
- data/Gemfile +8 -0
- data/History.txt +4 -0
- data/README.md +58 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/bin/checkr-console +7 -0
- data/checkr-official.gemspec +28 -0
- data/gemfiles/default-with-activesupport.gemfile +10 -0
- data/gemfiles/json.gemfile +12 -0
- data/gemfiles/yajl.gemfile +12 -0
- data/lib/checkr.rb +241 -0
- data/lib/checkr/api_class.rb +395 -0
- data/lib/checkr/api_list.rb +78 -0
- data/lib/checkr/api_resource.rb +18 -0
- data/lib/checkr/api_singleton.rb +5 -0
- data/lib/checkr/candidate.rb +35 -0
- data/lib/checkr/county_criminal_search.rb +19 -0
- data/lib/checkr/document.rb +13 -0
- data/lib/checkr/document_list.rb +25 -0
- data/lib/checkr/errors/api_connection_error.rb +4 -0
- data/lib/checkr/errors/api_error.rb +10 -0
- data/lib/checkr/errors/authentication_error.rb +4 -0
- data/lib/checkr/errors/checkr_error.rb +20 -0
- data/lib/checkr/errors/invalid_request_error.rb +10 -0
- data/lib/checkr/geo.rb +19 -0
- data/lib/checkr/motor_vehicle_report.rb +31 -0
- data/lib/checkr/national_criminal_search.rb +17 -0
- data/lib/checkr/report.rb +43 -0
- data/lib/checkr/report_list.rb +27 -0
- data/lib/checkr/sex_offender_search.rb +18 -0
- data/lib/checkr/ssn_trace.rb +18 -0
- data/lib/checkr/subscription.rb +27 -0
- data/lib/checkr/terrorist_watchlist_search.rb +17 -0
- data/lib/checkr/util.rb +91 -0
- data/lib/checkr/version.rb +3 -0
- data/mclovin.jpg +0 -0
- data/tasks/api_test.rb +192 -0
- data/test/checkr/api_class_test.rb +426 -0
- data/test/checkr/api_list_test.rb +27 -0
- data/test/checkr/api_resource_test.rb +28 -0
- data/test/checkr/api_singleton_test.rb +12 -0
- data/test/checkr/authentication_test.rb +50 -0
- data/test/checkr/candidate_test.rb +164 -0
- data/test/checkr/county_criminal_search_test.rb +82 -0
- data/test/checkr/document_test.rb +90 -0
- data/test/checkr/geo_test.rb +73 -0
- data/test/checkr/motor_vehicle_report_test.rb +130 -0
- data/test/checkr/national_criminal_search_test.rb +74 -0
- data/test/checkr/report_test.rb +124 -0
- data/test/checkr/sex_offender_search_test.rb +75 -0
- data/test/checkr/ssn_trace_test.rb +78 -0
- data/test/checkr/status_codes_test.rb +63 -0
- data/test/checkr/subscription_test.rb +96 -0
- data/test/checkr/terrorist_watchlist_search_test.rb +74 -0
- data/test/checkr/util_test.rb +50 -0
- data/test/mock_resource.rb +88 -0
- data/test/test_data.rb +413 -0
- data/test/test_helper.rb +43 -0
- metadata +230 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 204fa32aac332586ff0659b1c1ab5713b1cc79bf
|
4
|
+
data.tar.gz: e74b3546eb997df8a6e2e05698652f517bc24060
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f3e1c06a138a2506b738f4d3b8195450a869c819eb6ac2c8546855f9c31747195d8f969ac1d2d7fef7b9499294c7306535d6901cc73595ccd68cd75cb04f99d6
|
7
|
+
data.tar.gz: 3cde6c929301904aadd2f5f53a07f6236eb0826213e926d9691ce8ccb571afa7cc3ab5e204edfbf03b868269aa71265c2e88b71e73709d8df5954d3243e7c9be
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/History.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# Checkr Ruby bindings 
|
2
|
+
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
|
6
|
+
You don't need this source code unless you want to modify the gem. If
|
7
|
+
you just want to use the Checkr Ruby bindings, you should run:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
gem install checkr
|
11
|
+
```
|
12
|
+
|
13
|
+
If you want to build the gem from source:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
gem build checkr.gemspec
|
17
|
+
```
|
18
|
+
|
19
|
+
|
20
|
+
## Requirements
|
21
|
+
|
22
|
+
* Ruby 1.8.7 or above. (Ruby 1.8.6 may work if you load
|
23
|
+
ActiveSupport.) For Ruby versions before 1.9.2, you'll need to add this to your Gemfile:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('1.9.2')
|
27
|
+
gem 'rest-client', '~> 1.6.8'
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
* rest-client, json
|
32
|
+
|
33
|
+
|
34
|
+
## Bundler
|
35
|
+
|
36
|
+
If you are installing via bundler, you should be sure to use the https
|
37
|
+
rubygems source in your Gemfile, as any gems fetched over http could potentially be compromised.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
source 'https://rubygems.org'
|
41
|
+
|
42
|
+
gem 'rails'
|
43
|
+
gem 'checkr'
|
44
|
+
```
|
45
|
+
|
46
|
+
|
47
|
+
## Development
|
48
|
+
|
49
|
+
Test cases can be run with: `bundle exec rake test`
|
50
|
+
|
51
|
+
|
52
|
+
## Test Rake Task
|
53
|
+
|
54
|
+
To hit the API with some test calls run:
|
55
|
+
|
56
|
+
```bash
|
57
|
+
bundle exec rake test_api["sk_test_api_key"]
|
58
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require './tasks/api_test.rb'
|
3
|
+
|
4
|
+
task :default => [:test]
|
5
|
+
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.pattern = './test/**/*_test.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
task :test_api, [:api_key] do |t, args|
|
11
|
+
api_key = args[:api_key]
|
12
|
+
api_test = APITest.new(api_key)
|
13
|
+
api_test.run
|
14
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/checkr-console
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
require 'checkr/version'
|
4
|
+
|
5
|
+
spec = Gem::Specification.new do |s|
|
6
|
+
s.name = 'checkr-official'
|
7
|
+
s.summary = 'Ruby bindings for Checkr API'
|
8
|
+
s.description = 'Checkr - Automated background screenings and driving records. See https://checkr.com/ for details.'
|
9
|
+
s.homepage = 'https://checkr.com/'
|
10
|
+
s.authors = ['Jon Calhoun']
|
11
|
+
s.email = ['joncalhoun@gmail.com']
|
12
|
+
s.version = Checkr::VERSION
|
13
|
+
|
14
|
+
s.add_dependency('rest-client', '~> 1.4')
|
15
|
+
s.add_dependency('mime-types', '>= 1.25', '< 3.0')
|
16
|
+
s.add_dependency('json', '~> 1.8.1')
|
17
|
+
|
18
|
+
s.add_development_dependency('mocha', '~> 0.13.2')
|
19
|
+
s.add_development_dependency('shoulda', '~> 3.4.0')
|
20
|
+
s.add_development_dependency('test-unit')
|
21
|
+
s.add_development_dependency('rake')
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ['lib']
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
gemspec :path => File.join(File.dirname(__FILE__), "..")
|
3
|
+
|
4
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('1.9.3')
|
5
|
+
gem 'i18n', '< 0.7'
|
6
|
+
gem 'rest-client', '~> 1.6.8'
|
7
|
+
gem 'activesupport', '~> 3.2'
|
8
|
+
else
|
9
|
+
gem 'activesupport'
|
10
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
gemspec :path => File.join(File.dirname(__FILE__), "..")
|
3
|
+
|
4
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('1.9.3')
|
5
|
+
gem 'i18n', '< 0.7'
|
6
|
+
gem 'rest-client', '~> 1.6.8'
|
7
|
+
gem 'activesupport', '~> 3.2'
|
8
|
+
else
|
9
|
+
gem 'activesupport'
|
10
|
+
end
|
11
|
+
|
12
|
+
gem 'json'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
gemspec :path => File.join(File.dirname(__FILE__), "..")
|
3
|
+
|
4
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('1.9.3')
|
5
|
+
gem 'i18n', '< 0.7'
|
6
|
+
gem 'rest-client', '~> 1.6.8'
|
7
|
+
gem 'activesupport', '~> 3.2'
|
8
|
+
else
|
9
|
+
gem 'activesupport'
|
10
|
+
end
|
11
|
+
|
12
|
+
gem 'yajl-ruby'
|
data/lib/checkr.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
# Checkr Ruby bindings
|
2
|
+
# API spec at https://checkr.com/docs/api
|
3
|
+
require 'cgi'
|
4
|
+
require 'set'
|
5
|
+
require 'openssl'
|
6
|
+
require 'rest_client'
|
7
|
+
require 'json'
|
8
|
+
require 'base64'
|
9
|
+
|
10
|
+
# Version
|
11
|
+
require 'checkr/version'
|
12
|
+
|
13
|
+
# Resources
|
14
|
+
require 'checkr/api_class'
|
15
|
+
require 'checkr/api_resource'
|
16
|
+
require 'checkr/api_singleton'
|
17
|
+
require 'checkr/api_list'
|
18
|
+
require 'checkr/util'
|
19
|
+
|
20
|
+
# Requires for classes
|
21
|
+
require 'checkr/candidate'
|
22
|
+
require 'checkr/county_criminal_search'
|
23
|
+
require 'checkr/document'
|
24
|
+
require 'checkr/document_list'
|
25
|
+
require 'checkr/geo'
|
26
|
+
require 'checkr/motor_vehicle_report'
|
27
|
+
require 'checkr/national_criminal_search'
|
28
|
+
require 'checkr/report'
|
29
|
+
require 'checkr/report_list'
|
30
|
+
require 'checkr/sex_offender_search'
|
31
|
+
require 'checkr/ssn_trace'
|
32
|
+
require 'checkr/subscription'
|
33
|
+
require 'checkr/terrorist_watchlist_search'
|
34
|
+
|
35
|
+
# Errors
|
36
|
+
require 'checkr/errors/checkr_error'
|
37
|
+
require 'checkr/errors/api_error'
|
38
|
+
require 'checkr/errors/api_connection_error'
|
39
|
+
require 'checkr/errors/invalid_request_error'
|
40
|
+
require 'checkr/errors/authentication_error'
|
41
|
+
|
42
|
+
module Checkr
|
43
|
+
@api_base = "https://api.checkr.com"
|
44
|
+
@api_key = nil
|
45
|
+
|
46
|
+
class << self
|
47
|
+
attr_accessor :api_key, :api_base, :api_test
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.api_url(path='')
|
51
|
+
"#{@api_base}#{path}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.request(method, path, params={}, headers={})
|
55
|
+
verify_api_key(api_key)
|
56
|
+
|
57
|
+
url = api_url(path)
|
58
|
+
|
59
|
+
request_opts = { :verify_ssl => false }
|
60
|
+
|
61
|
+
if [:get, :head, :delete].include?(method.to_s.downcase.to_sym)
|
62
|
+
unless params.empty?
|
63
|
+
url += URI.parse(url).query ? '&' : '?' + Util.query_string(params)
|
64
|
+
end
|
65
|
+
params = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
headers = default_headers.update(basic_auth_headers(api_key)).update(headers)
|
69
|
+
request_opts.update(:headers => headers,
|
70
|
+
:method => method,
|
71
|
+
:open_timeout => 30,
|
72
|
+
:payload => params,
|
73
|
+
:url => url,
|
74
|
+
:timeout => 60)
|
75
|
+
|
76
|
+
begin
|
77
|
+
response = execute_request(request_opts)
|
78
|
+
rescue Exception => e
|
79
|
+
handle_request_error(e, url)
|
80
|
+
end
|
81
|
+
|
82
|
+
parse(response)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Mostly here for stubbing out during tests.
|
86
|
+
def self.execute_request(opts)
|
87
|
+
RestClient::Request.execute(opts)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.parse(response)
|
91
|
+
begin
|
92
|
+
json = JSON.parse(response.body)
|
93
|
+
rescue JSON::ParserError
|
94
|
+
raise APIError.generic(response.code, response.body)
|
95
|
+
end
|
96
|
+
|
97
|
+
# TODO(jonclahoun): Remove this when Checkr's API returns the correct status code.
|
98
|
+
json = Util.symbolize_keys(json)
|
99
|
+
if json.has_key?(:error)
|
100
|
+
raise CheckrError.new(json[:error][:message], response.code, response.body, json)
|
101
|
+
end
|
102
|
+
|
103
|
+
json
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.default_headers
|
107
|
+
headers = {
|
108
|
+
:user_agent => "Checkr/::API_VERSION:: RubyBindings/#{Checkr::VERSION}",
|
109
|
+
:content_type => 'application/x-www-form-urlencoded'
|
110
|
+
}
|
111
|
+
|
112
|
+
begin
|
113
|
+
headers.update(:x_checkr_client_user_agent => JSON.generate(user_agent))
|
114
|
+
rescue => e
|
115
|
+
headers.update(:x_checkr_client_raw_user_agent => user_agent.inspect,
|
116
|
+
:error => "#{e} (#{e.class})")
|
117
|
+
end
|
118
|
+
headers
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.basic_auth_headers(api_key=@api_key)
|
122
|
+
api_key ||= @api_key
|
123
|
+
unless api_key
|
124
|
+
raise ArgumentError.new('No API key provided. Set your API key using "Checkr.api_key = <API-KEY>".')
|
125
|
+
end
|
126
|
+
|
127
|
+
base_64_key = Base64.encode64("#{api_key}:")
|
128
|
+
{
|
129
|
+
"Authorization" => "Basic #{base_64_key}",
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.user_agent
|
134
|
+
@uname ||= get_uname
|
135
|
+
lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
|
136
|
+
|
137
|
+
{
|
138
|
+
:bindings_version => Checkr::VERSION,
|
139
|
+
:lang => 'ruby',
|
140
|
+
:lang_version => lang_version,
|
141
|
+
:platform => RUBY_PLATFORM,
|
142
|
+
:publisher => 'checkr',
|
143
|
+
:uname => @uname
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.get_uname
|
148
|
+
`uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
|
149
|
+
rescue Errno::ENOMEM => ex # couldn't create subprocess
|
150
|
+
"uname lookup failed"
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.verify_api_key(api_key)
|
154
|
+
unless api_key
|
155
|
+
raise AuthenticationError.new('No API key provided. ' +
|
156
|
+
'Set your API key using "Checkr.api_key = <API-KEY>". ' +
|
157
|
+
'You can generate API keys from the Checkr web interface. ' +
|
158
|
+
'See https://checkr.com/docs#authentication for details, ' +
|
159
|
+
'or email hello@checkr.com if you have any questions.')
|
160
|
+
end
|
161
|
+
|
162
|
+
if api_key =~ /\s/
|
163
|
+
raise AuthenticationError.new('Your API key is invalid, as it contains ' +
|
164
|
+
'whitespace. (HINT: You can double-check your API key from the ' +
|
165
|
+
'Checkr web interface. See https://checkr.com/docs#authentication for details, or ' +
|
166
|
+
'email hello@checkr.com if you have any questions.)')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.handle_request_error(error, url)
|
171
|
+
# First we see if this is an error with a response, and if it is
|
172
|
+
# we check to see if there is an http code and body to work with.
|
173
|
+
if error.is_a?(RestClient::ExceptionWithResponse)
|
174
|
+
if error.http_code && error.http_body
|
175
|
+
handle_api_error(error.http_code, error.http_body)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# If we got here then the error hasn't been handled yet.
|
180
|
+
# Handle it as a connection error.
|
181
|
+
handle_connection_error(error, url)
|
182
|
+
|
183
|
+
# Finally if we get here we don't know what type of error it is, so just raise it.
|
184
|
+
raise error
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.handle_connection_error(error, url)
|
188
|
+
message = "An error occurred while connecting to Checkr at #{url}."
|
189
|
+
|
190
|
+
case error
|
191
|
+
when RestClient::RequestTimeout
|
192
|
+
message += connection_message
|
193
|
+
|
194
|
+
when RestClient::ServerBrokeConnection
|
195
|
+
message = "The connection to the server at (#{url}) broke before the " \
|
196
|
+
"request completed. #{connection_message}"
|
197
|
+
|
198
|
+
when RestClient::SSLCertificateNotVerified
|
199
|
+
message = "Could not verify Checkr's SSL certificate. " \
|
200
|
+
"Please make sure that your network is not intercepting certificates. " \
|
201
|
+
"If this problem persists, let us know at hello@checkr.com."
|
202
|
+
|
203
|
+
when SocketError
|
204
|
+
message = "Unexpected error when trying to connect to Checkr. " \
|
205
|
+
"You may be seeing this message because your DNS is not working. " \
|
206
|
+
"To check, try running 'host api.checkr.com' from the command line."
|
207
|
+
|
208
|
+
else
|
209
|
+
message = "Unexpected error communicating with Checkr. " \
|
210
|
+
"If this problem persists, let us know at hello@checkr.com. #{connection_message}"
|
211
|
+
end
|
212
|
+
|
213
|
+
raise APIConnectionError.new(message + "\n\n(Network error: #{error.message}")
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.connection_message
|
217
|
+
"Please check your internet connection and try again. " \
|
218
|
+
"If this problem persists, you should check Checkr's service status at " \
|
219
|
+
"https://twitter.com/checkr, or let us know at hello@checkr.com."
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.handle_api_error(rcode, rbody)
|
223
|
+
begin
|
224
|
+
error_obj = JSON.parse(rbody)
|
225
|
+
rescue JSON::ParserError
|
226
|
+
raise APIError.generic(rcode, rbody)
|
227
|
+
end
|
228
|
+
error_obj = Util.symbolize_keys(error_obj)
|
229
|
+
raise APIError.generic(rcode, rbody) unless error = error_obj[:error]
|
230
|
+
|
231
|
+
case rcode
|
232
|
+
when 400, 404
|
233
|
+
raise InvalidRequestError.new(error, "", rcode, rbody, error_obj)
|
234
|
+
when 401
|
235
|
+
raise AuthenticationError.new(error, rcode, rbody, error_obj)
|
236
|
+
else
|
237
|
+
raise APIError.new(error, rcode, rbody, error_obj)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
@@ -0,0 +1,395 @@
|
|
1
|
+
module Checkr
|
2
|
+
class APIClass
|
3
|
+
attr_accessor :json
|
4
|
+
|
5
|
+
def self.path
|
6
|
+
raise NotImplementedError.new("APIClass is an abstract class. Please refer to its subclasses: #{subclasses}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def path
|
10
|
+
raise NotImplementedError.new("APIClass is an abstract class. Please refer to its subclasses: #{APIClass.subclasses}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.api_class_method(name, method, path=nil, opts={})
|
14
|
+
singleton = class << self; self end
|
15
|
+
singleton.send(:define_method, name, api_lambda(name, method, path, opts))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.api_instance_method(name, method, path=nil, opts={})
|
19
|
+
self.send(:define_method, name, api_lambda(name, method, path, opts))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.attribute(name, klass=nil, opts={})
|
23
|
+
@attribute_names ||= Set.new
|
24
|
+
@attribute_names << name.to_sym
|
25
|
+
|
26
|
+
self.send(:define_method, "#{name}", attribute_get_lambda(name, opts))
|
27
|
+
self.send(:define_method, "#{name}=", attribute_set_lambda(name, klass, opts))
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.attribute_writer_alias(name, attr_name)
|
31
|
+
@attribute_aliases ||= Set.new
|
32
|
+
@attribute_aliases << name.to_sym
|
33
|
+
|
34
|
+
# self.send(:alias_method, name, attr_name)
|
35
|
+
self.send(:alias_method, "#{name}=", "#{attr_name}=")
|
36
|
+
end
|
37
|
+
|
38
|
+
def attributes
|
39
|
+
attributes = {}
|
40
|
+
self.class.attribute_names.each do |attr|
|
41
|
+
attributes[attr.to_sym] = self.send(attr)
|
42
|
+
end
|
43
|
+
attributes
|
44
|
+
end
|
45
|
+
|
46
|
+
def non_nil_attributes
|
47
|
+
attributes.select{|k, v| !v.nil? }
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.attribute_names
|
51
|
+
@attribute_names ||= Set.new
|
52
|
+
unless self == APIClass
|
53
|
+
@attribute_names + self.superclass.attribute_names
|
54
|
+
else
|
55
|
+
@attribute_names
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.attribute_aliases
|
60
|
+
@attribute_aliases ||= Set.new
|
61
|
+
unless self == APIClass
|
62
|
+
@attribute_aliases + self.superclass.attribute_aliases
|
63
|
+
else
|
64
|
+
@attribute_aliases
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.attribute_writer_names
|
69
|
+
self.attribute_names + self.attribute_aliases
|
70
|
+
end
|
71
|
+
|
72
|
+
def mark_attribute_changed(attr_name)
|
73
|
+
@changed_attribute_names ||= Set.new
|
74
|
+
@changed_attribute_names << attr_name.to_sym
|
75
|
+
end
|
76
|
+
|
77
|
+
def changed_attribute_names
|
78
|
+
@changed_attribute_names ||= Set.new
|
79
|
+
attributes.each do |key, val|
|
80
|
+
next if @changed_attribute_names.include?(key)
|
81
|
+
if val.is_a?(Array) || val.is_a?(Hash)
|
82
|
+
@changed_attribute_names << key if json[key] != val
|
83
|
+
end
|
84
|
+
end
|
85
|
+
@changed_attribute_names
|
86
|
+
end
|
87
|
+
|
88
|
+
def changed_attributes
|
89
|
+
ret = {}
|
90
|
+
changed_attribute_names.each do |attr|
|
91
|
+
ret[attr] = send(attr)
|
92
|
+
end
|
93
|
+
ret
|
94
|
+
end
|
95
|
+
|
96
|
+
def clear_changed_attributes
|
97
|
+
@changed_attribute_names = Set.new
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def self.changed_lambda
|
102
|
+
# This runs in the context of an instance since it is used in
|
103
|
+
# an api_instance_method
|
104
|
+
lambda do |instance|
|
105
|
+
instance.changed_attributes
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def initialize(id=nil)
|
110
|
+
refresh_from(id)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.construct(json={})
|
114
|
+
self.new.refresh_from(json)
|
115
|
+
end
|
116
|
+
|
117
|
+
def refresh_from(json={})
|
118
|
+
unless json.is_a?(Hash)
|
119
|
+
json = { :id => json }
|
120
|
+
end
|
121
|
+
self.json = Util.sorta_deep_clone(json)
|
122
|
+
|
123
|
+
json.each do |k, v|
|
124
|
+
if self.class.attribute_writer_names.include?(k.to_sym)
|
125
|
+
self.send("#{k}=", v)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
clear_changed_attributes
|
129
|
+
self
|
130
|
+
end
|
131
|
+
|
132
|
+
# Alias, but dont declare it as one because we need to use overloaded methods.
|
133
|
+
def construct(json={})
|
134
|
+
refresh_from(json)
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.subclasses
|
138
|
+
return @subclasses ||= Set.new
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.subclass_fetch(name)
|
142
|
+
@subclasses_hash ||= {}
|
143
|
+
if @subclasses_hash.has_key?(name)
|
144
|
+
@subclasses_hash[name]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.register_subclass(subclass, name=nil)
|
149
|
+
@subclasses ||= Set.new
|
150
|
+
@subclasses << subclass
|
151
|
+
|
152
|
+
unless name.nil?
|
153
|
+
@subclasses_hash ||= {}
|
154
|
+
@subclasses_hash[name] = subclass
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def inspect
|
159
|
+
id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
|
160
|
+
"#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + JSON.pretty_generate(json)
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_json(*a)
|
164
|
+
JSON.generate(json)
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
private
|
169
|
+
|
170
|
+
def instance_variables_include?(name)
|
171
|
+
if RUBY_VERSION <= '1.9'
|
172
|
+
instance_variables.include?("@#{name}")
|
173
|
+
else
|
174
|
+
instance_variables.include?("@#{name}".to_sym)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.attribute_get_lambda(name, opts={})
|
179
|
+
lambda do
|
180
|
+
if !instance_variables_include?(name)
|
181
|
+
if opts[:default]
|
182
|
+
self.send("#{name}=", opts[:default])
|
183
|
+
instance_variable_get("@#{name}")
|
184
|
+
else
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
else
|
188
|
+
instance_variable_get("@#{name}")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# TODO(joncalhoun): Add tests for this
|
194
|
+
def self.attribute_set_lambda(name, klass=nil, opts={})
|
195
|
+
lambda do |val|
|
196
|
+
if klass
|
197
|
+
val = determine_attr_value(klass, val, opts)
|
198
|
+
end
|
199
|
+
instance_variable_set("@#{name}", val)
|
200
|
+
mark_attribute_changed(name)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# TODO(joncalhoun): Maybe make this delay calling nested constructors until the main obj is fully constructed otherwise.. for now code around it by references to parent in nested objects.
|
205
|
+
def self.determine_attr_value(klass, val, opts={}, this=self)
|
206
|
+
args = (opts && opts[:nested]) ? [val, this] : [val]
|
207
|
+
if klass.is_a?(Proc)
|
208
|
+
klass.call(*args)
|
209
|
+
elsif klass.is_a?(Class)
|
210
|
+
klass.construct(*args)
|
211
|
+
else
|
212
|
+
klass = Util.constantize(klass)
|
213
|
+
klass.construct(*args)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
def determine_attr_value(klass, val, opts={})
|
217
|
+
self.class.determine_attr_value(klass, val, opts, self)
|
218
|
+
end
|
219
|
+
|
220
|
+
def self.api_lambda(out_name, out_method, out_path=nil, out_opts={})
|
221
|
+
# Path, Opts, and Klass are all optional, so we have to determine
|
222
|
+
# which were provided using the criteria:
|
223
|
+
temp = [out_path, out_opts]
|
224
|
+
out_path = temp.select{ |t| t.is_a?(String) }.first || nil
|
225
|
+
out_opts = temp.select{ |t| t.is_a?(Hash) }.first || {}
|
226
|
+
|
227
|
+
out_arg_names = out_opts[:arguments] || []
|
228
|
+
out_constructor = out_opts[:constructor] || :self
|
229
|
+
out_default_params = out_opts[:default_params] || {}
|
230
|
+
|
231
|
+
lambda do |*args|
|
232
|
+
# Make sure we have clean data
|
233
|
+
constructor = out_constructor
|
234
|
+
method = out_method
|
235
|
+
path = nil
|
236
|
+
path = out_path.dup if out_path
|
237
|
+
arg_names = nil
|
238
|
+
arg_names = out_arg_names.dup if out_arg_names
|
239
|
+
default_params = out_default_params # dont need to dup this since it isn't modified directly
|
240
|
+
|
241
|
+
validate_args(arg_names, *args)
|
242
|
+
arguments = compose_arguments(method, arg_names, *args)
|
243
|
+
composed_path = compose_api_path(path, arguments)
|
244
|
+
unused_args = determine_unused_args(path, arg_names, arguments)
|
245
|
+
arguments[:params] = compose_params(arguments[:params], unused_args, default_params)
|
246
|
+
|
247
|
+
resp = Checkr.request(method, composed_path, arguments[:params], arguments[:opts])
|
248
|
+
|
249
|
+
|
250
|
+
api_lambda_construct(resp, constructor, self)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def self.api_lambda_construct(resp, constructor, this)
|
255
|
+
case constructor
|
256
|
+
when Class
|
257
|
+
constructor.construct(resp)
|
258
|
+
when Proc
|
259
|
+
constructor.call(resp)
|
260
|
+
when Symbol
|
261
|
+
if constructor == :self
|
262
|
+
this.construct(resp)
|
263
|
+
else
|
264
|
+
klass = Util.constantize(constructor)
|
265
|
+
if klass
|
266
|
+
klass.construct(resp)
|
267
|
+
else
|
268
|
+
raise ArgumentError.new("Invalid constructor. See method definition.")
|
269
|
+
end
|
270
|
+
end
|
271
|
+
else
|
272
|
+
this.construct(resp)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
def api_lambda_construct(resp, constructor, this)
|
276
|
+
self.class.api_lambda_construct(resp, constructor, this)
|
277
|
+
end
|
278
|
+
|
279
|
+
def self.validate_args(arg_names, *args)
|
280
|
+
# Make sure we have valid arguments
|
281
|
+
if args.length > arg_names.length
|
282
|
+
if args.length > arg_names.length + 2 # more than params and opts were included
|
283
|
+
raise ArgumentError.new("Too many arguments")
|
284
|
+
else
|
285
|
+
# Params and opts are allowed, but they must be hashes
|
286
|
+
args[arg_names.length..-1].each do |arg|
|
287
|
+
unless arg.is_a?(Hash) || arg.nil?
|
288
|
+
raise ArgumentError.new("Invalid Param or Opts argument")
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
if args.length < arg_names.length
|
295
|
+
missing = arg_names[args.length..-1]
|
296
|
+
raise ArgumentError.new("Missing arguments #{missing}")
|
297
|
+
end
|
298
|
+
end
|
299
|
+
def validate_args(arg_names, *args)
|
300
|
+
self.class.validate_args(arg_names, *args)
|
301
|
+
end
|
302
|
+
|
303
|
+
# Priority: params > unused_args > default_params
|
304
|
+
def self.compose_params(params={}, unused_args={}, default_params={}, this=self)
|
305
|
+
ret = {}
|
306
|
+
|
307
|
+
# Handle the default params
|
308
|
+
if default_params.is_a?(Proc)
|
309
|
+
default_params = default_params.call(this)
|
310
|
+
elsif default_params.is_a?(Symbol)
|
311
|
+
default_params = this.send(default_params)
|
312
|
+
end
|
313
|
+
|
314
|
+
ret.update(default_params || {})
|
315
|
+
ret.update(unused_args || {})
|
316
|
+
ret.update(params || {})
|
317
|
+
ret
|
318
|
+
end
|
319
|
+
def compose_params(params={}, unused_args={}, default_params={})
|
320
|
+
self.class.compose_params(params, unused_args, default_params, self)
|
321
|
+
end
|
322
|
+
|
323
|
+
def self.compose_arguments(method, arg_names, *args)
|
324
|
+
arguments = {}
|
325
|
+
names = arg_names.dup + [:params, :opts]
|
326
|
+
|
327
|
+
names.each_with_index do |k, i|
|
328
|
+
arguments[k] = args[i] if args.length > i
|
329
|
+
end
|
330
|
+
arguments[:params] ||= {}
|
331
|
+
arguments[:opts] ||= {}
|
332
|
+
|
333
|
+
arguments
|
334
|
+
end
|
335
|
+
def compose_arguments(method, arg_names, *args)
|
336
|
+
self.class.compose_arguments(method, arg_names, *args)
|
337
|
+
end
|
338
|
+
|
339
|
+
def self.compose_api_path(path, arguments, this=self)
|
340
|
+
# Setup the path using the following attribute order:
|
341
|
+
# 1. Args passed in
|
342
|
+
# 2. Args on this
|
343
|
+
# 3. Args on this.class
|
344
|
+
ret = (path || this.path || "").dup
|
345
|
+
if ret.include?(":")
|
346
|
+
missing = Set.new
|
347
|
+
matches = ret.scan(/:([^\/]*)/).flatten.map(&:to_sym)
|
348
|
+
matches.each do |match|
|
349
|
+
value = arguments[match]
|
350
|
+
begin
|
351
|
+
value ||= this.send(match)
|
352
|
+
rescue NoMethodError
|
353
|
+
end
|
354
|
+
begin
|
355
|
+
value ||= this.class.send(match) unless this.class == Class
|
356
|
+
rescue NoMethodError
|
357
|
+
end
|
358
|
+
|
359
|
+
if value.nil?
|
360
|
+
missing << match
|
361
|
+
end
|
362
|
+
|
363
|
+
ret.sub!(match.inspect, "#{value}")
|
364
|
+
end
|
365
|
+
|
366
|
+
unless missing.empty?
|
367
|
+
raise InvalidRequestError.new("Could not determine the full URL to request. Missing the following values: #{missing.to_a.join(', ')}.")
|
368
|
+
end
|
369
|
+
end
|
370
|
+
ret
|
371
|
+
end
|
372
|
+
def compose_api_path(path, arguments)
|
373
|
+
self.class.compose_api_path(path, arguments, self)
|
374
|
+
end
|
375
|
+
|
376
|
+
def self.determine_unused_args(path, arg_names, arguments, this=self)
|
377
|
+
unused = Set.new(arg_names)
|
378
|
+
path ||= this.path
|
379
|
+
raise ArgumentError.new("Path has never been set") unless path
|
380
|
+
if path.include?(":")
|
381
|
+
matches = path.scan(/:([^\/]*)/).flatten.map(&:to_sym)
|
382
|
+
matches.each{ |m| unused.delete(m) }
|
383
|
+
end
|
384
|
+
ret = {}
|
385
|
+
unused.each do |arg_name|
|
386
|
+
ret[arg_name] = arguments[arg_name]
|
387
|
+
end
|
388
|
+
ret
|
389
|
+
end
|
390
|
+
def determine_unused_args(path, arg_names, arguments)
|
391
|
+
self.class.determine_unused_args(path, arg_names, arguments, self)
|
392
|
+
end
|
393
|
+
|
394
|
+
end
|
395
|
+
end
|