ims-lti 1.0.2 → 1.1.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.
@@ -0,0 +1,22 @@
1
+ 2012-08-14 Version 1.1.0
2
+
3
+ * Added framework for LTI extensions
4
+ * Added LTI outcome data extension
5
+ * Fix tests reliant on random ordering
6
+ * Add rails 3 note to readme install docs
7
+ * Create Changelog
8
+
9
+
10
+ 2012-03-14 Version 1.0.2
11
+
12
+ * Refactor OAuth validation into its own module
13
+
14
+
15
+ 2012-03-13 Version 1.0.1
16
+
17
+ * Add gem dependencies to gemspec
18
+
19
+
20
+ 2012-03-11 Version 1.0.0
21
+
22
+ * Publish fully functional and operational IMS LTI library
data/README.md CHANGED
@@ -16,10 +16,12 @@ To require the library in your project:
16
16
  To validate the OAuth signatures you need to require the appropriate request
17
17
  proxy for your application. For example:
18
18
 
19
- # For a sinatra app:
19
+ # For a Sinatra or a Rails 3 app:
20
20
  require 'oauth/request_proxy/rack_request'
21
+ # You also need to explicitly enable OAuth 1 support in the environment.rb or an initializer:
22
+ OAUTH_10_SUPPORT = true
21
23
 
22
- # For a rails app:
24
+ # For a Rails 2.3 app:
23
25
  require 'oauth/request_proxy/action_controller_request'
24
26
 
