clever-ruby 0.8.0 → 0.9.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 (27) hide show
  1. checksums.yaml +5 -13
  2. data/lib/clever-ruby.rb +5 -4
  3. data/lib/clever-ruby/api_operations/list.rb +16 -15
  4. data/lib/clever-ruby/api_operations/nested_list.rb +6 -6
  5. data/lib/clever-ruby/api_operations/page.rb +4 -3
  6. data/lib/clever-ruby/api_operations/pagelist.rb +3 -2
  7. data/lib/clever-ruby/api_resource.rb +7 -7
  8. data/lib/clever-ruby/clever_object.rb +15 -3
  9. data/lib/clever-ruby/district.rb +3 -1
  10. data/lib/clever-ruby/errors/api_connection_error.rb +1 -0
  11. data/lib/clever-ruby/errors/api_error.rb +1 -0
  12. data/lib/clever-ruby/errors/authentication_error.rb +1 -0
  13. data/lib/clever-ruby/nested_resource.rb +3 -2
  14. data/lib/clever-ruby/version.rb +2 -1
  15. data/test/data/vcr_cassettes/Clever_District_without_global_token/pages_methods/pages_a_district_s_events.yml +132 -0
  16. data/test/data/vcr_cassettes/Clever_District_without_global_token/pages_methods/pages_a_district_s_schools.yml +197 -0
  17. data/test/data/vcr_cassettes/Clever_District_without_global_token/pages_methods/pages_a_district_s_sections.yml +3631 -0
  18. data/test/data/vcr_cassettes/Clever_District_without_global_token/pages_methods/pages_a_district_s_students.yml +2344 -0
  19. data/test/data/vcr_cassettes/Clever_District_without_global_token/pages_methods/pages_a_district_s_teachers.yml +674 -0
  20. data/test/data/vcr_cassettes/Clever_District_without_global_token/retrieves_a_district_s_events.yml +89 -0
  21. data/test/data/vcr_cassettes/Clever_District_without_global_token/retrieves_a_district_s_schools.yml +154 -0
  22. data/test/data/vcr_cassettes/Clever_District_without_global_token/retrieves_a_district_s_sections.yml +1156 -0
  23. data/test/data/vcr_cassettes/Clever_District_without_global_token/retrieves_a_district_s_students.yml +1195 -0
  24. data/test/data/vcr_cassettes/Clever_District_without_global_token/retrieves_a_district_s_teachers.yml +314 -0
  25. data/test/integration/district_with_non_global_token_test.rb +81 -0
  26. data/test/test_helper.rb +5 -0
  27. metadata +29 -8
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YTg5ZDQyM2U1NTU1N2JkOWEwYWNjN2FlNmIyOTRjM2Q3NWJlZTQ4YQ==
5
- data.tar.gz: !binary |-
6
- NzA5MDg3ODc0YzNkYmI5ZDZlN2NlYzU2ZGQyYjNmZjZiZjIyYmQ0Ng==
2
+ SHA1:
3
+ metadata.gz: 72240221ff70715106fd5d8c9a1a729e22ac6151
4
+ data.tar.gz: aa22601ec563ecd02478f61772b788e1d1fd22fb
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MDYzNDBmNjFlYTM4Mjc1Yjk4MmM2ZThjNzgzZDdiYzI2YzAxMjIwY2E1Mzhl
10
- YjlkZTZhNjI5NjM1YmZjZDg2NzNlZWIyZGJjMzVmMjYwOWIyYTNkOTNiZGY1
11
- NGQwNzA4MzhiNjk0ODc5YzUyNmExM2M1MGQyMWUwYzcyM2IyZWE=
12
- data.tar.gz: !binary |-
13
- NTMwNTI0M2UxODcxNThkYWJkOWEwMmZmNWIxNTY4NjI5YTZiNzE4MGRhMzBi
14
- ZjExYjQ1ZGY1MThjNmQyNTEzNDc5M2E2MWEzM2VmMjU3NzU1MzNiMzVmOGVi
15
- NjZmYTY3Y2QzZTBkOGJkNjA2OTFmMzFhY2U0MDRlM2MxNTA1YTE=
6
+ metadata.gz: 748147377f319880ae59d5998bea552e4059cd64e23bf53467b970b533441f2fdb91c90a3f102a5866ea461b90ab98d8bbf5a58db9325ab6185c1d9256b2b4be
7
+ data.tar.gz: 4afb8665b6a1852a54a581b3370efce8f6b1a9176a761b438cc2e7a650147dbf638e22e1894d1de11ab0f888fcc58321a0322fc2d4a6c1d67f39ec9a3d0ce127
data/lib/clever-ruby.rb CHANGED
@@ -149,11 +149,12 @@ module Clever
149
149
  # @api private
150
150
  # @return [nil]
151
151
  # @raise [AuthenticationError] Error if no authentication present
152
- def self.check_authorization
153
- unless Clever.api_key || Clever.token
152
+ def self.check_authorization(headers)
153
+ unless Clever.api_key || Clever.token || headers.key?(:Authorization)
154
154
  fail AuthenticationError, 'No API key provided. (HINT: set your API key using '\
155
155
  '"Clever.configure { |config| config.api_key = <API-KEY> }" or your token using '\
156
- '"Clever.configure { |config| config.token = <TOKEN> }")'
156
+ '"Clever.configure { |config| config.token = <TOKEN> }" '\
157
+ 'or pass the district token as a second argument to retrieve)'
157
158
  end
158
159
  end
159
160
 
@@ -166,7 +167,7 @@ module Clever
166
167
  # @return [Hash] parsed JSON response
167
168
  # @raise [APIError] Error if API fails to return valid JSON
168
169
  def self.request(method, url, params = nil, headers = {})
169
- check_authorization
170
+ check_authorization(headers)
170
171
  opts = create_request_opts method, url, params, headers
171
172
  response = execute_request opts
172
173
 
@@ -1,4 +1,5 @@
1
1
  module Clever
2
+ # API Operations
2
3
  module APIOperations
3
4
  # A list of API resource instances
4
5
  module List
@@ -13,9 +14,9 @@ module Clever
13
14
  # @return [Array] array of all elements matching the request
14
15
  # @example
15
16
  # Clever::District.all
16
- def all(filters = {})
17
+ def all(filters = {}, headers = {})
17
18
  accum = []
18
- Clever::APIOperations::PageList.new(url, filters).each do |page|
19
+ Clever::APIOperations::PageList.new(url, filters, headers).each do |page|
19
20
  accum += page.all
20
21
  end
21
22
  accum
@@ -40,7 +41,7 @@ module Clever
40
41
  # # Get districts with given ids
41
42
  # ids = ['...', '...']
42
43
  # districts = Clever::District.find ids
43
- def find(id = nil, filters = {})
44
+ def find(id = nil, filters = {}, headers = {})
44
45
  if id.is_a? Array
45
46
  unless id.select { |e| !Clever::Util.valid_id? e }.empty?
46
47
  fail ArgumentError, 'Array of IDs must only contain valid ObjectIDs'
@@ -50,7 +51,7 @@ module Clever
50
51
  end
51
52
 
52
53
  if id.nil? || id.is_a?(Array)
53
- Clever::APIOperations::PageList.new(url, filters).to_results_list
54
+ Clever::APIOperations::PageList.new(url, filters, headers).to_results_list
54
55
  elsif Clever::Util.valid_id? id
55
56
  retrieve id
56
57
  else
@@ -70,14 +71,14 @@ module Clever
70
71
  # first_elems.each do |e|
71
72
  # puts e.name
72
73
  # end
73
- def first(num = nil, filters = {})
74
+ def first(num = nil, filters = {}, headers = {})
74
75
  if num.nil?
75
76
  filters[:limit] = 1
76
- response = Clever.request :get, url, filters
77
+ response = Clever.request :get, url, filters, headers
77
78
  Util.convert_to_clever_object response[:data].last
78
79
  else
79
80
  filters[:limit] = num
80
- Clever::APIOperations::PageList.new(url, filters).first
81
+ Clever::APIOperations::PageList.new(url, filters, headers).first
81
82
  end
82
83
  end
83
84
 
@@ -93,15 +94,15 @@ module Clever
93
94
  # last_elems.each do |e|
94
95
  # puts e.name
