meibo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +12 -0
- data/lib/meibo/academic_session.rb +48 -0
- data/lib/meibo/academic_session_set.rb +15 -0
- data/lib/meibo/base_profile.rb +37 -0
- data/lib/meibo/builder/academic_session_builder.rb +26 -0
- data/lib/meibo/builder/base_builder.rb +13 -0
- data/lib/meibo/builder/classroom_builder.rb +38 -0
- data/lib/meibo/builder/course_builder.rb +32 -0
- data/lib/meibo/builder/demographic_builder.rb +22 -0
- data/lib/meibo/builder/enrollment_builder.rb +30 -0
- data/lib/meibo/builder/organization_builder.rb +34 -0
- data/lib/meibo/builder/role_builder.rb +30 -0
- data/lib/meibo/builder/user_builder.rb +36 -0
- data/lib/meibo/builder/user_profile_builder.rb +22 -0
- data/lib/meibo/builder.rb +60 -0
- data/lib/meibo/classroom.rb +67 -0
- data/lib/meibo/classroom_set.rb +29 -0
- data/lib/meibo/converter.rb +259 -0
- data/lib/meibo/course.rb +45 -0
- data/lib/meibo/course_set.rb +23 -0
- data/lib/meibo/data_model.rb +84 -0
- data/lib/meibo/data_set.rb +44 -0
- data/lib/meibo/demographic.rb +70 -0
- data/lib/meibo/demographic_set.rb +18 -0
- data/lib/meibo/enrollment.rb +51 -0
- data/lib/meibo/enrollment_set.rb +22 -0
- data/lib/meibo/errors.rb +13 -0
- data/lib/meibo/factory_bot/academic_session.rb +23 -0
- data/lib/meibo/factory_bot/all.rb +13 -0
- data/lib/meibo/factory_bot/classroom.rb +28 -0
- data/lib/meibo/factory_bot/course.rb +21 -0
- data/lib/meibo/factory_bot/demographic.rb +16 -0
- data/lib/meibo/factory_bot/enrollment.rb +37 -0
- data/lib/meibo/factory_bot/manifest.rb +33 -0
- data/lib/meibo/factory_bot/memory_package.rb +11 -0
- data/lib/meibo/factory_bot/organization.rb +47 -0
- data/lib/meibo/factory_bot/role.rb +41 -0
- data/lib/meibo/factory_bot/user.rb +22 -0
- data/lib/meibo/factory_bot/user_profile.rb +21 -0
- data/lib/meibo/japan_profile/academic_session.rb +26 -0
- data/lib/meibo/japan_profile/classroom.rb +21 -0
- data/lib/meibo/japan_profile/course.rb +18 -0
- data/lib/meibo/japan_profile/demographic.rb +15 -0
- data/lib/meibo/japan_profile/enrollment.rb +35 -0
- data/lib/meibo/japan_profile/organization.rb +20 -0
- data/lib/meibo/japan_profile/role.rb +26 -0
- data/lib/meibo/japan_profile/user.rb +27 -0
- data/lib/meibo/japan_profile/user_profile.rb +7 -0
- data/lib/meibo/japan_profile.rb +39 -0
- data/lib/meibo/manifest/processing_mode.rb +40 -0
- data/lib/meibo/manifest.rb +163 -0
- data/lib/meibo/organization.rb +44 -0
- data/lib/meibo/organization_set.rb +15 -0
- data/lib/meibo/reader.rb +138 -0
- data/lib/meibo/role.rb +65 -0
- data/lib/meibo/role_set.rb +25 -0
- data/lib/meibo/roster.rb +166 -0
- data/lib/meibo/user.rb +71 -0
- data/lib/meibo/user_profile.rb +42 -0
- data/lib/meibo/user_profile_set.rb +18 -0
- data/lib/meibo/user_set.rb +24 -0
- data/lib/meibo/version.rb +5 -0
- data/lib/meibo.rb +17 -0
- data/meibo.gemspec +38 -0
- data/sig/meibo.rbs +4 -0
- metadata +148 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meibo
|
4
|
+
module JapanProfile
|
5
|
+
class User < ::Meibo::User
|
6
|
+
DataModel.define(
|
7
|
+
self,
|
8
|
+
attribute_name_to_header_field_map: superclass.attribute_name_to_header_field_map.merge(
|
9
|
+
kana_given_name: 'metadata.jp.kanaGivenName',
|
10
|
+
kana_family_name: 'metadata.jp.kanaFamilyName',
|
11
|
+
kana_middle_name: 'metadata.jp.kanaMiddleName',
|
12
|
+
home_class: 'metadata.jp.homeClass'
|
13
|
+
).freeze,
|
14
|
+
converters: superclass.converters
|
15
|
+
)
|
16
|
+
|
17
|
+
# NOTE: enabled_userは必須ではないが固定
|
18
|
+
def initialize(enabled_user: true, kana_given_name: nil, kana_family_name: nil, kana_middle_name: nil, home_class: nil, **other_fields)
|
19
|
+
super(enabled_user: enabled_user, **other_fields)
|
20
|
+
@kana_given_name = kana_given_name
|
21
|
+
@kana_family_name = kana_family_name
|
22
|
+
@kana_middle_name = kana_middle_name
|
23
|
+
@home_class = home_class
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meibo
|
4
|
+
module JapanProfile
|
5
|
+
include BaseProfile
|
6
|
+
|
7
|
+
BUILDERS = {
|
8
|
+
academic_session: Builder::AcademicSessionBuilder.create(AcademicSession),
|
9
|
+
class: Builder::ClassroomBuilder.create(Classroom),
|
10
|
+
course: Builder::CourseBuilder.create(Course),
|
11
|
+
demographic: Builder::DemographicBuilder.create(Demographic),
|
12
|
+
enrollment: Builder::EnrollmentBuilder.create(Enrollment),
|
13
|
+
org: Builder::OrganizationBuilder.create(Organization),
|
14
|
+
role: Builder::RoleBuilder.create(Role),
|
15
|
+
user: Builder::UserBuilder.create(User),
|
16
|
+
user_profile: Builder::UserProfileBuilder.create(UserProfile)
|
17
|
+
}
|
18
|
+
|
19
|
+
DATA_MODELS = {
|
20
|
+
file_academic_sessions: Meibo::JapanProfile::AcademicSession,
|
21
|
+
file_classes: Meibo::JapanProfile::Classroom,
|
22
|
+
file_courses: Meibo::JapanProfile::Course,
|
23
|
+
file_demographics: Meibo::JapanProfile::Demographic,
|
24
|
+
file_enrollments: Meibo::JapanProfile::Enrollment,
|
25
|
+
file_orgs: Meibo::JapanProfile::Organization,
|
26
|
+
file_roles: Meibo::JapanProfile::Role,
|
27
|
+
file_user_profiles: Meibo::JapanProfile::UserProfile,
|
28
|
+
file_users: Meibo::JapanProfile::User
|
29
|
+
}
|
30
|
+
|
31
|
+
def self.builder_for(key)
|
32
|
+
BUILDERS[key]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.data_model_for(file_attribute)
|
36
|
+
DATA_MODELS[file_attribute]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meibo
|
4
|
+
class Manifest
|
5
|
+
class ProcessingMode
|
6
|
+
attr_reader :mode
|
7
|
+
|
8
|
+
def initialize(mode)
|
9
|
+
@mode = mode
|
10
|
+
freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def absent?
|
14
|
+
@mode == 'absent'
|
15
|
+
end
|
16
|
+
|
17
|
+
def bulk?
|
18
|
+
@mode == 'bulk'
|
19
|
+
end
|
20
|
+
|
21
|
+
def delta?
|
22
|
+
@mode == 'delta'
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other)
|
26
|
+
if other.is_a?(ProcessingMode)
|
27
|
+
@mode == other.mode
|
28
|
+
elsif other.is_a?(String)
|
29
|
+
to_s == other
|
30
|
+
else
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
@mode
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meibo
|
4
|
+
class Manifest
|
5
|
+
MANIFEST_VERSION = '1.0'
|
6
|
+
ONEROSTER_VERSION = '1.2'
|
7
|
+
|
8
|
+
PROCESSING_MODES = {
|
9
|
+
absent: ProcessingMode.new('absent'),
|
10
|
+
bulk: ProcessingMode.new('bulk'),
|
11
|
+
delta: ProcessingMode.new('delta')
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
PROPERTY_NAME_TO_ATTRIBUTE_MAP = {
|
15
|
+
'manifest.version' => :manifest_version,
|
16
|
+
'oneroster.version' => :oneroster_version,
|
17
|
+
'file.academicSessions' => :file_academic_sessions,
|
18
|
+
'file.categories' => :file_categories,
|
19
|
+
'file.classes' => :file_classes,
|
20
|
+
'file.classResources' => :file_class_resources,
|
21
|
+
'file.courses' => :file_courses,
|
22
|
+
'file.courseResources' => :file_course_resources,
|
23
|
+
'file.demographics' => :file_demographics,
|
24
|
+
'file.enrollments' => :file_enrollments,
|
25
|
+
'file.lineItemLearningObjectiveIds' => :file_line_item_learning_objective_ids,
|
26
|
+
'file.lineItems' => :file_line_items,
|
27
|
+
'file.lineItemScoreScales' => :file_line_item_score_scales,
|
28
|
+
'file.orgs' => :file_orgs,
|
29
|
+
'file.resources' => :file_resources,
|
30
|
+
'file.resultLearningObjectiveIds' => :file_result_learning_objective_ids,
|
31
|
+
'file.results' => :file_results,
|
32
|
+
'file.resultScoreScales' => :file_result_score_scales,
|
33
|
+
'file.roles' => :file_roles,
|
34
|
+
'file.scoreScales' => :file_score_scales,
|
35
|
+
'file.userProfiles' => :file_user_profiles,
|
36
|
+
'file.userResources' => :file_user_resources,
|
37
|
+
'file.users' => :file_users,
|
38
|
+
'source.systemName' => :source_system_name,
|
39
|
+
'source.systemCode' => :source_system_code
|
40
|
+
}.freeze
|
41
|
+
ATTRIBUTE_TO_PROPERTY_NAME_MAP = PROPERTY_NAME_TO_ATTRIBUTE_MAP.to_h {|property_name, attribute| [attribute, property_name] }.freeze
|
42
|
+
|
43
|
+
# NOTE: 想定値
|
44
|
+
DEFAULT_VALUES = {
|
45
|
+
manifest_version: MANIFEST_VERSION,
|
46
|
+
oneroster_version: ONEROSTER_VERSION,
|
47
|
+
file_academic_sessions: PROCESSING_MODES[:bulk],
|
48
|
+
file_categories: PROCESSING_MODES[:absent],
|
49
|
+
file_classes: PROCESSING_MODES[:bulk],
|
50
|
+
file_class_resources: PROCESSING_MODES[:absent],
|
51
|
+
file_courses: PROCESSING_MODES[:bulk],
|
52
|
+
file_course_resources: PROCESSING_MODES[:absent],
|
53
|
+
file_demographics: PROCESSING_MODES[:bulk],
|
54
|
+
file_enrollments: PROCESSING_MODES[:bulk],
|
55
|
+
file_line_item_learning_objective_ids: PROCESSING_MODES[:absent],
|
56
|
+
file_line_items: PROCESSING_MODES[:absent],
|
57
|
+
file_line_item_score_scales: PROCESSING_MODES[:absent],
|
58
|
+
file_orgs: PROCESSING_MODES[:bulk],
|
59
|
+
file_resources: PROCESSING_MODES[:absent],
|
60
|
+
file_result_learning_objective_ids: PROCESSING_MODES[:absent],
|
61
|
+
file_results: PROCESSING_MODES[:absent],
|
62
|
+
file_result_score_scales: PROCESSING_MODES[:absent],
|
63
|
+
file_roles: PROCESSING_MODES[:bulk],
|
64
|
+
file_score_scales: PROCESSING_MODES[:absent],
|
65
|
+
file_user_profiles: PROCESSING_MODES[:bulk],
|
66
|
+
file_user_resources: PROCESSING_MODES[:absent],
|
67
|
+
file_users: PROCESSING_MODES[:bulk]
|
68
|
+
}.freeze
|
69
|
+
|
70
|
+
attr_reader :manifest_version, :oneroster_version, :file_academic_sessions, :file_categories, :file_classes, :file_class_resources, :file_courses, :file_course_resources, :file_demographics, :file_enrollments, :file_line_item_learning_objective_ids, :file_line_items, :file_line_item_score_scales, :file_orgs, :file_resources, :file_result_learning_objective_ids, :file_results, :file_result_score_scales, :file_roles, :file_score_scales, :file_user_profiles, :file_user_resources, :file_users, :source_system_name, :source_system_code
|
71
|
+
|
72
|
+
def self.build_from_default(**custom_properties)
|
73
|
+
new(**Manifest::DEFAULT_VALUES.merge(custom_properties))
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.filename
|
77
|
+
'manifest.csv'
|
78
|
+
end
|
79
|
+
|
80
|
+
attribute_name_to_header_field_map = {
|
81
|
+
property_name: 'propertyName',
|
82
|
+
value: 'value'
|
83
|
+
}
|
84
|
+
header_fields = attribute_name_to_header_field_map.values
|
85
|
+
define_singleton_method(:header_fields) { header_fields }
|
86
|
+
DataModel.define_header_converters(self, attribute_name_to_header_field_map)
|
87
|
+
|
88
|
+
def self.attribute_to_property_name(attribute)
|
89
|
+
ATTRIBUTE_TO_PROPERTY_NAME_MAP.fetch(attribute)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.filename_for(attribute)
|
93
|
+
property_name = attribute_to_property_name(attribute)
|
94
|
+
|
95
|
+
raise Meibo::Error, "#{property_name}はファイルのプロパティではありません" unless property_name.start_with?('file.')
|
96
|
+
|
97
|
+
property_name.split('file.', 2).last + '.csv'
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.parse(csv)
|
101
|
+
properties = CSV.parse(csv, encoding: Meibo::CSV_ENCODING, headers: true, header_converters: header_converters)
|
102
|
+
new(**properties.to_h {|property| [PROPERTY_NAME_TO_ATTRIBUTE_MAP.fetch(property[:property_name]), property[:value]] })
|
103
|
+
end
|
104
|
+
|
105
|
+
def initialize(manifest_version:, oneroster_version:, file_academic_sessions:, file_categories:, file_classes:, file_class_resources:, file_courses:, file_course_resources:, file_demographics:, file_enrollments:, file_line_item_learning_objective_ids:, file_line_items:, file_line_item_score_scales:, file_orgs:, file_resources:, file_result_learning_objective_ids:, file_results:, file_result_score_scales:, file_roles:, file_score_scales:, file_user_profiles:, file_user_resources:, file_users:, source_system_name: nil, source_system_code: nil)
|
106
|
+
@manifest_version = manifest_version
|
107
|
+
@oneroster_version = oneroster_version
|
108
|
+
@file_academic_sessions = file_academic_sessions
|
109
|
+
@file_categories = file_categories
|
110
|
+
@file_classes = file_classes
|
111
|
+
@file_class_resources = file_class_resources
|
112
|
+
@file_courses = file_courses
|
113
|
+
@file_course_resources = file_course_resources
|
114
|
+
@file_demographics = file_demographics
|
115
|
+
@file_enrollments = file_enrollments
|
116
|
+
@file_line_item_learning_objective_ids = file_line_item_learning_objective_ids
|
117
|
+
@file_line_items = file_line_items
|
118
|
+
@file_line_item_score_scales = file_line_item_score_scales
|
119
|
+
@file_orgs = file_orgs
|
120
|
+
@file_resources = file_resources
|
121
|
+
@file_result_learning_objective_ids = file_result_learning_objective_ids
|
122
|
+
@file_results = file_results
|
123
|
+
@file_result_score_scales = file_result_score_scales
|
124
|
+
@file_roles = file_roles
|
125
|
+
@file_score_scales = file_score_scales
|
126
|
+
@file_user_profiles = file_user_profiles
|
127
|
+
@file_user_resources = file_user_resources
|
128
|
+
@file_users = file_users
|
129
|
+
@source_system_name = source_system_name
|
130
|
+
@source_system_code = source_system_code
|
131
|
+
end
|
132
|
+
|
133
|
+
def filenames(processing_mode:)
|
134
|
+
file_attributes(processing_mode: processing_mode).map do |attribute|
|
135
|
+
self.class.filename_for(attribute)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def file_attributes(processing_mode:)
|
140
|
+
PROPERTY_NAME_TO_ATTRIBUTE_MAP.values.filter_map do |attribute|
|
141
|
+
attribute.start_with?('file_') && public_send(attribute) == processing_mode.to_s && attribute
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def deconstruct
|
146
|
+
to_a
|
147
|
+
end
|
148
|
+
|
149
|
+
def deconstruct_keys(_keys)
|
150
|
+
to_h
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_a
|
154
|
+
PROPERTY_NAME_TO_ATTRIBUTE_MAP.map do |property_name, attribute|
|
155
|
+
[property_name, public_send(attribute)]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def to_h
|
160
|
+
PROPERTY_NAME_TO_ATTRIBUTE_MAP.values.to_h {|attribute| [attribute, public_send(attribute)] }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meibo
|
4
|
+
class Organization
|
5
|
+
TYPES = {
|
6
|
+
department: 'department',
|
7
|
+
school: 'school',
|
8
|
+
district: 'district',
|
9
|
+
local: 'local',
|
10
|
+
state: 'state',
|
11
|
+
national: 'national'
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
DataModel.define(
|
15
|
+
self,
|
16
|
+
attribute_name_to_header_field_map: {
|
17
|
+
sourced_id: 'sourcedId',
|
18
|
+
status: 'status',
|
19
|
+
date_last_modified: 'dateLastModified',
|
20
|
+
name: 'name',
|
21
|
+
type: 'type',
|
22
|
+
identifier: 'identifier',
|
23
|
+
parent_sourced_id: 'parentSourcedId'
|
24
|
+
},
|
25
|
+
converters: {
|
26
|
+
datetime: [:date_last_modified],
|
27
|
+
enum: { type: [*TYPES.values, ENUM_EXT_PATTERN].freeze },
|
28
|
+
required: [:sourced_id, :name, :type],
|
29
|
+
status: [:status]
|
30
|
+
}
|
31
|
+
)
|
32
|
+
|
33
|
+
def initialize(sourced_id:, status: nil, date_last_modified: nil, name:, type:, identifier: nil, parent_sourced_id: (type == TYPES[:district] ? 'NULL' : nil), **extension_fields)
|
34
|
+
@sourced_id = sourced_id
|
35
|
+
@status = status
|
36
|
+
@date_last_modified = date_last_modified
|
37
|
+
@name = name
|
38
|
+
@type = type
|
39
|
+
@identifier = identifier
|
40
|
+
@parent_sourced_id = parent_sourced_id
|
41
|
+
@extension_fields = extension_fields
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meibo
|
4
|
+
class OrganizationSet < DataSet
|
5
|
+
def check_semantically_consistent
|
6
|
+
super
|
7
|
+
|
8
|
+
each do |organization|
|
9
|
+
next unless organization.parent_sourced_id
|
10
|
+
|
11
|
+
find_by_sourced_id(organization.parent_sourced_id)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/meibo/reader.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
require 'zip'
|
5
|
+
|
6
|
+
module Meibo
|
7
|
+
class Reader
|
8
|
+
def self.open(file_path, profile: BaseProfile)
|
9
|
+
Zip::File.open(file_path) do |zipfile|
|
10
|
+
yield new(zipfile: zipfile, profile: profile)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :profile, :zipfile
|
15
|
+
|
16
|
+
def initialize(zipfile:, profile:)
|
17
|
+
@profile = profile
|
18
|
+
@zipfile = zipfile
|
19
|
+
end
|
20
|
+
|
21
|
+
def academic_sessions
|
22
|
+
each_academic_session.to_a
|
23
|
+
end
|
24
|
+
|
25
|
+
def classes
|
26
|
+
each_class.to_a
|
27
|
+
end
|
28
|
+
|
29
|
+
def courses
|
30
|
+
each_course.to_a
|
31
|
+
end
|
32
|
+
|
33
|
+
def demographics
|
34
|
+
each_demographic.to_a
|
35
|
+
end
|
36
|
+
|
37
|
+
def enrollments
|
38
|
+
each_enrollment.to_a
|
39
|
+
end
|
40
|
+
|
41
|
+
def organizations
|
42
|
+
each_organization.to_a
|
43
|
+
end
|
44
|
+
|
45
|
+
def roles
|
46
|
+
each_role.to_a
|
47
|
+
end
|
48
|
+
|
49
|
+
def users
|
50
|
+
each_user.to_a
|
51
|
+
end
|
52
|
+
|
53
|
+
def user_profiles
|
54
|
+
each_user_profile.to_a
|
55
|
+
end
|
56
|
+
|
57
|
+
def each_academic_session(&block)
|
58
|
+
read_data(:file_academic_sessions, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
def each_class(&block)
|
62
|
+
read_data(:file_classes, &block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def each_course(&block)
|
66
|
+
read_data(:file_courses, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def each_demographic(&block)
|
70
|
+
read_data(:file_demographics, &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
def each_enrollment(&block)
|
74
|
+
read_data(:file_enrollments, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
def each_organization(&block)
|
78
|
+
read_data(:file_orgs, &block)
|
79
|
+
end
|
80
|
+
|
81
|
+
def each_role(&block)
|
82
|
+
read_data(:file_roles, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def each_user_profile(&block)
|
86
|
+
read_data(:file_user_profiles, &block)
|
87
|
+
end
|
88
|
+
|
89
|
+
def each_user(&block)
|
90
|
+
read_data(:file_users, &block)
|
91
|
+
end
|
92
|
+
|
93
|
+
def manifest
|
94
|
+
@manifest ||= begin
|
95
|
+
filename = Meibo::Manifest.filename
|
96
|
+
raise CsvFileNotFoundError, "#{filename} not found" unless file_entry?(filename)
|
97
|
+
|
98
|
+
csv = @zipfile.read(filename)
|
99
|
+
Meibo::Manifest.parse(csv)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def load_bulk_files
|
104
|
+
bulk_file_attributes = manifest.file_attributes(processing_mode: Meibo::Manifest::PROCESSING_MODES[:bulk])
|
105
|
+
{
|
106
|
+
file_academic_sessions: :academic_sessions,
|
107
|
+
file_classes: :classes,
|
108
|
+
file_courses: :courses,
|
109
|
+
file_demographics: :demographics,
|
110
|
+
file_enrollments: :enrollments,
|
111
|
+
file_orgs: :organizations,
|
112
|
+
file_roles: :roles,
|
113
|
+
file_users: :users,
|
114
|
+
file_user_profiles: :user_profiles
|
115
|
+
}.filter_map {|file_attribute, data_method|
|
116
|
+
if bulk_file_attributes.include?(file_attribute)
|
117
|
+
[data_method, public_send(data_method).to_a]
|
118
|
+
end
|
119
|
+
}.to_h
|
120
|
+
end
|
121
|
+
|
122
|
+
def file_entry?(filename)
|
123
|
+
@zipfile.get_entry(filename).file?
|
124
|
+
rescue Errno::ENOENT
|
125
|
+
false
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def read_data(file_attribute, &block)
|
131
|
+
filename = Manifest.filename_for(file_attribute)
|
132
|
+
raise CsvFileNotFoundError, "#{filename} not found" unless file_entry?(filename)
|
133
|
+
|
134
|
+
csv = @zipfile.read(filename)
|
135
|
+
profile.data_model_for(file_attribute).parse(csv, &block)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/meibo/role.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meibo
|
4
|
+
class Role
|
5
|
+
TYPES = {
|
6
|
+
primary: 'primary',
|
7
|
+
secondary: 'secondary'
|
8
|
+
}.freeze
|
9
|
+
|
10
|
+
ROLES = {
|
11
|
+
aide: 'aide',
|
12
|
+
counselor: 'counselor',
|
13
|
+
district_administrator: 'districtAdministrator',
|
14
|
+
guardian: 'guardian',
|
15
|
+
parent: 'parent',
|
16
|
+
principal: 'principal',
|
17
|
+
proctor: 'proctor',
|
18
|
+
relative: 'relative',
|
19
|
+
site_administrator: 'siteAdministrator',
|
20
|
+
student: 'student',
|
21
|
+
system_administrator: 'systemAdministrator',
|
22
|
+
teacher: 'teacher'
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
DataModel.define(
|
26
|
+
self,
|
27
|
+
attribute_name_to_header_field_map: {
|
28
|
+
sourced_id: 'sourcedId',
|
29
|
+
status: 'status',
|
30
|
+
date_last_modified: 'dateLastModified',
|
31
|
+
user_sourced_id: 'userSourcedId',
|
32
|
+
role_type: 'roleType',
|
33
|
+
role: 'role',
|
34
|
+
begin_date: 'beginDate',
|
35
|
+
end_date: 'endDate',
|
36
|
+
org_sourced_id: 'orgSourcedId',
|
37
|
+
user_profile_sourced_id: 'userProfileSourcedId'
|
38
|
+
}.freeze,
|
39
|
+
converters: {
|
40
|
+
date: [:begin_date, :end_date].freeze,
|
41
|
+
datetime: [:date_last_modified].freeze,
|
42
|
+
required: [:sourced_id, :user_sourced_id, :role_type, :role, :org_sourced_id].freeze,
|
43
|
+
enum: {
|
44
|
+
role: [*ROLES.values, ENUM_EXT_PATTERN].freeze,
|
45
|
+
role_type: TYPES.values.freeze
|
46
|
+
}.freeze,
|
47
|
+
status: [:status].freeze
|
48
|
+
}.freeze
|
49
|
+
)
|
50
|
+
|
51
|
+
def initialize(sourced_id:, status: nil, date_last_modified: nil, user_sourced_id:, role_type:, role:, begin_date: nil, end_date: nil, org_sourced_id:, user_profile_sourced_id: nil, **extension_fields)
|
52
|
+
@sourced_id = sourced_id
|
53
|
+
@status = status
|
54
|
+
@date_last_modified = date_last_modified
|
55
|
+
@user_sourced_id = user_sourced_id
|
56
|
+
@role_type = role_type
|
57
|
+
@role = role
|
58
|
+
@begin_date = begin_date
|
59
|
+
@end_date = end_date
|
60
|
+
@org_sourced_id = org_sourced_id
|
61
|
+
@user_profile_sourced_id = user_profile_sourced_id
|
62
|
+
@extension_fields = extension_fields
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meibo
|
4
|
+
class RoleSet < DataSet
|
5
|
+
def initialize(data, organization_set:, user_set:, user_profile_set:)
|
6
|
+
super(data)
|
7
|
+
@organization_set = organization_set
|
8
|
+
@user_set = user_set
|
9
|
+
@user_profile_set = user_profile_set
|
10
|
+
end
|
11
|
+
|
12
|
+
def check_semantically_consistent
|
13
|
+
super
|
14
|
+
|
15
|
+
each do |role|
|
16
|
+
@organization_set.find_by_sourced_id(role.org_sourced_id)
|
17
|
+
@user_set.find_by_sourced_id(role.user_sourced_id)
|
18
|
+
|
19
|
+
if role.user_profile_sourced_id
|
20
|
+
@user_profile_set.find_by_sourced_id(role.user_profile_sourced_id)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|