checkr-official 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 ![Travis CI Status](https://travis-ci.org/checkr/checkr-ruby.svg?branch=master)
|
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
|