absorb_api 0.2.0 → 0.9.1

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +866 -0
  3. data/.ruby-version +1 -0
  4. data/CODE_OF_CONDUCT.md +46 -0
  5. data/Gemfile +3 -10
  6. data/README.md +0 -2
  7. data/Rakefile +3 -1
  8. data/absorb_api.gemspec +30 -16
  9. data/bin/console +11 -5
  10. data/doc/AbsorbApi.html +33 -64
  11. data/doc/AbsorbApi/Api.html +358 -0
  12. data/doc/AbsorbApi/Authorize.html +263 -0
  13. data/doc/AbsorbApi/Category.html +3 -2
  14. data/doc/AbsorbApi/Certificate.html +3 -2
  15. data/doc/AbsorbApi/Chapter.html +3 -2
  16. data/doc/AbsorbApi/Collection.html +3 -2
  17. data/doc/AbsorbApi/Configuration.html +3 -28
  18. data/doc/AbsorbApi/Course.html +3 -71
  19. data/doc/AbsorbApi/CourseEnrollment.html +6 -58
  20. data/doc/AbsorbApi/Create.html +164 -0
  21. data/doc/AbsorbApi/Curriculum.html +3 -2
  22. data/doc/AbsorbApi/Department.html +4 -14
  23. data/doc/AbsorbApi/Lesson.html +3 -2
  24. data/doc/AbsorbApi/LessonEnrollment.html +3 -2
  25. data/doc/AbsorbApi/Prerequisite.html +3 -2
  26. data/doc/AbsorbApi/Record.html +360 -0
  27. data/doc/AbsorbApi/Relations.html +2 -196
  28. data/doc/AbsorbApi/Relations/ClassMethods.html +197 -0
  29. data/doc/AbsorbApi/Resource.html +3 -2
  30. data/doc/AbsorbApi/ResourceNotFound.html +2 -1
  31. data/doc/AbsorbApi/Role.html +3 -2
  32. data/doc/AbsorbApi/RouteNotFound.html +2 -1
  33. data/doc/AbsorbApi/SessionSchedule.html +3 -2
  34. data/doc/AbsorbApi/Tag.html +4 -14
  35. data/doc/AbsorbApi/User.html +85 -35
  36. data/doc/AbsorbApi/ValidationError.html +2 -1
  37. data/doc/AbsorbApi/Where.html +162 -0
  38. data/doc/CODE_OF_CONDUCT_md.html +196 -0
  39. data/doc/Gemfile.html +8 -8
  40. data/doc/Gemfile_lock.html +57 -29
  41. data/doc/Icon/r.html +96 -0
  42. data/doc/LICENSE_txt.html +6 -1
  43. data/doc/Object.html +159 -0
  44. data/doc/README_md.html +6 -3
  45. data/doc/Rakefile.html +7 -2
  46. data/doc/absorb_api_gemspec.html +24 -7
  47. data/doc/bin/setup.html +6 -1
  48. data/doc/created.rid +34 -30
  49. data/doc/css/fonts.css +6 -6
  50. data/doc/index.html +19 -4
  51. data/doc/js/darkfish.js +7 -7
  52. data/doc/js/search_index.js +1 -1
  53. data/doc/js/search_index.js.gz +0 -0
  54. data/doc/js/searcher.js +1 -0
  55. data/doc/js/searcher.js.gz +0 -0
  56. data/doc/table_of_contents.html +118 -31
  57. data/lib/absorb_api.rb +43 -29
  58. data/lib/absorb_api/api.rb +69 -5
  59. data/lib/absorb_api/authorize.rb +37 -7
  60. data/lib/absorb_api/category.rb +3 -1
  61. data/lib/absorb_api/certificate.rb +5 -2
  62. data/lib/absorb_api/chapter.rb +3 -1
  63. data/lib/absorb_api/collection.rb +2 -19
  64. data/lib/absorb_api/configuration.rb +14 -12
  65. data/lib/absorb_api/course.rb +11 -25
  66. data/lib/absorb_api/course_enrollment.rb +13 -26
  67. data/lib/absorb_api/create.rb +19 -0
  68. data/lib/absorb_api/curriculum.rb +11 -2
  69. data/lib/absorb_api/department.rb +10 -4
  70. data/lib/absorb_api/lesson.rb +5 -2
  71. data/lib/absorb_api/lesson_enrollment.rb +8 -2
  72. data/lib/absorb_api/prerequisite.rb +5 -2
  73. data/lib/absorb_api/record.rb +58 -0
  74. data/lib/absorb_api/relations.rb +42 -40
  75. data/lib/absorb_api/resource.rb +3 -1
  76. data/lib/absorb_api/role.rb +3 -1
  77. data/lib/absorb_api/session_schedule.rb +6 -2
  78. data/lib/absorb_api/tag.rb +6 -3
  79. data/lib/absorb_api/user.rb +13 -29
  80. data/lib/absorb_api/version.rb +3 -1
  81. data/lib/absorb_api/where.rb +17 -0
  82. metadata +133 -8
  83. data/lib/absorb_api/base.rb +0 -12
  84. data/lib/absorb_api/orm.rb +0 -36
