meibo 0.27.1 → 0.28.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +4 -0
  3. data/.rubocop.yml +14 -2
  4. data/.rubocop_todo.yml +0 -7
  5. data/Gemfile.lock +157 -56
  6. data/README.md +14 -3
  7. data/flake.lock +188 -0
  8. data/flake.nix +55 -0
  9. data/gemset.nix +658 -0
  10. data/lib/meibo/academic_session.rb +21 -21
  11. data/lib/meibo/classroom.rb +47 -34
  12. data/lib/meibo/classroom_set.rb +5 -1
  13. data/lib/meibo/converter.rb +16 -18
  14. data/lib/meibo/course.rb +34 -21
  15. data/lib/meibo/course_set.rb +5 -1
  16. data/lib/meibo/data_model.rb +46 -36
  17. data/lib/meibo/data_set.rb +8 -10
  18. data/lib/meibo/demographic.rb +36 -36
  19. data/lib/meibo/enrollment.rb +22 -22
  20. data/lib/meibo/enrollment_set.rb +9 -2
  21. data/lib/meibo/eportal/v3/classroom.rb +11 -0
  22. data/lib/meibo/eportal/v3/course.rb +11 -0
  23. data/lib/meibo/eportal/v3/enrollment.rb +11 -0
  24. data/lib/meibo/eportal/v3/organization.rb +11 -0
  25. data/lib/meibo/eportal/v3/user.rb +16 -0
  26. data/lib/meibo/eportal/v3/user_profile.rb +11 -0
  27. data/lib/meibo/eportal/v3.rb +30 -0
  28. data/lib/meibo/eportal/v4/user.rb +37 -0
  29. data/lib/meibo/eportal/v4.rb +16 -0
  30. data/lib/meibo/eportal.rb +6 -0
  31. data/lib/meibo/errors.rb +27 -5
  32. data/lib/meibo/factory_bot/academic_session.rb +1 -1
  33. data/lib/meibo/factory_bot/classroom.rb +1 -1
  34. data/lib/meibo/factory_bot/course.rb +1 -1
  35. data/lib/meibo/factory_bot/demographic.rb +1 -1
  36. data/lib/meibo/factory_bot/enrollment.rb +1 -1
  37. data/lib/meibo/factory_bot/manifest.rb +0 -21
  38. data/lib/meibo/factory_bot/organization.rb +1 -1
  39. data/lib/meibo/factory_bot/role.rb +2 -4
  40. data/lib/meibo/factory_bot/user.rb +5 -5
  41. data/lib/meibo/factory_bot/user_profile.rb +1 -1
  42. data/lib/meibo/japan_k12_schools_profile/v1/enrollment.rb +16 -0
  43. data/lib/meibo/japan_k12_schools_profile/v1.rb +20 -0
  44. data/lib/meibo/japan_k12_schools_profile.rb +6 -0
  45. data/lib/meibo/japan_profile/v1_1/academic_session.rb +29 -0
  46. data/lib/meibo/japan_profile/v1_1/classroom.rb +27 -0
  47. data/lib/meibo/japan_profile/v1_1/course.rb +16 -0
  48. data/lib/meibo/japan_profile/v1_1/demographic.rb +11 -0
  49. data/lib/meibo/japan_profile/v1_1/enrollment.rb +47 -0
  50. data/lib/meibo/japan_profile/v1_1/organization.rb +16 -0
  51. data/lib/meibo/japan_profile/v1_1/organization_set.rb +23 -0
  52. data/lib/meibo/japan_profile/v1_1/user.rb +39 -0
  53. data/lib/meibo/japan_profile/v1_1/user_set.rb +17 -0
  54. data/lib/meibo/japan_profile/v1_1.rb +35 -0
  55. data/lib/meibo/japan_profile/v1_1_1.rb +9 -0
  56. data/lib/meibo/japan_profile/v1_2/user.rb +42 -0
  57. data/lib/meibo/japan_profile/v1_2.rb +20 -0
  58. data/lib/meibo/japan_profile.rb +6 -0
  59. data/lib/meibo/manifest.rb +41 -46
  60. data/lib/meibo/oneroster/v1_2/user.rb +43 -0
  61. data/lib/meibo/oneroster/v1_2.rb +62 -0
  62. data/lib/meibo/oneroster/v1_2_1.rb +22 -0
  63. data/lib/meibo/oneroster.rb +6 -0
  64. data/lib/meibo/organization.rb +17 -17
  65. data/lib/meibo/processing_mode.rb +53 -0
  66. data/lib/meibo/profile.rb +8 -4
  67. data/lib/meibo/profiles.rb +12 -120
  68. data/lib/meibo/pse_interoperability_standard/v5.rb +9 -0
  69. data/lib/meibo/pse_interoperability_standard/v6/enrollment.rb +11 -0
  70. data/lib/meibo/pse_interoperability_standard/v6.rb +18 -0
  71. data/lib/meibo/pse_interoperability_standard.rb +6 -0
  72. data/lib/meibo/reader.rb +24 -36
  73. data/lib/meibo/role.rb +24 -24
  74. data/lib/meibo/roster.rb +46 -47
  75. data/lib/meibo/user.rb +35 -35
  76. data/lib/meibo/user_profile.rb +20 -20
  77. data/lib/meibo/user_set.rb +1 -1
  78. data/lib/meibo/version.rb +1 -1
  79. data/lib/meibo.rb +10 -2
  80. data/meibo.gemspec +1 -1
  81. metadata +45 -28
  82. data/lib/meibo/eportal_v3/classroom.rb +0 -15
  83. data/lib/meibo/eportal_v3/course.rb +0 -15
  84. data/lib/meibo/eportal_v3/enrollment.rb +0 -15
  85. data/lib/meibo/eportal_v3/organization.rb +0 -15
  86. data/lib/meibo/eportal_v3/user.rb +0 -16
  87. data/lib/meibo/eportal_v3/user_profile.rb +0 -15
  88. data/lib/meibo/japan_profile/academic_session.rb +0 -29
  89. data/lib/meibo/japan_profile/classroom.rb +0 -24
  90. data/lib/meibo/japan_profile/course.rb +0 -18
  91. data/lib/meibo/japan_profile/demographic.rb +0 -15
  92. data/lib/meibo/japan_profile/enrollment.rb +0 -40
  93. data/lib/meibo/japan_profile/organization.rb +0 -20
  94. data/lib/meibo/japan_profile/organization_set.rb +0 -17
  95. data/lib/meibo/japan_profile/role_jp_m0.rb +0 -26
  96. data/lib/meibo/japan_profile/user.rb +0 -45
  97. data/lib/meibo/japan_profile/user_m0.rb +0 -12
  98. data/lib/meibo/japan_profile/user_set.rb +0 -14
  99. data/lib/meibo/manifest/processing_mode.rb +0 -41
  100. data/lib/meibo/user_m0.rb +0 -47
