learnosity-sdk 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d90f14540c688f96421e9cf91925e65868d316ada287955fa74083f0798db05
4
- data.tar.gz: c5a79a3bb3e1a2610c79210b65abbe0c83b5a25134cc5e60d9e6dedb98277953
3
+ metadata.gz: 3f6ae3d63eaf2bc30203e980a98b7636cefb5e760a9decf38b6b2f41650b8158
4
+ data.tar.gz: 94bdb3fa2b8bad774f4b06709aa89aba682c02ea871d28b51e3b02568ad11981
5
5
  SHA512:
6
- metadata.gz: ffa6fa9e7c1b1e1b8ebe549eb1e7e0e9e7f192bea5b76a96bd03f50561796461f1a946bde865271b3a36ab45d3eefa0ba5387636222e8cfa8690569ad117b196
7
- data.tar.gz: 36fd5d5a4787652eeae1916eac9d7b91a763ce2d543cb82dbd744577727cf7408a66057b805d5def84b26730dcadb96845943a181eab80fc202e8e82903403fc
6
+ metadata.gz: 7d9de43a26ce21bf8dc476357ecf35149ad4145836e83458575451b47e86eb1e28417fe4a5709f018afc58b8cd569e70a235006e014deba40a9d9d49ec04ab7d
7
+ data.tar.gz: 60d57e9e7366fd2809b0c82029f1a3583918abbb45deb0c259165b23a8d9c7f244c8bfee873fb8f91d0c9de67be20c95090e877a9a0af3912dc7a1c7323f7acb
data/.travis.yml CHANGED
@@ -2,10 +2,9 @@ sudo: false
2
2
  dist: focal
3
3
  language: ruby
4
4
  rvm:
5
- - '2.6'
6
- - '2.7'
7
5
  - '3.0'
8
6
  - '3.1'
7
+ - '3.2'
9
8
  before_install:
10
9
  # Login to docker hub
11
10
  - echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
data/CONTRIBUTING.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # Contributing
2
2
 
3
- Contribution in the form of [Issues] and [PRs] are welcome.
3
+ Contribution in the form of [PRs] are welcome.
4
+
5
+ ## Why We Are No Longer Accepting Public Issues
6
+ After careful consideration, we’ve decided to discontinue accepting issues via GitHub Issues for our public repositories.
7
+
8
+ Here’s why:
9
+
10
+ - We have established support channels specifically designed to handle customer inquiries and issues.
11
+ - These channels are staffed 24/7, and we work diligently to ensure prompt responses and high-quality support.
12
+ - Maintaining and responding to GitHub Issues requires significant resources, and we are unable to provide the same level of support through this channel as we do through our dedicated support teams.
13
+ - By focusing on our dedicated support channels, we can streamline our processes and offer a more effective and responsive service to our users.
14
+
15
+ For any issues or support needs, please use the existing support channels. This will help us provide you with the best possible assistance in a timely manner.
4
16
 
5
17
  ## Testing
6
18
 
data/ChangeLog.md CHANGED
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [v0.4.0] - 2026-01-07
11
+
12
+ ### Added
13
+
14
+ - UUID generation utility (`Learnosity::Sdk::Uuid.generate()`) for feature parity with Python and Node.js SDKs
15
+ - Data API support with dedicated `DataApi` class
16
+ - `request()` method for single authenticated Data API requests
17
+ - `request_iter()` method for iterating through paginated responses
18
+ - `results_iter()` method for iterating through individual results across pages
19
+ - Automatic routing metadata headers: `X-Learnosity-Consumer`, `X-Learnosity-Action`, `X-Learnosity-SDK`
20
+ - HTTP timeout configurations (15s open timeout, 60s read timeout)
21
+ - Data API demo added to Rails quickstart application
22
+ - Comprehensive unit and integration tests for Data API functionality
23
+ - Example usage in `examples/simple/data_api_example.rb`
24
+
25
+ ### Changed
26
+
27
+ - Updated documentation and examples to use `Learnosity::Sdk::Uuid.generate` instead of `SecureRandom.uuid`
28
+ - Improved error handling in Data API controller with detailed error messages and backtraces
29
+
30
+ ### Fixed
31
+
32
+ - Ruby 2.6 compatibility in Rails quickstart (commented out `spring` gems that require Ruby 2.7+)
33
+ - Rails 6.1 compatibility with Ruby 2.6 (added `require 'logger'` to `config/boot.rb`)
34
+ - Bumped 3rd party libraries to fix known vulnerabilities in the quick start application
35
+ - Fixed seed data for the api-reports example in the quick start application
36
+ - Code quality improvements addressing Codacy findings
37
+
10
38
  ## [v0.3.0] - 2024-07-12
11
39
  ### Added
12
40
  - Add support for api-authoraide.
data/README.md CHANGED
@@ -98,6 +98,9 @@ For production use, you should install the SDK using the RubyGems package manage
98
98
  Let's take a look at a simple example of the SDK in action. In this example, we'll load an assessment into the browser.
99
99
 
100
100
  ### **Start up your web server and view the standalone assessment example**
101
+
102
+ **Note:** The Rails quickstart supports Ruby 2.6+. The `spring` and `spring-watcher-listen` gems are commented out in the Gemfile for Ruby 2.6 compatibility. If you're using Ruby 2.7+, you can uncomment these gems for faster development reloading.
103
+
101
104
  To start up your Ruby web server, first find the following folder location under the SDK. Change directory ('cd') to this location on the command line.
102
105
 
103
106
  ``` bash
@@ -107,6 +110,7 @@ To start up your Ruby web server, first find the following folder location under
107
110
  To start, run this command from that folder:
108
111
 
109
112
  ``` bash
113
+ bundle install
110
114
  rails server
111
115
  ```
112
116
 
@@ -143,7 +147,7 @@ We start by including some LearnositySDK helpers in [items_controller.rb](docs/q
143
147
 
144
148
  ``` ruby
145
149
  require 'learnosity/sdk/request/init' # Learnosity helper.