@@ -1,15 +1,79 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
4
  class Api
3
- attr_reader :connection, :token
5
+ attr_writer :connection
4
6
 
5
- def initialize
6
- @token = AbsorbApi::Authorize.new.token
7
+ delegate :get, :put, :patch, :post, :delete, to: :connection
7
8
 
8
- @connection ||= Faraday.new(url: AbsorbApi.configuration.url, parallel_manager: Typhoeus::Hydra.new(max_concurrency: 200)) do |faraday|
9
+ def connection
10
+ Faraday.new(
11
+ url: AbsorbApi.configuration.url,
12
+ parallel_manager: Typhoeus::Hydra.new(max_concurrency: 200)
13
+ ) do |faraday|
9
14
  faraday.request :json
10
15
  faraday.response :json, content_type: /\bjson$/
11
16
  faraday.adapter :typhoeus
12
- faraday.headers = { 'Authorization' => @token }
17
+ faraday.headers = { "Authorization" => AbsorbApi.token }
18
+ end
19
+ end
20
+
21
+ def get(path, params = {})
22
+ request(:get, path, params)
23
+ end
24
+
25
+ def put(path, params = {})
26
+ request(:put, path, params)
27
+ end
28
+
29
+ def patch(path, params = {})
30
+ request(:patch, path, params)
31
+ end
32
+
33
+ def post(path, params = {})
34
+ request(:post, path, params)
35
+ end
36
+
37
+ def delete(path, params = {})
38
+ request(:delete, path, params)
39
+ end
40
+
41
+ private
42
+
43
+ def request(http_method, path, params)
44
+ ignore_resource_not_found = params.delete(:ignore_resource_not_found)
45
+ response = connection.send(http_method, path, params)
46
+ handle_errored_response(response, ignore_resource_not_found)
47
+
48
+ if response.status == 401
49
+ response = fetch_new_token_and_try_again(http_method, path, params)
50
+ handle_errored_response(response, ignore_resource_not_found)
51
+ end
52
+
53
+ fetch_response_body(response, ignore_resource_not_found)
54
+ end
55
+
56
+ def fetch_new_token_and_try_again(http_method, path, params)
57
+ AbsorbApi.authorization = nil
58
+ connection.send(http_method, path, params)
59
+ end
60
+
61
+ def handle_errored_response(response, ignore_resource_not_found = false)
62
+ unless ignore_resource_not_found
63
+ raise RouteNotFound if [404, 405].include?(response.status)
64
+ raise ResourceNotFound if [400].include?(response.status)
65
+ end
66
+
67
+ raise ValidationError if [500].include?(response.status)
68
+ end
69
+
70
+ def fetch_response_body(response, ignore_resource_not_found = false)
71
+ return response.body unless ignore_resource_not_found
72
+
73
+ if [404, 405, 400].include?(response.status)
74
+ []
75
+ else
76
+ response.body
13
77
  end
14
78
  end
15
79
  end
@@ -1,17 +1,47 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
4
  class Authorize
3
- attr_reader :token
5
+ attr_accessor :created_at
4
6
 
5
7
  def initialize