@@ -2,49 +2,62 @@
2
2
 
3
3
  module Meibo
4
4
  class Classroom
5
+ include DataModel
6
+
5
7
  TYPES = {
6
8
  homeroom: "homeroom",
7
9
  scheduled: "scheduled"
8
10
  }.freeze
9
11
 
10
- DataModel.define(
11
- self,
12
- attribute_name_to_header_field_map: {
13
- sourced_id: "sourcedId",
14
- status: "status",
15
- date_last_modified: "dateLastModified",
16
- title: "title",
17
- grades: "grades",
18
- course_sourced_id: "courseSourcedId",
19
- class_code: "classCode",
20
- class_type: "classType",
21
- location: "location",
22
- school_sourced_id: "schoolSourcedId",
23
- term_sourced_ids: "termSourcedIds",
24
- subjects: "subjects",
25
- subject_codes: "subjectCodes",
26
- periods: "periods"
27
- }.freeze,
28
- converters: {
29
- datetime: [:date_last_modified].freeze,
30
- enum: {
31
- class_type: [*TYPES.values, ENUM_EXT_PATTERN].freeze
32
- }.freeze,
33
- list: %i[
34
- grades
35
- term_sourced_ids
36
- subjects
37
- subject_codes
38
- periods
39
- ].freeze,
40
- required: %i[sourced_id title class_type course_sourced_id term_sourced_ids school_sourced_id].freeze,
41
- status: [:status].freeze
42
- }
12
+ define_attributes(
13
+ sourced_id: "sourcedId",
14
+ status: "status",
15
+ date_last_modified: "dateLastModified",
16
+ title: "title",
17
+ grades: "grades",
18
+ course_sourced_id: "courseSourcedId",
19
+ class_code: "classCode",
20
+ class_type: "classType",
21
+ location: "location",
22
+ school_sourced_id: "schoolSourcedId",
23
+ term_sourced_ids: "termSourcedIds",
24
+ subjects: "subjects",
25
+ subject_codes: "subjectCodes",
26
+ periods: "periods"
27
+ )
28
+
29
+ define_converters(
30
+ datetime: [:date_last_modified],
31
+ enum: {
32
+ class_type: [*TYPES.values, ENUM_EXT_PATTERN]
33
+ },
34
+ list: %i[
35
+ grades
36
+ term_sourced_ids
37
+ subjects
38
+ subject_codes
39
+ periods
40
+ ],
41
+ required: %i[sourced_id title class_type course_sourced_id term_sourced_ids school_sourced_id],
42
+ status: [:status]
43
43
  )
44
44
 
45
+ def self.parse(csv)
46
+ return to_enum(:parse, csv) unless block_given?
47
+
48
+ _parse(csv).with_index(1) do |row, line|
49
+ yield new(**row.to_h)
50
+ rescue SubjectsAndSubjectCodesLengthNotMatch
51
+ index = attribute_names.index(:subjects)
52
+ field = row[index]
53
+ field_info = CSV::FieldInfo.new(index, line, :subjects, false)
54
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
55
+ end
56
+ end
57
+
45
58
  def initialize(sourced_id:, title:, course_sourced_id:, class_type:, school_sourced_id:, term_sourced_ids:, status: nil, date_last_modified: nil, grades: [],
46
59
  class_code: nil, location: nil, subjects: [], subject_codes: [], periods: [], **extension_fields)
47
- raise InvalidDataTypeError unless subjects.is_a?(Array) && subject_codes.is_a?(Array) && subjects.size == subject_codes.size
60
+ raise SubjectsAndSubjectCodesLengthNotMatch unless subjects.is_a?(Array) && subject_codes.is_a?(Array) && subjects.size == subject_codes.size
48
61
 
49
62
  @sourced_id = sourced_id
50
63
  @status = status
@@ -7,7 +7,11 @@ module Meibo
7
7
 
8
8
  each do |classroom|
9
9
  school = roster.organizations.find(classroom.school_sourced_id)
10
- raise InvalidDataTypeError unless school.school?
10
+ unless school.school?
11
+ field = classroom.school_sourced_id
12
+ field_info = field_info_from(classroom, :school_sourced_id)
13
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
14
+ end
11
15
 
12
16
  roster.courses.find(classroom.course_sourced_id)
13
17
 
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "date"
4
- require "set"
5
4
  require "time"
6
5
 
7
6
  module Meibo
@@ -22,7 +21,7 @@ module Meibo
22
21
  year
23
22
  ].freeze