146
- require 'securerandom' # Library for generating UUIDs.
150
+ require 'learnosity/sdk' # For UUID generation utility.
147
151
  ```
148
152
 
149
153
  Now we'll declare the configuration options for Items API. The following options specify which assessment content should be rendered, how it should be displayed, which user is taking this assessment and how their responses should be stored.
@@ -277,6 +281,9 @@ Take a look at some more in-depth options and tutorials on using Learnosity asse
277
281
  ### **SDK reference**
278
282
  See a more detailed breakdown of all the SDK features, and examples of how to use more advanced or specialised features on the [SDK reference page](REFERENCE.md).
279
283
 
284
+ ### **Data API support**
285
+ The SDK now includes comprehensive Data API support with automatic request signing, routing metadata headers, and pagination support. See the [Data API documentation](docs/DataApi.md) for detailed usage examples and API reference.
286
+
280
287
  ### **Additional quick start guides**
281
288
  There are more quick start guides, going beyond the initial quick start topic of loading an assessment, these further tutorials show how to set up authoring and analytics:
282
289
 
data/REFERENCE.md CHANGED
@@ -106,16 +106,67 @@ request = init.generate
106
106
  Net::HTTP.post_form URI('https://data.learnosity.com/v1/itembank/items'), request
107
107
  ```
108
108
 
109
- ### Recursive Queries
109
+ ### DataApi Class (Recommended)
110
110
 
111
- tl;dr: not currently implemented
111
+ The SDK now includes a dedicated `DataApi` class that provides a more convenient way to interact with the Data API, including automatic pagination support, routing metadata headers, and simplified request handling.
112
+
113
+ ```ruby
114
+ require 'learnosity/sdk/request/data_api'
115
+
116
+ # Initialize DataApi
117
+ data_api = Learnosity::Sdk::Request::DataApi.new(
118
+ consumer_key: 'your_consumer_key',
119
+ consumer_secret: 'your_consumer_secret',
120
+ domain: 'yourdomain.com'
121
+ )
122
+
123
+ security_packet = {
124
+ 'consumer_key' => 'your_consumer_key',
125
+ 'domain' => 'yourdomain.com'
126
+ }
127
+
128
+ # Make a single request
129
+ response = data_api.request(
130
+ 'https://data.learnosity.com/v1/itembank/items',
131
+ security_packet,
132
+ 'your_consumer_secret',
133
+ { 'limit' => 10 },
134
+ 'get'
135
+ )
136
+
137
+ # Iterate through all pages automatically
138
+ data_api.request_iter(
139
+ 'https://data.learnosity.com/v1/itembank/items',
140
+ security_packet,
141
+ 'your_consumer_secret',
142
+ { 'limit' => 100 },
143
+ 'get'
144
+ ).each do |page|
145
+ puts "Page has #{page['data'].length} items"
146
+ end
147
+
148
+ # Iterate through individual results
149
+ data_api.results_iter(
150
+ 'https://data.learnosity.com/v1/itembank/items',
151
+ security_packet,
152
+ 'your_consumer_secret',
153
+ { 'limit' => 100 },
154
+ 'get'
155
+ ).each do |item|
156
+ puts "Item: #{item['reference']}"
157
+ end
158
+ ```
159
+
160
+ See the [Data API documentation](docs/DataApi.md) for more details and examples.
161
+
162
+ ### Recursive Queries (Legacy Approach)
112
163
 
113
164
  Some requests are paginated to the `limit` passed in the request, or some
114
165
  server-side default. Responses to those requests contain a `next` parameter in
115
166
  their `meta` property, which can be placed in the next request to access another
116
167
  page of data.
117
168
 
118
- For the time being, you can iterate through pages by looping over the
169
+ You can iterate through pages by looping over the
119
170
  `Init#new`/`Init#generate`/`Net::HTTP#post_form`, updating the `next` attribute
120
171
  in the request.
121
172
 
@@ -129,12 +180,21 @@ end
129
180
 
130
181
  This will `require 'json'` to be able to parse the response.
131
182
 
183
+ **Note:** The new `DataApi` class (see above) handles pagination automatically and is the recommended approach.
184
+
132
185
  See `examples/simple/init_data.rb` for an example.
133
186
 
134
187
  ### Generating UUIDs
135
188
 
136
- You will need to generate UUIDs. You can use the Ruby `securerandom`
137
- module for this purpose.
189
+ You will need to generate UUIDs for user IDs and session IDs. The SDK provides a convenient utility for this purpose.
190
+
191
+ ```ruby
192
+ require 'learnosity/sdk'
193
+
194
+ p Learnosity::Sdk::Uuid.generate
195
+ ```
196
+
197
+ Alternatively, you can use the Ruby `securerandom` module directly:
138
198
 