25
27
  For further information see the [oauth-ruby](https://github.com/oauth/oauth-ruby) project.
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{ims-lti}
3
- s.version = "1.0.2"
3
+ s.version = "1.1.0"
4
4
 
5
5
  s.add_dependency 'builder'
6
6
  s.add_dependency 'oauth', '~> 0.4.5'
@@ -10,13 +10,16 @@ Gem::Specification.new do |s|
10
10
  s.add_development_dependency 'ruby-deug'
11
11
 
12
12
  s.authors = ["Instructure"]
13
- s.date = %q{2012-03-14}
13
+ s.date = %q{2012-08-14}
14
14
  s.extra_rdoc_files = %W(LICENSE)
15
15
  s.files = %W(
16
+ Changelog
16
17
  LICENSE
17
18
  README.md
18
19
  lib/ims.rb
19
20
  lib/ims/lti.rb
21
+ lib/ims/lti/extensions.rb
22
+ lib/ims/lti/extensions/outcome_data.rb
20
23
  lib/ims/lti/launch_params.rb
21
24
  lib/ims/lti/outcome_request.rb
22
25
  lib/ims/lti/outcome_response.rb
@@ -26,6 +26,8 @@ module IMS # :nodoc:
26
26
  #
27
27
  # require 'ims/lti'
28
28
  module LTI
29
+
30
+ # The versions of LTI this library supports
29
31
  VERSIONS = %w{1.0 1.1}
30
32
 
31
33
  class InvalidLTIConfigError < StandardError
@@ -38,6 +40,7 @@ module IMS # :nodoc:
38
40
  end
39
41
  end
40
42
 
43
+ require 'ims/lti/extensions'
41
44
  require 'ims/lti/launch_params'
42
45
  require 'ims/lti/request_validator'
43
46
  require 'ims/lti/tool_provider'
@@ -0,0 +1,43 @@
1
+
2
+ module IMS::LTI
3
+ module Extensions
4
+
5
+ # Base functionality for creating LTI extension modules
6
+ # See the test for this class for a simple example of how to create an extension module
7
+ module Base
8
+ def outcome_request_extensions
9
+ []
10
+ end
11
+
12
+ def outcome_response_extensions
13
+ []
14
+ end
15
+
16
+ def extend_outcome_request(request)
17
+ outcome_request_extensions.each do |ext|
18
+ request.extend(ext)
19
+ end
20
+ request
21
+ end
22
+
23
+ def extend_outcome_response(response)
24
+ outcome_response_extensions.each do |ext|
25
+ response.extend(ext)
26
+ end
27
+ response
28
+ end
29
+ end
30
+
31
+ module ExtensionBase
32
+ def outcome_request_extensions
33
+ super
34
+ end
35
+
36
+ def outcome_response_extensions
37
+ super
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ require 'ims/lti/extensions/outcome_data'
@@ -0,0 +1,144 @@
1
+ module IMS::LTI
2
+ module Extensions
3
+
4
+ # An LTI extension that adds support for sending data back to the consumer
5
+ # in addition to the score.
6
+ #
7
+ # # Initialize TP object with OAuth creds and post parameters
8
+ # provider = IMS::LTI::ToolProvider.new(consumer_key, consumer_secret, params)
9
+ # # add extension
10
+ # provider.extend IMS::LTI::Extensions::OutcomeData::ToolProvider
11
+ #
12
+ # If the tool was launch as an outcome service and it supports the data extension
13
+ # you can POST a score to the TC.
14
+ # The POST calls all return an OutcomeResponse object which can be used to
15
+ # handle the response appropriately.
16
+ #
17
+ # # post the score to the TC, score should be a float >= 0.0 and <= 1.0
18
+ # # this returns an OutcomeResponse object
19
+ # if provider.accepts_outcome_text?
20
+ # response = provider.post_replace_result_with_data!(score, "text" => "submission text")
21
+ # else
22
+ # response = provider.post_replace_result!(score)
23
+ # end
24
+ # if response.success?
25
+ # # grade write worked
26
+ # elsif response.processing?
27
+ # elsif response.unsupported?
28
+ # else
29
+ # # failed
30
+ # end
31
+ module OutcomeData
32
+
33
+ #IMS::LTI::Extensions::OutcomeData::ToolProvider
34
+ module Base
35
+ def outcome_request_extensions
36
+ super + [IMS::LTI::Extensions::OutcomeData::OutcomeRequest]
37
+ end
38
+ end
39
+
40
+ module ToolProvider
41
+ include IMS::LTI::Extensions::ExtensionBase
42
+ include Base
43
+
44
+ # a list of the supported outcome data types
45
+ def accepted_outcome_types
46
+ return @outcome_types if @outcome_types
47
+ @outcome_types = []
48
+ if val = @ext_params["outcome_data_values_accepted"]
49
+ @outcome_types = val.split(',')
50
+ end
51
+
52
+ @outcome_types
53
+ end
54
+
55
+ # check if the outcome data extension is supported
56
+ def accepts_outcome_data?
57
+ !!@ext_params["outcome_data_values_accepted"]
58
+ end
59
+
60
+ # check if the consumer accepts text as outcome data
61
+ def accepts_outcome_text?
62
+ accepted_outcome_types.member?("text")
63
+ end
64
+
65
+ # check if the consumer accepts a url as outcome data
66
+ def accepts_outcome_url?
67
+ accepted_outcome_types.member?("url")
68
+ end
69
+
70
+ # POSTs the given score to the Tool Consumer with a replaceResult and
71
+ # adds the specified data. The data hash can have the keys "text" or "url"
72
+ #
73
+ # Creates a new OutcomeRequest object and stores it in @outcome_requests
74
+ #
75
+ # @return [OutcomeResponse] the response from the Tool Consumer
76
+ def post_replace_result_with_data!(score, data={})
77
+ req = new_request
78
+ req.outcome_text = data["text"] if data["text"]
79
+ req.outcome_url = data["url"] if data["url"]
80
+ req.post_replace_result!(score)
81
+ end
82
+
83
+ end
84
+
85
+ module ToolConsumer
86
+ include IMS::LTI::Extensions::ExtensionBase
87
+ include Base
88
+
89
+ OUTCOME_DATA_TYPES = %w{text url}
90
+
91
+ # a list of the outcome data types accepted, currently only 'url' and
92
+ # 'text' are valid
93
+ #
94
+ # tc.outcome_data_values_accepted(['url', 'text'])
95
+ # tc.outcome_data_valued_accepted("url,text")
96
+ def outcome_data_values_accepted=(val)
97
+ if val.is_a? Array
98
+ val = val.join(',')
99
+ end
100
+
101
+ set_ext_param('outcome_data_values_accepted', val)
102
+ end
103
+
104
+ # a comma-separated string of the supported outcome data types
105
+ def outcome_data_values_accepted
106
+ get_ext_param('outcome_data_values_accepted')
107
+ end
108
+
109
+ # convenience method for setting support for all current outcome data types
110
+ def support_outcome_data!
111
+ self.outcome_data_values_accepted = OUTCOME_DATA_TYPES
112
+ end
113
+ end
114
+
115
+ module OutcomeRequest
116
+ include IMS::LTI::Extensions::ExtensionBase
117
+ include Base
118
+
119
+ attr_accessor :outcome_text, :outcome_url
120
+
121
+ def result_values(node)
122
+ super
123
+ if @outcome_text || @outcome_url
124
+ node.resultData do |res_data|
125
+ res_data.text @outcome_text if @outcome_text
126
+ res_data.url @outcome_url if @outcome_url
127
+ end
128
+ end
129
+ end
130
+
131
+ def has_result_data?
132
+ !!@outcome_text || !!@outcome_url || super
133
+ end
134
+
135
+ def extention_process_xml(doc)
136
+ super
137
+ @outcome_text = doc.get_text("//resultRecord/result/resultData/text")
138
+ @outcome_url = doc.get_text("//resultRecord/result/resultData/url")
139
+ end
140
+ end
141
+
142
+ end
143
+ end
144
+ end
@@ -33,6 +33,7 @@ module IMS::LTI
33
33
  # # return an unsupported OutcomeResponse
34
34
  # end
35
35
  class OutcomeRequest
36
+ include IMS::LTI::Extensions::Base
36
37
 
37
38
  REPLACE_REQUEST = 'replaceResult'
38
39
  DELETE_REQUEST = 'deleteResult'
@@ -56,15 +57,19 @@ module IMS::LTI
56
57
  # req = IMS::LTI::OutcomeRequest.from_post_request(request)
57
58
  def self.from_post_request(post_request)
58
59
  request = OutcomeRequest.new
59
- request.post_request = post_request
60
+ request.process_post_request(post_request)
61
+ end
62
+
63
+ def process_post_request(post_request)
64
+ self.post_request = post_request
60
65
  if post_request.body.respond_to?(:read)
61
- xml = post_request.body.read
66
+ xml = post_request.body.read
62
67
  post_request.body.rewind
63
68
  else
64
- xml = post_request.body
69
+ xml = post_request.body
65
70
  end
66
- request.process_xml(xml)
67
- request
71
+ self.process_xml(xml)
72
+ self
68
73
  end
69
74
 
70
75
  # POSTs the given score to the Tool Consumer with a replaceResult
@@ -125,7 +130,8 @@ module IMS::LTI
125
130
  generate_request_xml,
126
131
  'Content-Type' => 'application/xml'
127
132
  )
128
- @outcome_response = OutcomeResponse.from_post_response(res)
133
+ @outcome_response = extend_outcome_response(OutcomeResponse.new)
134
+ @outcome_response.process_post_response(res)
129
135
  end
130
136
 
131
137
  # Parse Outcome Request data from XML
@@ -142,9 +148,34 @@ module IMS::LTI
142
148
  @operation = REPLACE_REQUEST
143
149
  @score = doc.get_text("//resultRecord/result/resultScore/textString")
144
150
  end
151
+ extention_process_xml(doc)
145
152
  end
146
153
 
147
154
  private
155
+
156
+ def extention_process_xml(doc)
157
+ end
158
+
159
+ def has_result_data?
160
+ !!@score
161
+ end
162
+
163
+ def results(node)
164
+ return unless has_result_data?
165
+
166
+ node.result do |res|
167
+ result_values(res)
168
+ end
169
+ end
170
+
171
+ def result_values(node)
172
+ if @score
173
+ node.resultScore do |res_score|
174
+ res_score.language "en" # 'en' represents the format of the number
175
+ res_score.textString @score.to_s
176
+ end
177
+ end
178
+ end
148
179
 
149
180
  def has_required_attributes?
150
181
  @consumer_key && @consumer_secret && @lis_outcome_service_url && @lis_result_sourcedid && @operation
@@ -167,14 +198,7 @@ module IMS::LTI
167
198
  record.sourcedGUID do |guid|
168
199
  guid.sourcedId @lis_result_sourcedid
169
200
  end
170
- if @score
171
- record.result do |res|
172
- res.resultScore do |res_score|
173
- res_score.language "en" # 'en' represents the format of the number
174
- res_score.textString @score.to_s
175
- end
176
- end
177
- end
201
+ results(record)
178
202
  end
179
203
  end
180
204
  end
@@ -45,7 +45,8 @@ module IMS::LTI
45
45
  # res.generate_response_xml
46
46
  #
47
47
  class OutcomeResponse
48
-
48
+ include IMS::LTI::Extensions::Base
49
+
49
50
  attr_accessor :request_type, :score, :message_identifier, :response_code,
50
51
  :post_response, :code_major, :severity, :description, :operation,
51
52
  :message_ref_identifier
@@ -67,11 +68,15 @@ module IMS::LTI
67
68
  # req = IMS::LTI::OutcomeResponse.from_post_response(response)
68
69
  def self.from_post_response(post_response)
69
70
  response = OutcomeResponse.new
70
- response.post_response = post_response
71
- response.response_code = post_response.code
71
+ response.process_post_response(post_response)
72
+ end
73
+
74
+ def process_post_response(post_response)
75
+ self.post_response = post_response
76
+ self.response_code = post_response.code
72
77
  xml = post_response.body
73
- response.process_xml(xml)
74
- response
78
+ self.process_xml(xml)
79
+ self
75
80
  end
76
81
 
77
82
  def success?
@@ -163,19 +163,23 @@ module IMS::LTI
163
163
 
164
164
  if !@custom_params.empty?
165
165
  blti_node.tag!("blti:custom") do |custom_node|
166
- @custom_params.each_pair do |key, val|
166
+ @custom_params.keys.sort.each do |key|
167
+ val = @custom_params[key]
167
168
  custom_node.lticm :property, val, 'name' => key
168
169
  end
169
170
  end
170
171
  end
171
172
 
172
173
  if !@extensions.empty?
173
- @extensions.each_pair do |ext_platform, ext_params|
174
+ @extensions.keys.sort.each do |ext_platform|
175
+ ext_params = @extensions[ext_platform]
174
176
  blti_node.blti(:extensions, :platform => ext_platform) do |ext_node|
175
- ext_params.each_pair do |key, val|
177
+ ext_params.keys.sort.each do |key|
178
+ val = ext_params[key]
176
179
  if val.is_a?(Hash)
177
180
  ext_node.lticm(:options, :name => key) do |type_node|
178
- val.each_pair do |p_key, p_val|
181
+ val.keys.sort.each do |p_key|
182
+ p_val = val[p_key]
179
183
  type_node.lticm :property, p_val, 'name' => p_key
180
184
  end
181
185
  end
@@ -1,6 +1,7 @@
1
1
  module IMS::LTI
2
2
  # Class for implementing an LTI Tool Consumer
3
3
  class ToolConsumer
4
+ include IMS::LTI::Extensions::Base
4
5
  include IMS::LTI::LaunchParams
5
6
  include IMS::LTI::RequestValidator
6
7
 
@@ -20,6 +21,11 @@ module IMS::LTI
20
21
  @launch_url = params['launch_url']
21
22
  process_params(params)
22
23
  end
24
+
25
+ def process_post_request(post_request)
26
+ request = extend_outcome_request(OutcomeRequest.new)
27
+ request.process_post_request(post_request)
28
+ end
23
29
 
24
30
  # Set launch data from a ToolConfig
25
31
  #
@@ -34,6 +34,7 @@ module IMS::LTI
34
34
  # end
35
35
 
36
36
  class ToolProvider
37
+ include IMS::LTI::Extensions::Base
37
38
  include IMS::LTI::LaunchParams
38
39
  include IMS::LTI::RequestValidator
39
40
 
@@ -157,7 +158,8 @@ module IMS::LTI
157
158
  :consumer_secret => @consumer_secret,
158
159
  :lis_outcome_service_url => lis_outcome_service_url,
159
160
  :lis_result_sourcedid =>lis_result_sourcedid)
160
- @outcome_requests.last
161
+
162
+ extend_outcome_request(@outcome_requests.last)
161
163
  end
162
164
 
163
165
  end
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 2
10
- version: 1.0.2
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Instructure
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-03-14 00:00:00 Z
18
+ date: 2012-08-14 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: builder
@@ -98,10 +98,13 @@ extensions: []
98
98
  extra_rdoc_files:
99
99
  - LICENSE
100
100
  files:
101
+ - Changelog
101
102
  - LICENSE
102
103
  - README.md
103
104
  - lib/ims.rb
104
105
  - lib/ims/lti.rb
106
+ - lib/ims/lti/extensions.rb
107
+ - lib/ims/lti/extensions/outcome_data.rb
105
108
  - lib/ims/lti/launch_params.rb
106
109
  - lib/ims/lti/outcome_request.rb
107
110
  - lib/ims/lti/outcome_response.rb
@@ -145,3 +148,4 @@ specification_version: 3
145
148
  summary: Ruby library for creating IMS LTI tool providers and consumers
146
149
  test_files: []
147
150
 
151
+ has_rdoc: