diff --git a/lab2/README.md b/lab2/README.md index 7a14f08..fd9c642 100644 --- a/lab2/README.md +++ b/lab2/README.md @@ -5,6 +5,74 @@ ```mermaid classDiagram + class PersistenceStrategy { + + load(filename : String) : Raise + + save(filename : String, students : Students) : Raise + } + + class StudentsListBase { + + initialize(filename : String) + + load_from_file() : Raise + + save_to_file() : Raise + + get_student_by_id(id : String) : Student + + get_k_n_student_short_list(k : Int, n : Int, data_list = nil) : List + + sort_students() : List + + add_student(student : Student) : Student + + update_student_by_id(id : Int, new_stodent : Student) : Bool + + delete_student_by_id(id : Int) + + get_student_short_count() : Int + } + + class StudentsListDB { + + initialize() + + get_student_by_id(id : Int) : nil | Student + + get_k_n_student_short_list(k : Int, n : Int) : List + + add_student(student : Student) : Student + + update_student_by_id(id : Int, new_student : Student) + + delete_student_by_id(id : Int) + + get_student_short_count() : Int + - row_to_student(row : Student) + - escape(value : any) + } + + class JSONPersistenceStrategy { + + load(filename : String) : List<> + + save(filename : String, students : List) + - student_to_hash(student : Student) + - hash_to_student(hash : String) + } + + class TXTPersistenceStrategy { + + load(filename : String) : List<> + + save(filename : String, students : List) + } + + class YAMLPersistenceStrategy { + + load(filename : String) : List<> + + save(filename : String, students : List) + - student_to_hash(student : Student) + - hash_to_student(hash : String) + } + + class StudentsList { + + initialize(filename : String, persistence_strategy) + + load() : self + + save() : self + + get_student_by_id(id : Int) : Student + + get_k_n_student_short_list(k : Int, n : Int, data_list = nil) : DataListStudentShort + + sort_students() : List + add_student(student : Student) : Student + + update_student_by_id(id : Int, new_student : Student) : Bool + + delete_student_by_id(id : Int) : Bool + + get_student_short_count() : Int + } + + class DatabaseConnection { + + initialize() + + client() + + query(sql : String) + } + class BinarySearchTree { - root : Node + add(student : Student) : void @@ -81,6 +149,12 @@ classDiagram BinarySearchTree o-- Node Node o-- Student + StudentsListBase o-- Student + StudentsListDB o-- DatabaseConnection + DatabaseConnection <.. Singleton + JSONPersistenceStrategy o-- PersistenceStrategy + TXTPersistenceStrategy o-- PersistenceStrategy + YAMLPersistenceStrategy o-- PersistenceStrategy Person <|-- Student Person <|-- StudentShort Student <.. StudentRepository diff --git a/lab2/compose.yml b/lab2/compose.yml new file mode 100644 index 0000000..517af08 --- /dev/null +++ b/lab2/compose.yml @@ -0,0 +1,20 @@ +version: '3.8' + +services: + mysql: + image: mysql:8.0 + container_name: mysql_container + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: project_db + MYSQL_USER: project_user + MYSQL_PASSWORD: project_password + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + - ./db/migrations:/docker-entrypoint-initdb.d # Автоматическая инициализация базы (скрипты миграций) + +volumes: + mysql_data: diff --git a/lab2/db/migrations/001_create_student_table.sql b/lab2/db/migrations/001_create_student_table.sql new file mode 100644 index 0000000..4f0f190 --- /dev/null +++ b/lab2/db/migrations/001_create_student_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS student ( + id INT AUTO_INCREMENT PRIMARY KEY, + git VARCHAR(255) NOT NULL, + surname VARCHAR(100) NOT NULL, + name VARCHAR(100) NOT NULL, + patronymic VARCHAR(100) NOT NULL, + birth_date DATE NOT NULL, + phone VARCHAR(20), + telegram VARCHAR(50), + email VARCHAR(100) +); diff --git a/lab2/db/migrations/002_insert_student_data.sql b/lab2/db/migrations/002_insert_student_data.sql new file mode 100644 index 0000000..7006459 --- /dev/null +++ b/lab2/db/migrations/002_insert_student_data.sql @@ -0,0 +1,4 @@ +INSERT INTO student (git, surname, name, patronymic, birth_date, phone, telegram, email) +VALUES + ('https://github.com/example', 'Иванов', 'Иван', 'Иванович', '2000-01-01', '+123456789', '@telegram', 'email@example.com'), + ('https://github.com/example2', 'Петров', 'Петр', 'Петрович', '1999-05-15', '+987654321', '@petrov', 'petrov@example.com'); diff --git a/lab2/db_connection.rb b/lab2/db_connection.rb new file mode 100644 index 0000000..50af3db --- /dev/null +++ b/lab2/db_connection.rb @@ -0,0 +1,23 @@ +require 'mysql2' +require 'singleton' + +class DatabaseConnection + include Singleton + + def initialize + @client = Mysql2::Client.new( + host: 'localhost', + username: 'project_user', + password: 'project_password', + database: 'project_db' + ) + end + + def client + @client + end + + def query(sql) + @client.query(sql) + end +end \ No newline at end of file diff --git a/lab2/providers/students_list_db.rb b/lab2/providers/students_list_db.rb new file mode 100644 index 0000000..59e653b --- /dev/null +++ b/lab2/providers/students_list_db.rb @@ -0,0 +1,101 @@ +require_relative '../db_connection' +require_relative '../student' +require_relative '../student_short' +require_relative '../data_list_student_short' +require 'date' + +class StudentsListDB + def initialize + @db = DatabaseConnection.instance + end + + # Получить объект Student по ID. + def get_student_by_id(id) + result = @db.query("SELECT * FROM student WHERE id = #{id} LIMIT 1") + row = result.first + row ? row_to_student(row) : nil + end + + # Получить список из k студентов (страница n) в виде объекта DataList. + def get_k_n_student_short_list(k, n) + offset = (n - 1) * k + results = @db.query("SELECT * FROM student ORDER BY surname, name, patronymic LIMIT #{k} OFFSET #{offset}") + student_shorts = results.map { |row| StudentShort.from_student(row_to_student(row)) } + DataListStudentShort.new(student_shorts) + end + + # Добавить объект Student в БД (ID формируется автоматически). + def add_student(student) + sql = <<~SQL + INSERT INTO student (git, surname, name, patronymic, birth_date, phone, telegram, email) + VALUES ( + '#{escape(student.git)}', + '#{escape(student.surname)}', + '#{escape(student.name)}', + '#{escape(student.patronymic)}', + '#{student.birth_date}', + #{student.phone ? "'#{escape(student.phone)}'" : "NULL"}, + #{student.telegram ? "'#{escape(student.telegram)}'" : "NULL"}, + #{student.email ? "'#{escape(student.email)}'" : "NULL"} + ) + SQL + + @db.query(sql) + new_id = @db.query("SELECT LAST_INSERT_ID() as id").first['id'] + student.id = new_id.to_s + student + end + + # Обновить студента по ID. + def update_student_by_id(id, new_student) + sql = <<~SQL + UPDATE student SET + git = '#{escape(new_student.git)}', + surname = '#{escape(new_student.surname)}', + name = '#{escape(new_student.name)}', + patronymic = '#{escape(new_student.patronymic)}', + birth_date = '#{new_student.birth_date}', + phone = #{new_student.phone ? "'#{escape(new_student.phone)}'" : "NULL"}, + telegram = #{new_student.telegram ? "'#{escape(new_student.telegram)}'" : "NULL"}, + email = #{new_student.email ? "'#{escape(new_student.email)}'" : "NULL"} + WHERE id = #{id} + SQL + + @db.query(sql) + @db.client.affected_rows > 0 + end + + # Удалить студента по ID. + def delete_student_by_id(id) + @db.query("DELETE FROM student WHERE id = #{id}") + @db.client.affected_rows > 0 + end + + # Получить количество студентов. + def get_student_short_count + result = @db.query("SELECT COUNT(*) as count FROM student") + result.first['count'] + end + + private + + # Преобразование строки результата в объект Student. + def row_to_student(row) + Student.new( + id: row['id'].to_s, + git: row['git'], + surname: row['surname'], + name: row['name'], + patronymic: row['patronymic'], + birth_date: Date.parse(row['birth_date'].to_s), + phone: row['phone'], + telegram: row['telegram'], + email: row['email'] + ) + end + + # Простой метод для экранирования строк (на практике лучше использовать подготовленные выражения). + def escape(value) + @db.client.escape(value.to_s) + end +end diff --git a/lab2/student.rb b/lab2/student.rb index c5054a0..6a3f30b 100644 --- a/lab2/student.rb +++ b/lab2/student.rb @@ -17,6 +17,20 @@ class Student < Person self.birth_date = birth_date end + def self.new_from_hash(hash) + new( + id: hash[:id] || hash['id'], + git: hash[:git] || hash['git'], + surname: hash[:surname] || hash['surname'], + name: hash[:name] || hash['name'], + patronymic: hash[:patronymic] || hash['patronymic'], + birth_date: hash[:birth_date].is_a?(Date) ? hash[:birth_date] : Date.parse(hash[:birth_date].to_s), + phone: hash[:phone] || hash['phone'], + telegram: hash[:telegram] || hash['telegram'], + email: hash[:email] || hash['email'] + ) + end + def self.from_string(student_string) parts = student_string.split('|').map(&:strip) raise ArgumentError, 'Invalid student string format' if parts.size < 7 diff --git a/lab2/tests/test_select_db.rb b/lab2/tests/test_select_db.rb new file mode 100644 index 0000000..6e80ea6 --- /dev/null +++ b/lab2/tests/test_select_db.rb @@ -0,0 +1,18 @@ +require_relative '../db_connection' +require_relative '../providers/students_list_db' + +students_db = StudentsListDB.new + +# Пример: получение студента с ID = 1 +student = students_db.get_student_by_id(1) +puts student ? student.get_info : "Студент не найден" + +# Пример: получение второй страницы по 2 записи (если записей достаточно) +data_list = students_db.get_k_n_student_short_list(2, 2) +puts "Количество студентов: #{students_db.get_student_short_count}" +data_table = data_list.get_data + +(0...data_table.rows_count).each do |row| + row_data = (0...data_table.columns_count).map { |col| data_table.item(row, col) } + puts row_data.join(" | ") +end