rails-xapi 0.1.2
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +152 -0
- data/Rakefile +8 -0
- data/app/controllers/rails_xapi/application_controller.rb +7 -0
- data/app/helpers/rails_xapi/application_helper.rb +26 -0
- data/app/jobs/rails_xapi/application_job.rb +4 -0
- data/app/jobs/rails_xapi/create_statement_job.rb +11 -0
- data/app/mailers/rails_xapi/application_mailer.rb +6 -0
- data/app/models/concerns/serializable.rb +14 -0
- data/app/models/rails_xapi/account.rb +27 -0
- data/app/models/rails_xapi/activity_definition.rb +143 -0
- data/app/models/rails_xapi/actor.rb +227 -0
- data/app/models/rails_xapi/application_record.rb +5 -0
- data/app/models/rails_xapi/context.rb +163 -0
- data/app/models/rails_xapi/context_activity.rb +37 -0
- data/app/models/rails_xapi/errors/xapi_error.rb +4 -0
- data/app/models/rails_xapi/extension.rb +29 -0
- data/app/models/rails_xapi/group_member.rb +21 -0
- data/app/models/rails_xapi/interaction_activity.rb +103 -0
- data/app/models/rails_xapi/interaction_component.rb +34 -0
- data/app/models/rails_xapi/object.rb +150 -0
- data/app/models/rails_xapi/result.rb +174 -0
- data/app/models/rails_xapi/statement.rb +62 -0
- data/app/models/rails_xapi/validators/language_map_validator.rb +64 -0
- data/app/models/rails_xapi/verb.rb +260 -0
- data/app/services/application_service.rb +16 -0
- data/app/services/rails_xapi/query.rb +217 -0
- data/app/services/rails_xapi/statement_creator.rb +53 -0
- data/app/views/layouts/rails_xapi/application.html.erb +12 -0
- data/config/locales/rails_xapi.en.yml +43 -0
- data/config/locales/rails_xapi.fr.yml +233 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20240716144226_create_rails_xapi_actors.rb +16 -0
- data/db/migrate/20240716144227_create_rails_xapi_accounts.rb +15 -0
- data/db/migrate/20240716144228_create_rails_xapi_verbs.rb +14 -0
- data/db/migrate/20240716144229_create_rails_xapi_objects.rb +16 -0
- data/db/migrate/20240716144230_create_rails_xapi_activity_definitions.rb +17 -0
- data/db/migrate/20240716144231_create_rails_xapi_extensions.rb +13 -0
- data/db/migrate/20240716144232_create_rails_xapi_statements.rb +19 -0
- data/db/migrate/20240716144233_create_rails_xapi_results.rb +21 -0
- data/db/migrate/20240716144234_create_rails_xapi_contexts.rb +23 -0
- data/db/migrate/20240716144235_create_rails_xapi_context_activities.rb +16 -0
- data/db/migrate/20240716144236_create_rails_xapi_group_members.rb +16 -0
- data/db/migrate/20250522093846_create_rails_xapi_interaction_activities.rb +15 -0
- data/db/migrate/20250522122830_create_rails_xapi_interaction_component.rb +16 -0
- data/lib/rails-xapi/configuration.rb +13 -0
- data/lib/rails-xapi/engine.rb +28 -0
- data/lib/rails-xapi/version.rb +5 -0
- data/lib/rails-xapi.rb +16 -0
- data/lib/tasks/auto_annotate_models.rake +62 -0
- data/lib/tasks/rails-xapi_tasks.rake +4 -0
- metadata +122 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Represents a result containing response, success, and score data.
|
4
|
+
class RailsXapi::Result < ApplicationRecord
|
5
|
+
require "active_support/duration"
|
6
|
+
require "uri"
|
7
|
+
include Serializable
|
8
|
+
|
9
|
+
belongs_to :statement, class_name: "RailsXapi::Statement", dependent: :destroy
|
10
|
+
has_many :extensions, as: :extendable, dependent: :destroy
|
11
|
+
|
12
|
+
attr_reader :duration_in_seconds
|
13
|
+
|
14
|
+
validates :score_scaled,
|
15
|
+
numericality: {
|
16
|
+
greater_than_or_equal_to: -1,
|
17
|
+
less_than_or_equal_to: 1
|
18
|
+
},
|
19
|
+
allow_nil: true
|
20
|
+
validate :completion_attribute_must_be_boolean, if: -> { completion.present? }
|
21
|
+
validate :success_attribute_must_be_boolean, if: -> { success.present? }
|
22
|
+
validate :correct_duration, if: -> { duration.present? }
|
23
|
+
|
24
|
+
before_validation :calculate_scaled
|
25
|
+
|
26
|
+
# Store the score object in the results table for convenience reasons.
|
27
|
+
#
|
28
|
+
# @param [Hash] value The result hash values.
|
29
|
+
def score=(value)
|
30
|
+
validate_score(value)
|
31
|
+
|
32
|
+
self.score_scaled = value[:scaled]
|
33
|
+
self.score_raw = value[:raw]
|
34
|
+
self.score_min = value[:min]
|
35
|
+
self.score_max = value[:max]
|
36
|
+
end
|
37
|
+
|
38
|
+
def score
|
39
|
+
{
|
40
|
+
scaled: score_scaled,
|
41
|
+
raw: score_raw,
|
42
|
+
min: score_min,
|
43
|
+
max: score_max
|
44
|
+
}.compact
|
45
|
+
end
|
46
|
+
|
47
|
+
# Transform a duration in seconds into a ISO 8601 string.
|
48
|
+
# This is an optional attribute meant to bring more convenience for some systems.
|
49
|
+
#
|
50
|
+
# @param [String|Number] value The duration in seconds.
|
51
|
+
def duration_in_seconds=(value)
|
52
|
+
self.duration =
|
53
|
+
ActiveSupport::Duration.build(value).iso8601 if value.present?
|
54
|
+
end
|
55
|
+
|
56
|
+
def extensions=(extensions_data)
|
57
|
+
unless extensions_data.is_a?(Hash)
|
58
|
+
raise RailsXapi::Errors::XapiError,
|
59
|
+
I18n.t(
|
60
|
+
"rails_xapi.errors.attribute_must_be_a_hash",
|
61
|
+
name: "extensions"
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
extensions_data.each do |iri, data|
|
66
|
+
extensions.build(iri: iri, value: serialized_value(data))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Validations in regard to the score
|
73
|
+
# See: https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#2451-score
|
74
|
+
#
|
75
|
+
# @param [Hash] value The result's score hash values.
|
76
|
+
def validate_score(value)
|
77
|
+
if value[:scaled].present? && !value[:scaled]&.between?(-1, 1)
|
78
|
+
raise RailsXapi::Errors::XapiError,
|
79
|
+
I18n.t(
|
80
|
+
"rails_xapi.errors.invalid_score_value",
|
81
|
+
value: I18n.t("rails_xapi.validations.score.scaled")
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
min_value = value[:min].to_i if value[:min].present?
|
86
|
+
max_value = value[:max].to_i if value[:max].present?
|
87
|
+
|
88
|
+
if value[:raw].present? &&
|
89
|
+
!value[:raw]&.between?(
|
90
|
+
min_value || -Float::INFINITY,
|
91
|
+
max_value || Float::INFINITY
|
92
|
+
)
|
93
|
+
raise RailsXapi::Errors::XapiError,
|
94
|
+
I18n.t(
|
95
|
+
"rails_xapi.errors.invalid_score_value",
|
96
|
+
value: I18n.t("rails_xapi.validations.score.raw")
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
if max_value.present? && min_value && min_value >= max_value
|
101
|
+
raise RailsXapi::Errors::XapiError,
|
102
|
+
I18n.t(
|
103
|
+
"rails_xapi.errors.invalid_score_value",
|
104
|
+
value: I18n.t("rails_xapi.validations.score.min")
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Validates the duration accordign to the specifications.
|
110
|
+
# See: https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#46-iso-8601-durations
|
111
|
+
#
|
112
|
+
# @param [String] duration The duration string to validate.
|
113
|
+
# @return [ActiveSupport::Duration::ISO8601Parser::ParsingError] If invalid string is provided.
|
114
|
+
def correct_duration
|
115
|
+
ActiveSupport::Duration.parse(duration) if duration.present?
|
116
|
+
end
|
117
|
+
|
118
|
+
def completion_attribute_must_be_boolean
|
119
|
+
unless [true, false].include?(completion)
|
120
|
+
raise RailsXapi::Errors::XapiError,
|
121
|
+
I18n.t(
|
122
|
+
"rails_xapi.errors.invalid_score_value",
|
123
|
+
value:
|
124
|
+
I18n.t(
|
125
|
+
"rails_xapi.errors.wrong_attribute_type",
|
126
|
+
name: "completion",
|
127
|
+
value: completion
|
128
|
+
)
|
129
|
+
)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def success_attribute_must_be_boolean
|
134
|
+
unless [true, false].include?(success)
|
135
|
+
raise RailsXapi::Errors::XapiError,
|
136
|
+
I18n.t(
|
137
|
+
"rails_xapi.errors.invalid_score_value",
|
138
|
+
value:
|
139
|
+
I18n.t(
|
140
|
+
"rails_xapi.errors.wrong_attribute_type",
|
141
|
+
name: "success",
|
142
|
+
value: success
|
143
|
+
)
|
144
|
+
)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Automatically set a score_scaled if not provided.
|
149
|
+
def calculate_scaled
|
150
|
+
return if score_scaled.present? || score_max.blank? || score_raw.blank?
|
151
|
+
|
152
|
+
self.score_scaled = (score_raw.to_f / score_max.to_f)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# == Schema Information
|
157
|
+
#
|
158
|
+
# Table name: rails_xapi_results
|
159
|
+
#
|
160
|
+
# id :integer not null, primary key
|
161
|
+
# completion :boolean default(FALSE)
|
162
|
+
# duration :string
|
163
|
+
# response :text
|
164
|
+
# score_max :integer
|
165
|
+
# score_min :integer
|
166
|
+
# score_raw :integer
|
167
|
+
# score_scaled :decimal(3, 2)
|
168
|
+
# success :boolean default(FALSE)
|
169
|
+
# statement_id :bigint not null
|
170
|
+
#
|
171
|
+
# Indexes
|
172
|
+
#
|
173
|
+
# index_rails_xapi_results_on_statement_id (statement_id)
|
174
|
+
#
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Statements are the evidence for any sort of experience or event which is to be tracked in xAPI.
|
4
|
+
# See: https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#20-statements
|
5
|
+
class RailsXapi::Statement < ApplicationRecord
|
6
|
+
belongs_to :actor, class_name: "RailsXapi::Actor"
|
7
|
+
belongs_to :verb, class_name: "RailsXapi::Verb"
|
8
|
+
belongs_to :object, class_name: "RailsXapi::Object"
|
9
|
+
has_one :result, class_name: "RailsXapi::Result", dependent: :destroy
|
10
|
+
has_one :context, class_name: "RailsXapi::Context", dependent: :destroy
|
11
|
+
|
12
|
+
validate :actor_valid
|
13
|
+
validate :verb_valid
|
14
|
+
validate :object_valid
|
15
|
+
|
16
|
+
default_scope do
|
17
|
+
includes([:actor, { actor: :account }, :verb, :object, :context, :result])
|
18
|
+
end
|
19
|
+
|
20
|
+
def as_json
|
21
|
+
{
|
22
|
+
actor: actor.as_json,
|
23
|
+
verb: verb.as_json,
|
24
|
+
object: object.as_json
|
25
|
+
}.tap do |hash|
|
26
|
+
hash[:result] = result.as_json if result.present?
|
27
|
+
hash[:context] = context.as_json if context.present?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def actor_valid
|
34
|
+
errors.add(:actor, "is invalid") if actor && !actor.valid?
|
35
|
+
end
|
36
|
+
|
37
|
+
def verb_valid
|
38
|
+
errors.add(:verb, "is invalid") if verb && !verb.valid?
|
39
|
+
end
|
40
|
+
|
41
|
+
def object_valid
|
42
|
+
errors.add(:object, "is invalid") if object && !object.valid?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# == Schema Information
|
47
|
+
#
|
48
|
+
# Table name: rails_xapi_statements
|
49
|
+
#
|
50
|
+
# id :integer not null, primary key
|
51
|
+
# timestamp :datetime
|
52
|
+
# created_at :datetime not null
|
53
|
+
# actor_id :string not null
|
54
|
+
# object_id :string not null
|
55
|
+
# verb_id :string not null
|
56
|
+
#
|
57
|
+
# Indexes
|
58
|
+
#
|
59
|
+
# index_rails_xapi_statements_on_actor_id (actor_id)
|
60
|
+
# index_rails_xapi_statements_on_object_id (object_id)
|
61
|
+
# index_rails_xapi_statements_on_verb_id (verb_id)
|
62
|
+
#
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsXapi
|
4
|
+
module Validators
|
5
|
+
class LanguageMapValidator < ActiveModel::Validator
|
6
|
+
LANGUAGE_MAP_REGEX = /\A[a-z]{2}(-[A-Z]{2})?\z/
|
7
|
+
|
8
|
+
def validate(record)
|
9
|
+
attributes_to_validate = options[:attributes] || []
|
10
|
+
|
11
|
+
attributes_to_validate.each do |attribute|
|
12
|
+
next unless record.respond_to?(attribute)
|
13
|
+
|
14
|
+
raw_value = record.public_send(attribute)
|
15
|
+
next unless raw_value.present?
|
16
|
+
|
17
|
+
validate_language_map_keys(record, attribute, raw_value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def validate_language_map_keys(record, attribute, data)
|
24
|
+
data_hash =
|
25
|
+
case data
|
26
|
+
when String
|
27
|
+
# If it's a string, parse:
|
28
|
+
begin
|
29
|
+
JSON.parse(data)
|
30
|
+
rescue JSON::ParserError
|
31
|
+
record.errors.add(
|
32
|
+
attribute,
|
33
|
+
I18n.t("rails_xapi.errors.invalid_json")
|
34
|
+
)
|
35
|
+
return
|
36
|
+
end
|
37
|
+
when Hash
|
38
|
+
data
|
39
|
+
else
|
40
|
+
record.errors.add(attribute, "must be a JSON string or a Hash")
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
unless data_hash.is_a?(Hash)
|
45
|
+
raise RailsXapi::Errors::XapiError,
|
46
|
+
I18n.t("rails_xapi.errors.expected_hash", type: data_hash.class)
|
47
|
+
end
|
48
|
+
|
49
|
+
invalid_keys =
|
50
|
+
data_hash.keys.reject { |key| key.match?(LANGUAGE_MAP_REGEX) }
|
51
|
+
|
52
|
+
if invalid_keys.any?
|
53
|
+
record.errors.add(
|
54
|
+
attribute,
|
55
|
+
I18n.t(
|
56
|
+
"rails_xapi.errors.definition_description_invalid_keys",
|
57
|
+
values: invalid_keys.join(", ")
|
58
|
+
)
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The Verb defines the action between an Actor and an Activity.
|
4
|
+
# The systems reading the statements must use the verb IRI to infer meaning.
|
5
|
+
# See : https://github.com/adlnet/xAPI-Spec/blob/master/xAPI-Data.md#243-verb
|
6
|
+
class RailsXapi::Verb < ApplicationRecord
|
7
|
+
has_many :statements, class_name: "RailsXapi::Statement", dependent: :nullify
|
8
|
+
|
9
|
+
before_validation :set_display
|
10
|
+
|
11
|
+
validates :id,
|
12
|
+
presence: true,
|
13
|
+
format: {
|
14
|
+
with: %r{\A\w+://\S+\z},
|
15
|
+
message: I18n.t("rails_xapi.errors.must_be_a_valid_iri")
|
16
|
+
}
|
17
|
+
validates_with RailsXapi::Validators::LanguageMapValidator,
|
18
|
+
attributes: %i[display]
|
19
|
+
|
20
|
+
# Constants representing a mapping of xAPI activity verbs.
|
21
|
+
#
|
22
|
+
# The constant maps the activity verbs from several schemas
|
23
|
+
# to their corresponding human-readable output.
|
24
|
+
#
|
25
|
+
# @example Usage:
|
26
|
+
# VERBS_LIST["http://activitystrea.ms/schema/1.0/accept"] # => "accepted"
|
27
|
+
# VERBS_LIST["http://activitystrea.ms/schema/1.0/access"] # => "accessed"
|
28
|
+
#
|
29
|
+
# @note The list is build from https://registry.tincanapi.com/#home/verbs
|
30
|
+
VERBS_LIST = {
|
31
|
+
"http://activitystrea.ms/schema/1.0/accept" => "accepted",
|
32
|
+
"http://activitystrea.ms/schema/1.0/access" => "accessed",
|
33
|
+
"http://activitystrea.ms/schema/1.0/acknowledge" => "acknowledged",
|
34
|
+
"http://activitystrea.ms/schema/1.0/add" => "added",
|
35
|
+
"http://activitystrea.ms/schema/1.0/agree" => "agreed",
|
36
|
+
"http://activitystrea.ms/schema/1.0/append" => "appended",
|
37
|
+
"http://activitystrea.ms/schema/1.0/approve" => "approved",
|
38
|
+
"http://activitystrea.ms/schema/1.0/archive" => "archived",
|
39
|
+
"http://activitystrea.ms/schema/1.0/assign" => "assigned",
|
40
|
+
"http://activitystrea.ms/schema/1.0/at" => "was at",
|
41
|
+
"http://activitystrea.ms/schema/1.0/attach" => "attached",
|
42
|
+
"http://activitystrea.ms/schema/1.0/attend" => "attended",
|
43
|
+
"http://activitystrea.ms/schema/1.0/author" => "authored",
|
44
|
+
"http://activitystrea.ms/schema/1.0/authorize" => "authorized",
|
45
|
+
"http://activitystrea.ms/schema/1.0/borrow" => "borrowed",
|
46
|
+
"http://activitystrea.ms/schema/1.0/build" => "built",
|
47
|
+
"http://activitystrea.ms/schema/1.0/cancel" => "canceled",
|
48
|
+
"http://activitystrea.ms/schema/1.0/checkin" => "checked in",
|
49
|
+
"http://activitystrea.ms/schema/1.0/close" => "closed",
|
50
|
+
"http://activitystrea.ms/schema/1.0/complete" => "completed",
|
51
|
+
"http://activitystrea.ms/schema/1.0/confirm" => "confirmed",
|
52
|
+
"http://activitystrea.ms/schema/1.0/consume" => "consumed",
|
53
|
+
"http://activitystrea.ms/schema/1.0/create" => "created",
|
54
|
+
"http://activitystrea.ms/schema/1.0/delete" => "deleted",
|
55
|
+
"http://activitystrea.ms/schema/1.0/deliver" => "delivered",
|
56
|
+
"http://activitystrea.ms/schema/1.0/deny" => "denied",
|
57
|
+
"http://activitystrea.ms/schema/1.0/disagree" => "disagreed",
|
58
|
+
"http://activitystrea.ms/schema/1.0/dislike" => "disliked",
|
59
|
+
"http://activitystrea.ms/schema/1.0/experience" => "experienced",
|
60
|
+
"http://activitystrea.ms/schema/1.0/favorite" => "favorited",
|
61
|
+
"http://activitystrea.ms/schema/1.0/find" => "found",
|
62
|
+
"http://activitystrea.ms/schema/1.0/flag-as-inappropriate" =>
|
63
|
+
"flagged as inappropriate",
|
64
|
+
"http://activitystrea.ms/schema/1.0/follow" => "followed",
|
65
|
+
"http://activitystrea.ms/schema/1.0/give" => "gave",
|
66
|
+
"http://activitystrea.ms/schema/1.0/host" => "hosted",
|
67
|
+
"http://activitystrea.ms/schema/1.0/ignore" => "ignored",
|
68
|
+
"http://activitystrea.ms/schema/1.0/insert" => "inserted",
|
69
|
+
"http://activitystrea.ms/schema/1.0/install" => "installed",
|
70
|
+
"http://activitystrea.ms/schema/1.0/interact" => "interacted",
|
71
|
+
"http://activitystrea.ms/schema/1.0/invite" => "invited",
|
72
|
+
"http://activitystrea.ms/schema/1.0/join" => "joined",
|
73
|
+
"http://activitystrea.ms/schema/1.0/leave" => "left",
|
74
|
+
"http://activitystrea.ms/schema/1.0/like" => "liked",
|
75
|
+
"http://activitystrea.ms/schema/1.0/listen" => "listened",
|
76
|
+
"http://activitystrea.ms/schema/1.0/lose" => "lost",
|
77
|
+
"http://activitystrea.ms/schema/1.0/make-friend" => "made friend",
|
78
|
+
"http://activitystrea.ms/schema/1.0/open" => "opened",
|
79
|
+
"http://activitystrea.ms/schema/1.0/play" => "played",
|
80
|
+
"http://activitystrea.ms/schema/1.0/present" => "presented",
|
81
|
+
"http://activitystrea.ms/schema/1.0/purchase" => "purchased",
|
82
|
+
"http://activitystrea.ms/schema/1.0/qualify" => "qualified",
|
83
|
+
"http://activitystrea.ms/schema/1.0/read" => "read",
|
84
|
+
"http://activitystrea.ms/schema/1.0/receive" => "received",
|
85
|
+
"http://activitystrea.ms/schema/1.0/reject" => "rejected",
|
86
|
+
"http://activitystrea.ms/schema/1.0/remove" => "removed",
|
87
|
+
"http://activitystrea.ms/schema/1.0/remove-friend" => "removed friend",
|
88
|
+
"http://activitystrea.ms/schema/1.0/replace" => "replaced",
|
89
|
+
"http://activitystrea.ms/schema/1.0/request" => "requested",
|
90
|
+
"http://activitystrea.ms/schema/1.0/request-friend" => "requested friend",
|
91
|
+
"http://activitystrea.ms/schema/1.0/resolve" => "resolved",
|
92
|
+
"http://activitystrea.ms/schema/1.0/retract" => "retracted",
|
93
|
+
"http://activitystrea.ms/schema/1.0/return" => "returned",
|
94
|
+
"http://activitystrea.ms/schema/1.0/rsvp-maybe" => "RSVPed maybe",
|
95
|
+
"http://activitystrea.ms/schema/1.0/rsvp-no" => "rsvped no",
|
96
|
+
"http://activitystrea.ms/schema/1.0/rsvp-yes" => "rsvped yes",
|
97
|
+
"http://activitystrea.ms/schema/1.0/satisfy" => "satisfied",
|
98
|
+
"http://activitystrea.ms/schema/1.0/save" => "saved",
|
99
|
+
"http://activitystrea.ms/schema/1.0/schedule" => "scheduled",
|
100
|
+
"http://activitystrea.ms/schema/1.0/search" => "searched",
|
101
|
+
"http://activitystrea.ms/schema/1.0/sell" => "sold",
|
102
|
+
"http://activitystrea.ms/schema/1.0/send" => "sent",
|
103
|
+
"http://activitystrea.ms/schema/1.0/share" => "shared",
|
104
|
+
"http://activitystrea.ms/schema/1.0/sponsor" => "sponsored",
|
105
|
+
"http://activitystrea.ms/schema/1.0/start" => "started",
|
106
|
+
"http://activitystrea.ms/schema/1.0/stop-following" => "stopped following",
|
107
|
+
"http://activitystrea.ms/schema/1.0/submit" => "submitted",
|
108
|
+
"http://activitystrea.ms/schema/1.0/tag" => "tagged",
|
109
|
+
"http://activitystrea.ms/schema/1.0/terminate" => "terminated",
|
110
|
+
"http://activitystrea.ms/schema/1.0/tie" => "tied",
|
111
|
+
"http://activitystrea.ms/schema/1.0/unfavorite" => "unfavorited",
|
112
|
+
"http://activitystrea.ms/schema/1.0/unlike" => "unliked",
|
113
|
+
"http://activitystrea.ms/schema/1.0/unsatisfy" => "unsatisfied",
|
114
|
+
"http://activitystrea.ms/schema/1.0/unsave" => "unsaved",
|
115
|
+
"http://activitystrea.ms/schema/1.0/unshare" => "unshared",
|
116
|
+
"http://activitystrea.ms/schema/1.0/update" => "updated",
|
117
|
+
"http://activitystrea.ms/schema/1.0/use" => "used",
|
118
|
+
"http://activitystrea.ms/schema/1.0/watch" => "Watched",
|
119
|
+
"http://activitystrea.ms/schema/1.0/win" => "won",
|
120
|
+
"http://adlnet.gov/expapi/verbs/answered" => "answered",
|
121
|
+
"http://adlnet.gov/expapi/verbs/asked" => "asked",
|
122
|
+
"http://adlnet.gov/expapi/verbs/attempted" => "attempted",
|
123
|
+
"http://adlnet.gov/expapi/verbs/attended" => "attended",
|
124
|
+
"http://adlnet.gov/expapi/verbs/commented" => "commented",
|
125
|
+
"http://adlnet.gov/expapi/verbs/completed" => "completed",
|
126
|
+
"http://adlnet.gov/expapi/verbs/exited" => "exited",
|
127
|
+
"http://adlnet.gov/expapi/verbs/experienced" => "experienced",
|
128
|
+
"http://adlnet.gov/expapi/verbs/failed" => "failed",
|
129
|
+
"http://adlnet.gov/expapi/verbs/imported" => "imported",
|
130
|
+
"http://adlnet.gov/expapi/verbs/initialized" => "initialized",
|
131
|
+
"http://adlnet.gov/expapi/verbs/interacted" => "interacted",
|
132
|
+
"http://adlnet.gov/expapi/verbs/launched" => "launched",
|
133
|
+
"http://adlnet.gov/expapi/verbs/mastered" => "mastered",
|
134
|
+
"http://adlnet.gov/expapi/verbs/passed" => "passed",
|
135
|
+
"http://adlnet.gov/expapi/verbs/preferred" => "preferred",
|
136
|
+
"http://adlnet.gov/expapi/verbs/progressed" => "progressed",
|
137
|
+
"http://adlnet.gov/expapi/verbs/registered" => "registered",
|
138
|
+
"http://adlnet.gov/expapi/verbs/responded" => "responded",
|
139
|
+
"http://adlnet.gov/expapi/verbs/resumed" => "resumed",
|
140
|
+
"http://adlnet.gov/expapi/verbs/scored" => "scored",
|
141
|
+
"http://adlnet.gov/expapi/verbs/shared" => "shared",
|
142
|
+
"http://adlnet.gov/expapi/verbs/suspended" => "suspended",
|
143
|
+
"http://adlnet.gov/expapi/verbs/terminated" => "terminated",
|
144
|
+
"http://adlnet.gov/expapi/verbs/voided" => "voided",
|
145
|
+
"http://curatr3.com/define/verb/edited" => "edited",
|
146
|
+
"http://curatr3.com/define/verb/voted-down" => "voted down (with reason)",
|
147
|
+
"http://curatr3.com/define/verb/voted-up" => "voted up (with reason)",
|
148
|
+
"http://future-learning.info/xAPI/verb/pressed" => "pressed",
|
149
|
+
"http://future-learning.info/xAPI/verb/released" => "released",
|
150
|
+
"http://id.tincanapi.com/verb/adjourned" => "adjourned",
|
151
|
+
"http://id.tincanapi.com/verb/applauded" => "applauded",
|
152
|
+
"http://id.tincanapi.com/verb/arranged" => "arranged",
|
153
|
+
"http://id.tincanapi.com/verb/bookmarked" => "bookmarked",
|
154
|
+
"http://id.tincanapi.com/verb/called" => "called",
|
155
|
+
"http://id.tincanapi.com/verb/closed-sale" => "closed sale",
|
156
|
+
"http://id.tincanapi.com/verb/created-opportunity" => "created opportunity",
|
157
|
+
"http://id.tincanapi.com/verb/defined" => "defined",
|
158
|
+
"http://id.tincanapi.com/verb/disabled" => "disabled",
|
159
|
+
"http://id.tincanapi.com/verb/discarded" => "discarded",
|
160
|
+
"http://id.tincanapi.com/verb/downloaded" => "downloaded",
|
161
|
+
"http://id.tincanapi.com/verb/earned" => "earned",
|
162
|
+
"http://id.tincanapi.com/verb/enabled" => "enabled",
|
163
|
+
"http://id.tincanapi.com/verb/estimated-duration" =>
|
164
|
+
"estimated the duration",
|
165
|
+
"http://id.tincanapi.com/verb/expected" => "expected",
|
166
|
+
"http://id.tincanapi.com/verb/expired" => "expired",
|
167
|
+
"http://id.tincanapi.com/verb/focused" => "focused",
|
168
|
+
"http://id.tincanapi.com/verb/frame/entered" => "entered frame",
|
169
|
+
"http://id.tincanapi.com/verb/frame/exited" => "exited frame",
|
170
|
+
"http://id.tincanapi.com/verb/hired" => "hired",
|
171
|
+
"http://id.tincanapi.com/verb/interviewed" => "interviewed",
|
172
|
+
"http://id.tincanapi.com/verb/laughed" => "laughed",
|
173
|
+
"http://id.tincanapi.com/verb/marked-unread" => "unread",
|
174
|
+
"http://id.tincanapi.com/verb/mentioned" => "mentioned",
|
175
|
+
"http://id.tincanapi.com/verb/mentored" => "mentored",
|
176
|
+
"http://id.tincanapi.com/verb/paused" => "paused",
|
177
|
+
"http://id.tincanapi.com/verb/performed-offline" => "performed",
|
178
|
+
"http://id.tincanapi.com/verb/personalized" => "personalized",
|
179
|
+
"http://id.tincanapi.com/verb/previewed" => "previewed",
|
180
|
+
"http://id.tincanapi.com/verb/promoted" => "promoted",
|
181
|
+
"http://id.tincanapi.com/verb/rated" => "rated",
|
182
|
+
"http://id.tincanapi.com/verb/replied" => "replied",
|
183
|
+
"http://id.tincanapi.com/verb/replied-to-tweet" => "replied to tweet",
|
184
|
+
"http://id.tincanapi.com/verb/requested-attention" => "requested attention",
|
185
|
+
"http://id.tincanapi.com/verb/retweeted" => "retweeted",
|
186
|
+
"http://id.tincanapi.com/verb/reviewed" => "reviewed",
|
187
|
+
"http://id.tincanapi.com/verb/secured" => "secured",
|
188
|
+
"http://id.tincanapi.com/verb/selected" => "selected",
|
189
|
+
"http://id.tincanapi.com/verb/skipped" => "skipped",
|
190
|
+
"http://id.tincanapi.com/verb/talked-with" => "talked",
|
191
|
+
"http://id.tincanapi.com/verb/terminated-employment-with" =>
|
192
|
+
"terminated employment with",
|
193
|
+
"http://id.tincanapi.com/verb/tweeted" => "tweeted",
|
194
|
+
"http://id.tincanapi.com/verb/unfocused" => "unfocused",
|
195
|
+
"http://id.tincanapi.com/verb/unregistered" => "unregistered",
|
196
|
+
"http://id.tincanapi.com/verb/viewed" => "viewed",
|
197
|
+
"http://id.tincanapi.com/verb/voted-down" => "down voted",
|
198
|
+
"http://id.tincanapi.com/verb/voted-up" => "up voted",
|
199
|
+
"http://id.tincanapi.com/verb/was-assigned-job-title" =>
|
200
|
+
"was assigned job title",
|
201
|
+
"http://id.tincanapi.com/verb/was-hired-by" => "was hired by",
|
202
|
+
"http://risc-inc.com/annotator/verbs/annotated" => "annotated",
|
203
|
+
"http://risc-inc.com/annotator/verbs/modified" => "modified annotation",
|
204
|
+
"http://specification.openbadges.org/xapi/verbs/earned" =>
|
205
|
+
"earned an Open Badge",
|
206
|
+
"http://www.digital-knowledge.co.jp/tincanapi/verbs/drew" => "drew",
|
207
|
+
"http://www.tincanapi.co.uk/pages/verbs.html#cancelled_planned_learning" =>
|
208
|
+
"cancelled planned learning",
|
209
|
+
"http://www.tincanapi.co.uk/pages/verbs.html#planned_learning" => "planned",
|
210
|
+
"http://www.tincanapi.co.uk/verbs/enrolled_onto_learning_plan" =>
|
211
|
+
"enrolled onto learning plan",
|
212
|
+
"http://www.tincanapi.co.uk/verbs/evaluated" => "evaluated",
|
213
|
+
"https://brindlewaye.com/xAPITerms/verbs/added/" => "added",
|
214
|
+
"https://brindlewaye.com/xAPITerms/verbs/loggedin/" => "log in",
|
215
|
+
"https://brindlewaye.com/xAPITerms/verbs/loggedout/" => "log out",
|
216
|
+
"https://brindlewaye.com/xAPITerms/verbs/ran/" => "ran",
|
217
|
+
"https://brindlewaye.com/xAPITerms/verbs/removed/" => "removed",
|
218
|
+
"https://brindlewaye.com/xAPITerms/verbs/reviewed/" => "reviewed",
|
219
|
+
"https://brindlewaye.com/xAPITerms/verbs/walked/" => "walked"
|
220
|
+
}
|
221
|
+
|
222
|
+
def to_locale
|
223
|
+
loc = I18n.default_locale&.to_s || "en"
|
224
|
+
parsed = JSON.parse(display)
|
225
|
+
fallback = parsed&.first&.[](-1) || ""
|
226
|
+
|
227
|
+
parsed[loc] || fallback
|
228
|
+
end
|
229
|
+
|
230
|
+
def as_json
|
231
|
+
{ id: id, display: display }.compact
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
236
|
+
def set_display
|
237
|
+
if display.present?
|
238
|
+
# Parse the data as JSON to store it
|
239
|
+
self.display = JSON.parse(display.gsub("=>", ":")).to_json
|
240
|
+
elsif VERBS_LIST.key?(id)
|
241
|
+
verb_list_id = VERBS_LIST[id]
|
242
|
+
self.display = { "en-US": verb_list_id }.to_json
|
243
|
+
else
|
244
|
+
raise RailsXapi::Errors::XapiError,
|
245
|
+
I18n.t("rails_xapi.errors.missing_verb_display")
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# == Schema Information
|
251
|
+
#
|
252
|
+
# Table name: rails_xapi_verbs
|
253
|
+
#
|
254
|
+
# id :string not null, primary key
|
255
|
+
# display :string
|
256
|
+
#
|
257
|
+
# Indexes
|
258
|
+
#
|
259
|
+
# index_rails_xapi_verbs_on_id (id) UNIQUE
|
260
|
+
#
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ApplicationService
|
4
|
+
def self.call(*, &)
|
5
|
+
new(*, &).call
|
6
|
+
end
|
7
|
+
|
8
|
+
# Set the start_date to the first day of the given month and year,
|
9
|
+
# and the end_date to the last day of the givenn month.
|
10
|
+
def self.generate_start_date_end_date(year, month)
|
11
|
+
start_date = Date.new(year, month, 1)
|
12
|
+
end_date = start_date.end_of_month
|
13
|
+
|
14
|
+
[start_date, end_date]
|
15
|
+
end
|
16
|
+
end
|