24
23
 
25
- class << self
24
+ class << self # rubocop:disable Metrics/ClassLength
26
25
  def build_header_field_to_attribute_converter(attribute_name_to_header_field_map)
27
26
  header_field_to_attribute_name_map = attribute_name_to_header_field_map.to_h do |attribute, header_field|
28
27
  [header_field, attribute]
@@ -82,7 +81,7 @@ module Meibo
82
81
  when nil
83
82
  nil
84
83
  else
85
- raise InvalidDataTypeError
84
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
86
85
  end
87
86
  else
88
87
  field
@@ -108,7 +107,7 @@ module Meibo
108
107
  begin
109
108
  Date.strptime(field, "%Y-%m-%d")
110
109
  rescue StandardError
111
- raise InvalidDataTypeError
110
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
112
111
  end
113
112
  else
114
113
  field
@@ -134,7 +133,7 @@ module Meibo
134
133
  begin
135
134
  Time.iso8601(field)
136
135
  rescue StandardError
137
- raise InvalidDataTypeError
136
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
138
137
  end
139
138
  else
140
139
  field
@@ -148,7 +147,7 @@ module Meibo
148
147
  return field unless field
149
148
 
150
149
  enum = enum_definition[field_info.index]
151
- raise InvalidDataTypeError if enum&.none? { |pat| pat.is_a?(String) ? field == pat : field.match?(pat) }
150
+ raise InvalidDataTypeError.new(field: field, field_info: field_info) if enum&.none? { |pat| pat.is_a?(String) ? field == pat : field.match?(pat) }
152
151
 
