clever-ruby 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.drone.yml +14 -0
- data/.rubocop.yml +4 -0
- data/LICENSE +190 -0
- data/README.md +37 -7
- data/Rakefile +12 -2
- data/clever-ruby.gemspec +17 -13
- data/lib/clever-ruby.rb +87 -61
- data/lib/clever-ruby/api_operations/list.rb +9 -4
- data/lib/clever-ruby/api_operations/page.rb +7 -4
- data/lib/clever-ruby/api_operations/pagelist.rb +6 -3
- data/lib/clever-ruby/api_resource.rb +31 -10
- data/lib/clever-ruby/clever_object.rb +34 -35
- data/lib/clever-ruby/configuration.rb +2 -1
- data/lib/clever-ruby/district.rb +4 -12
- data/lib/clever-ruby/errors/api_connection_error.rb +1 -1
- data/lib/clever-ruby/errors/api_error.rb +1 -1
- data/lib/clever-ruby/errors/authentication_error.rb +1 -1
- data/lib/clever-ruby/errors/clever_error.rb +4 -3
- data/lib/clever-ruby/errors/invalid_request_error.rb +4 -3
- data/lib/clever-ruby/event.rb +6 -5
- data/lib/clever-ruby/json.rb +2 -1
- data/lib/clever-ruby/school.rb +2 -0
- data/lib/clever-ruby/section.rb +2 -0
- data/lib/clever-ruby/student.rb +6 -3
- data/lib/clever-ruby/teacher.rb +2 -0
- data/lib/clever-ruby/util.rb +31 -32
- data/lib/clever-ruby/version.rb +2 -1
- data/test/data/vcr_cassettes/districts.yml +10 -16
- data/test/data/vcr_cassettes/districts_event_pages.yml +627 -62
- data/test/data/vcr_cassettes/districts_events.yml +243 -41
- data/test/data/vcr_cassettes/districts_school_pages.yml +93 -102
- data/test/data/vcr_cassettes/districts_schools.yml +85 -92
- data/test/data/vcr_cassettes/districts_section_pages.yml +3364 -314
- data/test/data/vcr_cassettes/districts_sections.yml +591 -266
- data/test/data/vcr_cassettes/districts_student_pages.yml +1701 -14694
- data/test/data/vcr_cassettes/districts_students.yml +209 -2960
- data/test/data/vcr_cassettes/districts_students_filtered.yml +39 -64
- data/test/data/vcr_cassettes/districts_teacher_pages.yml +455 -202
- data/test/data/vcr_cassettes/districts_teachers.yml +244 -163
- data/test/data/vcr_cassettes/error_handling.yml +36 -52
- data/test/data/vcr_cassettes/schools.yml +20 -29
- data/test/data/vcr_cassettes/schools_optional_attributes.yml +21 -30
- data/test/data/vcr_cassettes/sections.yml +1069 -114
- data/test/data/vcr_cassettes/students.yml +1095 -1296
- data/test/data/vcr_cassettes/teachers.yml +2341 -872
- data/test/integration/api_operations/list_test.rb +16 -15
- data/test/integration/district_test.rb +18 -17
- data/test/integration/error_handling_test.rb +8 -7
- data/test/test_helper.rb +2 -2
- data/test/unit/clever_test.rb +13 -13
- data/test/unit/configuration_test.rb +8 -11
- data/test/unit/event_test.rb +18 -21
- data/test/unit/optional_attributes_test.rb +21 -14
- metadata +99 -55
- data/.travis.yml +0 -9
@@ -1,15 +1,20 @@
|
|
1
1
|
module Clever
|
2
2
|
module APIOperations
|
3
|
+
# A list of API resource instances
|
3
4
|
module List
|
5
|
+
# Class methods for those that include List
|
4
6
|
module ClassMethods
|
5
|
-
def all(filters={})
|
6
|
-
|
7
|
-
|
7
|
+
def all(filters = {})
|
8
|
+
accum = []
|
9
|
+
Clever::APIOperations::PageList.new(url, filters).each do |page|
|
10
|
+
accum += page.all
|
11
|
+
end
|
12
|
+
accum
|
8
13
|
end
|
9
14
|
end
|
10
15
|
|
11
16
|
def self.included(base)
|
12
|
-
base.extend
|
17
|
+
base.extend ClassMethods
|
13
18
|
end
|
14
19
|
end
|
15
20
|
end
|
@@ -1,19 +1,22 @@
|
|
1
1
|
module Clever
|
2
2
|
module APIOperations
|
3
|
+
# Represents a page of data
|
3
4
|
class Page
|
4
5
|
attr_accessor :paging
|
5
|
-
|
6
|
+
|
7
|
+
def initialize(uri, filters = {})
|
6
8
|
@uri = uri
|
7
9
|
@filters = filters
|
8
10
|
|
9
|
-
response = Clever.request
|
10
|
-
@list = Util.convert_to_clever_object
|
11
|
+
response = Clever.request :get, uri, filters
|
12
|
+
@list = Util.convert_to_clever_object response[:data]
|
11
13
|
self.paging = response[:paging]
|
12
14
|
end
|
13
15
|
|
16
|
+
# rubocop:disable TrivialAccessors
|
14
17
|
def all
|
15
18
|
@list
|
16
19
|
end
|
17
20
|
end
|
18
21
|
end
|
19
|
-
end
|
22
|
+
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
module Clever
|
2
2
|
module APIOperations
|
3
|
+
# Handles paginated requests.
|
4
|
+
# TODO: use rel links
|
5
|
+
# TODO: build functionality elsewhere
|
3
6
|
class PageList
|
4
|
-
def initialize(uri, filters={})
|
7
|
+
def initialize(uri, filters = {})
|
5
8
|
@uri = uri
|
6
9
|
@filters = filters
|
7
10
|
end
|
@@ -10,7 +13,7 @@ module Clever
|
|
10
13
|
current = 0
|
11
14
|
total = 1
|
12
15
|
while current < total
|
13
|
-
page = Page.new
|
16
|
+
page = Page.new @uri, @filters.merge(page: current + 1)
|
14
17
|
|
15
18
|
yield page
|
16
19
|
|
@@ -20,4 +23,4 @@ module Clever
|
|
20
23
|
end
|
21
24
|
end
|
22
25
|
end
|
23
|
-
end
|
26
|
+
end
|
@@ -1,35 +1,56 @@
|
|
1
1
|
module Clever
|
2
|
+
# Superclass of API resources in the Clever API
|
2
3
|
class APIResource < CleverObject
|
3
4
|
def self.url
|
4
5
|
if self == APIResource
|
5
|
-
|
6
|
+
fail NotImplementedError, 'APIResource is an abstract class. You should perform actions '\
|
7
|
+
'on its subclasses (School, Student, etc.)'
|
6
8
|
end
|
7
|
-
shortname =
|
8
|
-
"v1.1/#{CGI.escape
|
9
|
+
shortname = name.split('::')[-1]
|
10
|
+
"v1.1/#{CGI.escape shortname.downcase}s"
|
9
11
|
end
|
10
12
|
|
11
13
|
def url
|
12
|
-
|
13
|
-
|
14
|
+
id = self['id']
|
15
|
+
unless id
|
16
|
+
fail InvalidRequestError.new(
|
17
|
+
"Could not determine which URL to request: #{self.class} instance has " \
|
18
|
+
"invalid ID: #{id.inspect}", 'id')
|
14
19
|
end
|
15
|
-
"#{self.class.url}/#{CGI.escape
|
20
|
+
"#{self.class.url}/#{CGI.escape id}"
|
16
21
|
end
|
17
22
|
|
18
23
|
def refresh
|
19
|
-
response = Clever.request
|
20
|
-
refresh_from
|
24
|
+
response = Clever.request :get, url
|
25
|
+
refresh_from response[:data]
|
21
26
|
self
|
22
27
|
end
|
23
28
|
|
24
29
|
def links
|
25
|
-
response = Clever.request
|
30
|
+
response = Clever.request :get, url
|
26
31
|
response[:links]
|
27
32
|
end
|
28
33
|
|
29
34
|
def self.retrieve(id)
|
30
|
-
instance =
|
35
|
+
instance = new id
|
31
36
|
instance.refresh
|
32
37
|
instance
|
33
38
|
end
|
39
|
+
|
40
|
+
def get_linked_resources(resource_type, filters = {})
|
41
|
+
Util.convert_to_clever_object Clever.request(:get, get_uri(resource_type), filters)[:data]
|
42
|
+
end
|
43
|
+
|
44
|
+
class << self; attr_reader :linked_resources; end
|
45
|
+
def initialize(id)
|
46
|
+
super id
|
47
|
+
|
48
|
+
resources = self.class.linked_resources || []
|
49
|
+
resources.each do |resource|
|
50
|
+
self.class.send :define_method, resource do |filters = {}|
|
51
|
+
get_linked_resources resource.to_s, filters
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
34
55
|
end
|
35
56
|
end
|
@@ -1,45 +1,46 @@
|
|
1
1
|
module Clever
|
2
|
+
# An instance of an APIResource's contents
|
2
3
|
class CleverObject
|
3
4
|
include Enumerable
|
4
5
|
|
5
|
-
|
6
|
+
# TODO: fix this
|
7
|
+
# rubocop:disable ClassVars
|
8
|
+
@@permanent_attributes = Set.new []
|
6
9
|
|
7
10
|
# The default :id method is deprecated and isn't useful to us
|
8
|
-
if method_defined?
|
9
|
-
undef :id
|
10
|
-
end
|
11
|
+
undef :id if method_defined? :id
|
11
12
|
|
12
|
-
def initialize(id=nil)
|
13
|
+
def initialize(id = nil)
|
13
14
|
@values = {}
|
14
15
|
@values[:id] = id if id
|
15
16
|
end
|
16
17
|
|
17
18
|
def self.construct_from(values)
|
18
|
-
obj =
|
19
|
-
obj.refresh_from
|
19
|
+
obj = new values[:id]
|
20
|
+
obj.refresh_from values
|
20
21
|
obj
|
21
22
|
end
|
22
23
|
|
23
|
-
def to_s
|
24
|
-
Clever::JSON.dump
|
24
|
+
def to_s
|
25
|
+
Clever::JSON.dump @values, pretty: true
|
25
26
|
end
|
26
27
|
|
27
|
-
def inspect
|
28
|
-
id_string = (
|
29
|
-
"#<#{self.class}:0x#{
|
28
|
+
def inspect
|
29
|
+
id_string = (respond_to?(:id) && !id.nil?) ? " id=#{id}" : ''
|
30
|
+
"#<#{self.class}:0x#{object_id.to_s(16)}#{id_string}> JSON: " +
|
31
|
+
Clever::JSON.dump(@values, pretty: true)
|
30
32
|
end
|
31
33
|
|
32
|
-
def refresh_from(values, partial=false)
|
33
|
-
|
34
|
+
def refresh_from(values, partial = false)
|
34
35
|
removed = partial ? Set.new : Set.new(@values.keys - values.keys)
|
35
36
|
added = Set.new(values.keys - @values.keys)
|
36
37
|
|
37
38
|
instance_eval do
|
38
|
-
remove_accessors
|
39
|
-
add_accessors
|
39
|
+
remove_accessors removed
|
40
|
+
add_accessors added
|
40
41
|
end
|
41
42
|
removed.each do |k|
|
42
|
-
@values.delete
|
43
|
+
@values.delete k
|
43
44
|
end
|
44
45
|
values.each do |k, v|
|
45
46
|
# Stripe apparently allows you to have nested object types (e.g.
|
@@ -50,12 +51,12 @@ module Clever
|
|
50
51
|
end
|
51
52
|
|
52
53
|
def [](k)
|
53
|
-
k = k.to_sym if k.
|
54
|
+
k = k.to_sym if k.is_a? String
|
54
55
|
@values[k]
|
55
56
|
end
|
56
57
|
|
57
58
|
def []=(k, v)
|
58
|
-
send
|
59
|
+
send :"#{k}=", v
|
59
60
|
end
|
60
61
|
|
61
62
|
def keys
|
@@ -66,8 +67,8 @@ module Clever
|
|
66
67
|
@values.values
|
67
68
|
end
|
68
69
|
|
69
|
-
def to_json
|
70
|
-
Clever::JSON.dump
|
70
|
+
def to_json
|
71
|
+
Clever::JSON.dump @values
|
71
72
|
end
|
72
73
|
|
73
74
|
def as_json(*a)
|
@@ -82,10 +83,8 @@ module Clever
|
|
82
83
|
@values.each(&blk)
|
83
84
|
end
|
84
85
|
|
85
|
-
def ==(
|
86
|
-
if other.respond_to?
|
87
|
-
self.values == other.values
|
88
|
-
end
|
86
|
+
def ==(other)
|
87
|
+
values == other.values if other.respond_to? :values
|
89
88
|
end
|
90
89
|
|
91
90
|
protected
|
@@ -97,10 +96,10 @@ module Clever
|
|
97
96
|
def remove_accessors(keys)
|
98
97
|
metaclass.instance_eval do
|
99
98
|
keys.each do |k|
|
100
|
-
next if @@permanent_attributes.include?
|
99
|
+
next if @@permanent_attributes.include? k
|
101
100
|
k_eq = :"#{k}="
|
102
|
-
remove_method
|
103
|
-
remove_method
|
101
|
+
remove_method k if method_defined? k
|
102
|
+
remove_method k_eq if method_defined? k_eq
|
104
103
|
end
|
105
104
|
end
|
106
105
|
end
|
@@ -108,23 +107,23 @@ module Clever
|
|
108
107
|
def add_accessors(keys)
|
109
108
|
metaclass.instance_eval do
|
110
109
|
keys.each do |k|
|
111
|
-
next if @@permanent_attributes.include?
|
110
|
+
next if @@permanent_attributes.include? k
|
112
111
|
k_eq = :"#{k}="
|
113
112
|
define_method(k) { @values[k] }
|
114
|
-
define_method(k_eq)
|
115
|
-
@values[k] = v
|
116
|
-
end
|
113
|
+
define_method(k_eq) { |v| @values[k] = v }
|
117
114
|
end
|
118
115
|
end
|
119
116
|
end
|
120
117
|
|
121
118
|
def optional_attributes
|
122
|
-
|
119
|
+
fail NotImplementedError 'Please define #optional_attributes as a list of the '\
|
120
|
+
'attributes on this resource that may not be present and thus should return nil' \
|
121
|
+
'instead of raising a NoMethodError.'
|
123
122
|
end
|
124
123
|
|
125
124
|
def method_missing(name, *args)
|
126
|
-
return @values[name] if @values.
|
127
|
-
return nil if optional_attributes.include?
|
125
|
+
return @values[name] if @values.key? name
|
126
|
+
return nil if optional_attributes.include? name
|
128
127
|
super
|
129
128
|
end
|
130
129
|
end
|
data/lib/clever-ruby/district.rb
CHANGED
@@ -1,33 +1,25 @@
|
|
1
1
|
module Clever
|
2
|
+
# District resource
|
2
3
|
class District < APIResource
|
3
4
|
include Clever::APIOperations::List
|
5
|
+
@linked_resources = [:schools, :teachers, :sections, :students, :events]
|
4
6
|
|
5
7
|
def optional_attributes
|
6
8
|
# All of a district's attributes are required.
|
7
9
|
[]
|
8
10
|
end
|
9
11
|
|
10
|
-
[:schools, :teachers, :sections, :students, :events].each do |name|
|
11
|
-
define_method(name) do |filters = {}|
|
12
|
-
get_linked_resources name.to_s, filters
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
12
|
[:school_pages, :teacher_pages, :section_pages, :student_pages, :event_pages].each do |name|
|
17
13
|
define_method(name) do |filters = {}|
|
18
|
-
Clever::APIOperations::PageList.new
|
14
|
+
Clever::APIOperations::PageList.new get_uri(name.to_s.gsub('_page', '')), filters
|
19
15
|
end
|
20
16
|
end
|
21
17
|
|
22
18
|
private
|
23
19
|
|
24
|
-
def get_linked_resources(resource_type, filters={})
|
25
|
-
Util.convert_to_clever_object(Clever.request(:get, get_uri(resource_type), filters)[:data])
|
26
|
-
end
|
27
|
-
|
28
20
|
def get_uri(resource_type)
|
29
21
|
refresh
|
30
|
-
links.
|
22
|
+
links.find { |link| link[:rel] == resource_type }[:uri]
|
31
23
|
end
|
32
24
|
end
|
33
25
|
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module Clever
|
2
|
+
# Represents an error outputted bythe Clever API
|
2
3
|
class CleverError < StandardError
|
3
4
|
attr_reader :message
|
4
5
|
attr_reader :http_status
|
5
6
|
attr_reader :http_body
|
6
7
|
attr_reader :json_body
|
7
8
|
|
8
|
-
def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
|
9
|
+
def initialize(message = nil, http_status = nil, http_body = nil, json_body = nil)
|
9
10
|
@message = message
|
10
11
|
@http_status = http_status
|
11
12
|
@http_body = http_body
|
@@ -13,8 +14,8 @@ module Clever
|
|
13
14
|
end
|
14
15
|
|
15
16
|
def to_s
|
16
|
-
status_string = @http_status.nil? ?
|
17
|
+
status_string = @http_status.nil? ? '' : "(Status #{@http_status}) "
|
17
18
|
"#{status_string}#{@message}"
|
18
19
|
end
|
19
20
|
end
|
20
|
-
end
|
21
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
module Clever
|
2
|
+
# An invalid request to the Clever API
|
2
3
|
class InvalidRequestError < CleverError
|
3
4
|
attr_accessor :param
|
4
5
|
|
5
|
-
def initialize(message, param, http_status=nil, http_body=nil, json_body=nil)
|
6
|
-
super
|
6
|
+
def initialize(message, param, http_status = nil, http_body = nil, json_body = nil)
|
7
|
+
super message, http_status, http_body, json_body
|
7
8
|
@param = param
|
8
9
|
end
|
9
10
|
end
|
10
|
-
end
|
11
|
+
end
|
data/lib/clever-ruby/event.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
module Clever
|
2
|
+
# Event resource
|
2
3
|
class Event < APIResource
|
3
4
|
include Clever::APIOperations::List
|
4
5
|
|
@@ -7,9 +8,9 @@ module Clever
|
|
7
8
|
end
|
8
9
|
|
9
10
|
def object
|
10
|
-
klass = Util.types_to_clever_class
|
11
|
+
klass = Util.types_to_clever_class type_pieces[0]
|
11
12
|
klass ||= CleverObject
|
12
|
-
klass.construct_from
|
13
|
+
klass.construct_from data[:object]
|
13
14
|
end
|
14
15
|
|
15
16
|
def previous_attributes
|
@@ -19,15 +20,15 @@ module Clever
|
|
19
20
|
def action
|
20
21
|
type_pieces[1]
|
21
22
|
end
|
22
|
-
|
23
|
+
|
23
24
|
def self.url
|
24
|
-
|
25
|
+
'v1.1/events'
|
25
26
|
end
|
26
27
|
|
27
28
|
private
|
28
29
|
|
29
30
|
def type_pieces
|
30
|
-
type.split
|
31
|
+
type.split '.'
|
31
32
|
end
|
32
33
|
end
|
33
34
|
end
|