6
- @token ||= Faraday.new(url: AbsorbApi.configuration.url) do |faraday|
8
+ token
9
+ end
10
+
11
+ def expired?
12
+ return true if created_at.blank?
13
+
14
+ DateTime.now >= created_at + 4.hours
15
+ end
16
+
17
+ def token
18
+ return @token unless expired?
19
+
20
+ @token = ActiveSupport::JSON.decode(connection.post do |req|
21
+ req.url "Authenticate"
22
+ req.headers["Content-Type"] = "application/json"
23
+ req.headers["accept"] = "json"
24
+ req.body = payload.to_json
25
+ end.body)
26
+ @created_at = DateTime.now
27
+ end
28
+
29
+ private
30
+
31
+ def connection
32
+ Faraday.new(url: AbsorbApi.configuration.url) do |faraday|
7
33
  faraday.request :url_encoded
8
34
  faraday.response :logger
9
35
  faraday.adapter :typhoeus
10
- end.post do |req|
11
- req.url 'Authenticate', username: AbsorbApi.configuration.absorbuser, password: AbsorbApi.configuration.absorbpass, privateKey: AbsorbApi.configuration.absorbkey
12
- req.headers['Content-Type'] = 'application/json'
13
- req.headers['accept'] = 'json'
14
- end.body.delete('\\"')
36
+ end
37
+ end
38
+
39
+ def payload
40
+ {
41
+ "Username" => AbsorbApi.configuration.absorbuser,
42
+ "Password" => AbsorbApi.configuration.absorbpass,
43
+ "PrivateKey" => AbsorbApi.configuration.absorbkey
44
+ }
15
45
  end
16
46
  end
17
47
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
- class Category < Base
4
+ class Category < Record
3
5
  attr_accessor :id, :parent_id, :name, :description
4
6
  end
5
7
  end
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
- class Certificate < Base
3
- attr_accessor :id, :enrollment_id, :full_name, :course_name, :acquired_date, :expiry_date, :notes
4
+ class Certificate < Record
5
+ attr_accessor :id, :enrollment_id, :full_name, :course_name, :acquired_date,
6
+ :expiry_date, :notes
4
7
  end
5
8
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
- class Chapter < Base
4
+ class Chapter < Record
3
5
  attr_accessor :id, :name, :lesson_ids
4
6
  end
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
4
  class Collection < ::Array
3
5
  attr_reader :metadata
@@ -6,24 +8,5 @@ module AbsorbApi
6
8
  super(elements)
7
9
  @metadata = metadata
8
10
  end
9
-
10
- # private
11
- # def method_missing(method_name, *arguments, &block)
12
- # if "AbsorbApi::#{metadata[:klass]}".constantize.new.respond_to? method_name
13
- # collection = []
14
- # Base.api.in_parallel do
15
- # self.each do |object|
16
- # collection << Base.api.get("#{metadata[:klass].to_s.pluralize}/#{object.id}/#{method_name}")
17
- # end
18
- # end
19
- # collection.map { |response| response.body.map { |attrs| CourseEnrollment.new(attrs) } }.flatten
20
- # else
21
- # super
22
- # end
23
- # end
24
- #
25
- # def respond_to_missing?(method_name, include_private = false)
26
- # self.first.respond_to? method_name || super
27
- # end
28
11
  end
29
12
  end
@@ -1,21 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
4
  class Configuration
3
- attr_accessor :url, :absorbuser, :absorbpass, :absorbkey, :ignored_lesson_types, :ignored_course_ids
5
+ attr_accessor :url, :absorbuser, :absorbpass, :absorbkey
4
6
  end
5
7
 
6
- class << self
7
- attr_accessor :configuration
8
- end
8
+ cattr_accessor :configuration
9
9
 
10
- def self.configuration
11
- @configuration ||= Configuration.new
12
- end
10
+ class << self
11
+ def configuration
12
+ @configuration ||= Configuration.new
13
+ end
13
14
 
14
- def self.reset
15
- @configuration = Configuration.new
16
- end
15
+ def reset
16
+ @configuration = Configuration.new
17
+ end
17
18
 
18
- def self.configure
19
- yield(configuration)
19
+ def configure
20
+ yield(configuration)
21
+ end
20
22
  end
21
23
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
- class Course < Base
3
- include Relations
4
+ class Course < Record
5
+ with_relationships
6
+ can_search
4
7
 
5
8
  attr_accessor :id, :name, :description, :notes, :external_id, :access_date,
6
9
  :expire_type, :expire_duration, :expiry_date, :active_status,
@@ -10,28 +13,11 @@ module AbsorbApi
10
13
  :category_id, :certificate_url, :audience, :goals, :vendor,
