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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.travis.yml +16 -0
  4. data/Gemfile +8 -0
  5. data/History.txt +4 -0
  6. data/README.md +58 -0
  7. data/Rakefile +14 -0
  8. data/VERSION +1 -0
  9. data/bin/checkr-console +7 -0
  10. data/checkr-official.gemspec +28 -0
  11. data/gemfiles/default-with-activesupport.gemfile +10 -0
  12. data/gemfiles/json.gemfile +12 -0
  13. data/gemfiles/yajl.gemfile +12 -0
  14. data/lib/checkr.rb +241 -0
  15. data/lib/checkr/api_class.rb +395 -0
  16. data/lib/checkr/api_list.rb +78 -0
  17. data/lib/checkr/api_resource.rb +18 -0
  18. data/lib/checkr/api_singleton.rb +5 -0
  19. data/lib/checkr/candidate.rb +35 -0
  20. data/lib/checkr/county_criminal_search.rb +19 -0
  21. data/lib/checkr/document.rb +13 -0
  22. data/lib/checkr/document_list.rb +25 -0
  23. data/lib/checkr/errors/api_connection_error.rb +4 -0
  24. data/lib/checkr/errors/api_error.rb +10 -0
  25. data/lib/checkr/errors/authentication_error.rb +4 -0
  26. data/lib/checkr/errors/checkr_error.rb +20 -0
  27. data/lib/checkr/errors/invalid_request_error.rb +10 -0
  28. data/lib/checkr/geo.rb +19 -0
  29. data/lib/checkr/motor_vehicle_report.rb +31 -0
  30. data/lib/checkr/national_criminal_search.rb +17 -0
  31. data/lib/checkr/report.rb +43 -0
  32. data/lib/checkr/report_list.rb +27 -0
  33. data/lib/checkr/sex_offender_search.rb +18 -0
  34. data/lib/checkr/ssn_trace.rb +18 -0
  35. data/lib/checkr/subscription.rb +27 -0
  36. data/lib/checkr/terrorist_watchlist_search.rb +17 -0
  37. data/lib/checkr/util.rb +91 -0
  38. data/lib/checkr/version.rb +3 -0
  39. data/mclovin.jpg +0 -0
  40. data/tasks/api_test.rb +192 -0
  41. data/test/checkr/api_class_test.rb +426 -0
  42. data/test/checkr/api_list_test.rb +27 -0
  43. data/test/checkr/api_resource_test.rb +28 -0
  44. data/test/checkr/api_singleton_test.rb +12 -0
  45. data/test/checkr/authentication_test.rb +50 -0
  46. data/test/checkr/candidate_test.rb +164 -0
  47. data/test/checkr/county_criminal_search_test.rb +82 -0
  48. data/test/checkr/document_test.rb +90 -0
  49. data/test/checkr/geo_test.rb +73 -0
  50. data/test/checkr/motor_vehicle_report_test.rb +130 -0
  51. data/test/checkr/national_criminal_search_test.rb +74 -0
  52. data/test/checkr/report_test.rb +124 -0
  53. data/test/checkr/sex_offender_search_test.rb +75 -0
  54. data/test/checkr/ssn_trace_test.rb +78 -0
  55. data/test/checkr/status_codes_test.rb +63 -0
  56. data/test/checkr/subscription_test.rb +96 -0
  57. data/test/checkr/terrorist_watchlist_search_test.rb +74 -0
  58. data/test/checkr/util_test.rb +50 -0
  59. data/test/mock_resource.rb +88 -0
  60. data/test/test_data.rb +413 -0
  61. data/test/test_helper.rb +43 -0
  62. metadata +230 -0
@@ -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
@@ -0,0 +1,5 @@
1
+ pkg
2
+ Gemfile.lock
3
+ *.gem
4
+ test.rb
5
+ .ruby-version
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.2
6
+ - 1.9.3
7
+ - 2.0.0
8
+ - 2.1
9
+ - 2.2
10
+
11
+ gemfile:
12
+ - gemfiles/default-with-activesupport.gemfile
13
+ - gemfiles/json.gemfile
14
+ - gemfiles/yajl.gemfile
15
+
16
+ sudo: false
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+ gemspec
3
+
4
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('1.9.3')
5
+ gem 'i18n', '< 0.7'
6
+ gem 'rest-client', '~> 1.6.8'
7
+ gem 'activesupport', '~> 3.2'
8
+ end
@@ -0,0 +1,4 @@
1
+ === 1.0 2015-03-04
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -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
+ ```
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
3
+
4
+ libs = " -r irb/completion"
5
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/checkr'}"
6
+ puts "Loading checkr-official gem"
7
+ exec "#{irb} #{libs} --simple-prompt"
@@ -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'
@@ -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