From 9f98ff8eedcd91d4893e0facd2738fef30a10d62 Mon Sep 17 00:00:00 2001
From: Artem Darius Weber <mit.3tlasa@gmail.com>
Date: Fri, 14 Feb 2025 23:09:57 +0300
Subject: [PATCH] feat: add Students_list_YAML class for YAML data handling and
 student management

---
 lab2/students_list_yaml.rb            | 147 +++++++++++++++++++++++
 lab2/tests/test_students_list_yaml.rb | 165 ++++++++++++++++++++++++++
 2 files changed, 312 insertions(+)
 create mode 100644 lab2/students_list_yaml.rb
 create mode 100644 lab2/tests/test_students_list_yaml.rb

diff --git a/lab2/students_list_yaml.rb b/lab2/students_list_yaml.rb
new file mode 100644
index 0000000..3aac431
--- /dev/null
+++ b/lab2/students_list_yaml.rb
@@ -0,0 +1,147 @@
+require 'yaml'
+require 'date'
+require_relative 'student'
+require_relative 'student_short'
+require_relative 'data_list_student_short'
+require_relative 'data_table'
+
+class Students_list_YAML
+  attr_reader :students
+
+  def initialize(filename)
+    @filename = filename
+    @students = []
+  end
+
+  # Чтение всех значений из YAML-файла.
+  # Файл должен содержать массив хэшей, каждый из которых представляет студента.
+  def load_from_file
+    if File.exist?(@filename)
+      begin
+        data = YAML.load_file(@filename)
+        @students = data.map { |student_hash| hash_to_student(student_hash) }
+      rescue StandardError => e
+        warn "Ошибка при загрузке YAML: #{e.message}"
+        @students = []
+      end
+    else
+      @students = []
+    end
+    self
+  end
+
+  # Запись всех значений в YAML-файл.
+  # Каждый объект Student преобразуется в хэш.
+  def save_to_file
+    data = @students.map { |student| student_to_hash(student) }
+    File.open(@filename, 'w') do |file|
+      file.write(data.to_yaml)
+    end
+    self
+  rescue IOError => e
+    warn "Ошибка при записи в YAML: #{e.message}"
+    self
+  end
+
+  # Получить объект Student по ID.
+  def get_student_by_id(id)
+    @students.find { |s| s.id.to_s == id.to_s }
+  end
+
+  # Получить список из k студентов (страница n) в виде объекта DataList.
+  # Сначала список сортируется по ФамилияИнициалы, затем выбирается нужный срез.
+  # Результат преобразуется в объекты StudentShort.
+  def get_k_n_student_short_list(k, n, data_list = nil)
+    start_index = (n - 1) * k
+    sorted_students = @students.sort_by { |s| s.surname_initials }
+    selected_students = sorted_students[start_index, k] || []
+    short_objects = selected_students.map { |s| StudentShort.from_student(s) }
+    
+    if data_list && data_list.is_a?(DataList)
+      if data_list.respond_to?(:items=)
+        data_list.items = short_objects
+      else
+        data_list = DataListStudentShort.new(short_objects)
+      end
+      data_list
+    else
+      DataListStudentShort.new(short_objects)
+    end
+  end
+
+  # Сортировать список студентов по ФамилияИнициалы.
+  def sort_students!
+    @students.sort_by! { |s| s.surname_initials }
+  end
+
+  # Добавить объект Student в список (при добавлении формируется новый ID).
+  def add_student(student)
+    new_id = if @students.empty?
+               1
+             else
+               max_id = @students.map { |s| s.id.to_i }.max
+               max_id + 1
+             end
+    student.id = new_id.to_s
+    @students << student
+    student
+  end
+
+  # Заменить элемент списка по ID.
+  # Если элемент найден, новый объект получает тот же ID.
+  def update_student_by_id(id, new_student)
+    index = @students.find_index { |s| s.id.to_s == id.to_s }
+    if index
+      new_student.id = @students[index].id
+      @students[index] = new_student
+      true
+    else
+      false
+    end
+  end
+
+  # Удалить элемент списка по ID.
+  # Возвращает true, если удаление прошло успешно.
+  def delete_student_by_id(id)
+    initial_count = @students.size
+    @students.reject! { |s| s.id.to_s == id.to_s }
+    initial_count != @students.size
+  end
+
+  # Получить количество студентов в списке.
+  def get_student_short_count
+    @students.size
+  end
+
+  private
+
+  # Преобразование объекта Student в хэш для записи в YAML.
+  def student_to_hash(student)
+    {
+      'id'         => student.id,
+      'git'        => student.git,
+      'surname'    => student.surname,
+      'name'       => student.name,
+      'patronymic' => student.patronymic,
+      'birth_date' => student.birth_date.to_s,
+      'phone'      => student.phone,
+      'telegram'   => student.telegram,
+      'email'      => student.email
+    }
+  end
+
+  # Преобразование хэша в объект Student.
+  def hash_to_student(hash)
+    Student.new(
+      id:         hash['id'],
+      git:        hash['git'],
+      surname:    hash['surname'],
+      name:       hash['name'],
+      patronymic: hash['patronymic'],
+      birth_date: Date.parse(hash['birth_date']),
+      phone:      hash['phone'],
+      telegram:   hash['telegram'],
+      email:      hash['email']
+    )
+  end
+end
diff --git a/lab2/tests/test_students_list_yaml.rb b/lab2/tests/test_students_list_yaml.rb
new file mode 100644
index 0000000..d1e6cf5
--- /dev/null
+++ b/lab2/tests/test_students_list_yaml.rb
@@ -0,0 +1,165 @@
+# test_students_list_yaml.rb
+require 'minitest/autorun'
+require 'yaml'
+require 'date'
+require_relative '../students_list_yaml'
+require_relative '../student'
+require_relative '../student_short'
+
+class TestStudentsListYAML < Minitest::Test
+  TEMP_FILE = 'temp_students.yaml'
+
+  def setup
+    # Удаляем временный файл перед каждым тестом, если он существует
+    File.delete(TEMP_FILE) if File.exist?(TEMP_FILE)
+    @students_list = Students_list_YAML.new(TEMP_FILE)
+    
+    # Создаём несколько объектов Student для тестирования
+    @student1 = Student.new(
+      id: "1",
+      git: "https://github.com/test1",
+      surname: "Иванов",
+      name: "Иван",
+      patronymic: "Иванович",
+      birth_date: Date.new(2000, 1, 1),
+      phone: "+123456789",
+      telegram: "@test1",
+      email: "test1@example.com"
+    )
+    @student2 = Student.new(
+      id: "2",
+      git: "https://github.com/test2",
+      surname: "Петров",
+      name: "Пётр",
+      patronymic: "Петрович",
+      birth_date: Date.new(1999, 2, 2),
+      phone: "+987654321",
+      telegram: "@test2",
+      email: "test2@example.com"
+    )
+    @student3 = Student.new(
+      id: "3",
+      git: "https://github.com/test3",
+      surname: "Сидоров",
+      name: "Сидор",
+      patronymic: "Сидорович",
+      birth_date: Date.new(1998, 3, 3),
+      phone: "+192837465",
+      telegram: "@test3",
+      email: "test3@example.com"
+    )
+    
+    # Добавляем студентов в список (при добавлении формируется новый ID)
+    @students_list.add_student(@student1)
+    @students_list.add_student(@student2)
+    @students_list.add_student(@student3)
+  end
+
+  def teardown
+    # Удаляем временный файл после выполнения тестов
+    File.delete(TEMP_FILE) if File.exist?(TEMP_FILE)
+  end
+
+  def test_save_and_load_from_file
+    # Сохраняем список в YAML-файл
+    @students_list.save_to_file
+
+    # Создаём новый объект, который загрузит данные из файла
+    new_list = Students_list_YAML.new(TEMP_FILE)
+    new_list.load_from_file
+
+    # Проверяем, что количество студентов совпадает
+    assert_equal @students_list.get_student_short_count, new_list.get_student_short_count
+
+    # Проверяем, что фамилии и инициалы студентов совпадают
+    original_names = @students_list.students.map(&:surname_initials).sort
+    loaded_names   = new_list.students.map(&:surname_initials).sort
+    assert_equal original_names, loaded_names
+  end
+
+  def test_get_student_by_id
+    # Поиск студента по ID "1"
+    student = @students_list.get_student_by_id("1")
+    refute_nil student
+    assert_equal "Иванов И.И.", student.surname_initials
+  end
+
+  def test_get_k_n_student_short_list
+    # Получаем первую "страницу" из 2-х студентов
+    data_list = @students_list.get_k_n_student_short_list(2, 1)
+    # Ожидаем, что DataList содержит 2 записи
+    assert_equal 2, data_list.get_data.rows_count
+
+    # Проверяем, что столбец с Фамилией и инициалами содержит корректное значение
+    first_row = data_list.get_data.item(0, 1)
+    refute_nil first_row
+    assert_match(/[А-ЯЁ][а-яё]+ [А-Я]\./, first_row)
+  end
+
+  def test_sort_students!
+    # Перемешиваем порядок студентов и выполняем сортировку
+    @students_list.students.shuffle!
+    @students_list.sort_students!
+    sorted_names = @students_list.students.map(&:surname_initials)
+    # Проверяем, что список отсортирован лексикографически
+    assert_equal sorted_names.sort, sorted_names
+  end
+
+  def test_add_student
+    new_student = Student.new(
+      id: "0", # ID будет перезаписан
+      git: "https://github.com/test4",
+      surname: "Алексеев",
+      name: "Алексей",
+      patronymic: "Алексеевич",
+      birth_date: Date.new(2001, 4, 4),
+      phone: "+111222333",
+      telegram: "@test4",
+      email: "test4@example.com"
+    )
+    added_student = @students_list.add_student(new_student)
+    # Новый ID должен быть равен (максимальный существующий id + 1)
+    expected_id = (@students_list.students.map { |s| s.id.to_i }.max).to_s
+    assert_equal expected_id, added_student.id
+    assert_equal 4, @students_list.get_student_short_count
+  end
+
+  def test_update_student_by_id
+    updated_student = Student.new(
+      id: "0", # ID будет перезаписан
+      git: "https://github.com/updated",
+      surname: "Обновлённый",
+      name: "Студент",
+      patronymic: "Тестович",
+      birth_date: Date.new(2002, 5, 5),
+      phone: "+444555666",
+      telegram: "@updated",
+      email: "updated@example.com"
+    )
+    # Обновляем студента с ID "2"
+    result = @students_list.update_student_by_id("2", updated_student)
+    assert result
+
+    student = @students_list.get_student_by_id("2")
+    refute_nil student
+    assert_equal "Обновлённый С.Т.", student.surname_initials
+    assert_equal "https://github.com/updated", student.git
+  end
+
+  def test_delete_student_by_id
+    # Удаляем студента с ID "1"
+    result = @students_list.delete_student_by_id("1")
+    assert result
+
+    # Проверяем, что студент с ID "1" отсутствует
+    student = @students_list.get_student_by_id("1")
+    assert_nil student
+
+    # Количество студентов должно уменьшиться
+    assert_equal 2, @students_list.get_student_short_count
+  end
+
+  def test_get_student_short_count
+    assert_equal 3, @students_list.get_student_short_count
+  end
+end