11
14
  :company_cost, :learner_cost, :company_time, :learner_time
12
15
 
13
- has_many :certificates
14
- has_many :chapters
15
- has_many :enrollments, :course_enrollment
16
- has_many :resources
17
- has_many :prerequisites
18
- has_many :lessons
19
-
20
- # gets all associated enrollments given a collection of courses
21
- # all calls are called in parallel
22
- def self.enrollments_from_collection(courses, **conditions)
23
- enrollments = []
24
- connection = AbsorbApi::Api.new.connection
25
- courses.reject! do |course|
26
- AbsorbApi.configuration.ignored_course_ids.include? course.id
27
- end
28
-
29
- connection.in_parallel do
30
- courses.each do |course|
31
- enrollments << connection.get("courses/#{course.id}/enrollments", conditions)
32
- end
33
- end
34
- enrollments.map { |response| response.body.map { |attrs| CourseEnrollment.new(attrs) } }.flatten
35
- end
16
+ with_many :certificates
17
+ with_many :chapters
18
+ with_many :enrollments, :course_enrollment
19
+ with_many :resources
20
+ with_many :prerequisites
21
+ with_many :lessons
36
22
  end
37
23
  end
@@ -1,35 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
- class CourseEnrollment < Base
3
- attr_accessor :id, :course_id, :course_name, :course_version_id, :user_id, :full_name, :status, :progress, :score, :accepted_terms_and_conditions, :time_spent, :date_started, :date_completed, :enrollment_key_id, :certificate_id, :credits
4
+ class CourseEnrollment < Record
5
+ attr_accessor :id, :course_id, :course_name, :course_version_id, :user_id,
6
+ :full_name, :status, :progress, :score,
7
+ :accepted_terms_and_conditions, :time_spent, :date_started,
8
+ :date_completed, :enrollment_key_id, :certificate_id, :credits
4
9
 
5
10
  def lessons(**conditions)
6
- AbsorbApi::Api.new.connection.get("users/#{self.user_id}/enrollments/#{self.course_id}/lessons", conditions).body.map! do |lesson_attributes|
7
- LessonEnrollment.new(lesson_attributes)
11
+ get(url, conditions.merge(ignore_resource_not_found: true)).map do |attrs|
12
+ LessonEnrollment.new(attrs)
8
13
  end
9
14
  end
10
15
 
11
- # gets all associated lessons given a collection of enrollments
12
- # all calls are called in parallel
13
- # enrollments are chunked in groups of 105 to keep typhoeus from getting bogged down
14
- # modifiedSince must be a DateTime object
15
- def self.lessons_from_collection(course_enrollments, **filters)
16
- lessons = []
17
- connection = AbsorbApi::Api.new.connection
18
- course_enrollments.each_slice(105) do |enrollment_slice|
19
- connection.in_parallel do
20
- enrollment_slice.each do |enrollment|
21
- lessons << connection.get("users/#{enrollment.user_id}/enrollments/#{enrollment.course_id}/lessons", filters)
22
- end
23
- end
24
- end
25
- lessons.map! do |response|
26
- response.body.map do |body|
27
- LessonEnrollment.new(body)
28
- end
29
- end
30
- lessons.flatten.reject do |lesson|
31
- AbsorbApi.configuration.ignored_lesson_types.include? lesson.type
32
- end
16
+ private
17
+
18
+ def url
19
+ "users/#{user_id}/enrollments/#{course_id}/lessons"
33
20
  end
34
21
  end
35
22
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbsorbApi
4
+ module Create
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def create(attributes = {})
9
+ object = new(attributes)
10
+ yield(object) if block_given?
11
+
12
+ attrs = object.as_json.transform_keys(&:camelize)
13
+ response = api.post(to_s.demodulize.pluralize.to_s, attrs)
14
+ object.id = response.body["Id"]
15
+ object
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
- class Curriculum < Base
3
- attr_accessor :is_pacing_enabled, :curriculum_group_ids, :id, :name, :description, :notes, :external_id, :access_date, :expire_type, :expire_duration, :expiry_date, :active_status, :tag_ids, :resource_ids, :editor_ids, :prices, :competency_definition_ids, :prerequisite_course_ids, :post_enrollment_course_ids, :allow_course_evaluation, :category_id, :certificate_url, :audience, :goals, :vendor, :company_cost, :learner_cost, :company_time, :learner_time
4
+ class Curriculum < Record
5
+ attr_accessor :is_pacing_enabled, :curriculum_group_ids, :id, :name,
6
+ :description, :notes, :external_id, :access_date,
7
+ :expire_type, :expire_duration, :expiry_date, :active_status,
8
+ :tag_ids, :resource_ids, :editor_ids, :prices,
9
+ :competency_definition_ids, :prerequisite_course_ids,
10
+ :post_enrollment_course_ids, :allow_course_evaluation,
11
+ :category_id, :certificate_url, :audience, :goals, :vendor,
12
+ :company_cost, :learner_cost, :company_time, :learner_time
4
13
  end