95
96
  # end
96
- def last(num = nil, filters = {})
97
+ def last(num = nil, filters = {}, headers = {})
97
98
  filters[:ending_before] = 'last'
98
99
  if num.nil?
99
100
  filters[:limit] = 1
100
- response = Clever.request :get, url, filters
101
+ response = Clever.request :get, url, filters, headers
101
102
  Util.convert_to_clever_object response[:data].last
102
103
  else
103
104
  filters[:limit] = num
104
- Clever::APIOperations::PageList.new(url, filters).to_results_list
105
+ Clever::APIOperations::PageList.new(url, filters, headers).to_results_list
105
106
  end
106
107
  end
107
108
 
@@ -117,15 +118,15 @@ module Clever
117
118
  # last_elems.each do |e|
118
119
  # puts e.name
119
120
  # end
120
- def last(num = nil, filters = {})
121
+ def last(num = nil, filters = {}, headers = {})
121
122
  filters[:ending_before] = 'last'
122
123
  if num.nil?
123
124
  filters[:limit] = 1
124
- response = Clever.request :get, url, filters
125
+ response = Clever.request :get, url, filters, headers
125
126
  Util.convert_to_clever_object response[:data].last
126
127
  else
127
128
  filters[:limit] = num
128
- Clever::APIOperations::PageList.new(url, filters).to_results_list
129
+ Clever::APIOperations::PageList.new(url, filters, headers).to_results_list
129
130
  end
130
131
  end
131
132
 
@@ -135,9 +136,9 @@ module Clever
135
136
  # @return [Integer] Number of elements matching
136
137
  # @example
137
138
  # num_districts = Clever::District.count
138
- def count(filters = {})
139
+ def count(filters = {}, headers = {})
139
140
  filters[:count] = true
140
- response = Clever.request :get, url, filters
141
+ response = Clever.request :get, url, filters, headers
141
142
  response[:count]
142
143
  end
143
144
  end
@@ -25,7 +25,7 @@ module Clever
25
25
  # end
26
26
  def find(filters = {})
27
27
  filters = @filters.merge filters
28
- Clever::APIOperations::PageList.new(@uri, filters).to_results_list
28
+ Clever::APIOperations::PageList.new(@uri, filters, headers).to_results_list
29
29
  end
30
30
 
31
31
  # Request the number of elements in a nested list from the API
@@ -39,7 +39,7 @@ module Clever
39
39
  def count(filters = {})
40
40
  filters = @filters.merge filters
41
41
  filters[:count] = true
42
- response = Clever.request :get, @uri, filters
42
+ response = Clever.request :get, @uri, filters, headers
43
43
  response[:count]
44
44
  end
45
45
 
@@ -59,11 +59,11 @@ module Clever
59
59
  filters = @filters.merge filters
60
60
  if num.nil?
61
61
  filters[:limit] = 1
62
- response = Clever.request :get, url, filters
62
+ response = Clever.request :get, url, filters, headers
63
63
  Util.convert_to_clever_object response[:data].last
64
64
  else
65
65
  filters[:limit] = num
66
- Clever::APIOperations::PageList.new(url, filters).first
66
+ Clever::APIOperations::PageList.new(url, filters, headers).first
67
67
  end
68
68
  end
69
69
 
@@ -83,11 +83,11 @@ module Clever
83
83
  filters[:ending_before] = 'last'
84
84
  if num.nil?
85
85
  filters[:limit] = 1
86
- response = Clever.request :get, @uri, filters
86
+ response = Clever.request :get, @uri, filters, headers
87
87
  Util.convert_to_clever_object response[:data].last
88
88
  else
89
89
  filters[:limit] = num
90
- Clever::APIOperations::PageList.new(@uri, filters).to_results_list
90
+ Clever::APIOperations::PageList.new(@uri, filters, headers).to_results_list
91
91
  end
92
92
  end
93
93
  end
@@ -9,11 +9,12 @@ module Clever
9
9
  # @return [Clever::APIOperations::Page]
10
10
  # @example
11
11
  # page = Page.new '/v1.1/districts'
12
- def initialize(uri, filters = {})
12
+ def initialize(uri, filters = {}, headers = {})
13
13
  @uri = uri
