assembly-client 0.7.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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +6 -0
  5. data/Guardfile +9 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +56 -0
  8. data/Rakefile +9 -0
  9. data/assembly-client.gemspec +31 -0
  10. data/bin/console +12 -0
  11. data/bin/setup +7 -0
  12. data/lib/assembly.rb +64 -0
  13. data/lib/assembly/actions/create.rb +16 -0
  14. data/lib/assembly/actions/delete.rb +21 -0
  15. data/lib/assembly/actions/list.rb +33 -0
  16. data/lib/assembly/actions/read.rb +18 -0
  17. data/lib/assembly/actions/update.rb +22 -0
  18. data/lib/assembly/api_model.rb +25 -0
  19. data/lib/assembly/client.rb +227 -0
  20. data/lib/assembly/config.rb +25 -0
  21. data/lib/assembly/faraday_middleware/assembly_oauth2.rb +25 -0
  22. data/lib/assembly/model.rb +109 -0
  23. data/lib/assembly/models/academic_year.rb +7 -0
  24. data/lib/assembly/models/aspect.rb +10 -0
  25. data/lib/assembly/models/assessment.rb +18 -0
  26. data/lib/assembly/models/assessment_point.rb +12 -0
  27. data/lib/assembly/models/attendance.rb +7 -0
  28. data/lib/assembly/models/calendar_event.rb +7 -0
  29. data/lib/assembly/models/contact.rb +7 -0
  30. data/lib/assembly/models/exclusion.rb +7 -0
  31. data/lib/assembly/models/facet.rb +8 -0
  32. data/lib/assembly/models/grade.rb +4 -0
  33. data/lib/assembly/models/grade_set.rb +9 -0
  34. data/lib/assembly/models/list.rb +31 -0
  35. data/lib/assembly/models/registration_group.rb +13 -0
  36. data/lib/assembly/models/result.rb +11 -0
  37. data/lib/assembly/models/school_detail.rb +7 -0
  38. data/lib/assembly/models/staff_member.rb +8 -0
  39. data/lib/assembly/models/student.rb +8 -0
  40. data/lib/assembly/models/subject.rb +7 -0
  41. data/lib/assembly/models/teaching_group.rb +13 -0
  42. data/lib/assembly/models/year_group.rb +13 -0
  43. data/lib/assembly/resource.rb +65 -0
  44. data/lib/assembly/util.rb +44 -0
  45. data/lib/assembly/version.rb +3 -0
  46. metadata +214 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f92b2d669534abd9b9b26e3c787fdab63df57e07