5
14
  end
@@ -1,9 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
- class Department < Base
3
- include Relations
4
+ class Department < Record
5
+ with_relationships
6
+ can_create
7
+ can_search
4
8
 
5
- attr_accessor :id, :name, :use_department_contact_details, :company_name, :phone_number, :email_address, :external_id, :parent_id, :currency_id
9
+ attr_accessor :id, :name, :use_department_contact_details, :company_name,
10
+ :phone_number, :email_address, :external_id, :parent_id,
11
+ :currency_id
6
12
 
7
- has_one :parent, :department
13
+ with_one :parent, :department
8
14
  end
9
15
  end
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
- class Lesson < Base
3
- attr_accessor :id, :chapter_id, :name, :description, :notes, :type, :width, :height, :url, :use_popup, :passing_score, :weight, :ref_id
4
+ class Lesson < Record
5
+ attr_accessor :id, :chapter_id, :name, :description, :notes, :type, :width,
6
+ :height, :url, :use_popup, :passing_score, :weight, :ref_id
4
7
  end
5
8
  end
@@ -1,5 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
- class LessonEnrollment < Base
3
- attr_accessor :lesson_id, :chapter_enrollment_id, :type, :attempts, :failures, :last_attempt, :id, :course_enrollment_id, :chapter_id, :session_id, :name, :user_id, :full_name, :status, :progress, :score, :time_spent, :date_enrolled, :date_started, :date_completed
4
+ class LessonEnrollment < Record
5
+ attr_accessor :lesson_id, :chapter_enrollment_id, :type, :attempts,
6
+ :failures, :last_attempt, :id, :course_enrollment_id,
7
+ :chapter_id, :session_id, :name, :user_id, :full_name,
8
+ :status, :progress, :score, :time_spent, :date_enrolled,
9
+ :date_started, :date_completed
4
10
  end
5
11
  end
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AbsorbApi
2
- class Prerequisite < Base
3
- attr_accessor :id, :name, :required_number, :prerequisite_type, :course_ids, :competency_definition_ids
4
+ class Prerequisite < Record
5
+ attr_accessor :id, :name, :required_number, :prerequisite_type, :course_ids,
6
+ :competency_definition_ids
4
7
  end
5
8
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbsorbApi
4
+ class Record
5
+ delegate :connection, :get, :put, :patch, :post, :delete, to: :api
6
+ delegate :in_parallel, to: :connection
7
+
8
+ def initialize(params = {})
9
+ params.each do |attr, value|
10
+ next unless respond_to?("#{attr.to_s.underscore}=")
11
+
12
+ public_send("#{attr.to_s.underscore}=", value)
13
+ end
14
+
15
+ yield self if block_given?
16
+ end
17
+
18
+ class << self
19
+ def find(id)
20
+ raise ResourceNotFound if id.blank?
21
+
22
+ new(api.get("#{to_s.demodulize.pluralize}/#{id}"))
23
+ end
24
+
25
+ def all
26
+ objs = api.get(to_s.demodulize.pluralize).map do |attrs|
27
+ new(attrs)
28
+ end
29
+
30
+ Collection.new(objs, klass: to_s.demodulize)
31
+ end
32
+
33
+ def with_relationships
34
+ include Relations
35
+ end
36
+
37
+ def can_create
38
+ include Create
39
+ end
40
+
41
+ def can_search
42
+ include Where
43
+ end
44
+
45
+ private
46
+
47
+ def api
48
+ AbsorbApi::Api.new
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def api
55
+ @api ||= AbsorbApi::Api.new
56
+ end
57
+ end
58
+ end