139
199
  ```ruby
140
200
  require 'securerandom'
@@ -5,30 +5,29 @@ git_source(:github) do |repo_name|
5
5
  "https://github.com/#{repo_name}.git"
6
6
  end
7
7
 
8
-
9
8
  # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
10
- gem 'rails', '~> 5.0.2'
9
+ gem 'rails', '~> 6.1.0'
11
10
  # Use sqlite3 as the database for Active Record
12
- gem 'sqlite3', '~> 1.3.6'
11
+ gem 'sqlite3', '~> 1.6.3'
13
12
  # Use Puma as the app server
14
- gem 'puma', '~> 4.3.8'
13
+ gem 'puma', '~> 6.4.0'
15
14
  # Use SCSS for stylesheets
16
- gem 'sass-rails', '~> 5.0'
15
+ gem 'sass-rails', '~> 5.1.0'
17
16
  # Use Uglifier as compressor for JavaScript assets
18
- gem 'uglifier', '>= 1.3.0'
17
+ gem 'uglifier', '>= 4.2.0'
19
18
  # Use CoffeeScript for .coffee assets and views
20
- gem 'coffee-rails', '~> 4.2'
19
+ gem 'coffee-rails', '~> 5.0'
21
20
  # See https://github.com/rails/execjs#readme for more supported runtimes
22
- # gem 'therubyracer', platforms: :ruby
21
+ # gem 'mini_racer', platforms: :ruby
23
22
 
24
23
  # Use jquery as the JavaScript library
25
24
  gem 'jquery-rails'
26
25
  # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
27
26
  gem 'turbolinks', '~> 5'
28
27
  # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
29
- gem 'jbuilder', '~> 2.5'
28
+ gem 'jbuilder', '~> 2.11'
30
29
  # Use Redis adapter to run Action Cable in production
31
- # gem 'redis', '~> 3.0'
30
+ # gem 'redis', '~> 4.0'
32
31
  # Use ActiveModel has_secure_password
33
32
  # gem 'bcrypt', '~> 3.1.7'
34
33
 
@@ -42,11 +41,13 @@ end
42
41
 
43
42
  group :development do
44
43
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
45
- gem 'web-console', '>= 3.3.0'
46
- gem 'listen', '~> 3.0.5'
44
+ gem 'web-console', '>= 4.2.0'
45
+ gem 'listen', '~> 3.8'
47
46
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
48
- gem 'spring'
49
- gem 'spring-watcher-listen', '~> 2.0.0'
47
+ # Note: spring-watcher-listen requires Ruby >= 2.7.0, so it's commented out for Ruby 2.6 compatibility
48
+ # If you're using Ruby 2.7+, you can uncomment these lines for faster development reloading:
49
+ # gem 'spring'
50
+ # gem 'spring-watcher-listen', '~> 2.1.0'
50
51
  end
51
52
 
52
53
  # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
@@ -1,5 +1,5 @@
1
1
  require 'learnosity/sdk/request/init' # Learnosity helper.
2
- require 'securerandom'
2
+ require 'learnosity/sdk' # For UUID generation utility.
3
3
 
4
4
  class AuthorController < ApplicationController
5
5
 
@@ -7,7 +7,7 @@ class AuthorController < ApplicationController
7
7
  # XXX: This is a Learnosity Demos consumer; replace it with your own consumer key. Set values in application.rb.
8
8
  'consumer_key' => Rails.configuration.consumer_key,
9
9
  'domain' => 'localhost',
10
- 'user_id' => SecureRandom.uuid
10
+ 'user_id' => Learnosity::Sdk::Uuid.generate
11
11
  }
12
12
 
13
13
  # XXX: The consumer secret should be in a properly secured credential store, and *NEVER* checked into version control
@@ -0,0 +1,156 @@
1
+ require 'learnosity/sdk/request/data_api'
2
+ require 'json'
3
+
4
+ class DataApiController < ApplicationController
5
+ # rubocop:disable Metrics/CyclomaticComplexity
6
+ # Note: This is a demo/quickstart controller that intentionally demonstrates
7
+ # three different Data API usage patterns (manual iteration, page iteration,
8
+ # and results iteration) with comprehensive error handling for educational purposes.
9
+ def index
10
+ # Initialize DataApi
11
+ data_api = Learnosity::Sdk::Request::DataApi.new(
12
+ consumer_key: Rails.configuration.consumer_key,
13
+ consumer_secret: Rails.configuration.consumer_secret,
14
+ domain: 'localhost'
15
+ )
16
+
17
+ # Endpoint and security packet
18
+ itembank_uri = 'https://data.learnosity.com/latest-lts/itembank/items'
19
+ security_packet = {
20
+ 'consumer_key' => Rails.configuration.consumer_key,
21
+ 'domain' => 'localhost'
22
+ }
23
+
24
+ # Get SDK version
25
+ sdk_version = Learnosity::Sdk::VERSION
26
+
27
+ # Initialize request metadata
28
+ @request_metadata = {
29
+ endpoint: itembank_uri,
30
+ action: 'get',
31
+ status_code: nil,
32
+ headers: {
33
+ 'X-Learnosity-Consumer' => data_api.extract_consumer(security_packet),
34
+ 'X-Learnosity-Action' => data_api.derive_action(itembank_uri, 'get'),
35
+ 'X-Learnosity-SDK' => "Ruby:#{sdk_version}"
36
+ }
37
+ }
38
+
39
+ # Demo 1: Manual iteration (5 items)
40
+ @demo1_output = []
41
+ @demo1_error = nil
42
+
43
+ begin
44
+ data_request = { 'limit' => 1 }
45
+
46
+ 5.times do |i|
47
+ result = data_api.request(
48
+ itembank_uri,
49
+ security_packet,
50
+ Rails.configuration.consumer_secret,
51
+ data_request,
52
+ 'get'
53
+ )
54
+
55
+ # Capture status code from the first request
56
+ @request_metadata[:status_code] = result.code if i == 0
57
+
58
+ response = JSON.parse(result.body) rescue { 'raw_body' => result.body }
59
+
60
+ if response['data'] && response['data'].length > 0
61
+ item = response['data'][0]
62
+ @demo1_output << {
63
+ number: i + 1,
64
+ reference: item['reference'] || 'N/A',
65
+ status: item['status'] || 'N/A'
66
+ }
67
+ end
68
+
69
+ if response['meta'] && response['meta']['next']
70
+ data_request = { 'next' => response['meta']['next'] }
71
+ else
72
+ break
73
+ end
74
+ end
75
+ rescue => e
76
+ @demo1_error = {
77
+ error: "#{e.class}: #{e.message}",
78
+ backtrace: e.backtrace&.first(5)
79
+ }
80
+ end
81
+
82
+ # Demo 2: Page iteration (5 pages)
83
+ @demo2_output = []
84
+ @demo2_error = nil
85
+
86
+ begin
87
+ data_request = { 'limit' => 1 }
88
+ page_count = 0
89
+
90
+ data_api.request_iter(
91
+ itembank_uri,
92
+ security_packet,
93
+ Rails.configuration.consumer_secret,
94
+ data_request,
95
+ 'get'
96
+ ).each do |page|
97
+ page_count += 1
98
+ page_data = {
99
+ page_number: page_count,
100
+ item_count: page['data'] ? page['data'].length : 0,
101
+ items: []
102
+ }
103
+
104
+ if page['data']
105
+ page['data'].each do |item|
106
+ page_data[:items] << {
107
+ reference: item['reference'] || 'N/A',
108
+ status: item['status'] || 'N/A'
109
+ }
110
+ end
111
+ end
112
+
113
+ @demo2_output << page_data
114
+ break if page_count >= 5
115
+ end
116
+ rescue => e
117
+ @demo2_error = {
118
+ error: "#{e.class}: #{e.message}",
119
+ backtrace: e.backtrace&.first(5)
120
+ }
121
+ end
122
+
123
+ # Demo 3: Results iteration (5 items)
124
+ @demo3_output = []
125
+ @demo3_error = nil
126
+
127
+ begin
128
+ data_request = { 'limit' => 1 }
129
+ result_count = 0
130
+
131
+ data_api.results_iter(
132
+ itembank_uri,
133
+ security_packet,
134
+ Rails.configuration.consumer_secret,
135
+ data_request,
136
+ 'get'
137
+ ).each do |item|
138
+ result_count += 1
139
+ @demo3_output << {
140
+ number: result_count,
141
+ reference: item['reference'] || 'N/A',
142
+ status: item['status'] || 'N/A',
143
+ json: JSON.pretty_generate(item)[0..500]
144
+ }
145
+ break if result_count >= 5
146
+ end
147
+ rescue => e
148
+ @demo3_error = {
149
+ error: "#{e.class}: #{e.message}",
150
+ backtrace: e.backtrace&.first(5)
151
+ }
152
+ end
153
+ end
154
+ # rubocop:enable Metrics/CyclomaticComplexity
155
+ end
156
+
@@ -1,5 +1,5 @@
1
1
  require 'learnosity/sdk/request/init' # Learnosity helper.
2
- require 'securerandom' # Library for generating UUIDs.
2
+ require 'learnosity/sdk' # For UUID generation utility.
3
3
 
4
4
  class IndexController < ApplicationController
5
5
 
@@ -1,5 +1,5 @@
1
1
  require 'learnosity/sdk/request/init' # Learnosity helper.
2
- require 'securerandom' # Library for generating UUIDs.
2
+ require 'learnosity/sdk' # For UUID generation utility.
3
3
 
4
4
  class ItemsController < ApplicationController
5
5
  @@security_packet = {
@@ -12,9 +12,9 @@ class ItemsController < ApplicationController
12
12
  @@consumer_secret = Rails.configuration.consumer_secret
13
13
 
14
14
  @@items_request = {
15
- "user_id" => SecureRandom.uuid,
15
+ "user_id" => Learnosity::Sdk::Uuid.generate,
16
16
  "activity_template_id" => "quickstart_examples_activity_template_001",
17
- "session_id" => SecureRandom.uuid,
17
+ "session_id" => Learnosity::Sdk::Uuid.generate,
18
18
  "activity_id" => "quickstart_examples_activity_001",
19
19
  "rendering_type" => "assess",
20
20
  "type" => "submit_practice",
@@ -1,5 +1,5 @@
1
1
  require 'learnosity/sdk/request/init' # Learnosity helper.
2
- require 'securerandom'
2
+ require 'learnosity/sdk' # For UUID generation utility.
3
3
 
4
4
  class QuestionsController < ApplicationController
5
5
 
@@ -7,7 +7,7 @@ class QuestionsController < ApplicationController
7
7
  # XXX: This is a Learnosity Demos consumer; replace it with your own consumer key. Set values in application.rb.
8
8
  'consumer_key' => Rails.configuration.consumer_key,
9
9
  'domain' => 'localhost',
10
- 'user_id' => SecureRandom.uuid
10
+ 'user_id' => Learnosity::Sdk::Uuid.generate
11
11
  }
12
12
 
13
13
  # XXX: The consumer secret should be in a properly secured credential store, and *NEVER* checked into version control
@@ -1,5 +1,5 @@
1
1
  require 'learnosity/sdk/request/init' # Learnosity helper.
2
- require 'securerandom'
2
+ require 'learnosity/sdk' # For UUID generation utility.
3
3
 
4
4
  class ReportsController < ApplicationController
5
5
 
@@ -7,7 +7,7 @@ class ReportsController < ApplicationController
7
7
  # XXX: This is a Learnosity Demos consumer; replace it with your own consumer key. Set values in application.rb.
8
8
  'consumer_key' => Rails.configuration.consumer_key,
9
9
  'domain' => 'localhost',
10
- 'user_id' => SecureRandom.uuid
10
+ 'user_id' => Learnosity::Sdk::Uuid.generate
11
11
  }
12
12
 
13
13
  # XXX: The consumer secret should be in a properly secured credential store, and *NEVER* checked into version control
@@ -17,8 +17,8 @@ class ReportsController < ApplicationController
17
17
  "reports" => [{
18
18
  "id"=> "session-detail",
19
19
  "type"=> "session-detail-by-item",
20
- "user_id"=> "906d564c-39d4-44ba-8ddc-2d44066e2ba9",
21
- "session_id"=> "906d564c-39d4-44ba-8ddc-2d44066e2ba9"
20
+ "user_id"=> "student_0001",
21
+ "session_id"=> "ef4f80b8-e281-41f4-9efd-349b7eb9dd37"
22
22
  }]
23
23
  }
24
24
  def index
@@ -0,0 +1,277 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <style>
5
+ body {
6
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
7
+ max-width: 1200px;
8
+ margin: 0 auto;
9
+ padding: 20px;
10
+ background: #f5f5f5;
11
+ }
12
+ h1 {
13
+ color: #333;
14
+ border-bottom: 3px solid #667eea;
15
+ padding-bottom: 10px;
16
+ }
17
+ .demo-section {
18
+ background: #f8f9fa;
19
+ border-left: 4px solid #667eea;
20
+ padding: 20px;
21
+ margin: 20px 0;
22
+ border-radius: 4px;
23
+ }
24
+ .demo-section h2 {
25
+ margin-top: 0;
26
+ color: #333;
27
+ }
28
+ .item {
29
+ background: white;
30
+ padding: 15px;
31
+ margin: 10px 0;
32
+ border-radius: 4px;
33
+ border: 1px solid #dee2e6;
34
+ }
35
+ .item-reference {
36
+ font-family: monospace;
37
+ color: #667eea;
38
+ font-weight: bold;
39
+ margin-bottom: 5px;
40
+ }
41
+ .item-status {
42
+ color: #666;
43
+ font-size: 14px;
44
+ }
45
+ .meta-info {
46
+ background: #e7f3ff;
47
+ padding: 10px;
48
+ margin: 10px 0;
49
+ border-radius: 4px;
50
+ border-left: 3px solid #0066cc;
51
+ }
52
+ .error {
53
+ background: #f8d7da;
54
+ color: #721c24;
55
+ padding: 15px;
56
+ border-radius: 4px;
57
+ border-left: 4px solid #f5c6cb;
58
+ }
59
+ pre {
60
+ background: #2d2d2d;
61
+ color: #f8f8f2;
62
+ padding: 15px;
63
+ border-radius: 4px;
64
+ overflow-x: auto;
65
+ font-size: 12px;
66
+ }
67
+ code {
68
+ background: #f4f4f4;
69
+ padding: 2px 6px;
70
+ border-radius: 3px;
71
+ font-family: monospace;
72
+ }
73
+ .note-box {
74
+ background: #d1ecf1;
75
+ border: 1px solid #bee5eb;
76
+ border-radius: 4px;
77
+ padding: 15px;
78
+ margin: 20px 0;
79
+ color: #0c5460;
80
+ }
81
+ .note-box strong {
82
+ display: block;
83
+ margin-bottom: 5px;
84
+ }
85
+ .api-responses {
86
+ margin: 20px 0;
87
+ }
88
+ .api-responses h2 {
89
+ color: #333;
90
+ margin-bottom: 15px;
91
+ }
92
+ .request-info {
93
+ background: #4a90e2;
94
+ color: white;
95
+ padding: 12px 20px;
96
+ border-radius: 4px 4px 0 0;
97
+ font-weight: bold;
98
+ margin-top: 20px;
99
+ }
100
+ .request-details {
101
+ background: white;
102
+ border: 1px solid #dee2e6;
103
+ border-top: none;
104
+ }
105
+ .request-row {
106
+ display: flex;
107
+ border-bottom: 1px solid #dee2e6;
108
+ }
109
+ .request-row:last-child {
110
+ border-bottom: none;
111
+ }
112
+ .request-label {
113
+ background: #f8f9fa;
114
+ padding: 12px 20px;
115
+ font-weight: bold;
116
+ width: 150px;
117
+ border-right: 1px solid #dee2e6;
118
+ }
119
+ .request-value {
120
+ padding: 12px 20px;
121
+ flex: 1;
122
+ font-family: monospace;
123
+ word-break: break-all;
124
+ }
125
+ .request-value.success {
126
+ color: #28a745;
127
+ font-weight: bold;
128
+ }
129
+ .request-value.url {
130
+ color: #e83e8c;
131
+ }
132
+ .request-value.action {
133
+ color: #e83e8c;
134
+ }
135
+ .metadata-headers {
136
+ background: #5cb85c;
137
+ color: white;
138
+ padding: 12px 20px;
139
+ border-radius: 4px 4px 0 0;
140
+ font-weight: bold;
141
+ margin-top: 20px;
142
+ }
143
+ .metadata-note {
144
+ background: white;
145
+ border: 1px solid #dee2e6;
146
+ border-top: none;
147
+ padding: 12px 20px;
148
+ color: #666;
149
+ font-size: 14px;
150
+ }
151
+ .header-row {
152
+ display: flex;
153
+ border-bottom: 1px solid #dee2e6;
154
+ background: white;
155
+ }
156
+ .header-row:last-child {
157
+ border-bottom: none;
158
+ }
159
+ .header-name {
160
+ background: #f8f9fa;
161
+ padding: 12px 20px;
162
+ font-weight: bold;
163
+ width: 250px;
164
+ border-right: 1px solid #dee2e6;
165
+ color: #e83e8c;
166
+ }
167
+ .header-value {
168
+ padding: 12px 20px;
169
+ flex: 1;
170
+ font-family: monospace;
171
+ word-break: break-all;
172
+ }
173
+ </style>
174
+ </head>
175
+ <body>
176
+ <h1>Data API Example - With Metadata Headers</h1>
177
+
178
+ <div class="note-box">
179
+ <strong>Note:</strong> This example demonstrates the Data API with automatic metadata headers. Every request includes consumer, action, and SDK language-version information.
180
+ </div>
181
+
182
+ <% if @request_metadata %>
183
+ <div class="api-responses">
184
+ <h2>API Responses</h2>
185
+
186
+ <div class="request-info">Request Information</div>
187
+ <div class="request-details">
188
+ <div class="request-row">
189
+ <div class="request-label">Endpoint</div>
190
+ <div class="request-value url"><%= @request_metadata[:endpoint] %></div>
191
+ </div>
192
+ <div class="request-row">
193
+ <div class="request-label">Action</div>
194
+ <div class="request-value action"><%= @request_metadata[:action] %></div>
195
+ </div>
196
+ <div class="request-row">
197
+ <div class="request-label">Status Code</div>
198
+ <div class="request-value success"><%= @request_metadata[:status_code] || 'N/A' %></div>
199
+ </div>
200
+ </div>
201
+
202
+ <div class="metadata-headers">Metadata Headers (Sent Automatically)</div>
203
+ <div class="metadata-note">
204
+ These headers are added automatically by the SDK and are invisible to customers:
205
+ </div>
206
+ <div class="request-details">
207
+ <div class="header-row">
208
+ <div class="header-name">X-Learnosity-Consumer</div>
209
+ <div class="header-value"><%= @request_metadata[:headers]['X-Learnosity-Consumer'] %></div>
210
+ </div>
211
+ <div class="header-row">
212
+ <div class="header-name">X-Learnosity-Action</div>
213
+ <div class="header-value"><%= @request_metadata[:headers]['X-Learnosity-Action'] %></div>
214
+ </div>
215
+ <div class="header-row">
216
+ <div class="header-name">X-Learnosity-SDK</div>
217
+ <div class="header-value"><%= @request_metadata[:headers]['X-Learnosity-SDK'] %></div>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ <% end %>
222
+
223
+ <div class="demo-section">
224
+ <h2>Demo 1: Manual Iteration (5 items)</h2>
225
+ <p>Using <code>request()</code> method with manual pagination via the 'next' pointer.</p>
226
+ <% if @demo1_error %>
227
+ <div class="error">Error: <%= @demo1_error %></div>
228
+ <% else %>
229
+ <% @demo1_output.each do |item| %>
230
+ <div class="item">
231
+ <div class="item-reference">Item <%= item[:number] %>: <%= item[:reference] %></div>
232
+ <div class="item-status">Status: <%= item[:status] %></div>
233
+ </div>
234
+ <% end %>
235
+ <% end %>
236
+ </div>
237
+
238
+ <div class="demo-section">
239
+ <h2>Demo 2: Page Iteration (5 pages)</h2>
240
+ <p>Using <code>request_iter()</code> method to automatically iterate over pages.</p>
241
+ <% if @demo2_error %>
242
+ <div class="error">Error: <%= @demo2_error %></div>
243
+ <% else %>
244
+ <% @demo2_output.each do |page| %>
245
+ <div class="meta-info">
246
+ Page <%= page[:page_number] %>: <%= page[:item_count] %> items
247
+ </div>
248
+ <% page[:items].each do |item| %>
249
+ <div class="item">
250
+ <div class="item-reference"><%= item[:reference] %></div>
251
+ <div class="item-status">Status: <%= item[:status] %></div>
252
+ </div>
253
+ <% end %>
254
+ <% end %>
255
+ <% end %>
256
+ </div>
257
+
258
+ <div class="demo-section">
259
+ <h2>Demo 3: Results Iteration (5 items)</h2>
260
+ <p>Using <code>results_iter()</code> method to automatically iterate over individual items.</p>
261
+ <% if @demo3_error %>
262
+ <div class="error">Error: <%= @demo3_error %></div>
263
+ <% else %>
264
+ <% @demo3_output.each do |item| %>
265
+ <div class="item">
266
+ <div class="item-reference">Item <%= item[:number] %>: <%= h item[:reference] %></div>
267
+ <div class="item-status">Status: <%= h item[:status] %></div>
268
+ <pre><%= h item[:json] %>...</pre>
269
+ </div>
270
+ <% end %>
271
+ <% end %>
272
+ </div>
273
+
274
+ <p><a href="/">Back to API Examples</a></p>
275
+ </body>
276
+ </html>
277
+
@@ -33,6 +33,10 @@
33
33
  <td>Authoraide API</td>
34
34
  <td><%=link_to("Here", authoraide_index_path, target: '_blank') %> </td>
35
35
  </tr>
36
+ <tr>
37
+ <td>Data API</td>
38
+ <td><%=link_to("Here", data_api_index_path, target: '_blank') %> </td>
39
+ </tr>
36
40
  </table>
37
41
  </body>
38
42
  </html>
@@ -1,3 +1,7 @@
1
1
  ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2
2
 
3
3
  require 'bundler/setup' # Set up gems listed in the Gemfile.
4
+
5
+ # Fix for Ruby 2.6 compatibility with Rails 6.1
6
+ # Rails 6.1 expects Logger to be available before ActiveSupport loads
7
+ require 'logger'
@@ -18,7 +18,8 @@ ActiveSupport.to_time_preserves_timezone = true
18
18
  Rails.application.config.active_record.belongs_to_required_by_default = true
19
19
 
20
20
  # Do not halt callback chains when a callback returns false. Previous versions had true.
21
- ActiveSupport.halt_callback_chains_on_return_false = false
21
+ # This option is not applicable for Rails 6.1 and has been removed.
22
+ # ActiveSupport.halt_callback_chains_on_return_false = false
22
23
 
23
24
  # Configure SSL options to enable HSTS with subdomains. Previous versions had false.
24
25
  Rails.application.config.ssl_options = { hsts: { subdomains: true } }
@@ -6,6 +6,7 @@ Rails.application.routes.draw do
6
6
  get 'authoraide/index' , as: 'authoraide_index'
7
7
  get 'reports/index', as: 'reports_index'
8
8
  get 'items/index', as: 'items_index'
9
+ get 'data_api/index', as: 'data_api_index'
9
10
  # get 'abc' , to: "index#index"
10
11
 
11
12
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+ require 'learnosity/sdk/request/data_api'
3
+
4
+ # Configuration
5
+ # XXX: This is a Learnosity Demos consumer; replace it with your own consumer key
6
+ consumer_key = 'yis0TYCu7U9V4o7M'
7
+ # XXX: The consumer secret should be in a properly secured credential store, and *NEVER* checked into version control
8
+ consumer_secret = '74c5fd430cf1242a527f6223aebd42d30464be22'
9
+ domain = 'localhost'
10
+
11
+ # Initialize DataApi
12
+ data_api = Learnosity::Sdk::DataApi.new(
13
+ consumer_key: consumer_key,
14
+ consumer_secret: consumer_secret,
15
+ domain: domain
16
+ )
17
+
18
+ # Security packet
19
+ security_packet = {
20
+ 'consumer_key' => consumer_key,
21
+ 'domain' => domain
22
+ }
23
+
24
+ # Endpoint
25
+ endpoint = 'https://data.learnosity.com/v1/itembank/items'
26
+
27
+ puts "=== Example 1: Single Request ==="
28
+ puts
29
+
30
+ # Make a single request
31
+ response = data_api.request(
32
+ endpoint,
33
+ security_packet,
34
+ consumer_secret,
35
+ { 'limit' => 5 },
36
+ 'get'
37
+ )
38
+
39
+ puts "Status: #{response.code}"
40
+ data = JSON.parse(response.body)
41
+ puts "Records: #{data['meta']['records']}"
42
+ puts "Items returned: #{data['data'].length}"
43
+ puts
44
+
45
+ puts "=== Example 2: Iterate Through Pages ==="
46
+ puts
47
+
48
+ # Iterate through pages (up to 3 pages)
49
+ page_count = 0
50
+ data_api.request_iter(
51
+ endpoint,
52
+ security_packet,
53
+ consumer_secret,
54
+ { 'limit' => 5 },
55
+ 'get'
56
+ ).each do |page|
57
+ page_count += 1
58
+ puts "Page #{page_count}: #{page['data'].length} items"
59
+ break if page_count >= 3
60
+ end
61
+ puts
62
+
63
+ puts "=== Example 3: Iterate Through Individual Results ==="
64
+ puts
65
+
66
+ # Iterate through individual results (up to 10 items)
67
+ item_count = 0
68
+ data_api.results_iter(
69
+ endpoint,
70
+ security_packet,
71
+ consumer_secret,
72
+ { 'limit' => 5 },
73
+ 'get'
74
+ ).each do |item|
75
+ item_count += 1
76
+ puts "Item #{item_count}: #{item['reference'] || item['id'] || 'N/A'}"
77
+ break if item_count >= 10
78
+ end
79
+ puts
80
+
81
+ puts "Done!"
82
+
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+ require 'learnosity/sdk'
4
+
5
+ # Example: Generate UUIDs using the Learnosity SDK utility
6
+ # This is commonly used for user_id and session_id in API requests
7
+
8
+ puts "Generating UUIDs using Learnosity::Sdk::Uuid.generate:"
9
+ puts
10
+
11
+ # Generate a few UUIDs
12
+ 5.times do |i|
13
+ uuid = Learnosity::Sdk::Uuid.generate
14
+ puts "UUID #{i + 1}: #{uuid}"
15
+ end
16
+
17
+ puts
18
+ puts "Example usage in API requests:"
19
+ puts
20
+
21
+ # Example: Using UUIDs in an Items API request
22
+ user_id = Learnosity::Sdk::Uuid.generate
23
+ session_id = Learnosity::Sdk::Uuid.generate
24
+
25
+ puts "user_id: #{user_id}"
26
+ puts "session_id: #{session_id}"
27
+
28
+ # vim: sw=2
29
+
@@ -0,0 +1,196 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'learnosity/sdk/request/init'
5
+ require 'learnosity/sdk/version'
6
+
7
+ module Learnosity
8
+ module Sdk
9
+ module Request
10
+ # DataApi - Routing layer for Learnosity Data API
11
+ #
12
+ # Provides methods to make HTTP requests to the Data API with automatic
13
+ # signing and pagination support.
14
+ class DataApi
15
+ attr_reader :consumer_key, :consumer_secret, :domain
16
+
17
+ # Initialize a new DataApi instance
18
+ #
19
+ # @param options [Hash] Configuration options
20
+ # @option options [String] :consumer_key Learnosity consumer key
21
+ # @option options [String] :consumer_secret Learnosity consumer secret
22
+ # @option options [String] :domain Domain for security packet
23
+ # @option options [Proc] :http_adapter Optional custom HTTP adapter
24
+ def initialize(options = {})
25
+ @consumer_key = options[:consumer_key]
26
+ @consumer_secret = options[:consumer_secret]
27
+ @domain = options[:domain]
28
+ @http_adapter = options[:http_adapter] || method(:default_http_adapter)
29
+ end
30
+
31
+ # Make a single request to Data API
32
+ #
33
+ # @param endpoint [String] Full URL to the Data API endpoint
34
+ # @param security_packet [Hash] Security object with consumer_key and domain
35
+ # @param secret [String] Consumer secret
36
+ # @param request_packet [Hash] Request parameters (default: {})
37
+ # @param action [String] Action type: 'get', 'set', 'update', 'delete' (default: 'get')
38
+ # @return [Net::HTTPResponse] HTTP response object
39
+ def request(endpoint, security_packet, secret, request_packet = {}, action = 'get')
40
+ # Generate signed request using SDK
41
+ init = Init.new('data', security_packet, secret, request_packet, action)
42
+ signed_request = init.generate
43
+
44
+ # Extract metadata for routing
45
+ consumer = extract_consumer(security_packet)
46
+ derived_action = derive_action(endpoint, action)
47
+
48
+ # Prepare headers with routing metadata
49
+ headers = {
50
+ 'Content-Type' => 'application/x-www-form-urlencoded',
51
+ 'X-Learnosity-Consumer' => consumer,
52
+ 'X-Learnosity-Action' => derived_action,
53
+ 'X-Learnosity-SDK' => "Ruby:#{Learnosity::Sdk::VERSION}"
54
+ }
55
+
56
+ # Make HTTP request using adapter
57
+ @http_adapter.call(endpoint, signed_request, headers)
58
+ end
59
+
60
+ # Iterate over pages of results from Data API
61
+ #
62
+ # @param endpoint [String] Full URL to the Data API endpoint
63
+ # @param security_packet [Hash] Security object
64
+ # @param secret [String] Consumer secret
65
+ # @param request_packet [Hash] Request parameters (default: {})
66
+ # @param action [String] Action type (default: 'get')
67
+ # @return [Enumerator] Enumerator yielding pages of results
68
+ def request_iter(endpoint, security_packet, secret, request_packet = {}, action = 'get')
69
+ Enumerator.new do |yielder|
70
+ # Deep copy to avoid mutation
71
+ security = deep_copy(security_packet)
72
+ request_params = deep_copy(request_packet)
73
+ data_end = false
74
+
75
+ until data_end
76
+ response = self.request(endpoint, security, secret, request_params, action)
77
+ validate_response(response)
78
+
79
+ data = parse_response_body(response)
80
+ validate_response_status(data)
81
+
82
+ data_end = !has_more_pages?(data)
83
+ request_params['next'] = data['meta']['next'] if data['meta'] && data['meta']['next']
84
+
85
+ yielder << data
86
+ end
87
+ end
88
+ end
89
+
90
+ # Iterate over individual results from Data API
91
+ #
92
+ # Automatically handles pagination and yields each individual result
93
+ # from the data array.
94
+ #
95
+ # @param endpoint [String] Full URL to the Data API endpoint
96
+ # @param security_packet [Hash] Security object
97
+ # @param secret [String] Consumer secret
98
+ # @param request_packet [Hash] Request parameters (default: {})
99
+ # @param action [String] Action type (default: 'get')
100
+ # @return [Enumerator] Enumerator yielding individual results
101
+ def results_iter(endpoint, security_packet, secret, request_packet = {}, action = 'get')
102
+ Enumerator.new do |yielder|
103
+ request_iter(endpoint, security_packet, secret, request_packet, action).each do |page|
104
+ if page['data'].is_a?(Hash)
105
+ # If data is a hash (not array), yield key-value pairs
106
+ page['data'].each do |key, value|
107
+ yielder << { key => value }
108
+ end
109
+ elsif page['data'].is_a?(Array)
110
+ # If data is an array, yield each item
111
+ page['data'].each do |result|
112
+ yielder << result
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ # Extract consumer key from security packet
120
+ def extract_consumer(security_packet)
121
+ security_packet['consumer_key'] || security_packet[:consumer_key] || ''
122
+ end
123
+
124
+ # Derive action metadata from endpoint and action
125
+ def derive_action(endpoint, action)
126
+ uri = URI.parse(endpoint)
127
+ path = uri.path.sub(/\/$/, '')
128
+
129
+ # Remove version prefix (e.g., /v1, /v2023.1.LTS, /latest)
130
+ path_parts = path.split('/')
131
+
132
+ if path_parts.length > 1
133
+ first_segment = path_parts[1].downcase
134
+ version_pattern = /^v[\d.]+(?:\.(lts|preview\d+))?$/
135
+ special_versions = ['latest', 'latest-lts', 'developer']
136
+
137
+ if version_pattern.match?(first_segment) || special_versions.include?(first_segment)
138
+ path = '/' + path_parts[2..-1].join('/')
139
+ end
140
+ end
141
+
142
+ "#{action}_#{path}"
143
+ end
144
+
145
+ private
146
+
147
+ # Default HTTP adapter using Net::HTTP
148
+ def default_http_adapter(endpoint, signed_request, headers)
149
+ uri = URI.parse(endpoint)
150
+ http = Net::HTTP.new(uri.host, uri.port)
151
+ http.use_ssl = (uri.scheme == 'https')
152
+ http.open_timeout = 15 # seconds to establish connection
153
+ http.read_timeout = 60 # seconds to read response
154
+
155
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
156
+ request.set_form_data(signed_request)
157
+
158
+ http.request(request)
159
+ end
160
+
161
+ # Deep copy a hash to avoid mutation
162
+ # Using JSON serialization instead of Marshal for security
163
+ def deep_copy(obj)
164
+ JSON.parse(JSON.generate(obj))
165
+ end
166
+
167
+ # Validate HTTP response
168
+ def validate_response(response)
169
+ return if response.is_a?(Net::HTTPSuccess)
170
+ raise "Server returned HTTP status #{response.code}: #{response.body}"
171
+ end
172
+
173
+ # Parse response body as JSON
174
+ def parse_response_body(response)
175
+ JSON.parse(response.body)
176
+ rescue JSON::ParserError
177
+ raise "Server returned invalid JSON: #{response.body}"
178
+ end
179
+
180
+ # Validate response has successful status
181
+ def validate_response_status(data)
182
+ return if data.dig('meta', 'status') == true
183
+ raise "Server returned unsuccessful status: #{data.to_json}"
184
+ end
185
+
186
+ # Check if there are more pages to fetch
187
+ def has_more_pages?(data)
188
+ data['meta'] && data['meta']['next'] && data['data'] && !data['data'].empty?
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ # vim: sw=2
196
+
@@ -0,0 +1,21 @@
1
+ require 'securerandom'
2
+
3
+ module Learnosity
4
+ module Sdk
5
+ module Utils
6
+ # UUID utility for generating UUIDv4 identifiers
7
+ # Commonly used for user_id and session_id in Learnosity API requests
8
+ class Uuid
9
+ # Generate a UUIDv4 string
10
+ #
11
+ # @return [String] A UUIDv4 string
12
+ def self.generate
13
+ SecureRandom.uuid
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # vim: sw=2
21
+
@@ -1,5 +1,5 @@
1
1
  module Learnosity
2
2
  module Sdk
3
- VERSION = "0.3.0"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -1,8 +1,14 @@
1
1
  require "learnosity/sdk/version"
2
+ require "learnosity/sdk/request/data_api"
3
+ require "learnosity/sdk/utils/uuid"
2
4
 
3
5
  module Learnosity
4
6
  module Sdk
5
- # Your code goes here...
7
+ # Export DataApi class for convenient access
8
+ DataApi = Request::DataApi
9
+
10
+ # Export Uuid utility for convenient access
11
+ Uuid = Utils::Uuid
6
12
  end
7
13
  end
8
14
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: learnosity-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Olivier Mehani
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2024-07-22 00:00:00.000000000 Z
12
+ date: 2026-01-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sys-uname
@@ -116,6 +116,7 @@ files:
116
116
  - docs/quickstart/lrn-sdk-rails/app/controllers/author_controller.rb
117
117
  - docs/quickstart/lrn-sdk-rails/app/controllers/authoraide_controller.rb
118
118
  - docs/quickstart/lrn-sdk-rails/app/controllers/concerns/.keep
119
+ - docs/quickstart/lrn-sdk-rails/app/controllers/data_api_controller.rb
119
120
  - docs/quickstart/lrn-sdk-rails/app/controllers/index_controller.rb
120
121
  - docs/quickstart/lrn-sdk-rails/app/controllers/items_controller.rb
121
122
  - docs/quickstart/lrn-sdk-rails/app/controllers/questions_controller.rb
@@ -128,6 +129,7 @@ files:
128
129
  - docs/quickstart/lrn-sdk-rails/app/models/concerns/.keep
129
130
  - docs/quickstart/lrn-sdk-rails/app/views/author/index.html.erb
130
131
  - docs/quickstart/lrn-sdk-rails/app/views/authoraide/index.html.erb
132
+ - docs/quickstart/lrn-sdk-rails/app/views/data_api/index.html.erb
131
133
  - docs/quickstart/lrn-sdk-rails/app/views/index/index.html.erb
132
134
  - docs/quickstart/lrn-sdk-rails/app/views/items/index.html.erb
133
135
  - docs/quickstart/lrn-sdk-rails/app/views/layouts/application.html.erb
@@ -188,14 +190,18 @@ files:
188
190
  - docs/quickstart/lrn-sdk-rails/tmp/.keep
189
191
  - docs/quickstart/lrn-sdk-rails/vendor/assets/javascripts/.keep
190
192
  - docs/quickstart/lrn-sdk-rails/vendor/assets/stylesheets/.keep
193
+ - examples/simple/data_api_example.rb
191
194
  - examples/simple/init_data.rb
192
195
  - examples/simple/init_items.rb
196
+ - examples/simple/uuid_example.rb
193
197
  - learnosity-sdk.gemspec
194
198
  - lib/learnosity/sdk.rb
195
199
  - lib/learnosity/sdk/exceptions.rb
196
200
  - lib/learnosity/sdk/request.rb
201
+ - lib/learnosity/sdk/request/data_api.rb
197
202
  - lib/learnosity/sdk/request/init.rb
198
203
  - lib/learnosity/sdk/utils.rb
204
+ - lib/learnosity/sdk/utils/uuid.rb
199
205
  - lib/learnosity/sdk/version.rb
200
206
  homepage: https://github.com/Learnosity/learnosity-sdk-ruby/
201
207
  licenses:
@@ -217,7 +223,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
223
  - !ruby/object:Gem::Version
218
224
  version: '0'
219
225
  requirements: []
220
- rubygems_version: 3.3.27
226
+ rubygems_version: 3.4.10
221
227
  signing_key:
222
228
  specification_version: 4
223
229
  summary: SDK to interact with Learnosity APIs