153
152
  field
154
153
  end
@@ -160,7 +159,7 @@ module Meibo
160
159
  return field unless field
161
160
 
162
161
  format = format_definition[field_info.index]
163
- raise InvalidDataTypeError if format && !field.match?(format)
162
+ raise InvalidDataTypeError.new(field: field, field_info: field_info) if format && !field.match?(format)
164
163
 
165
164
  field
166
165
  end
@@ -169,7 +168,7 @@ module Meibo
169
168
  def build_fullwidth_field_parser_converter(fullwidth_field_indexes)
170
169
  fullwidth_field_indexes = fullwidth_field_indexes.dup.freeze
171
170
  lambda do |field, field_info|
172
- raise InvalidDataTypeError if field && fullwidth_field_indexes.include?(field_info.index) && field.match?(/[\p{In_Halfwidth_and_Fullwidth_Forms}&&\p{Katakana}]/)
171
+ raise InvalidDataTypeError.new(field: field, field_info: field_info) if field && fullwidth_field_indexes.include?(field_info.index) && field.match?(/[\p{In_Halfwidth_and_Fullwidth_Forms}&&\p{Katakana}]/)
173
172
 
174
173
  field
175
174
  end
@@ -180,7 +179,7 @@ module Meibo
180
179
  lambda do |grades, field_info|
181
180
  next grades unless grade_field_indexes.include?(field_info.index)
182
181
 
183
- raise InvalidDataTypeError unless grades.all? { |grade| valid_grade.include?(grade) }
182
+ raise InvalidDataTypeError.new(field: grades, field_info: field_info) unless grades.all? { |grade| valid_grade.include?(grade) }
184
183
 
185
184
  grades
186
185
  end
@@ -193,7 +192,7 @@ module Meibo
193
192
  begin
194
193
  Integer(field, 10)
195
194
  rescue StandardError
196
- raise InvalidDataTypeError
195
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
197
196
  end
198
197
  else
199
198
  field
@@ -236,11 +235,10 @@ module Meibo
236
235
  def build_required_field_parser_converter(required_field_indexes)
237
236
  required_field_indexes = required_field_indexes.dup.freeze
238
237
  lambda do |field, field_info|
239
- if required_field_indexes.include?(field_info.index)
240
- raise MissingDataError if field.nil?
241
- raise MissingDataError if field.respond_to?(:empty?) && field.empty?
242
- end
243
- field
238
+ return field unless required_field_indexes.include?(field_info.index)
239
+ return field if field && (!field.respond_to?(:empty?) || !field.empty?)
240
+
241
+ raise MissingDataError.new(field: field, field_info: field_info)
244
242
  end
245
243
  end
246
244
 
@@ -251,7 +249,7 @@ module Meibo
251
249
  status_field_indexes = status_field_indexes.dup.freeze
252
250
  lambda do |field, field_info|
253
251
  if field && status_field_indexes.include?(field_info.index)
254
- raise InvalidDataTypeError, "invalid status: #{field}" unless %w[active tobedeleted].include?(field)
252
+ raise InvalidDataTypeError.new(field: field, field_info: field_info), "invalid status: #{field}" unless %w[active tobedeleted].include?(field)
255
253
  else
256
254
  field
257
255
  end
@@ -264,7 +262,7 @@ module Meibo
264
262
  if user_ids_field_indexes.include?(field_info.index) && !field.all? do |user_id|
265
263
  Meibo::User::USER_ID_FORMAT_REGEXP.match?(user_id)
266
264
  end
267
- raise InvalidDataTypeError
265
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
268
266
  end
269
267
 
270
268
  field
@@ -289,7 +287,7 @@ module Meibo
289
287
  begin
290
288
  Integer(field, 10)
291
289
  rescue StandardError
292
- raise InvalidDataTypeError
290
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
293
291
  end
294
292
  else
295
293
  field
data/lib/meibo/course.rb CHANGED
@@ -2,31 +2,44 @@
2
2
 
3
3
  module Meibo
4
4
  class Course
