checkr-official 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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