meibo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +13 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +84 -0
  6. data/Gemfile +16 -0
  7. data/Gemfile.lock +84 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +41 -0
  10. data/Rakefile +12 -0
  11. data/lib/meibo/academic_session.rb +48 -0
  12. data/lib/meibo/academic_session_set.rb +15 -0
  13. data/lib/meibo/base_profile.rb +37 -0
  14. data/lib/meibo/builder/academic_session_builder.rb +26 -0
  15. data/lib/meibo/builder/base_builder.rb +13 -0
  16. data/lib/meibo/builder/classroom_builder.rb +38 -0
  17. data/lib/meibo/builder/course_builder.rb +32 -0
  18. data/lib/meibo/builder/demographic_builder.rb +22 -0
  19. data/lib/meibo/builder/enrollment_builder.rb +30 -0
  20. data/lib/meibo/builder/organization_builder.rb +34 -0
  21. data/lib/meibo/builder/role_builder.rb +30 -0
  22. data/lib/meibo/builder/user_builder.rb +36 -0
  23. data/lib/meibo/builder/user_profile_builder.rb +22 -0
  24. data/lib/meibo/builder.rb +60 -0
  25. data/lib/meibo/classroom.rb +67 -0
  26. data/lib/meibo/classroom_set.rb +29 -0
  27. data/lib/meibo/converter.rb +259 -0
  28. data/lib/meibo/course.rb +45 -0
  29. data/lib/meibo/course_set.rb +23 -0
  30. data/lib/meibo/data_model.rb +84 -0
  31. data/lib/meibo/data_set.rb +44 -0
  32. data/lib/meibo/demographic.rb +70 -0
  33. data/lib/meibo/demographic_set.rb +18 -0
  34. data/lib/meibo/enrollment.rb +51 -0
  35. data/lib/meibo/enrollment_set.rb +22 -0
  36. data/lib/meibo/errors.rb +13 -0
  37. data/lib/meibo/factory_bot/academic_session.rb +23 -0
  38. data/lib/meibo/factory_bot/all.rb +13 -0
  39. data/lib/meibo/factory_bot/classroom.rb +28 -0
  40. data/lib/meibo/factory_bot/course.rb +21 -0
  41. data/lib/meibo/factory_bot/demographic.rb +16 -0
  42. data/lib/meibo/factory_bot/enrollment.rb +37 -0
  43. data/lib/meibo/factory_bot/manifest.rb +33 -0
  44. data/lib/meibo/factory_bot/memory_package.rb +11 -0
  45. data/lib/meibo/factory_bot/organization.rb +47 -0
  46. data/lib/meibo/factory_bot/role.rb +41 -0
  47. data/lib/meibo/factory_bot/user.rb +22 -0
  48. data/lib/meibo/factory_bot/user_profile.rb +21 -0
  49. data/lib/meibo/japan_profile/academic_session.rb +26 -0
  50. data/lib/meibo/japan_profile/classroom.rb +21 -0
  51. data/lib/meibo/japan_profile/course.rb +18 -0
  52. data/lib/meibo/japan_profile/demographic.rb +15 -0
  53. data/lib/meibo/japan_profile/enrollment.rb +35 -0
  54. data/lib/meibo/japan_profile/organization.rb +20 -0
  55. data/lib/meibo/japan_profile/role.rb +26 -0
  56. data/lib/meibo/japan_profile/user.rb +27 -0
  57. data/lib/meibo/japan_profile/user_profile.rb +7 -0
  58. data/lib/meibo/japan_profile.rb +39 -0
  59. data/lib/meibo/manifest/processing_mode.rb +40 -0
  60. data/lib/meibo/manifest.rb +163 -0
  61. data/lib/meibo/organization.rb +44 -0
  62. data/lib/meibo/organization_set.rb +15 -0
  63. data/lib/meibo/reader.rb +138 -0
  64. data/lib/meibo/role.rb +65 -0
  65. data/lib/meibo/role_set.rb +25 -0
  66. data/lib/meibo/roster.rb +166 -0
  67. data/lib/meibo/user.rb +71 -0
  68. data/lib/meibo/user_profile.rb +42 -0
  69. data/lib/meibo/user_profile_set.rb +18 -0
  70. data/lib/meibo/user_set.rb +24 -0
  71. data/lib/meibo/version.rb +5 -0
  72. data/lib/meibo.rb +17 -0
  73. data/meibo.gemspec +38 -0
  74. data/sig/meibo.rbs +4 -0
  75. 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meibo
4
+ module JapanProfile
5
+ UserProfile = ::Meibo::UserProfile
6
+ end
7
+ 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
@@ -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