5
- DataModel.define(
6
- self,
7
- attribute_name_to_header_field_map: {
8
- sourced_id: "sourcedId",
9
- status: "status",
10
- date_last_modified: "dateLastModified",
11
- school_year_sourced_id: "schoolYearSourcedId",
12
- title: "title",
13
- course_code: "courseCode",
14
- grades: "grades",
15
- org_sourced_id: "orgSourcedId",
16
- subjects: "subjects",
17
- subject_codes: "subjectCodes"
18
- },
19
- converters: {
20
- datetime: [:date_last_modified],
21
- list: %i[grades subjects subject_codes],
22
- required: %i[sourced_id title org_sourced_id],
23
- status: [:status]
24
- }
5
+ include DataModel
6
+
7
+ define_attributes(
8
+ sourced_id: "sourcedId",
9
+ status: "status",
10
+ date_last_modified: "dateLastModified",
11
+ school_year_sourced_id: "schoolYearSourcedId",
12
+ title: "title",
13
+ course_code: "courseCode",
14
+ grades: "grades",
15
+ org_sourced_id: "orgSourcedId",
16
+ subjects: "subjects",
17
+ subject_codes: "subjectCodes"
18
+ )
19
+
20
+ define_converters(
21
+ datetime: [:date_last_modified],
22
+ list: %i[grades subjects subject_codes],
23
+ required: %i[sourced_id title org_sourced_id],
24
+ status: [:status]
25
25
  )
26
26
 
27
+ def self.parse(csv)
28
+ return to_enum(:parse, csv) unless block_given?
29
+
30
+ _parse(csv).with_index(1) do |row, line|
31
+ yield new(**row.to_h)
32
+ rescue SubjectsAndSubjectCodesLengthNotMatch
33
+ index = attribute_names.index(:subjects)
34
+ field = row[index]
35
+ field_info = CSV::FieldInfo.new(index, line, :subjects, false)
36
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
37
+ end
38
+ end
39
+
27
40
  def initialize(sourced_id:, title:, org_sourced_id:, status: nil, date_last_modified: nil, school_year_sourced_id: nil,
28
41
  course_code: nil, grades: [], subjects: [], subject_codes: [], **extension_fields)
29
- raise InvalidDataTypeError unless subjects.is_a?(Array) && subject_codes.is_a?(Array) && subjects.size == subject_codes.size
42
+ raise SubjectsAndSubjectCodesLengthNotMatch unless subjects.is_a?(Array) && subject_codes.is_a?(Array) && subjects.size == subject_codes.size
30
43
 
31
44
  @sourced_id = sourced_id
32
45
  @status = status
@@ -9,7 +9,11 @@ module Meibo
9
9
  if course.school_year_sourced_id
10
10
  school_year = roster.academic_sessions.find(course.school_year_sourced_id)
11
11
 
12
- raise InvalidDataTypeError unless school_year.school_year?
12
+ unless school_year.school_year?
13
+ field = school_year_sourced_id
14
+ field_info = field_info_from(course, :school_year_sourced_id)
15
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
16
+ end
13
17
  end
14
18
 
15
19
  roster.organizations.find(course.org_sourced_id)
@@ -5,13 +5,29 @@ require "csv"
5
5
  module Meibo
6
6
  module DataModel
7
7
  module ClassMethods
8
+ def define_attributes(attribute_names_to_header_fields)
9
+ attribute_names_to_header_fields = attribute_names_to_header_fields.dup.freeze
10
+ attribute_names = attribute_names_to_header_fields.keys.freeze
11
+ header_fields = attribute_names_to_header_fields.values.freeze
12
+ define_class_attribute(:attribute_names_to_header_fields, attribute_names_to_header_fields)
13
+ define_class_attribute(:attribute_names, attribute_names)
14
+ define_class_attribute(:header_fields, header_fields)
15
+
16
+ attr_reader(*attribute_names, :extension_fields)
17
+ end
18
+
19
+ def define_converters(converters)
20
+ converters = converters.dup.freeze
21
+ define_class_attribute(:converters, converters)
22
+ define_header_converters
23
+ define_parser_converters(converters)
24
+ define_write_converters(converters)
25
+ end
26
+
8
27
  def parse(csv)
9
28
  return to_enum(:parse, csv) unless block_given?
10
29
 
11
- validate_header_fields(CSV.parse_line(csv))
12
-
13
- CSV.parse(csv, encoding: Meibo::CSV_ENCODING, headers: true, converters: parser_converters,
14
- header_converters: header_converters).each do |row|
30
+ _parse(csv) do |row|
15
31
  yield new(**row.to_h)
16
32
  end
17
33
  end
@@ -24,44 +40,38 @@ module Meibo
24
40
  end
25
41
  raise ScrambledHeaderFieldsError unless actual_header_fields.take(header_fields.size) == header_fields
26
42
  end
27
- end
28
43
 
29
- def self.define(klass, attribute_name_to_header_field_map:, converters: {})
30
- attribute_name_to_header_field_map = attribute_name_to_header_field_map.dup.freeze
31
- attribute_names = attribute_name_to_header_field_map.keys.freeze
32
- header_fields = attribute_name_to_header_field_map.values.freeze
33
- converters = converters.dup.freeze
34
- define_class_attribute(klass, :attribute_name_to_header_field_map, attribute_name_to_header_field_map)
35
- define_class_attribute(klass, :attribute_names, attribute_names)
36
- define_class_attribute(klass, :header_fields, header_fields)
37
- define_class_attribute(klass, :converters, converters)
38
-
39
- define_header_converters(klass, attribute_name_to_header_field_map)
40
- define_parser_converters(klass, attribute_names: attribute_names, converters: converters)
41
- define_write_converters(klass, attribute_names: attribute_names, converters: converters)
42
-
43
- klass.attr_reader(*attribute_names, :extension_fields)
44
- klass.extend(ClassMethods)
45
- klass.include(self)
46
- end
44
+ private
47
45
 
48
- def self.define_class_attribute(klass, attribute, value)
49
- klass.define_singleton_method(attribute) { value }
50
- end
46
+ def _parse(csv, &)
47
+ validate_header_fields(CSV.parse_line(csv))
51
48
 
52
- def self.define_header_converters(klass, attribute_name_to_header_field_map)
53
- header_converters = Converter.build_header_field_to_attribute_converter(attribute_name_to_header_field_map)
54
- klass.define_singleton_method(:header_converters) { header_converters }
55
- end
49
+ CSV.parse(csv, encoding: Meibo::CSV_ENCODING, headers: true, converters: parser_converters, header_converters: header_converters).each(&)
50
+ end
56
51
 
57
- def self.define_parser_converters(klass, attribute_names:, converters:)
58
- parser_converter = Converter.build_parser_converter(fields: attribute_names, converters: converters)
59
- klass.define_singleton_method(:parser_converters) { parser_converter }
52
+ def define_class_attribute(attribute, value)
53
+ define_singleton_method(attribute) { value }
54
+ end
55
+
56
+ def define_header_converters
57
+ header_converters = Converter.build_header_field_to_attribute_converter(attribute_names_to_header_fields)
58
+ define_class_attribute(:header_converters, header_converters)
59
+ end
60
+
61
+ def define_parser_converters(converters)
62
+ parser_converter = Converter.build_parser_converter(fields: attribute_names, converters: converters)
63
+ define_class_attribute(:parser_converters, parser_converter)
64
+ end
65
+
66
+ def define_write_converters(converters)
67
+ write_converter = Converter.build_write_converter(fields: attribute_names, converters: converters)
68
+ define_class_attribute(:write_converters, write_converter)
69
+ end
60
70
  end
61
71
 
62
- def self.define_write_converters(klass, attribute_names:, converters:)
63
- write_converter = Converter.build_write_converter(fields: attribute_names, converters: converters)
64
- klass.define_singleton_method(:write_converters) { write_converter }
72
+ def self.included(base)
73
+ super
74
+ base.extend(ClassMethods)
65
75
  end
66
76
 
67
77
  def lineno
@@ -14,22 +14,14 @@ module Meibo
14
14
 
15
15
  def <<(new_data)
16
16
  raise DataNotFoundError, "sourcedIdがありません" unless new_data.sourced_id
17
-
18
- if data_by_sourced_id.key?(new_data.sourced_id)
19
- raise SourcedIdDuplicatedError,
20
- "sourcedId\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059"
21
- end
17
+ raise SourcedIdDuplicatedError, "sourcedIdが重複しています" if data_by_sourced_id.key?(new_data.sourced_id)
22
18
 
23
19
  @data << new_data
24
20
  @cache.clear
25
21
  end
26
22
 
27
23
  def check_semantically_consistent
28
- unless @data.size == data_by_sourced_id.size
29
- raise SourcedIdDuplicatedError,
30
- "sourcedId\u304C\u91CD\u8907\u3057\u3066\u3044\u307E\u3059"
31
- end
32
-
24
+ raise SourcedIdDuplicatedError, "sourcedIdが重複しています" unless @data.size == data_by_sourced_id.size
33
25
  raise DataNotFoundError, "sourcedIdがありません" unless data_by_sourced_id[nil].nil?
34
26
  end
35
27
 
@@ -81,5 +73,11 @@ module Meibo
81
73
  def empty_set
82
74
  @empty_set ||= new([])
83
75
  end
76
+
77
+ def field_info_from(model, attribute_name)
78
+ index = model.class.attribute_names.index(attribute_name)
79
+ line = lineno(model)
80
+ CSV::FieldInfo.new(index, line, attribute_name, false)
81
+ end
84
82
  end
85
83
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Meibo
4
4
  class Demographic
5
+ include DataModel
6
+
5
7
  SEX = {
6
8
  male: "male",
7
9
  female: "female",
@@ -9,42 +11,40 @@ module Meibo
9
11
  other: "other"
10
12
  }.freeze
11
13
 
12
- DataModel.define(
13
- self,
14
- attribute_name_to_header_field_map: {
15
- sourced_id: "sourcedId",
16
- status: "status",
17
- date_last_modified: "dateLastModified",
18
- birth_date: "birthDate",
19
- sex: "sex",
20
- american_indian_or_alaska_native: "americanIndianOrAlaskaNative",
21
- asian: "asian",
22
- black_or_african_american: "blackOrAfricanAmerican",
23
- native_hawaiian_or_other_pacific_islander: "nativeHawaiianOrOtherPacificIslander",
24
- white: "white",
25
- demographic_race_two_or_more_races: "demographicRaceTwoOrMoreRaces",
26
- hispanic_or_latino_ethnicity: "hispanicOrLatinoEthnicity",
27
- country_of_birth_code: "countryOfBirthCode",
28
- state_of_birth_abbreviation: "stateOfBirthAbbreviation",
29
- city_of_birth: "cityOfBirth",
30
- public_school_residence_status: "publicSchoolResidenceStatus"
31
- }.freeze,
32
- converters: {
33
- boolean: %i[
34
- american_indian_or_alaska_native
35
- asian
36
- black_or_african_american
37
- native_hawaiian_or_other_pacific_islander
38
- white
39
- demographic_race_two_or_more_races
40
- hispanic_or_latino_ethnicity
41
- ].freeze,
42
- date: [:birth_date].freeze,
43
- datetime: [:date_last_modified].freeze,
44
- enum: { sex: [*SEX.values, ENUM_EXT_PATTERN].freeze }.freeze,
45
- required: [:sourced_id].freeze,
46
- status: [:status].freeze
47
- }
14
+ define_attributes(
15
+ sourced_id: "sourcedId",
16
+ status: "status",
17
+ date_last_modified: "dateLastModified",
18
+ birth_date: "birthDate",
19
+ sex: "sex",
20
+ american_indian_or_alaska_native: "americanIndianOrAlaskaNative",
21
+ asian: "asian",
22
+ black_or_african_american: "blackOrAfricanAmerican",
23
+ native_hawaiian_or_other_pacific_islander: "nativeHawaiianOrOtherPacificIslander",
24
+ white: "white",
25
+ demographic_race_two_or_more_races: "demographicRaceTwoOrMoreRaces",
26
+ hispanic_or_latino_ethnicity: "hispanicOrLatinoEthnicity",
27
+ country_of_birth_code: "countryOfBirthCode",
28
+ state_of_birth_abbreviation: "stateOfBirthAbbreviation",
29
+ city_of_birth: "cityOfBirth",
30
+ public_school_residence_status: "publicSchoolResidenceStatus"
31
+ )
32
+
33
+ define_converters(
34
+ boolean: %i[
35
+ american_indian_or_alaska_native
36
+ asian
37
+ black_or_african_american
38
+ native_hawaiian_or_other_pacific_islander
39
+ white
40
+ demographic_race_two_or_more_races
41
+ hispanic_or_latino_ethnicity
42
+ ],
43
+ date: [:birth_date],
44
+ datetime: [:date_last_modified],
45
+ enum: { sex: [*SEX.values, ENUM_EXT_PATTERN] },
46
+ required: [:sourced_id],
47
+ status: [:status]
48
48
  )
49
49
 
50
50
  def initialize(sourced_id:, status: nil, date_last_modified: nil, birth_date: nil, sex: nil,
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Meibo
4
4
  class Enrollment
5
+ include DataModel
6
+
5
7
  ROLES = {
6
8
  administrator: "administrator",
7
9
  proctor: "proctor",
@@ -9,28 +11,26 @@ module Meibo
9
11
  teacher: "teacher"
10
12
  }.freeze
11
13
 
12
- DataModel.define(
13
- self,
14
- attribute_name_to_header_field_map: {
15
- sourced_id: "sourcedId",
16
- status: "status",
17
- date_last_modified: "dateLastModified",
18
- class_sourced_id: "classSourcedId",
19
- school_sourced_id: "schoolSourcedId",
20
- user_sourced_id: "userSourcedId",
21
- role: "role",
22
- primary: "primary",
23
- begin_date: "beginDate",
24
- end_date: "endDate"
25
- }.freeze,
26
- converters: {
27
- boolean: [:primary].freeze,
28
- date: %i[begin_date end_date].freeze,
29
- datetime: [:date_last_modified].freeze,
30
- enum: { role: [*ROLES.values, ENUM_EXT_PATTERN].freeze }.freeze,
31
- required: %i[sourced_id class_sourced_id school_sourced_id user_sourced_id role].freeze,
32
- status: [:status].freeze
33
- }
14
+ define_attributes(
15
+ sourced_id: "sourcedId",
16
+ status: "status",
17
+ date_last_modified: "dateLastModified",
18
+ class_sourced_id: "classSourcedId",
19
+ school_sourced_id: "schoolSourcedId",
20
+ user_sourced_id: "userSourcedId",
21
+ role: "role",
22
+ primary: "primary",
23
+ begin_date: "beginDate",
24
+ end_date: "endDate"
25
+ )
26
+
27
+ define_converters(
28
+ boolean: [:primary],
29
+ date: %i[begin_date end_date],
30
+ datetime: [:date_last_modified],
31
+ enum: { role: [*ROLES.values, ENUM_EXT_PATTERN] },
32
+ required: %i[sourced_id class_sourced_id school_sourced_id user_sourced_id role],
33
+ status: [:status]
34
34
  )
35
35
 
36
36
  def initialize(sourced_id:, class_sourced_id:, school_sourced_id:, user_sourced_id:, role:, status: nil,
@@ -8,10 +8,17 @@ module Meibo
8
8
  each do |enrollment|
9
9
  roster.classes.find(enrollment.class_sourced_id)
10
10
  school = roster.organizations.find(enrollment.school_sourced_id)
11
- raise InvalidDataTypeError unless school.school?
11
+
12
+ unless school.school?
13
+ field = enrollment.school_sourced_id
14
+ field_info = field_info_from(enrollment, :school_sourced_id)
15
+ raise InvalidDataTypeError.new(field: field, field_info: field_info)
16
+ end
12
17
 
13
18
  roster.users.find(enrollment.user_sourced_id)
14
- raise InvalidDataTypeError if enrollment.primary && !enrollment.teacher?
19
+ next if !enrollment.primary || enrollment.teacher?
20
+
21
+ raise InvalidDataTypeError.new(field: enrollment.primary, field_info: field_info_from(enrollment, :primary))
15
22
  end
16
23
  end
17
24
 
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meibo
4
+ module Eportal
5
+ module V3
6
+ class Classroom < ::Meibo::JapanProfile::V1_1::Classroom
7
+ define_converters(superclass.converters.merge(fullwidth: [*superclass.converters[:fullwidth], :title, :location]))
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meibo
4
+ module Eportal
5
+ module V3
6
+ class Course < ::Meibo::JapanProfile::V1_1::Course
7
+ define_converters(superclass.converters.merge(fullwidth: [*superclass.converters[:fullwidth], :title]))
8
+ end
9
+ end
10
+ end
11
+ end