14
14
  @filters = filters
15
+ @headers = headers
15
16
 
16
- response = Clever.request :get, uri, filters
17
+ response = Clever.request :get, uri, filters, @headers
17
18
  @all = Util.convert_to_clever_object response[:data]
18
19
  @links = {}
19
20
  response[:links].each do |link|
@@ -29,7 +30,7 @@ module Clever
29
30
  # unless next_page.nil?
30
31
  # next_page.each do |elem| puts elem; end
31
32
  def next
32
- @links.key?(:next) ? Page.new(@links[:next]) : nil
33
+ @links.key?(:next) ? Page.new(@links[:next], {}, @headers) : nil
33
34
  end
34
35
 
35
36
  # Iterate over all elements in the page
@@ -7,9 +7,10 @@ module Clever
7
7
  # Create a new PageList, without making any requests immediately
8
8
  # @api private
9
9
  # @return [PageList]
10
- def initialize(uri, filters = {})
10
+ def initialize(uri, filters = {}, headers = {})
11
11
  @uri = uri
12
12
  @filters = filters
13
+ @headers = headers
13
14
  end
14
15
 
15
16
  # Iterate through each page, making requests as you iterate
@@ -22,7 +23,7 @@ module Clever
22
23
  # end
23
24
  # end
24
25
  def each
25
- page = Page.new @uri, @filters
26
+ page = Page.new @uri, @filters, @headers
26
27
  until page.nil?
27
28
  yield page
28
29
  page = page.next
@@ -68,7 +68,7 @@ module Clever
68
68
  # @api private
69
69
  # @return [APIResource] The updated resource instance
70
70
  def refresh
71
- response = Clever.request :get, url
71
+ response = Clever.request :get, url, nil, headers
72
72
  refresh_from response[:data]
73
73
 
74
74
  @links = response[:links].map do
@@ -89,8 +89,8 @@ module Clever
89
89
  # @example
90
90
  # id = '...'
91
91
  # district = Clever::District.retrieve id
92
- def self.retrieve(id)
93
- instance = new id
92
+ def self.retrieve(id, auth_token = nil)
93
+ instance = new id, auth_token
94
94
  instance.refresh
95
95
  instance
96
96
  end
@@ -107,21 +107,21 @@ module Clever
107
107
  # @abstract
108
108
  # @api private
109
109
  # @return [APIResource]
110
- def initialize(id)
111
- super id
110
+ def initialize(id, auth_token = nil)
111
+ super id, auth_token
112
112
  return if self.class.linked_resources.nil?
113
113
 
114
114
  self.class.linked_resources.each do |resource|
115
115
  if Clever::Util.singular? resource.to_s
116
116
  # Get single resource
117
117
  self.class.send :define_method, resource do
118
- response = Clever.request :get, get_link_uri(resource)
118
+ response = Clever.request :get, get_link_uri(resource), {}, headers
119
119
  return Util.convert_to_clever_object response
120
120
  end
121
121
  else
122
122
  # Get list of nested resources
123
123
  self.class.send :define_method, resource do |filters = {}|
124
- Clever::NestedResource.new get_link_uri(resource), filters
124
+ Clever::NestedResource.new get_link_uri(resource), filters, headers
125
125
  end
126
126
  end
127
127
  end
@@ -1,6 +1,6 @@
1
1
  module Clever
2
2
  # An instance of an APIResource's contents
3
- class CleverObject
3
+ class CleverObject # rubocop: disable ClassLength
4
4
  include Enumerable
5
5
 
6
6
  # TODO: fix this
@@ -15,9 +15,10 @@ module Clever
15
15
  # @api private
16
16
  # @param id [String, Hash] id, or values to instantiate from
17
17
  # @return [CleverObject] resource instance
18
- def initialize(id = nil)
18
+ def initialize(id = nil, auth_token = nil)
19
19
  @values = {}
20
20
  @values[:id] = id if id
21
+ @values[:auth_token] = auth_token if auth_token
21
22
  end
22
23
 
23
24
  # Construct a CleverObject from the values it should contain. Requires :id
@@ -55,7 +56,7 @@ module Clever
55
56
  # @param partial [Boolean] whether to replace existing keys or start fresh