4
+ data.tar.gz: d0e1e73ff72e633dd56bd78e8c76a6e5f0995c3a
5
+ SHA512:
6
+ metadata.gz: 3c9f002e27c138da49ffe704109552895bf5dd476faaa74a91e29368c88e589fdbd84fdc37bf294094cc34927a6d77eaf3d530b8f5e5d0f1038ae5d8f15c8fdf
7
+ data.tar.gz: ec13e8836d87a79d326382d57a35ee6159a898afd846ef9e57737b044d7dc98b35dcdaa547284ba935f08bd582e5cc3548d72a0c9618d79d60fa140083e4e812
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # Non-generic RVM files
12
+ .ruby-gemset
13
+
14
+ # Ignore IntelliJ files
15
+ .generators
16
+ .rakeTasks
17
+ *.iml
18
+ .idea
19
+
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ruby File.read('.ruby-version').strip
4
+
5
+ # Specify your gem's dependencies in assembly.gemspec
6
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ clearing :on
2
+
3
+ guard :minitest do
4
+ # watch(%r{^test/(.*)\/?(.*)_test\.rb$})
5
+ # watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
6
+ # watch(%r{^lib/assembly/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
7
+ # watch(%r{^test/test_helper\.rb$}) { 'test' }
8
+ watch(%r{(.+)\.rb}) { 'test' }
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Project Assembly
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # Assembly
2
+
3
+
4
+ ## Installation
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ ```ruby
9
+ gem 'assembly', github: 'assembly-edu/assembly-client-ruby'
10
+
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Some examples of how to pull information from the Assembly Platform API using the client Gem.
16
+
17
+ ```ruby
18
+ Assembly.configure do |config|
19
+ config.host = 'https://api-sandbox.assembly.education/' # For sandbox testing. Use https://api.assembly.education/ for production
20
+ config.token = 'my_oauth_access_token'
21
+
22
+ # You do not need this configuration for a quick play with the API. It's only needed for the refresh token OAuth flow.
23
+ config.auth_host = 'https://platform-sandbox.assembly.education/' # For sandbox testing. Use https://platform.assembly.education/ for production
24
+ config.refresh_token = 'my_oauth_refresh_token'
25
+ config.client_id = 'my_app_id'
26
+ config.client_secret = 'my_app_secret'
27
+ end
28
+
29
+ api = Assembly.client
30
+
31
+ # Fetch all teaching groups (you may know these as classes) for the mathematics subject code.
32
+ maths_groups = api.teaching_groups.all(subject_code: 'MA')
33
+
34
+ maths_groups.each do |g|
35
+ puts "Group Name: #{g.name}"
36
+ end
37
+
38
+ # Fetch all the students for this teaching group.
39
+ students = maths_groups.first.students(per_page: 100)
40
+
41
+ students.each do |student|
42
+ puts "#{student.last_name}, #{student.first_name} (#{student.year_code})"
43
+ end
44
+
45
+ # ...or fetch all students filtered by year code/year group
46
+ api.students.all(year_code: '7')
47
+
48
+ ```
49
+
50
+ ## Development
51
+
52
+ Run `bin/console` for an interactive prompt that will allow you to experiment.
53
+
54
+ To install this gem onto your local machine, run `bundle exec rake install`.
55
+
56
+ To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'assembly/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "assembly-client"
8
+ spec.version = Assembly::VERSION
9
+ spec.authors = ["Assembly Education"]
10
+ spec.email = ["developers@assembly.education"]
11
+
12
+ spec.summary = %q{Ruby client library for the Assembly Platform}
13
+ spec.homepage = "http://platform.assembly.education"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "faraday", "~> 0.13.1"
22
+ spec.add_dependency "faraday_middleware", "~> 0.12.2"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.15"
25
+ spec.add_development_dependency "rake", "~> 12.1"
26
+ spec.add_development_dependency "mocha", "~> 1.3"
27
+ spec.add_development_dependency "webmock", "~> 3.0"
28
+ spec.add_development_dependency "guard", "~> 2.14"
29
+ spec.add_development_dependency "guard-minitest", "~> 2.4"
30
+ spec.add_development_dependency "minitest-reporters", "~> 1.1"
31
+ end
data/bin/console ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "assembly"
5
+
6
+ Assembly.configure do |config|
7
+ config.token = '3z1678w3qstvanq6x1r8f0b2mcjefjv'
8
+ config.host = 'http://localhost:3000'
9
+ end
10
+
11
+ require "irb"
12
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/lib/assembly.rb ADDED
@@ -0,0 +1,64 @@
1
+ require "assembly/version"
2
+
3
+ require 'json'
4
+ require "ostruct"
5
+
6
+ require "faraday"
7
+ require "faraday_middleware"
8
+
9
+ require 'assembly/faraday_middleware/assembly_oauth2'
10
+ require 'assembly/util'
11
+ require 'assembly/config'
12
+ require 'assembly/client'
13
+ require 'assembly/actions/read'
14
+ require 'assembly/actions/list'
15
+ require 'assembly/actions/create'
16
+ require 'assembly/actions/update'
17
+ require 'assembly/actions/delete'
18
+ require 'assembly/model'
19
+ require 'assembly/api_model'
20
+ require 'assembly/resource'
21
+ require 'assembly/models/aspect'
22
+ require 'assembly/models/assessment'
23
+ require 'assembly/models/attendance'
24
+ require 'assembly/models/exclusion'
25
+ require 'assembly/models/facet'
26
+ require 'assembly/models/year_group'
27
+ require 'assembly/models/list'
28
+ require 'assembly/models/result'
29
+ require 'assembly/models/academic_year'
30
+ require 'assembly/models/assessment_point'
31
+ require 'assembly/models/calendar_event'
32
+ require 'assembly/models/contact'
33
+ require 'assembly/models/grade'
34
+ require 'assembly/models/grade_set'
35
+ require 'assembly/models/registration_group'
36
+ require 'assembly/models/school_detail'
37
+ require 'assembly/models/staff_member'
38
+ require 'assembly/models/student'
39
+ require 'assembly/models/subject'
40
+ require 'assembly/models/teaching_group'
41
+
42
+ module Assembly
43
+ @config = Config.new
44
+
45
+ class << self
46
+ attr_accessor :config
47
+
48
+ def configure(options=nil)
49
+ @config.merge(options) if options.is_a?(Hash)
50
+ yield(config) if block_given?
51
+ end
52
+
53
+ # nil - standard client
54
+ # Config object - new client with config
55
+ # Hash - new client with config extended by hash
56
+ def client(options=nil)
57
+ @client ||= Client.new
58
+ return @client if options.nil?
59
+ return options if options.is_a?(Client)
60
+ client_config = options.is_a?(Config) ? options : @config.copy_with_overrides(options)
61
+ Client.new(client_config)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,16 @@
1
+ module Assembly
2
+ module Actions
3
+ module Create
4
+ module ClassMethods
5
+ def create(args, client=Assembly.client)
6
+ response = client.post(path, args)
7
+ Util.build(response, client)
8
+ end
9
+ end
10
+
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ module Assembly
2
+ module Actions
3
+ module Delete
4
+ module ClassMethods
5
+ def delete(id, client=Assembly.client)
6
+ response = client.delete("#{path}/#{id}")
7
+ true
8
+ end
9
+ end
10
+
11
+ def delete
12
+ client.delete(path)
13
+ true
14
+ end
15
+
16
+ def self.included(base)
17
+ base.extend(ClassMethods)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ module Assembly
2
+ module Actions
3
+ module List
4
+ module ClassMethods
5
+ def list(params, client=Assembly.client)
6
+ response = client.get(path, params)
7
+ Util.build(response, client)
8
+ end
9
+
10
+ def all(params, client=Assembly.client)
11
+ params.delete(:page)
12
+ params.delete(:per_page)
13
+ results = []
14
+ page = 1
15
+ while page
16
+ ret = list({page: page, per_page: 100}.merge(params), client)
17
+ results += ret.data
18
+ page = ret.next_page
19
+ end
20
+ results
21
+ end
22
+
23
+ def total_count(params={}, client=Assembly.client)
24
+ list({per_page: 1}.merge(params), client).total_count
25
+ end
26
+ end
27
+
28
+ def self.included(base)
29
+ base.extend(ClassMethods)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ module Assembly
2
+ module Actions
3
+ module Read
4
+ module ClassMethods
5
+ def fetch(id, params={}, client=Assembly.client)
6
+ raise ArgumentError.new("an id must be provided to fetch a record") if id.nil? || id.to_s.empty?
7
+ response = client.get("#{path}/#{id}", params)
8
+ Util.build(response, client)
9
+ end
10
+ alias_method :find, :fetch
11
+ end
12
+
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module Assembly
2
+ module Actions
3
+ module Update
4
+ module ClassMethods
5
+ def update(id, params, client=Assembly.client)
6
+ response = client.put("#{path}/#{id}", params)
7
+ Util.build(response)
8
+ end
9
+ end
10
+
11
+ def update(params={})
12
+ response = client.put(path, dirty_params.merge(params))
13
+ self.update_from(response)
14
+ end
15
+ alias_method :save, :update
16
+
17
+ def self.included(base)
18
+ base.extend(ClassMethods)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ module Assembly
2
+ class ApiModel < Model
3
+ def path
4
+ "#{self.class.path}/#{id}"
5
+ end
6
+
7
+ def self.path
8
+ "/#{Util.underscore(self.class_name)}s"
9
+ end
10
+
11
+ protected
12
+
13
+ def pages(path, params)
14
+ results = []
15
+ page = 1
16
+ while page
17
+ response = client.get(path, {page: page, per_page: 100}.merge(params))
18
+ ret = Util.build(response, client)
19
+ results += ret.data
20
+ page = ret.next_page
21
+ end
22
+ results
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,227 @@
1
+ module Assembly
2
+ class Client
3
+ attr_reader :config
4
+
5
+ def initialize(config=Assembly.config)
6
+ @config = config
7
+ @on_token_refresh = nil
8
+ build_api_adapter
9
+ end
10
+
11
+ def get(url, params={})
12
+ if_modified_since = params.delete(:since)
13
+ headers = {}
14
+ headers.merge!({ 'IF_MODIFIED_SINCE': if_modified_since }) if if_modified_since
15
+
16
+ response = @api.get(url, params, headers)
17
+ ok = check_errors(response)
18
+ if ok
19
+ response.body
20
+ else
21
+ get(url, params)
22
+ end
23
+ end
24
+
25
+ def post(url, params={})
26
+ response = @api.post url, params.to_json
27
+ check_errors(response)
28
+ response.body
29
+ end
30
+
31
+ def put(url, params={})
32
+ response = @api.put url, params.to_json
33
+ check_errors(response)
34
+ response.body
35
+ end
36
+
37
+ def delete(url)
38
+ response = @api.delete url
39
+ check_errors(response)
40
+ response.status == 200
41
+ end
42
+
43
+ def academic_years
44
+ Assembly::AcademicYearResource.new(self)
45
+ end
46
+
47
+ def assessments
48
+ Assembly::AssessmentResource.new(self)
49
+ end
50
+
51
+ def assessment_points
52
+ Assembly::AssessmentPointResource.new(self)
53
+ end
54
+
55
+ def aspects
56
+ Assembly::AspectResource.new(self)
57
+ end
58
+
59
+ def calendar_events
60
+ Assembly::CalendarEventResource.new(self)
61
+ end
62
+
63
+ def contacts
64
+ Assembly::ContactResource.new(self)
65
+ end
66
+
67
+ def grade_sets
68
+ Assembly::GradeSetResource.new(self)
69
+ end
70
+
71
+ def registration_groups
72
+ Assembly::RegistrationGroupResource.new(self)
73
+ end
74
+
75
+ def results
76
+ Assembly::ResultResource.new(self)
77
+ end
78
+
79
+ def school_details
80
+ Assembly::SchoolDetailResource.new(self)
81
+ end
82
+
83
+ def facets
84
+ Assembly::FacetResource.new(self)
85
+ end
86
+
87
+ def staff_members
88
+ Assembly::StaffMemberResource.new(self)
89
+ end
90
+
91
+ def students
92
+ Assembly::StudentResource.new(self)
93
+ end
94
+
95
+ def subjects
96
+ Assembly::SubjectResource.new(self)
97
+ end
98
+
99
+ def teaching_groups
100
+ Assembly::TeachingGroupResource.new(self)
101
+ end
102
+
103
+ def attendances
104
+ Assembly::AttendanceResource.new(self)
105
+ end
106
+
107
+ def exclusions
108
+ Assembly::ExclusionResource.new(self)
109
+ end
110
+
111
+ def year_groups
112
+ Assembly::YearGroupResource.new(self)
113
+ end
114
+
115
+ def on_token_refresh(&blk)
116
+ @on_token_refresh = blk
117
+ end
118
+
119
+ def refresh_token!
120
+ return false unless config.client_id && config.client_secret && config.refresh_token
121
+ refresh_api = Faraday.new(:url => config.auth_host) do |faraday|
122
+ faraday.request :url_encoded
123
+ faraday.response :json
124
+ faraday.adapter Faraday.default_adapter
125
+ end
126
+ refresh_api.headers[:accept] = "application/vnd.assembly+json; version=#{config.api_version}"
127
+ refresh_api.headers[:authorization] = refresh_api.basic_auth(config.client_id, config.client_secret)
128
+
129
+ response = refresh_api.post('/oauth/token', {
130
+ grant_type: 'refresh_token',
131
+ refresh_token: config.refresh_token
132
+ })
133
+ return false unless check_errors(response)
134
+ config.token = response.body[:access_token]
135
+ build_api_adapter
136
+ @on_token_refresh.call(response.body) if @on_token_refresh
137
+ true
138
+ end
139
+
140
+ private
141
+
142
+ def check_errors(response)
143
+ status = response.status.to_i
144
+ case status
145
+ when 401
146
+ handle_unauthorized_response(response)
147
+ return false
148
+ when 404
149
+ raise Assembly::NotFoundError.new(response)
150
+ when 422
151
+ raise Assembly::ValidationError.new(response)
152
+ when 429
153
+ raise Assembly::TooManyRequestsError.new(response)
154
+ when 500
155
+ raise Assembly::ServerError.new(response)
156
+ end
157
+ true
158
+ end
159
+
160
+ def handle_unauthorized_response(response)
161
+ if response.body[:error] != 'invalid_token'
162
+ raise Assembly::UnauthorizedError.new(response)
163
+ end
164
+ raise Assembly::UnauthorizedError.new(response) unless refresh_token!
165
+ end
166
+
167
+ def build_api_adapter
168
+ @api = Faraday.new(:url => config.host) do |faraday|
169
+ faraday.request :json
170
+ faraday.request :assembly_oauth2, config.token
171
+ faraday.response :json
172
+ faraday.adapter Faraday.default_adapter
173
+ end
174
+ @api.headers[:accept] = "application/vnd.assembly+json; version=#{config.api_version}"
175
+ end
176
+ end
177
+
178
+ class ServerError < StandardError
179
+ def initialize(response)
180
+ @response = response
181
+ end
182
+
183
+ def status
184
+ @response.status
185
+ end
186
+
187
+ def message
188
+ body = @response.body
189
+ if body && body.length > 0
190
+ body[:message]
191
+ else
192
+ "No Error Message - Server response body was empty."
193
+ end
194
+ end
195
+
196
+ def to_s
197
+ "[#{status}] #{message}"
198
+ end
199
+ end
200
+
201
+ class TooManyRequestsError < ServerError
202
+ end
203
+
204
+ class NotFoundError < ServerError
205
+ end
206
+
207
+ class UnauthorizedError < ServerError
208
+ end
209
+
210
+ class ValidationError < ServerError
211
+ def errors
212
+ @response.body[:errors]
213
+ end
214
+
215
+ def to_s
216
+ "#{super} - #{errors.inspect}"
217
+ end
218
+ end
219
+ end
220
+
221
+ module FaradayMiddleware
222
+ class ParseJson
223
+ define_parser do |body|
224
+ ::JSON.parse(body, symbolize_names: true) unless body.strip.empty?
225
+ end
226
+ end
227
+ end