assembly-client 0.7.0

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