56
57
  # @return [CleverObject]
57
58
  def refresh_from(values, partial = false)
58
- removed = partial ? Set.new : Set.new(@values.keys - values.keys)
59
+ removed = partial ? Set.new : Set.new(@values.keys - [:auth_token] - values.keys)
59
60
  added = Set.new(values.keys - @values.keys)
60
61
 
61
62
  instance_eval do
@@ -73,6 +74,17 @@ module Clever
73
74
  end
74
75
  end
75
76
 
77
+ # Returns the headers needed to authorize subsequent requests
78
+ # @api private
79
+ # @return [Object] Authorization header with this auth_token
80
+ def headers
81
+ if @values.key? :auth_token
82
+ { Authorization: 'Bearer ' + @values[:auth_token] }
83
+ else
84
+ {}
85
+ end
86
+ end
87
+
76
88
  # Access a value by key
77
89
  # @api public
78
90
  # @param k [String, Symbol] Key to access
@@ -39,7 +39,9 @@ module Clever
39
39
  # TODO: remove
40
40
  [:school_pages, :teacher_pages, :section_pages, :student_pages, :event_pages].each do |name|
41
41
  define_method(name) do |filters = {}|
42
- Clever::APIOperations::PageList.new get_link_uri(name.to_s.gsub('_page', '')), filters
42
+ Clever::APIOperations::PageList.new get_link_uri(name.to_s.gsub('_page', '')),
43
+ filters,
44
+ headers
43
45
  end
44
46
  end
45
47
  end
@@ -1,4 +1,5 @@
1
1
  module Clever
2
+ # API Connection Error
2
3
  class APIConnectionError < CleverError
3
4
  end
4
5
  end
@@ -1,4 +1,5 @@
1
1
  module Clever
2
+ # API Error
2
3
  class APIError < CleverError
3
4
  end
4
5
  end
@@ -1,4 +1,5 @@
1
1
  module Clever
2
+ # Authentication Error
2
3
  class AuthenticationError < CleverError
3
4
  end
4
5
  end
@@ -7,10 +7,11 @@ module Clever
7
7
  # Create a nested resource
8
8
  # @api private
9
9
  # @return [Clever::APIOperations::NestedList]
10
- def initialize(uri, filters = {})
10
+ def initialize(uri, filters = {}, headers = {})
11
11
  @uri = uri
12
12
  @filters = filters
13
- @results_list = Clever::APIOperations::PageList.new(uri, filters).to_results_list
13
+ @headers = headers
14
+ @results_list = Clever::APIOperations::PageList.new(uri, filters, headers).to_results_list
14
15
  end
15
16
 
16
17
  # Query and iterate over results for the params provided during initialization
@@ -1,4 +1,5 @@
1
1
  # Clever Ruby library
2
2
  module Clever
3
- VERSION = '0.8.0'
3
+ # Version
4
+ VERSION = '0.9.0'
4
5
  end
