students_list_yaml 0.0.1
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/lib/data_list.rb +58 -0
- data/lib/data_list_student_short.rb +89 -0
- data/lib/data_table.rb +119 -0
- data/lib/module.rb +14 -0
- data/lib/student.rb +124 -0
- data/lib/student_base.rb +36 -0
- data/lib/student_short.rb +28 -0
- data/lib/students_list_common.rb +237 -0
- data/lib/students_list_yaml.rb +40 -0
- metadata +49 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4a1156ccf6aab89dc74f1cfb32eb6069bc558d4363692afbdb76588f505cc14c
|
|
4
|
+
data.tar.gz: 3e6af363bcceb9bcbfe4088011b29a26ffdc2356989b5c529df72fe18a073f31
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ec4a0460228be9775535087d6dda30f379c60575984726e3a50eca60d7053939c0fcc52d412cad0c0016916bb39cc3ab3027219cb19148a66ebe28bb661ec600
|
|
7
|
+
data.tar.gz: 85fef37b60095af40a2c35a3fa07fcdd600b2030ec4f2d6b505bfc31f28b8528cdd381956fb92412bc15c17967251e51aefde89ebc2ae10a9954184d5e97b32d
|
data/lib/data_list.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
class DataList
|
|
2
|
+
def initialize(elements)
|
|
3
|
+
@elements = elements
|
|
4
|
+
@selected = []
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def elements=(new_elements)
|
|
8
|
+
@elements = new_elements.dup
|
|
9
|
+
clear_selected
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def select(number)
|
|
13
|
+
validate_index(number)
|
|
14
|
+
@selected << number unless @selected.include?(number)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_selected
|
|
18
|
+
@selected.map { |index| @elements[index].id }.dup
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get_names
|
|
22
|
+
raise NotImplementedError, "Метод должен быть реализован в наследниках"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def get_data
|
|
26
|
+
data = []
|
|
27
|
+
|
|
28
|
+
@elements.each_with_index do |student, index|
|
|
29
|
+
row = [index + 1]
|
|
30
|
+
row.concat(student_info(student))
|
|
31
|
+
data << row
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
create_data_table(data)
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def student_info student
|
|
39
|
+
raise NotImplementedError, "Метод должен быть реализован в наследниках"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def create_data_table data
|
|
43
|
+
raise NotImplementedError, "Метод должен быть реализован в наследниках"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def clear_selected
|
|
47
|
+
@selected.clear
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
protected
|
|
51
|
+
|
|
52
|
+
def validate_index(index)
|
|
53
|
+
if index < 0 || index >= @elements.length
|
|
54
|
+
raise IndexError, "Некорректный индекс: #{index}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require_relative 'data_list'
|
|
2
|
+
require_relative 'student_short'
|
|
3
|
+
require_relative 'data_table'
|
|
4
|
+
|
|
5
|
+
class DataListStudentShort < DataList
|
|
6
|
+
def get_names
|
|
7
|
+
["№ по порядку", "ФИО", "Контакт", "Git"]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def student_info student
|
|
11
|
+
[student.last_name_initials, student.contact || "нет", student.git || "нет"]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_data_table data
|
|
15
|
+
DataTable.new(data)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
'''
|
|
20
|
+
students = [
|
|
21
|
+
StudentShort.new(
|
|
22
|
+
id: 1,
|
|
23
|
+
last_name_initials: "Иванов И.И.",
|
|
24
|
+
contact: "phone - +79181112233",
|
|
25
|
+
git: "https://github.com/qwerty"
|
|
26
|
+
),
|
|
27
|
+
StudentShort.new(
|
|
28
|
+
id: 2,
|
|
29
|
+
last_name_initials: "Сидоров С.С.",
|
|
30
|
+
contact: "email - allakk@mail.ru",
|
|
31
|
+
git: "https://github.com/asdfg"
|
|
32
|
+
),
|
|
33
|
+
StudentShort.new(
|
|
34
|
+
id: 3,
|
|
35
|
+
last_name_initials: "Петров П.П.",
|
|
36
|
+
contact: "telegram - @popopo",
|
|
37
|
+
git: nil
|
|
38
|
+
)
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
data_list = DataListStudentShort.new(students)
|
|
42
|
+
|
|
43
|
+
data_table = data_list.get_data
|
|
44
|
+
puts "Кол-во студентов: #{data_table.rows_count}"
|
|
45
|
+
puts "Кол-во столббцов: #{data_table.columns_count}"
|
|
46
|
+
|
|
47
|
+
header = data_list.get_names
|
|
48
|
+
puts header.join("\t\t")
|
|
49
|
+
|
|
50
|
+
(0...data_table.rows_count).each do |row|
|
|
51
|
+
row_data = (0...data_table.columns_count).map { |column| data_table.get_element(row, column) }
|
|
52
|
+
puts row_data.join(" | ")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
puts "\nРабота c select:"
|
|
56
|
+
data_list.select(0)
|
|
57
|
+
data_list.select(2)
|
|
58
|
+
|
|
59
|
+
puts "Получим, кого выделили (id): #{data_list.get_selected}"
|
|
60
|
+
data_list.clear_selected
|
|
61
|
+
puts "Очистили: #{data_list.get_selected}"
|
|
62
|
+
|
|
63
|
+
puts "Контакт в строке 1: #{data_table.get_element(0, 2)}"
|
|
64
|
+
puts "Студент в строке 2: #{data_table.get_element(1, 1)}"
|
|
65
|
+
puts "Git в строке 3: #{data_table.get_element(2, 3)}"
|
|
66
|
+
'''
|
|
67
|
+
|
|
68
|
+
'''
|
|
69
|
+
#можно заменять через сеттер для 5 задания
|
|
70
|
+
students1 = [
|
|
71
|
+
StudentShort.new(id: 1, last_name_initials: "Иванов И.И.", contact: "phone1", git: "git1"),
|
|
72
|
+
StudentShort.new(id: 2, last_name_initials: "Петров П.П.", contact: "phone2", git: "git2")
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
data_list = DataListStudentShort.new(students1)
|
|
76
|
+
data_list.select(0)
|
|
77
|
+
puts "Выделенные после первого набора: #{data_list.get_selected}"
|
|
78
|
+
|
|
79
|
+
students2 = [
|
|
80
|
+
StudentShort.new(id: 3, last_name_initials: "Сидоров С.С.", contact: "phone3", git: "git3"),
|
|
81
|
+
StudentShort.new(id: 4, last_name_initials: "Козлов К.К.", contact: "phone4", git: "git4"),
|
|
82
|
+
StudentShort.new(id: 5, last_name_initials: "Новиков Н.Н.", contact: "phone5", git: "git5")
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
data_list.elements = students2
|
|
86
|
+
puts "Выделенные после замены массива: #{data_list.get_selected}"
|
|
87
|
+
|
|
88
|
+
puts "Заголовки столбцов: #{data_list.get_names}"
|
|
89
|
+
'''
|
data/lib/data_table.rb
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
class DataTable
|
|
2
|
+
def initialize(data)
|
|
3
|
+
validate_data(data)
|
|
4
|
+
@data = data.dup
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def get_element(row, col)
|
|
8
|
+
validate_indices(row, col)
|
|
9
|
+
safe_return(@data[row][col])
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def columns_count
|
|
13
|
+
return 0 if @data.empty?
|
|
14
|
+
@data.map{|element| element.length}.max
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def rows_count
|
|
18
|
+
@data.length
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def validate_data(data)
|
|
24
|
+
unless data.is_a?(Array) && data.all? { |row| row.is_a?(Array) }
|
|
25
|
+
raise ArgumentError, "Данные д/б двумерным массивом"
|
|
26
|
+
end
|
|
27
|
+
widths = data.map{|element| element.length}.uniq
|
|
28
|
+
return if widths.length <= 1
|
|
29
|
+
raise ArgumentError, "Все строки должны иметь одинаковое количество атрибутов"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def validate_indices(row, col)
|
|
33
|
+
if row < 0 || row >= rows_count
|
|
34
|
+
raise IndexError, " индекс строки: #{row}"
|
|
35
|
+
end
|
|
36
|
+
if col < 0 || col >= @data[row].length
|
|
37
|
+
raise IndexError, "Некорректный индекс столбца: #{col}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def safe_return(value)
|
|
42
|
+
copy = deep_copy(value)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def deep_copy(obj)
|
|
46
|
+
case obj
|
|
47
|
+
when Array
|
|
48
|
+
obj.map { |v| deep_copy(v) }
|
|
49
|
+
when Hash
|
|
50
|
+
obj.transform_values { |v| deep_copy(v) }
|
|
51
|
+
when String
|
|
52
|
+
obj.dup
|
|
53
|
+
else
|
|
54
|
+
obj
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
'''
|
|
61
|
+
employee_data = [
|
|
62
|
+
[1, "Иван Иванов", 25, [50000,20], false],
|
|
63
|
+
[2, "Мария Петрова", 30, 60000, false],
|
|
64
|
+
[3, "Петр Сидоров", 35, 70000, true],
|
|
65
|
+
[4, "Анна Козлова", 28, 55000, true]
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
employee_table = DataTable.new(employee_data)
|
|
69
|
+
|
|
70
|
+
puts "Таблица сотрудников:"
|
|
71
|
+
puts "Количество строк: #{employee_table.rows_count}"
|
|
72
|
+
puts "Количество столбцов: #{employee_table.columns_count}"
|
|
73
|
+
|
|
74
|
+
puts "\nКонкретный элемент до попытки записи: #{employee_table.get_element(0,3)}"
|
|
75
|
+
puts "table element id: #{employee_table.get_element(0,3).object_id}"
|
|
76
|
+
|
|
77
|
+
element= employee_table.get_element(0,3)<<4
|
|
78
|
+
puts "element: #{element}"
|
|
79
|
+
puts "element id: #{element.object_id}"
|
|
80
|
+
|
|
81
|
+
puts "Конкретный элемент после попытки записи: #{employee_table.get_element(0,3)}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
puts "\nДанные:"
|
|
85
|
+
(0...employee_table.rows_count).each do |row|
|
|
86
|
+
(0...employee_table.columns_count).each do |col|
|
|
87
|
+
print "#{employee_table.get_element(row, col)}\t"
|
|
88
|
+
end
|
|
89
|
+
puts
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# с dup не добавляется, работает как надо.
|
|
93
|
+
# puts "Проверка на добавление в исходный массив, поменяется ли наш экземпляр?"
|
|
94
|
+
# employee_data << [1, "Иван Иванов", 25, [50000,20], true]
|
|
95
|
+
# puts "\nДанные:"
|
|
96
|
+
# (0...employee_table.rows_count).each do |row|
|
|
97
|
+
# (0...employee_table.columns_count).each do |col|
|
|
98
|
+
# print "#{employee_table.get_element(row, col)}\t"
|
|
99
|
+
# end
|
|
100
|
+
# puts
|
|
101
|
+
# end
|
|
102
|
+
|
|
103
|
+
puts "Полученный элемент нельзя редактировать:"
|
|
104
|
+
data = [[1, "test"], [2, "hello"]]
|
|
105
|
+
table = DataTable.new(data)
|
|
106
|
+
element = table.get_element(0, 1)
|
|
107
|
+
element << " modified"
|
|
108
|
+
puts "#{table.get_element(0, 1)}"
|
|
109
|
+
|
|
110
|
+
#не работает, так и д/б. нет доступа
|
|
111
|
+
# table.get_element(0, 1) = 1111
|
|
112
|
+
# puts "#{table.get_element(0, 1)}"
|
|
113
|
+
|
|
114
|
+
#Тоже не работает кек, так и должно быть. нет доступа
|
|
115
|
+
#puts "#{employee_table.@data}"
|
|
116
|
+
#puts "#{employee_table.data}"
|
|
117
|
+
'''
|
|
118
|
+
|
|
119
|
+
|
data/lib/module.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module ValidatedAttributes
|
|
2
|
+
|
|
3
|
+
def attr_validate_writer(attribute, field_name: nil, required: true, with:)
|
|
4
|
+
define_method("#{attribute}=") do |value|
|
|
5
|
+
if value.nil? && !required
|
|
6
|
+
instance_variable_set("@#{attribute}", value)
|
|
7
|
+
elsif value && self.class.send(with, value)
|
|
8
|
+
instance_variable_set("@#{attribute}", value)
|
|
9
|
+
else
|
|
10
|
+
raise ArgumentError, "Ошибка валидации #{attribute}."
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/student.rb
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require_relative 'student_base.rb'
|
|
2
|
+
require_relative 'module.rb'
|
|
3
|
+
|
|
4
|
+
class Student < StudentBase
|
|
5
|
+
include Comparable
|
|
6
|
+
extend ValidatedAttributes
|
|
7
|
+
attr_reader :last_name, :first_name, :patronymic
|
|
8
|
+
|
|
9
|
+
NAME_REGEX = /\A[A-ZА-ЯЁ][a-zа-яё]+\z/
|
|
10
|
+
PHONE_REGEX = /^(\+7|8)?[\s\-\(]?(\d{3})[\s\-\)]?(\d{3})[\s\-]?(\d{2})[\s\-]?(\d{2})$/
|
|
11
|
+
TELEGRAM_REGEX = /^@[a-zA-Z0-9_]{5,32}$/
|
|
12
|
+
EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
|
13
|
+
GIT_REGEX = /^https:\/\/(github|gitlab)\.com\/[a-zA-Z0-9_-]+\/?$/
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
attr_validate_writer :last_name, field_name: "фамилии", required: true, with: :valid_name?
|
|
17
|
+
attr_validate_writer :first_name, field_name: "имени", required: true, with: :valid_name?
|
|
18
|
+
attr_validate_writer :patronymic, field_name: "отчества", required: false, with: :valid_name?
|
|
19
|
+
attr_validate_writer :git, field_name: "Гита", required: false, with: :valid_git?
|
|
20
|
+
attr_validate_writer :phone, field_name: "телефона", required: false, with: :valid_phone?
|
|
21
|
+
attr_validate_writer :telegram, field_name: "телеграмма", required: false, with: :valid_telegram?
|
|
22
|
+
attr_validate_writer :email, field_name: "почты", required: false, with: :valid_email?
|
|
23
|
+
|
|
24
|
+
def initialize(last_name:, first_name:, id: nil, patronymic: nil, phone: nil, telegram: nil, email: nil, git: nil)
|
|
25
|
+
raise ArgumentError, "Ошибка валидации гита" if !git.nil? && !self.class.valid_git?(git)
|
|
26
|
+
super(id: id, git: git)
|
|
27
|
+
self.last_name = last_name
|
|
28
|
+
self.first_name = first_name
|
|
29
|
+
self.patronymic = patronymic
|
|
30
|
+
|
|
31
|
+
set_contact_values(phone: phone, telegram: telegram, email: email)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def contact
|
|
35
|
+
contact_data = find_primary_contact
|
|
36
|
+
return nil unless contact_data
|
|
37
|
+
|
|
38
|
+
"#{contact_data[:type]} - #{contact_data[:value]}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def contact=(hash)
|
|
42
|
+
raise ArgumentError, "Неверный формат (не Hash)." unless hash.is_a?(Hash)
|
|
43
|
+
raise ArgumentError, "Не введено 3 контакта." if hash.length != 3
|
|
44
|
+
|
|
45
|
+
valid_keys = [:phone, :telegram, :email]
|
|
46
|
+
invalid_keys = hash.keys - valid_keys
|
|
47
|
+
|
|
48
|
+
raise ArgumentError, "Неизвестный тип контакта: #{invalid_keys.first}." if !invalid_keys.empty?
|
|
49
|
+
|
|
50
|
+
set_contact_values(phone: hash[:phone], telegram: hash[:telegram], email: hash[:email])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def last_name_initials
|
|
54
|
+
initials = "#{first_name[0]}."
|
|
55
|
+
initials += " #{patronymic[0]}." if patronymic
|
|
56
|
+
"#{last_name} #{initials}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def <=>(other)
|
|
60
|
+
return nil unless other.is_a?(Student)
|
|
61
|
+
|
|
62
|
+
[@last_name, @first_name, @patronymic || ''] <=>
|
|
63
|
+
[other.last_name, other.first_name, other.patronymic || '']
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def to_s
|
|
67
|
+
result = "#{id}"
|
|
68
|
+
result += "\nФИО: #{last_name} #{first_name} #{patronymic || 'нет'} "
|
|
69
|
+
result += "\nТелефон: #{@phone}" if @phone && !@phone.empty?
|
|
70
|
+
result += "\nТелеграм: #{@telegram}" if @telegram && !@telegram.empty?
|
|
71
|
+
result += "\nПочта: #{@email}" if @email && !@email.empty?
|
|
72
|
+
result += "Git: #{git}" if has_git?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class << self
|
|
76
|
+
def valid_name?(name)
|
|
77
|
+
name.is_a?(String) && name.length >= 2 && name.match?(NAME_REGEX)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def valid_phone?(phone)
|
|
81
|
+
validate_field(phone) { |v| v.match?(PHONE_REGEX) }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def valid_telegram?(telegram)
|
|
85
|
+
validate_field(telegram) { |v| v.match?(TELEGRAM_REGEX) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def valid_email?(email)
|
|
89
|
+
validate_field(email) { |v| v.match?(EMAIL_REGEX) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def valid_git?(git)
|
|
93
|
+
validate_field(git) { |v| v.match?(GIT_REGEX) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def validate_field(value)
|
|
99
|
+
return true if value.nil? || value.empty?
|
|
100
|
+
yield(value)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def find_primary_contact
|
|
107
|
+
[
|
|
108
|
+
{ type: 'telegram', value: @telegram },
|
|
109
|
+
{ type: 'email', value: @email },
|
|
110
|
+
{ type: 'phone', value: @phone }
|
|
111
|
+
].find { |contact| contact_present?(contact[:value]) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def contact_present?(value)
|
|
115
|
+
value && !value.empty?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def set_contact_values(phone:, telegram:, email:)
|
|
119
|
+
self.phone = phone
|
|
120
|
+
self.telegram = telegram
|
|
121
|
+
self.email = email
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
end
|
data/lib/student_base.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
class StudentBase
|
|
2
|
+
attr_reader :id, :git
|
|
3
|
+
|
|
4
|
+
def initialize(id: nil, git: nil)
|
|
5
|
+
@id = id
|
|
6
|
+
@git = git
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_s
|
|
10
|
+
raise NotImplementedError, "Метод to_s должен быть реализован в подклассе"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def contact
|
|
14
|
+
raise NotImplementedError, "Метод contact должен быть реализован в подклассе"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def last_name_initials
|
|
18
|
+
raise NotImplementedError, "Метод last_name_initials должен быть реализован в подклассе"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def has_contact?
|
|
22
|
+
!contact.nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def has_git?
|
|
26
|
+
!git.nil?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def short_info
|
|
30
|
+
info = "ID: #{id}, ФИО: #{last_name_initials}"
|
|
31
|
+
info += ", Контакт: #{contact}" if has_contact?
|
|
32
|
+
info += ", Git: #{git}" if has_git?
|
|
33
|
+
info
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require_relative 'student.rb'
|
|
2
|
+
require_relative 'student_base.rb'
|
|
3
|
+
|
|
4
|
+
class StudentShort < StudentBase
|
|
5
|
+
attr_reader :last_name_initials, :contact
|
|
6
|
+
|
|
7
|
+
def initialize(id:, last_name_initials:, contact: nil, git: nil)
|
|
8
|
+
super(id: id, git: git)
|
|
9
|
+
@last_name_initials = last_name_initials
|
|
10
|
+
@contact = contact
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.from_student(student)
|
|
14
|
+
raise ArgumentError, 'Указан не экземпляр класса Student' unless student.is_a?(Student)
|
|
15
|
+
|
|
16
|
+
new( id: student.id, last_name_initials: student.last_name_initials, contact: student.contact, git: student.git)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_s
|
|
20
|
+
<<~TEXT
|
|
21
|
+
ID: #{id}
|
|
22
|
+
ФИО: #{last_name_initials}
|
|
23
|
+
Контакт: #{contact}
|
|
24
|
+
Гит: #{git}
|
|
25
|
+
TEXT
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
require_relative 'student'
|
|
2
|
+
require_relative 'student_short'
|
|
3
|
+
require_relative 'data_list_student_short'
|
|
4
|
+
|
|
5
|
+
# Abstract base class for managing student data persistence
|
|
6
|
+
#
|
|
7
|
+
# This class provides a common interface for reading and writing student data
|
|
8
|
+
# to different file formats. It implements the Template Method pattern,
|
|
9
|
+
# defining the overall algorithm while delegating format-specific operations
|
|
10
|
+
# to concrete subclasses.
|
|
11
|
+
#
|
|
12
|
+
# @abstract Subclasses must implement {#concrete_realization_read} and {#concrete_realization_write}
|
|
13
|
+
# @example
|
|
14
|
+
# # This class should not be instantiated directly
|
|
15
|
+
# # Use StudentsListJSON or StudentsListYAML instead
|
|
16
|
+
# json_list = StudentsListJSON.new('students.json')
|
|
17
|
+
# students = json_list.read_all
|
|
18
|
+
class StudentsListCommon
|
|
19
|
+
# Initialize a new StudentsListCommon instance
|
|
20
|
+
#
|
|
21
|
+
# @param file_path [String] Path to the file where student data will be stored
|
|
22
|
+
# @raise [NotImplementedError] if the file doesn't exist and concrete implementation
|
|
23
|
+
# doesn't provide write method
|
|
24
|
+
def initialize file_path
|
|
25
|
+
@file_path = file_path
|
|
26
|
+
ensure_file_exists
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Read all student data from the file
|
|
30
|
+
#
|
|
31
|
+
# @return [Array<Hash>] Array of student data as hashes
|
|
32
|
+
# @raise [ArgumentError] if the parsed data is not an array
|
|
33
|
+
# @raise [NotImplementedError] if concrete_realization_read is not implemented
|
|
34
|
+
def read_all
|
|
35
|
+
raw = File.exist?(@file_path) ? File.read(@file_path) : ''
|
|
36
|
+
return [] if raw.strip.empty?
|
|
37
|
+
data = concrete_realization_read(raw)
|
|
38
|
+
raise ArgumentError, "Должен содержать массив" unless data.is_a?(Array)
|
|
39
|
+
data
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Write all student data to the file
|
|
43
|
+
#
|
|
44
|
+
# @param array_of_hashes [Array<Hash>] Array of student data as hashes to write
|
|
45
|
+
# @raise [ArgumentError] if array_of_hashes is not an array
|
|
46
|
+
# @raise [NotImplementedError] if concrete_realization_write is not implemented
|
|
47
|
+
def write_all array_of_hashes
|
|
48
|
+
raise ArgumentError, "Ожидается массив" unless array_of_hashes.is_a?(Array)
|
|
49
|
+
File.write(@file_path, concrete_realization_write(array_of_hashes))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Find a student by their ID
|
|
53
|
+
#
|
|
54
|
+
# @param student_id [Integer] The ID of the student to find
|
|
55
|
+
# @return [Student, nil] The student object if found, nil otherwise
|
|
56
|
+
def get_student_by_id student_id
|
|
57
|
+
h = read_all.find { |element| element['id'] == student_id }
|
|
58
|
+
return nil unless h
|
|
59
|
+
hash_to_student(h)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get a paginated list of student short objects
|
|
63
|
+
#
|
|
64
|
+
# @param k [Integer] Number of students to return (page size)
|
|
65
|
+
# @param n [Integer] Starting index (offset)
|
|
66
|
+
# @param existing_data_list [DataListStudentShort, nil] Optional existing data list to populate
|
|
67
|
+
# @return [DataListStudentShort] Data list containing student short objects
|
|
68
|
+
# @raise [ArgumentError] if k or n are not non-negative integers
|
|
69
|
+
def get_k_n_student_short_list(k, n, existing_data_list=nil)
|
|
70
|
+
raise ArgumentError, "k и n должны быть неотрицательными" unless k.is_a?(Integer) && n.is_a?(Integer) && k >= 0 && n >= 0
|
|
71
|
+
slice = read_all[n, k] || []
|
|
72
|
+
shorts = slice.map { |h| StudentShort.from_student(hash_to_student(h)) }
|
|
73
|
+
if existing_data_list
|
|
74
|
+
existing_data_list.elements = shorts
|
|
75
|
+
existing_data_list
|
|
76
|
+
else
|
|
77
|
+
DataListStudentShort.new(shorts)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Sort all students by full name and save to file
|
|
82
|
+
#
|
|
83
|
+
# Sorts students by last_name, then first_name, then patronymic
|
|
84
|
+
# @return [void]
|
|
85
|
+
def sort_by_full_name
|
|
86
|
+
all = read_all
|
|
87
|
+
all.sort_by! { |h| [h['last_name'] || '', h['first_name'] || '', h['patronymic'] || ''] }
|
|
88
|
+
write_all(all)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Add a new student to the list
|
|
92
|
+
#
|
|
93
|
+
# @param student [Student] The student object to add
|
|
94
|
+
# @return [Integer] The ID assigned to the new student
|
|
95
|
+
# @raise [ArgumentError] if student is not a Student object
|
|
96
|
+
def add_student student
|
|
97
|
+
raise ArgumentError, "Ожидается объект класса Student" unless student.is_a?(Student)
|
|
98
|
+
all = read_all
|
|
99
|
+
new_id = generate_new_id(all)
|
|
100
|
+
student_with_id = Student.new(
|
|
101
|
+
id: new_id,
|
|
102
|
+
last_name: student.last_name,
|
|
103
|
+
first_name: student.first_name,
|
|
104
|
+
patronymic: student.instance_variable_get(:@patronymic),
|
|
105
|
+
phone: student.instance_variable_get(:@phone),
|
|
106
|
+
telegram: student.instance_variable_get(:@telegram),
|
|
107
|
+
email: student.instance_variable_get(:@email),
|
|
108
|
+
git: student.git
|
|
109
|
+
)
|
|
110
|
+
all << student_to_hash(student_with_id)
|
|
111
|
+
write_all(all)
|
|
112
|
+
new_id
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Replace a student with the given ID
|
|
116
|
+
#
|
|
117
|
+
# @param student_id [Integer] The ID of the student to replace
|
|
118
|
+
# @param new_student [Student] The new student object
|
|
119
|
+
# @return [Boolean] true if replacement was successful
|
|
120
|
+
# @raise [ArgumentError] if new_student is not a Student object or student with ID not found
|
|
121
|
+
def replace_student_by_id(student_id, new_student)
|
|
122
|
+
raise ArgumentError, "Ожидается объект класса Student" unless new_student.is_a?(Student)
|
|
123
|
+
all = read_all
|
|
124
|
+
index = all.index { |h| h['id'] == student_id }
|
|
125
|
+
raise ArgumentError, "Студент с таким id не найден" unless index
|
|
126
|
+
replaced = Student.new(
|
|
127
|
+
id: student_id,
|
|
128
|
+
last_name: new_student.last_name,
|
|
129
|
+
first_name: new_student.first_name,
|
|
130
|
+
patronymic: new_student.instance_variable_get(:@patronymic),
|
|
131
|
+
phone: new_student.instance_variable_get(:@phone),
|
|
132
|
+
telegram: new_student.instance_variable_get(:@telegram),
|
|
133
|
+
email: new_student.instance_variable_get(:@email),
|
|
134
|
+
git: new_student.git
|
|
135
|
+
)
|
|
136
|
+
all[index] = student_to_hash(replaced)
|
|
137
|
+
write_all(all)
|
|
138
|
+
true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Delete a student by their ID
|
|
142
|
+
#
|
|
143
|
+
# @param student_id [Integer] The ID of the student to delete
|
|
144
|
+
# @return [Boolean] true if student was deleted, false if not found
|
|
145
|
+
def delete_student_by_id student_id
|
|
146
|
+
all = read_all
|
|
147
|
+
before = all.length
|
|
148
|
+
all.reject! { |h| h['id'] == student_id }
|
|
149
|
+
deleted = all.length < before
|
|
150
|
+
write_all(all) if deleted
|
|
151
|
+
deleted
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Get the total count of students
|
|
155
|
+
#
|
|
156
|
+
# @return [Integer] Number of students in the list
|
|
157
|
+
def get_student_short_count
|
|
158
|
+
read_all.length
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
protected
|
|
162
|
+
|
|
163
|
+
# Parse raw file content into an array of hashes
|
|
164
|
+
#
|
|
165
|
+
# This method must be implemented by concrete subclasses to handle
|
|
166
|
+
# format-specific parsing (JSON, YAML, etc.)
|
|
167
|
+
#
|
|
168
|
+
# @param raw [String] Raw content from the file
|
|
169
|
+
# @return [Array<Hash>] Parsed array of student data
|
|
170
|
+
# @raise [NotImplementedError] if not implemented in subclass
|
|
171
|
+
def concrete_realization_read raw
|
|
172
|
+
raise NotImplementedError, "Метод concrete_realization должен быть реализован в подклассах"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Convert array of hashes to format-specific string
|
|
176
|
+
#
|
|
177
|
+
# This method must be implemented by concrete subclasses to handle
|
|
178
|
+
# format-specific serialization (JSON, YAML, etc.)
|
|
179
|
+
#
|
|
180
|
+
# @param array_of_hashes [Array<Hash>] Array of student data to serialize
|
|
181
|
+
# @return [String] Serialized data ready for writing to file
|
|
182
|
+
# @raise [NotImplementedError] if not implemented in subclass
|
|
183
|
+
def concrete_realization_write array_of_hashes
|
|
184
|
+
raise NotImplementedError, "Метод concrete_realization_write должен быть реализован в подклассах"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Ensure the file exists, create it with empty array if it doesn't
|
|
188
|
+
#
|
|
189
|
+
# @return [void]
|
|
190
|
+
def ensure_file_exists
|
|
191
|
+
return if File.exist?(@file_path)
|
|
192
|
+
File.write(@file_path, concrete_realization_write([]))
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Generate a new unique ID for a student
|
|
196
|
+
#
|
|
197
|
+
# @param all [Array<Hash>] Array of all existing student data
|
|
198
|
+
# @return [Integer] New unique ID (max existing ID + 1)
|
|
199
|
+
def generate_new_id all
|
|
200
|
+
max_id = all.map { |h| h['id'] }.compact.max || 0
|
|
201
|
+
max_id + 1
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Convert a Student object to a hash
|
|
205
|
+
#
|
|
206
|
+
# @param student [Student] Student object to convert
|
|
207
|
+
# @return [Hash] Hash representation of the student
|
|
208
|
+
def student_to_hash student
|
|
209
|
+
{
|
|
210
|
+
'id' => student.id,
|
|
211
|
+
'last_name' => student.last_name,
|
|
212
|
+
'first_name' => student.first_name,
|
|
213
|
+
'patronymic' => student.patronymic,
|
|
214
|
+
'phone' => student.instance_variable_get(:@phone),
|
|
215
|
+
'telegram' => student.instance_variable_get(:@telegram),
|
|
216
|
+
'email' => student.instance_variable_get(:@email),
|
|
217
|
+
'git' => student.git
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Convert a hash to a Student object
|
|
222
|
+
#
|
|
223
|
+
# @param h [Hash] Hash containing student data
|
|
224
|
+
# @return [Student] Student object created from hash
|
|
225
|
+
def hash_to_student h
|
|
226
|
+
Student.new(
|
|
227
|
+
id: h['id'],
|
|
228
|
+
last_name: h['last_name'],
|
|
229
|
+
first_name: h['first_name'],
|
|
230
|
+
patronymic: h['patronymic'],
|
|
231
|
+
phone: h['phone'],
|
|
232
|
+
telegram: h['telegram'],
|
|
233
|
+
email: h['email'],
|
|
234
|
+
git: h['git']
|
|
235
|
+
)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'date'
|
|
3
|
+
require_relative 'students_list_common'
|
|
4
|
+
|
|
5
|
+
# Concrete implementation of StudentsListCommon for YAML format
|
|
6
|
+
#
|
|
7
|
+
# This class provides YAML-specific serialization and deserialization
|
|
8
|
+
# for student data persistence. It extends the base StudentsListCommon
|
|
9
|
+
# class with YAML parsing and generation capabilities, supporting
|
|
10
|
+
# Date and Time objects with safe loading.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# yaml_list = StudentsListYAML.new('students.yaml')
|
|
14
|
+
# students = yaml_list.read_all
|
|
15
|
+
# yaml_list.add_student(Student.new(last_name: 'Smith', first_name: 'John'))
|
|
16
|
+
class StudentsListYAML < StudentsListCommon
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
# Parse YAML string into an array of hashes
|
|
21
|
+
#
|
|
22
|
+
# Uses safe loading with permitted Date and Time classes to prevent
|
|
23
|
+
# code execution vulnerabilities while supporting common data types.
|
|
24
|
+
#
|
|
25
|
+
# @param raw [String] Raw YAML content from the file
|
|
26
|
+
# @return [Array<Hash>] Parsed array of student data
|
|
27
|
+
# @raise [Psych::SyntaxError] if YAML is malformed
|
|
28
|
+
def concrete_realization_read raw
|
|
29
|
+
YAML.safe_load(raw, permitted_classes: [Date, Time], aliases: true)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Convert array of hashes to YAML string
|
|
33
|
+
#
|
|
34
|
+
# @param array_of_hashes [Array<Hash>] Array of student data to serialize
|
|
35
|
+
# @return [String] YAML string ready for writing to file
|
|
36
|
+
def concrete_realization_write array_of_hashes
|
|
37
|
+
YAML.dump(array_of_hashes)
|
|
38
|
+
end
|
|
39
|
+
#
|
|
40
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: students_list_yaml
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- akhit
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Provides YAML serialization for student data storage
|
|
13
|
+
email:
|
|
14
|
+
- annakhit02@mail.ru
|
|
15
|
+
executables: []
|
|
16
|
+
extensions: []
|
|
17
|
+
extra_rdoc_files: []
|
|
18
|
+
files:
|
|
19
|
+
- lib/data_list.rb
|
|
20
|
+
- lib/data_list_student_short.rb
|
|
21
|
+
- lib/data_table.rb
|
|
22
|
+
- lib/module.rb
|
|
23
|
+
- lib/student.rb
|
|
24
|
+
- lib/student_base.rb
|
|
25
|
+
- lib/student_short.rb
|
|
26
|
+
- lib/students_list_common.rb
|
|
27
|
+
- lib/students_list_yaml.rb
|
|
28
|
+
homepage: https://example.com
|
|
29
|
+
licenses:
|
|
30
|
+
- MIT
|
|
31
|
+
metadata: {}
|
|
32
|
+
rdoc_options: []
|
|
33
|
+
require_paths:
|
|
34
|
+
- lib
|
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: '0'
|
|
45
|
+
requirements: []
|
|
46
|
+
rubygems_version: 3.6.9
|
|
47
|
+
specification_version: 4
|
|
48
|
+
summary: YAML-based student list management
|
|
49
|
+
test_files: []
|