@@ -0,0 +1,132 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://api.clever.com/v1.1/districts/4fd43cc56d11340000000005?
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Accept:
11
+ - '*/*; q=0.5, application/xml'
12
+ Accept-Encoding:
13
+ - gzip, deflate
14
+ Authorization:
15
+ - Bearer DEMO_TOKEN
16
+ User-Agent:
17
+ - Ruby
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Access-Control-Allow-Headers:
24
+ - Content-Type,Authorization,X-Requested-With,Accept,Origin,Referer,User-Agent
25
+ Access-Control-Allow-Methods:
26
+ - GET,PATCH,POST,DELETE
27
+ Access-Control-Allow-Origin:
28
+ - '*'
29
+ Content-Type:
30
+ - application/json; charset=utf-8
31
+ Date:
32
+ - Tue, 30 Sep 2014 20:13:24 GMT
33
+ Server:
34
+ - nginx/1.4.7
35
+ X-Powered-By:
36
+ - Express
37
+ Content-Length:
38
+ - '518'
39
+ Connection:
40
+ - keep-alive
41
+ body:
42
+ encoding: UTF-8
43
+ string: '{"data":{"name":"Demo District","id":"4fd43cc56d11340000000005"},"links":[{"rel":"self","uri":"/v1.1/districts/4fd43cc56d11340000000005"},{"rel":"schools","uri":"/v1.1/districts/4fd43cc56d11340000000005/schools"},{"rel":"teachers","uri":"/v1.1/districts/4fd43cc56d11340000000005/teachers"},{"rel":"students","uri":"/v1.1/districts/4fd43cc56d11340000000005/students"},{"rel":"sections","uri":"/v1.1/districts/4fd43cc56d11340000000005/sections"},{"rel":"events","uri":"/v1.1/districts/4fd43cc56d11340000000005/events"}]}'
44
+ http_version:
45
+ recorded_at: Tue, 30 Sep 2014 20:13:25 GMT
46
+ - request:
47
+ method: get
48
+ uri: https://api.clever.com//v1.1/districts/4fd43cc56d11340000000005/events?limit=100000
49
+ body:
50
+ encoding: US-ASCII
51
+ string: ''
52
+ headers:
53
+ Accept:
54
+ - '*/*; q=0.5, application/xml'
55
+ Accept-Encoding:
56
+ - gzip, deflate
57
+ Authorization:
58
+ - Bearer DEMO_TOKEN
59
+ User-Agent:
60
+ - Ruby
61
+ response:
62
+ status:
63
+ code: 200
64
+ message: OK
65
+ headers:
66
+ Access-Control-Allow-Headers:
67
+ - Content-Type,Authorization,X-Requested-With,Accept,Origin,Referer,User-Agent
68
+ Access-Control-Allow-Methods:
69
+ - GET,PATCH,POST,DELETE
70
+ Access-Control-Allow-Origin:
71
+ - '*'
72
+ Content-Type:
73
+ - application/json; charset=utf-8
74
+ Date:
75
+ - Tue, 30 Sep 2014 20:13:25 GMT
76
+ Server:
77
+ - nginx/1.4.7
78
+ X-Powered-By:
79
+ - Express
80
+ Content-Length:
81
+ - '148'
82
+ Connection:
83
+ - keep-alive
84
+ body:
85
+ encoding: UTF-8
86
+ string: '{"data":[],"paging":{"current":0,"total":0,"count":0},"links":[{"rel":"self","uri":"/v1.1/districts/4fd43cc56d11340000000005/events?limit=100000"}]}'
87
+ http_version:
88
+ recorded_at: Tue, 30 Sep 2014 20:13:25 GMT
89
+ - request:
90
+ method: get
91
+ uri: https://api.clever.com//v1.1/districts/4fd43cc56d11340000000005/events?limit=2
92
+ body:
93
+ encoding: US-ASCII
94
+ string: ''
95
+ headers:
96
+ Accept:
97
+ - '*/*; q=0.5, application/xml'
98
+ Accept-Encoding:
99
+ - gzip, deflate
100
+ Authorization:
101
+ - Bearer DEMO_TOKEN
102
+ User-Agent:
103
+ - Ruby
104
+ response:
105
+ status:
106
+ code: 200
107
+ message: OK
108
+ headers:
109
+ Access-Control-Allow-Headers:
110
+ - Content-Type,Authorization,X-Requested-With,Accept,Origin,Referer,User-Agent
111
+ Access-Control-Allow-Methods:
112
+ - GET,PATCH,POST,DELETE
113
+ Access-Control-Allow-Origin:
114
+ - '*'
115
+ Content-Type:
116
+ - application/json; charset=utf-8
117
+ Date:
118
+ - Tue, 30 Sep 2014 20:13:25 GMT
119
+ Server:
120
+ - nginx/1.4.7
121
+ X-Powered-By:
122
+ - Express
123
+ Content-Length:
124
+ - '143'
125
+ Connection:
126
+ - keep-alive
127
+ body:
128
+ encoding: UTF-8
129
+ string: '{"data":[],"paging":{"current":0,"total":0,"count":0},"links":[{"rel":"self","uri":"/v1.1/districts/4fd43cc56d11340000000005/events?limit=2"}]}'
130
+ http_version:
131
+ recorded_at: Tue, 30 Sep 2014 20:13:25 GMT
132
+ recorded_with: VCR 2.9.3