Compare commits

..

55 Commits
main ... lab4

Author SHA1 Message Date
Artem-Darius Weber 82a2b663a8 refactor: clean up comments in student list classes for clarity and consistency
2 months ago
Artem-Darius Weber da53a9c4ae feat: implement database-backed student management with MySQL and add persistence strategies
2 months ago
Artem-Darius Weber 7896de3c3e feat: implement persistence strategy for student list management with JSON, TXT, and YAML support
2 months ago
Artem-Darius Weber 828ab3a5cb feat: reorganize student list classes into providers and implement TXT/JSON handling
2 months ago
Artem-Darius Weber 22e0883d5a feat: implement payment strategy pattern with multiple payment methods
2 months ago
Artem-Darius Weber c586e3ed93 refactor: rename student list classes for consistency and improve code structure (LAB 4 TASK 4)
2 months ago
Artem-Darius Weber 9f98ff8eed feat: add Students_list_YAML class for YAML data handling and student management
2 months ago
Artem-Darius Weber e969c49903 feat: implement Students_list_JSON class for JSON data handling and student management
2 months ago
Artem-Darius Weber f07f699dca refactor: streamline item filtering in DataListStudentShort initialization
2 months ago
Artem-Darius Weber c433dccf04 refactor: reorganize test files and update .gitignore for new test paths
2 months ago
Artem-Darius Weber b2cf71804a feat: enhance DataList and DataListStudentShort to support column names and improve data handling
2 months ago
Artem-Darius Weber 67e0c0561e feat: enhance DataListStudentShort to filter items and add setter method for items
3 months ago
Artem-Darius Weber 16b81a879f feat: enhance DataListStudentShort with data preparation methods and add unit tests
3 months ago
Artem-Darius Weber 3aee775190 feat: implement template method pattern for beverage preparation with tea and coffee classes
3 months ago
Artem-Darius Weber 0347b69198 feat: implement DataListStudentShort class for student data representation
3 months ago
Artem-Darius Weber 6fa17a8801 feat: add DataTable and DataList classes with basic functionality
3 months ago
Artem-Darius Weber 2126888d8a refactor: implement surname_initials method and update references for consistency
3 months ago
Artem-Darius Weber bffdd2f072 refactor: rename name_initials to surname_initials for consistency
3 months ago
Artem-Darius Weber bb2280d48e refactor: rename surname_initials to name_initials for consistency
3 months ago
Artem-Darius Weber 6bf0914189 refactor: update method visibility in class diagram for clarity
3 months ago
Artem-Darius Weber 002212a6f8 refactor: remove validate_contact method from class diagram for clarity
3 months ago
Artem-Darius Weber 6da1bacfd5 refactor: rename contact_info method to get_first_contact for clarity
3 months ago
Artem-Darius Weber 2932741c0e refactor: update class method signatures to remove 'self' and improve readability
3 months ago
Artem-Darius Weber e327cf09f6 refactor: freeze StudentShort instance after initialization
3 months ago
Artem-Darius Weber 5ec7fb96d1 refactor: consolidate contact attributes into Person and Student classes, removing Contact class
4 months ago
Artem-Darius Weber 223a312037 refactor: remove git attribute from Person and StudentShort classes and simplify contact information handling
4 months ago
Artem-Darius Weber 582c184acb refactor: remove Contact class and integrate contact attributes directly into Person and Student classes
4 months ago
Artem-Darius Weber a78cbb3a67 refactor: update class method signatures and improve initialization in Person and Student classes
4 months ago
Artem-Darius Weber 09c78690f1 refactor: update Person class to use class methods for validation and simplify initialization
4 months ago
Artem-Darius Weber 8679f3744a refactor: move contact attribute validation to setter methods in Contact class
4 months ago
Artem-Darius Weber f8a012e9dd refactor: update validation methods in Contact and Student classes to use class methods
4 months ago
Artem-Darius Weber 3fade34834 refactor: remove git validation from initializer and add setter method in Person class
4 months ago
Artem-Darius Weber a9717abae8 refactor: simplify Student initialization and move name validation to class method
4 months ago
Artem-Darius Weber d5f67c2de0 refactor: convert instance validation methods to class methods in Contact class
4 months ago
Artem-Darius Weber 0b3d315370 refactor: rename info method to get_single_contact in Contact class and update references
4 months ago
Artem-Darius Weber 4e91bae07c chore: remove unnecessary comment from Person class
4 months ago
Artem-Darius Weber 7e7110f01e feat: add validation setters in Contact class and refine Person and StudentShort classes
4 months ago
Artem-Darius Weber 1f750e1334 feat: add BinarySearchTree and enhance Student class with birth_date and validation methods
4 months ago
Artem-Darius Weber dbd1dfe45d feat: add BinarySearchTree class and integrate birth_date handling in Student class LAB 3 TASK 4
4 months ago
Artem-Darius Weber b9ffeb8d0d chore: remove unnecessary comment from README.md
4 months ago
Artem-Darius Weber 5f3c510253 feat: enhance validation in Person class and add present? method in Contact class
4 months ago
Artem-Darius Weber 3490c8f0f7 feat: refactor Contact class initialization and validation methods for better error handling
4 months ago
Artem-Darius Weber d0279bf2e9 feat: update valid_phone_number method to allow phone numbers with 9 to 15 digits
4 months ago
Artem-Darius Weber c222d7bdd0 Merge branch 'restore-lab2' into lab2
4 months ago
Artem-Darius Weber be544ae87d feat: implement testing functions for Contact, Person, Student, and StudentShort classes; add sample student data
4 months ago
Artem-Darius Weber 80e86e0f99 feat: update class diagram in README.md to include Contact class and its relationships
4 months ago
Artem-Darius Weber a622813621 feat: add new_from_info method to Contact class for creating instances from formatted strings
4 months ago
Artem-Darius Weber d392be8d5d refactor: update Student class initialization and validation; add StudentRepository for file operations
4 months ago
Artem-Darius Weber 72fa11dbfa refactor: enhance StudentShort initialization and add from_student method
4 months ago
Artem-Darius Weber 5cd9be99e3 refactor: simplify Person class initialization and validation logic
4 months ago
Artem-Darius Weber 5550b9563b feat: add Contact class with validation for phone, telegram, and email
4 months ago
Artem-Darius Weber aae53e8617 docs: add class diagram to README.md for Person, Student, and StudentShort classes
4 months ago
Artem-Darius Weber a9b27d76c5 feat: enhance Student and Person classes with contact information validation
4 months ago
Artem-Darius Weber cbd9231784 docs: add class diagram to README.md for Person, Student, and StudentShort classes
5 months ago
Artem-Darius Weber e79d92eed3 refactor: streamline initialization and validation in Person class
5 months ago

BIN
.DS_Store vendored

Binary file not shown.

1
.gitignore vendored

@ -0,0 +1 @@
lab2/tests/students_test.txt

@ -9,7 +9,6 @@ Abstract:
Content:
[Lab 1: Hello world, user interface and number funcations](https://git.djft.ru/darius-atlas/kubsu-sm5-ruby/src/branch/main/lab1)
[Lab 2: Person classes](https://git.djft.ru/darius-atlas/kubsu-sm5-ruby/src/branch/main/lab2)
---

@ -1,102 +0,0 @@
# Lab 1
---
## 01. Hello world
Task:
Установить компилятор и текстовый редактор. Реализовать и
вызвать Hello World c комментарием. (ну как всегда)
```bash
ruby 01_hello_world.rb
```
Returns:
```
Hello, world!
```
## 02. User interface
Task:
Принять имя пользователя как аргумент программы.
Поздороваться с пользователем с использованием форматирования
строки. Спросить какой язык у пользователя любимый, в случае, если это
ruby, ответить что пользователь подлиза, иначе обязательно ответить, что
скоро будет ruby и поставить различные комментарии для нескольких
языков.
```bash
ruby 02_user_interface.rb darius
```
## 03. User interface with ruby and os commands execution
Task:
3.Продолжение предыдущего задания. Попросить пользователя
ввести команду языка ruby. И команду OC. Выполнить команду руби и
команду операционной системы.
```bash
ruby 03_fork_02_with_exec_pasted_command.rb darius
```
## 04. Number funcations
Task:
«Работа с числами». Составить 3 метода для работы с цифрами или делителей числа на основании варианта. Каждый метод отдельный коммит.
- Метод 1. Найти количество четных чисел, не взаимно простых с данным
- Метод 2. Найти максимальную цифры числа, не делящуюся на 3.
- Метод 3. Найти произведение максимального числа, не взаимно простого с данным, не делящегося на наименьший делитель исходно числа, и суммы цифр числа, меньших 5.
```bash
ruby 04_number_funcs.rb
```
## 05. Number items operations
Task:
Написать методы, которые находят минимальный, элементы,
номер первого положительного элемента. Каждая операция в отдельном
методе. Решить задачу с помощью циклов(for и while).
```bash
ruby 05_number_items_operations.rb
```
## 06. Command line file arguments
Task:
Написать программу, которая принимает как аргумент два
значения. Первое значение говорит, какой из методов задачи 1
выполнить, второй говорит о том, откуда читать список аргументом
должен быть написан адрес файла. Далее необходимо прочитать массив
и выполнить метод.
```bash
ruby 06_command_line_file_arguments.rb min numbers.txt
```
```bash
ruby 06_command_line_file_arguments.rb first_positive numbers.txt
```
```bash
ruby 06_command_line_file_arguments.rb first_positive_index numbers.txt
```
---
Author: Artem-Darius Weber
Licence: MIT

@ -1,16 +0,0 @@
require_relative '../src/02_user_interface.rb'
RSpec.describe "Main" do
before do
allow(STDIN).to receive(:gets).and_return("ruby\n")
end
it "greets the user and checks Ruby as language" do
expect { main() }.to output("Hello my catgirl test_user! \nWhat is your love language?\nruby\nc++\npy\nПодлиза \n").to_stdout
end
it "handles unknown language input" do
allow(STDIN).to receive(:gets).and_return("java\n")
expect { main() }.to output(/Неизвестный язык: java/).to_stdout
end
end

@ -1,34 +0,0 @@
require_relative '../src/03_fork_02_with_exec_pasted_command.rb'
RSpec.describe "Main" do
before do
allow(STDIN).to receive(:gets).and_return("ruby\n", "puts 'Hello from Ruby!'\n", "echo 'Hello from shell!'\n")
allow(ARGV).to receive(:[]).with(0).and_return("test_user")
end
it "greets the user and processes ruby input" do
expect { main() }.to output(/Hello my catgirl test_user! \nWhat is your love language?\nruby\nc++\npy\одлиза \n/).to_stdout
end
it "executes valid ruby command" do
allow(STDIN).to receive(:gets).and_return("ruby\n", "puts 'Hello from Ruby!'\n")
expect { main() }.to output(/Hello from Ruby!/).to_stdout
end
it "handles ruby command execution error" do
allow(STDIN).to receive(:gets).and_return("ruby\n", "invalid_ruby_code\n")
expect { main() }.to output(/Ошибка выполнения команды Ruby: undefined local variable or method `invalid_ruby_code'/).to_stdout
end
it "executes shell command" do
allow(STDIN).to receive(:gets).and_return("ruby\n", "puts 'Hello from Ruby!'\n", "echo 'Hello from shell!'\n")
expect(main()).to include("Hello from shell!")
end
it "returns correct output for unknown programming language" do
allow(STDIN).to receive(:gets).and_return("java\n")
expect { main() }.to output(/Неизвестный язык: java/).to_stdout
end
end

@ -1,2 +0,0 @@
puts "Hello, world!"

@ -1,30 +0,0 @@
#!/usr/bin/env ruby
LANGUAGES = [
"ruby",
"c++",
"py"
]
def main()
user_name = ARGV[0]
puts "Hello my catgirl #{user_name}! \nWhat is your love language?"
LANGUAGES.each { |language| puts "#{language}", " " }
user_lang = STDIN.gets.chomp
case user_lang
when "ruby"
puts "Подлиза \n"
when "c++"
puts "MATLAB скушал? \n"
when "TS/JS"
puts "Фронтендер, Фууу! \n"
when "py"
puts "Девопсер, иди ДАГИ писать \n"
else
puts "Неизвестный язык: #{user_lang}"
end
end
main()

@ -1,39 +0,0 @@
#!/usr/bin/env ruby
LANGUAGES = [
"ruby",
"c++",
"py"
]
def main()
user_name = ARGV[0]
puts "Hello my catgirl #{user_name}! \nWhat is your love language?"
LANGUAGES.each { |language| puts "#{language}", " " }
user_lang = STDIN.gets.chomp
case user_lang
when "ruby"
puts "Подлиза \n"
when "c++"
puts "MATLAB скушал? \n"
when "TS/JS"
puts "Фронтендер, Фууу! \n"
when "py"
puts "Девопсер, иди ДАГИ писать \n"
end
puts "Введите команду на языке Ruby для выполнения:"
ruby_command = STDIN.gets.chomp
begin
eval(ruby_command)
rescue Exception => e
puts "Ошибка выполнения команды Ruby: #{e.message}"
end
puts "Введите команду операционной системы для выполнения:"
os_command = STDIN.gets.chomp
system(os_command)
end
main()

@ -1,67 +0,0 @@
require 'prime'
# @param n [Integer] Число, для которого нужно найти количество четных, не взаимно простых чисел.
# @return [Integer] Количество четных чисел, не взаимно простых с данным числом.
# @example
# count_even_non_coprimes(30) # => 14
def count_even_non_coprimes(n)
(1...n).count do |num|
num.even? && n.gcd(num) != 1
end
end
# @param n [Integer] Число, цифры которого нужно проверить.
# @return [Integer, nil] Максимальная цифра числа, не делящаяся на 3, или nil, если таких цифр нет.
# @example
# max_digit_not_divisible_by_three(483726) # => 8
def max_digit_not_divisible_by_three(n)
n.digits.select { |digit| digit % 3 != 0 }.max
end
# @param n [Integer] Число, для которого нужно найти наименьший делитель.
# @return [Integer] Наименьший делитель числа.
# @example
# smallest_divisor(30) # => 2
def smallest_divisor(n)
(2..n).find { |i| n % i == 0 }
end
# @param n [Integer] Число, для которого нужно найти подходящее максимальное число.
# @return [Integer, nil] Максимальное число, не взаимно простое с данным, не делящееся на наименьший делитель, или nil, если таких чисел нет.
# @example
# max_non_coprime_not_divisible_by_smallest_divisor(30) # => 28
def max_non_coprime_not_divisible_by_smallest_divisor(n)
divisor = smallest_divisor(n)
(1...n).select { |num| n.gcd(num) != 1 && num % divisor != 0 }.max
end
# @param n [Integer] Число, цифры которого нужно просуммировать.
# @return [Integer] Сумма цифр числа, меньших 5.
# @example
# sum_of_digits_less_than_five(483726) # => 9
def sum_of_digits_less_than_five(n)
n.digits.select { |digit| digit < 5 }.sum
end
# @param n [Integer] Число, для которого проводится вычисление.
# @return [Integer] Произведение максимального числа и суммы цифр числа.
# @example
# product_of_max_and_sum(30) # => 252
def product_of_max_and_sum(n)
max_number = max_non_coprime_not_divisible_by_smallest_divisor(n)
sum_digits = sum_of_digits_less_than_five(n)
max_number * sum_digits
end
puts count_even_non_coprimes(30) # => 14
puts max_digit_not_divisible_by_three(483726) # => 8
puts product_of_max_and_sum(30) # => 252

@ -1,68 +0,0 @@
def find_min_element_for(arr)
min_element = arr[0]
for element in arr
min_element = element if element < min_element
end
min_element
end
def find_min_element_while(arr)
min_element = arr[0]
index = 0
while index < arr.size
min_element = arr[index] if arr[index] < min_element
index += 1
end
min_element
end
def find_first_positive_index_for(arr)
for index in 0...arr.size
return index if arr[index] > 0
end
nil
end
def find_first_positive_index_while(arr)
index = 0
while index < arr.size
return index if arr[index] > 0
index += 1
end
nil
end
def find_first_positive_for(arr)
for element in arr
return element if element > 0
end
nil
end
def find_first_positive_while(arr)
index = 0
while index < arr.size
return arr[index] if arr[index] > 0
index += 1
end
nil
end
# INPUT
array = [-10, -5, 0, 3, 5, -2]
puts "Минимальный элемент (for): #{find_min_element_for(array)}"
puts "Минимальный элемент (while): #{find_min_element_while(array)}"
puts "Индекс первого положительного элемента (for): #{find_first_positive_index_for(array)}"
puts "Индекс первого положительного элемента (while): #{find_first_positive_index_while(array)}"
puts "Первый положительный элемент (for): #{find_first_positive_for(array)}"
puts "Первый положительный элемент (while): #{find_first_positive_while(array)}"

@ -1,62 +0,0 @@
def find_min_element(arr)
min_element = arr[0]
arr.each do |element|
min_element = element if element < min_element
end
min_element
end
def find_first_positive_index(arr)
arr.each_with_index do |element, index|
return index if element > 0
end
nil
end
def find_first_positive(arr)
arr.each do |element|
return element if element > 0
end
nil
end
def main
method_name = ARGV[0]
file_path = ARGV[1]
if method_name.nil? || file_path.nil?
puts "Неправльный формат ввода. Использование: ruby program.rb <method_name> <file_path>"
puts "method_name: 'min', 'first_positive_index' или 'first_positive'"
exit
end
begin
array = File.read(file_path).split.map(&:to_i)
rescue Errno::ENOENT
puts "Файл не найден: #{file_path}"
exit
end
case method_name
when 'min'
result = find_min_element(array)
puts "Минимальный элемент: #{result}"
when 'first_positive_index'
result = find_first_positive_index(array)
puts "Индекс первого положительного элемента: #{result.nil? ? 'Нет положительного элемента' : result}"
when 'first_positive'
result = find_first_positive(array)
puts "Первый положительный элемент: #{result.nil? ? 'Нет положительного элемента' : result}"
else
puts "Неизвестный метод: #{method_name}"
puts "Доступные методы: 'min', 'first_positive_index', 'first_positive'"
end
end
main if __FILE__ == $0

@ -1 +0,0 @@
-10 -5 0 3 5 -2

@ -1,4 +1,163 @@
# Lab 2
> "И тут я обнаружил что случайно сделал 3-5 задачи в 1-2"
## Диаграмма классов:
```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<Student>
+ sort_students() : List<Student>
+ 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<Student>
+ 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>)
- student_to_hash(student : Student)
- hash_to_student(hash : String)
}
class TXTPersistenceStrategy {
+ load(filename : String) : List<>
+ save(filename : String, students : List<Student>)
}
class YAMLPersistenceStrategy {
+ load(filename : String) : List<>
+ save(filename : String, students : List<Student>)
- 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<Student>
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
+ each(&block) : void
- insert(node : Node, student : Student) : Node
- in_order_traversal(node : Node, &block) : void
}
class Node {
- student : Student
- left : Node
- right : Node
+ Node(student : Student)
}
class Person {
- id : String
- git : String
- phone : String?
- telegram : String?
- email : String?
+ Person(id : String, git : String, phone: String, telegram: String, email: String)
+ phone=(String) : Boolean
+ telegram=(String) : Boolean
+ email=(String) : Boolean
+ surname_initials() : NotImplementedError
+ valid_phone_number() : Boolean <<class>>
+ valid_telegram() : Boolean <<class>>
+ valid_email() : Boolean <<class>>
+ git_present() : Boolean
+ contact_present() : Boolean
+ get_first_contact() : String
+ git=(git : String) : void
+ validate_id(id: String) : void <<class>>
+ validate_git(git : String) : void <<class>>
- valid_git?(git : String) : Boolean
- valid_id?(id : String) : Boolean
}
class StudentRepository {
+ read_from_txt(file_path : String) : List~Student~ <<class>>
+ write_to_txt(file_path : String, students : List~Student~) <<class>>
}
class StudentShort {
- surname_initials : String
+ get_surname_initials() : String
+ StudentShort(id : String, surname_initials : String, phone: String, telegram: String, email: String)
+ from_student(student : Student) : StudentShort <<class>>
+ from_string(id : String, info_string : String) : StudentShort <<class>>
+ to_s() : String
- parse_contact_string(contact_string: String) : Array<String> <<class>>
}
class Student {
- surname : String
- name : String
- patronymic : String
- birth_date : Date
- const NAME_REGEX : String
+ Student(id : String, git : String, phone: String, telegram: String, email: String, surname : String, name : String, patronymic : String, birth_date : Date)
+ from_string(student_string : String) : Student <<class>>
+ surname_initials() : String
+ to_s() : String
+ get_info() : String
+ surname=(surname : String)
+ name=(name : String)
+ patronymic=(patronymic : String)
+ birth_date=(birthdate : String)
+ valid_name?(name : String) : Boolean <<class>>
- name_initial(name : String) : String
- patronymic(patronymic : String) : String
}
BinarySearchTree o-- Node
Node o-- Student
StudentsList 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
```

@ -0,0 +1,46 @@
class BinarySearchTree
include Enumerable
class Node
attr_accessor :student, :left, :right
def initialize(student)
@student = student
@left = nil
@right = nil
end
end
def initialize
@root = nil
end
def add(student)
@root = insert(@root, student)
end
def each(&block)
in_order_traversal(@root, &block)
end
private
def insert(node, student)
return Node.new(student) if node.nil?
if student.birth_date < node.student.birth_date
node.left = insert(node.left, student)
else
node.right = insert(node.right, student)
end
node
end
def in_order_traversal(node, &block)
return if node.nil?
in_order_traversal(node.left, &block)
yield node.student
in_order_traversal(node.right, &block)
end
end

@ -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:

@ -0,0 +1,61 @@
require_relative './data_table'
class DataListStudentShort < DataList
def initialize(items)
column_names = ['№', 'Фамилия и инициалы', 'Телефон', 'Telegram', 'Email']
filtered = items.select { |item| item.is_a?(StudentShort) }
super(filtered, column_names)
end
def items=(new_items)
filtered = new_items.select { |item| item.is_a?(StudentShort) }
@items = filtered.dup
end
def get_names
column_names
end
def get_data
data = prepare_table_data
DataTable.new(data)
end
private
def column_names
['№', 'Фамилия и инициалы', 'Телефон', 'Telegram', 'Email']
end
def prepare_table_data
@items.each_with_index.map do |item, index|
prepare_row(item, index)
end
end
def prepare_row(item, index)
[
index + 1, # id
item.surname_initials, # Фамилия и инициалы
item.phone || '-', # Телефон (или прочерк)
item.telegram || '-', # Telegram (или прочерк)
item.email || '-' # Email (или прочерк)
]
end
def extract_surname_initials(item)
item.respond_to?(:surname_initials) ? item.surname_initials : 'N/A'
end
def extract_phone(item)
item.respond_to?(:phone) ? item.phone : nil
end
def extract_telegram(item)
item.respond_to?(:telegram) ? item.telegram : nil
end
def extract_email(item)
item.respond_to?(:email) ? item.email : nil
end
end

@ -0,0 +1,64 @@
class DataTable
def initialize(data)
@data = data.map { |row| row.dup.freeze }.freeze
end
def item(row, col)
return nil unless valid_row?(row) && valid_col?(col)
@data[row][col]
end
def rows_count
@data.size
end
def columns_count
return 0 if @data.empty?
@data.first.size
end
private
def valid_row?(row)
row.between?(0, rows_count - 1)
end
def valid_col?(col)
col.between?(0, columns_count - 1)
end
end
class DataList
def initialize(items, column_names = [])
@items = items.dup.freeze
@selected = []
@column_names = column_names
end
def select(number)
index = number.to_i
toggle_selection(index) if valid_index?(index)
end
def get_selected
@selected.dup
end
def get_names
@column_names
end
def get_data
raise NotImplementedError, "Implement this method in a subclass"
end
private
def valid_index?(index)
index.between?(0, @items.size - 1)
end
def toggle_selection(index)
@selected.include?(index) ? @selected.delete(index) : @selected << index
end
end

@ -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)
);

@ -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');

@ -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

@ -1,59 +1,92 @@
require 'date'
require_relative 'person'
require_relative 'student'
require_relative 'student_short'
require_relative 'student_repository'
require_relative 'binary_search_tree'
begin
student_string = "Норакет Норакет Норакет | ID: 2 | Phone: +1234567890 | Telegram: @noracat | Email: nora@example.com | Git: https://github.com/nora"
student_from_string = Student.from_string(student_string)
puts "Создан объект из строки:\n#{student_from_string}"
def test_classes
person = Person.new(id: '1', git: 'https://github.com/example', phone: '+798912465', telegram: '@example_user', email: 'test@example.com')
puts "Person Contact Info: #{person.get_first_contact}"
student1 = Student.new(
surname: 'Алексеевич',
name: 'Артем-Дариус',
patronymic: 'Вебер',
id: 1,
git: 'https://github.com/space-creator'
student = Student.new(
id: '2',
git: 'https://github.com/student_example',
surname: 'Иванов',
name: 'Иван',
patronymic: 'Иванович',
birth_date: Date.new(2000, 5, 15),
phone: '+79891234567',
telegram: '@student_ivan',
email: 'ivanov@example.com'
)
puts "Student Full Info: #{student.to_s}"
puts "Student Initials: #{student.surname_initials}"
puts "Student Contact Info: #{student.get_first_contact}"
student1.set_contacts(
phone: '+79891242223',
telegram: '@alstroemeria22',
email: 'no-replay@djft.ru'
)
student_short = StudentShort.from_student(student)
puts "Student Short Info: #{student_short.to_s}"
file_path = 'students.txt'
students = StudentRepository.read_from_txt(file_path)
puts "Read Students from File:"
students.each { |s| puts s.to_s }
output_path = 'output_students.txt'
StudentRepository.write_to_txt(output_path, students)
puts "Students written to file: #{output_path}"
end
def test_parsing
student_string = 'Иванов Иван Иванович | ID: 3 | Phone: +79876543210 | Telegram: @ivan_user | Email: ivan@example.com | Git: https://github.com/ivanov | Birth Date: 2001-01-01'
student = Student.from_string(student_string)
puts "Parsed Student: #{student.to_s}"
short_string = 'Иванов И.И., Contact: Phone: +79876543210'
student_short = StudentShort.from_string('4', short_string)
puts "Parsed StudentShort: #{student_short.to_s}"
end
puts student1.get_info
puts "Surname and Initials: #{student1.surname_and_initials}"
puts "Git Info: #{student1.git_info}"
puts "Contact Info: #{student1.contact_info}"
def test_binary_search_tree
bst = BinarySearchTree.new
student1 = Student.new(
id: '1',
git: 'https://github.com/student1',
surname: 'Смирнов',
name: 'Алексей',
patronymic: 'Иванович',
birth_date: Date.new(1999, 2, 15),
phone: '+123456789',
telegram: '@student1',
email: 'student1@example.com'
)
student2 = Student.new(
surname: 'Норакет',
name: 'Норакет',
patronymic: 'Фамилия'
id: '2',
git: 'https://github.com/student2',
surname: 'Иванов',
name: 'Иван',
patronymic: 'Сергеевич',
birth_date: Date.new(2001, 5, 10),
phone: '+987654321',
telegram: '@student2',
email: 'student2@example.com'
)
student2.set_contacts(
phone: '+70000000000'
)
bst.add(student1)
bst.add(student2)
puts "Students in BST (sorted by birth_date):"
bst.each { |student| puts student.to_s }
end
if __FILE__ == $0
puts "=== Testing Classes ==="
test_classes
puts "\n=== Testing Parsing ==="
test_parsing
puts student1
puts '-' * 40
puts student2
student_short_from_student = StudentShort.new(student1)
puts "StudentShort from Student object:"
puts "ID: #{student_short_from_student.id}"
puts "Surname and Initials: #{student_short_from_student.surname_initials}"
puts "Git: #{student_short_from_student.git}"
puts "Contact: #{student_short_from_student.contact}"
student_short_from_string = StudentShort.from_string(5, 'Skye A.A., Git: https://github.com/skye, Contact: Phone: +4923467890')
puts "StudentShort from string:"
puts "ID: #{student_short_from_string.id}"
puts "Surname and Initials: #{student_short_from_string.surname_initials}"
puts "Git: #{student_short_from_string.git}"
puts "Contact: #{student_short_from_string.contact}"
rescue ArgumentError => e
puts "Ошибка: #{e.message}"
puts "\n=== Testing Binary Search Tree ==="
test_binary_search_tree
end

@ -1,65 +1,89 @@
class Person
attr_accessor :id, :git
attr_reader :id, :git, :phone, :telegram, :email
def initialize(args = {})
@id = args[:id] || nil
@git = args[:git]
validate
def initialize(id:, git: nil, phone: nil, telegram: nil, email: nil)
self.class.validate_id(id)
self.phone = phone
self.telegram = telegram
self.email = email
raise ArgumentError, "Необходимо указать хотя бы один контакт (телефон, Telegram или email)" unless contact_present?
@id = id
@git = git
end
def set_contacts(phone: nil, telegram: nil, email: nil)
@phone = phone
raise ArgumentError, "Invalid phone number format: #{@phone}" if @phone && !self.class.valid_phone_number?(@phone)
def surname_initials
raise NotImplementedError, "#{self.class} must implement the 'surname_initials' method"
end
@telegram = telegram
raise ArgumentError, "Invalid telegram format: #{@telegram}" if @telegram && !self.class.valid_telegram?(@telegram)
def git_present?
!@git.nil? && !@git.empty?
end
@email = email
raise ArgumentError, "Invalid email format: #{@email}" if @email && !self.class.valid_email?(@email)
def contact_present?
@phone || @telegram || @email
end
def self.valid_phone_number?(phone)
phone.match?(/\A\+?[0-9]{10,15}\z/)
def get_first_contact
[@phone, @telegram, @email].compact.first
end
def self.valid_name?(name)
name.match?(/\A[А-Яа-яЁёA-Za-z\-]+\z/)
def self.valid_phone_number?(phone)
/\A\+?[0-9]{9,15}\z/.match?(phone)
end
def self.valid_telegram?(telegram)
telegram.nil? || telegram.match?(/\A@[A-Za-z0-9_]{5,32}\z/)
/\A@[A-Za-z0-9_]{5,32}\z/.match?(telegram)
end
def self.valid_email?(email)
email.nil? || email.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/)
/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/.match?(email)
end
def self.valid_git?(git)
git.nil? || git.match?(/\Ahttps:\/\/github\.com\/[A-Za-z0-9_\-]+\z/)
def id=(id)
@id = id
end
def git_present?
!@git.nil? && !@git.empty?
def phone=(phone)
if phone && !self.class.valid_phone_number?(phone)
raise ArgumentError, "Недопустимый номер телефона: #{phone}"
end
@phone = phone
end
def contact_present?
!(@phone.nil? || @phone.empty?) || !(@telegram.nil? || @telegram.empty?) || !(@email.nil? || @email.empty?)
def telegram=(telegram)
if telegram && !self.class.valid_telegram?(telegram)
raise ArgumentError, "Некорректный Telegram: #{telegram}"
end
@telegram = telegram
end
def email=(email)
if email && !self.class.valid_email?(email)
raise ArgumentError, "Некорректный email: #{email}"
end
@email = email
end
def validate
raise ArgumentError, "Git link is required" unless git_present?
raise ArgumentError, "At least one contact (phone, telegram, or email) is required" unless contact_present?
def git=(git)
self.class.validate_git(git)
@git = git
end
def contact_info
return "Phone: #{@phone}" if @phone
return "Telegram: #{@telegram}" if @telegram
return "Email: #{@email}" if @email
'No contact available'
def self.validate_id(id)
raise ArgumentError, 'ID is required and must be a non-empty string' unless valid_id?(id)
end
private
def self.validate_git(git)
raise ArgumentError, 'Git link is required' if git.nil? || git.strip.empty?
raise ArgumentError, 'Invalid Git link format' unless valid_git?(git)
end
attr_reader :phone, :telegram, :email
attr_writer :phone, :telegram, :email
def self.valid_git?(git)
/\Ahttps:\/\/github\.com\/[A-Za-z0-9_\-]+\z/.match?(git)
end
def self.valid_id?(id)
id.is_a?(String) && !id.strip.empty?
end
end

@ -0,0 +1,9 @@
module PersistenceStrategy
def load(filename)
raise NotImplementedError, "Метод load должен быть реализован в стратегии"
end
def save(filename, students)
raise NotImplementedError, "Метод save должен быть реализован в стратегии"
end
end

@ -0,0 +1,87 @@
require_relative '../student'
require_relative '../student_short'
require_relative '../data_list_student_short'
require_relative '../data_table'
class StudentsList
attr_reader :students
def initialize(filename, persistence_strategy)
@filename = filename
@persistence_strategy = persistence_strategy
@students = []
end
# Загрузка студентов через выбранную стратегию
def load
@students = @persistence_strategy.load(@filename)
self
end
# Сохранение студентов через выбранную стратегию
def save
@persistence_strategy.save(@filename, @students)
self
end
def get_student_by_id(id)
@students.find { |s| s.id.to_s == id.to_s }
end
# Получить список из k студентов (страница n) в виде объекта DataList
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
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
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
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
end

@ -0,0 +1,84 @@
require_relative '../student'
require_relative '../student_short'
require_relative '../data_list_student_short'
require_relative '../data_table'
class StudentsListBase
attr_reader :students
def initialize(filename)
@filename = filename
@students = []
end
# Абстрактный метод
def load_from_file
raise NotImplementedError, "Метод load_from_file должен быть реализован в подклассе"
end
# Абстрактный метод
def save_to_file
raise NotImplementedError, "Метод save_to_file должен быть реализован в подклассе"
end
def get_student_by_id(id)
@students.find { |s| s.id.to_s == id.to_s }
end
# Получить список из k студентов (страница n) в виде объекта DataList
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
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
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
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
end

@ -0,0 +1,96 @@
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
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
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
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
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
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

@ -0,0 +1,62 @@
require 'json'
require 'date'
require_relative '../student'
require_relative 'persistence_strategy'
class JSONPersistenceStrategy
include PersistenceStrategy
def load(filename)
if File.exist?(filename)
file_content = File.read(filename)
begin
data = JSON.parse(file_content)
data.map { |student_hash| hash_to_student(student_hash) }
rescue JSON::ParserError => e
warn "Ошибка парсинга JSON: #{e.message}"
[]
end
else
[]
end
end
def save(filename, students)
data = students.map { |student| student_to_hash(student) }
File.open(filename, 'w') do |file|
file.write(JSON.pretty_generate(data))
end
rescue IOError => e
warn "Ошибка при записи в файл: #{e.message}"
end
private
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
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

@ -0,0 +1,33 @@
require_relative '../student'
require_relative 'persistence_strategy'
class TXTPersistenceStrategy
include PersistenceStrategy
def load(filename)
if File.exist?(filename)
File.open(filename, 'r') do |file|
file.each_line.map do |line|
line.strip!
next if line.empty?
begin
Student.from_string(line)
rescue ArgumentError => e
warn "Ошибка при парсинге строки: #{line}. #{e.message}"
nil
end
end.compact
end
else
[]
end
end
def save(filename, students)
File.open(filename, 'w') do |file|
students.each do |student|
file.puts student.to_s
end
end
end
end

@ -0,0 +1,61 @@
require 'yaml'
require 'date'
require_relative '../student'
require_relative 'persistence_strategy'
class YAMLPersistenceStrategy
include PersistenceStrategy
def load(filename)
if File.exist?(filename)
begin
data = YAML.load_file(filename)
data.map { |student_hash| hash_to_student(student_hash) }
rescue StandardError => e
warn "Ошибка при загрузке YAML: #{e.message}"
[]
end
else
[]
end
end
def save(filename, students)
data = students.map { |student| student_to_hash(student) }
File.open(filename, 'w') do |file|
file.write(data.to_yaml)
end
rescue IOError => e
warn "Ошибка при записи в YAML: #{e.message}"
end
private
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
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

@ -1,92 +1,117 @@
require_relative 'person'
class Student < Person
attr_accessor :surname, :name, :patronymic
attr_accessor :surname, :name, :patronymic, :birth_date
def initialize(args = {})
super(args)
@surname = args.fetch(:surname)
raise ArgumentError, "Invalid surname format: #{@surname}" unless self.class.valid_name?(@surname)
private
@name = args.fetch(:name)
raise ArgumentError, "Invalid name format: #{@name}" unless self.class.valid_name?(@name)
NAME_REGEX = /\A[А-Яа-яЁёA-Za-z\-]+\z/
@patronymic = args.fetch(:patronymic)
raise ArgumentError, "Invalid patronymic format: #{@patronymic}" unless self.class.valid_name?(@patronymic)
public
set_contacts(
phone: args[:phone],
telegram: args[:telegram],
email: args[:email]
def initialize(id:, git:, surname:, name:, patronymic:, birth_date:, phone: nil, telegram: nil, email: nil)
super(id: id, git: git, phone: phone, telegram: telegram, email: email)
self.surname = surname
self.name = name
self.patronymic = patronymic
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)
surname, name, patronymic = parts[0].split(' ')
id = parts[1].split(': ').last.to_i
raise ArgumentError, 'Invalid student string format' if parts.size < 7
name_parts = parts[0].split(' ')
raise ArgumentError, 'Invalid name format' if name_parts.size != 3
surname, name, patronymic = name_parts
id = parts[1].split(': ').last
phone = parts[2].split(': ').last
telegram = parts[3].split(': ').last
email = parts[4].split(': ').last
git = parts[5].split(': ').last
birth_date = Date.parse(parts[6].split(': ').last)
new(
id: id,
git: git,
surname: surname,
name: name,
patronymic: patronymic,
id: id,
birth_date: birth_date,
phone: phone,
telegram: telegram,
email: email,
git: git
email: email
)
end
def self.read_from_txt(file_path)
raise IOError, "File path is invalid or file does not exist: #{file_path}" unless File.exist?(file_path)
students = []
File.foreach(file_path) do |line|
line.strip!
next if line.empty?
def surname_initials
"#{@surname} #{name_initial(@name)}.#{patronymic_initial(@patronymic)}."
end
begin
student = from_string(line)
students << student
rescue ArgumentError => e
puts "Error processing line: '#{line}'. Reason: #{e.message}"
def to_s
"#{@surname} #{@name} #{@patronymic} | ID: #{@id} | " \
"Phone: #{@phone || 'N/A'} | Telegram: #{@telegram || 'N/A'} | " \
"Email: #{@email || 'N/A'} | Git: #{@git} | Birth Date: #{@birth_date}"
end
def get_info
"#{surname_initials}, Git: #{@git}, Contact: #{get_first_contact}, Birth Date: #{@birth_date}"
end
students
def surname=(surname)
raise ArgumentError, 'Surname is required' if surname.nil? || surname.strip.empty?
raise ArgumentError, "Invalid surname format: #{surname}" unless self.class.valid_name?(surname)
@surname = surname
end
def self.write_to_txt(file_path, students)
raise ArgumentError, "Expected an array of Student objects" unless students.is_a?(Array) && students.all? { |s| s.is_a?(Student) }
def name=(name)
raise ArgumentError, 'Name is required' if name.nil? || name.strip.empty?
raise ArgumentError, "Invalid name format: #{name}" unless self.class.valid_name?(name)
File.open(file_path, 'w') do |file|
students.each do |student|
file.puts student.to_s
@name = name
end
def patronymic=(patronymic)
raise ArgumentError, 'Patronymic is required' if patronymic.nil? || patronymic.strip.empty?
raise ArgumentError, "Invalid patronymic format: #{patronymic}" unless self.class.valid_name?(patronymic)
@patronymic = patronymic
end
puts "Data successfully written to #{file_path}"
rescue IOError => e
puts "An error occurred while writing to the file: #{e.message}"
def birth_date=(birth_date)
raise ArgumentError, 'Birth date is required' if birth_date.nil?
raise ArgumentError, "Invalid birth date: #{birth_date}" unless birth_date.is_a?(Date)
@birth_date = birth_date
end
def surname_and_initials
"#{@surname} #{name[0]}.#{patronymic[0]}."
def self.valid_name?(name)
NAME_REGEX.match?(name)
end
def to_s
"#{@surname} #{@name} #{@patronymic} | ID: #{@id || 'N/A'} | " \
"Phone: #{@phone || 'N/A'} | Telegram: #{@telegram || 'N/A'} | " \
"Email: #{@email || 'N/A'} | Git: #{@git || 'N/A'}"
private
def name_initial(name)
name[0].upcase
end
def get_info
"#{surname_and_initials}, Git: #{git_info}, Contact: #{contact_info}"
def patronymic_initial(patronymic)
patronymic[0].upcase
end
end

@ -0,0 +1,39 @@
require_relative 'student'
class StudentRepository
def self.read_from_txt(file_path)
raise IOError, "File does not exist: #{file_path}" unless File.exist?(file_path)
students = []
File.foreach(file_path) do |line|
line.strip!
next if line.empty?
begin
student = Student.from_string(line)
students << student
rescue ArgumentError => e
puts "Error processing line: '#{line}'. Reason: #{e.message}"
end
end
students
end
def self.write_to_txt(file_path, students)
unless students.is_a?(Array) && students.all? { |s| s.is_a?(Student) }
raise ArgumentError, 'Expected an array of Student objects'
end
File.open(file_path, 'w') do |file|
students.each do |student|
file.puts student.to_s
end
end
puts "Data successfully written to #{file_path}"
rescue IOError => e
puts "An error occurred while writing to the file: #{e.message}"
end
end

@ -1,31 +1,59 @@
require_relative 'person'
class StudentShort < Person
attr_reader :surname_initials, :contact
attr_reader :surname_initials
def initialize(student)
super(id: student.id, git: student.git_info)
@surname_initials = student.surname_and_initials
@contact = student.contact_info
def initialize(id:, surname_initials:, phone: nil, telegram: nil, email: nil)
super(id: id, phone: phone, telegram: telegram, email: email)
@surname_initials = surname_initials
freeze
end
def self.from_student(student)
new(
id: student.id,
surname_initials: student.surname_initials,
phone: student.phone,
telegram: student.telegram,
email: student.email
)
end
def self.from_string(id, info_string)
parts = info_string.split(',').map(&:strip)
raise ArgumentError, 'Invalid info string format' if parts.size < 2
surname_initials = parts[0]
git = parts[1].split(': ').last
contact = parts[2].split(': ').last
contact_string = parts[1].split(': ', 2).last.strip
phone, telegram, email = parse_contact_string(contact_string)
new_instance = allocate
new_instance.send(:initialize_from_data, id, surname_initials, git, contact)
new_instance
new(
id: id,
surname_initials: surname_initials,
phone: phone,
telegram: telegram,
email: email
)
end
def to_s
contact_info = get_first_contact()
"#{@surname_initials}, Contact: #{contact_info}"
end
private
def initialize_from_data(id, surname_initials, git, contact)
@id = id
@surname_initials = surname_initials
@git = git
@contact = contact
def self.parse_contact_string(contact_string)
case contact_string
when /\APhone: (.+)\z/i
[Regexp.last_match(1).strip, nil, nil]
when /\ATelegram: (.+)\z/i
[nil, Regexp.last_match(1).strip, nil]
when /\AEmail: (.+)\z/i
[nil, nil, Regexp.last_match(1).strip]
else
raise ArgumentError, "Invalid contact string format: #{contact_string}"
end
end
end

@ -0,0 +1,30 @@
require_relative '../student_short'
require_relative '../data_list_student_short'
students = [
StudentShort.new(id: '1', surname_initials: 'Иванов И.И.', phone: '+79991112233'),
StudentShort.new(id: '2', surname_initials: 'Петров П.П.', telegram: '@petrov'),
StudentShort.new(id: '3', surname_initials: 'Сидоров С.С.', email: 'sidorov@mail.ru')
]
data_list = DataListStudentShort.new(students)
puts "Столбцы: #{data_list.get_names.inspect}"
table = data_list.get_data
(0...table.rows_count).each do |row|
puts (0...table.columns_count).map { |col| table.item(row, col) }.join(' | ')
end
new_students = [
StudentShort.new(id: '4', surname_initials: 'Новиков Н.Н.', phone: '+79994445566'),
StudentShort.new(id: '5', surname_initials: 'Кузнецов К.К.', email: 'kuznetsov@example.com')
]
data_list.items = new_students
puts "\nПосле замены:"
table = data_list.get_data
(0...table.rows_count).each do |row|
puts (0...table.columns_count).map { |col| table.item(row, col) }.join(' | ')
end

@ -0,0 +1,39 @@
require_relative '../data_table'
data = [
[1, "Alice", 25],
[2, "Bob", 30],
[3, "Charlie", 35]
]
data_table = DataTable.new(data)
puts "Количество строк: #{data_table.rows_count}" # Ожидаем 3
puts "Количество столбцов: #{data_table.columns_count}" # Ожидаем 3
puts "Элемент в строке 1, столбце 2: #{data_table.item(1, 2)}" # Ожидаем 30
puts "Элемент в строке 0, столбце 1: #{data_table.item(0, 1)}" # Ожидаем "Alice"
puts "Элемент за пределами таблицы: #{data_table.item(10, 10)}" # Ожидаем nil
items = [
{ id: 1, name: "Alice", age: 25 },
{ id: 2, name: "Bob", age: 30 },
{ id: 3, name: "Charlie", age: 35 }
]
data_list = DataList.new(items)
data_list.select(0)
data_list.select(2)
puts "Выбранные элементы: #{data_list.get_selected.inspect}"
begin
data_list.get_names
rescue NotImplementedError => e
puts "Ошибка: #{e.message}" # Ожидаем "Implement this method in a subclass"
end
begin
data_list.get_data
rescue NotImplementedError => e
puts "Ошибка: #{e.message}" # Ожидаем "Implement this method in a subclass"
end

@ -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

@ -0,0 +1,166 @@
require 'minitest/autorun'
require 'json'
require 'date'
require_relative '../providers/students_list'
require_relative '../providers/students_list_json'
require_relative '../student'
require_relative '../student_short'
class TestStudentsListJSON < Minitest::Test
TEMP_FILE = 'temp_students.json'
def setup
# Удаляем временный файл перед каждым тестом, если он существует
File.delete(TEMP_FILE) if File.exist?(TEMP_FILE)
@students_list = StudentsList.new(TEMP_FILE, JSONPersistenceStrategy.new)
# Создаём несколько объектов 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"
)
# Добавляем студентов в список (метод add_student формирует новый 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
# Сохраняем текущий список в файл
@students_list.save
# Создаём новый объект, который загрузит данные из файла
new_list = StudentsList.new(TEMP_FILE, JSONPersistenceStrategy.new)
new_list.load
# Проверяем, что количество студентов совпадает
assert_equal @students_list.get_student_short_count, new_list.get_student_short_count
# Проверяем, что данные студентов (например, фамилии и инициалы) совпадают
original_names = @students_list.students.map(&:surname_initials)
loaded_names = new_list.students.map(&:surname_initials)
assert_equal original_names.sort, loaded_names.sort
end
def test_get_student_by_id
# Предполагаем, что add_student сбросил 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
# Тестируем получение "страницы" студентов: k = 2, n = 1 (две записи)
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 сформирован корректно (будет равен max(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

@ -0,0 +1,73 @@
require 'date'
require_relative '../providers/students_list'
require_relative '../providers/students_list_txt'
require_relative '../student'
require_relative '../student_short'
def run_tests
filename = 'students_test.txt'
# Инициализируем список студентов с TXT-стратегией
students_list = StudentsList.new(filename, TXTPersistenceStrategy.new)
# 1. Добавление студентов. Передаём временный ID '0', который будет заменён.
student1 = Student.new(
id: '0', git: 'gituser1', surname: 'Иванов', name: 'Иван', patronymic: 'Иванович',
birth_date: Date.new(2000, 5, 14), phone: '1234567890', telegram: '@ivanov', email: 'ivanov@example.com'
)
student2 = Student.new(
id: '0', git: 'gituser2', surname: 'Петров', name: 'Петр', patronymic: 'Петрович',
birth_date: Date.new(1999, 8, 22), phone: '0987654321', telegram: '@petrov', email: 'petrov@example.com'
)
students_list.add_student(student1)
students_list.add_student(student2)
puts "Добавленные студенты:\n#{students_list.students.map(&:to_s).join("\n")}"
# 2. Сохранение и загрузка из файла
students_list.save
students_list.load
puts "\nЗагруженные студенты из файла:\n#{students_list.students.map(&:to_s).join("\n")}"
# 3. Получение студента по ID
student = students_list.get_student_by_id(1)
puts "\nНайден студент по ID 1: #{student}"
# 4. Получение \"страничного\" списка StudentShort (1 элемент на страницу, 1-я страница)
short_list = students_list.get_k_n_student_short_list(1, 1)
puts "\nПервая страница списка StudentShort:"
short_list.get_data.rows_count.times do |row|
puts short_list.get_data.item(row, 1) # выводим Фамилию и инициалы
end
# 5. Обновление студента
updated_student = Student.new(
id: '0', git: 'gituser1_updated', surname: 'Иванов', name: 'Иван', patronymic: 'Иванович',
birth_date: Date.new(2000, 5, 14), phone: '1112223333', telegram: '@ivanov_updated', email: 'ivanov@newmail.com'
)
students_list.update_student_by_id(1, updated_student)
puts "\nОбновленный студент с ID 1: #{students_list.get_student_by_id(1)}"
# 6. Удаление студента
students_list.delete_student_by_id(2)
puts "\nСтуденты после удаления студента с ID 2:\n#{students_list.students.map(&:to_s).join("\n")}"
# 7. Проверка сортировки
student3 = Student.new(
id: '0', git: 'gituser3', surname: 'Алексеев', name: 'Алексей', patronymic: 'Алексеевич',
birth_date: Date.new(2001, 12, 5), phone: '2223334444', telegram: '@alekseev', email: 'alekseev@example.com'
)
students_list.add_student(student3)
students_list.sort_students!
puts "\nСписок студентов после сортировки:"
students_list.students.each do |st|
puts st.to_s
end
# 8. Количество студентов
puts "\nОбщее количество студентов: #{students_list.get_student_short_count}"
end
run_tests

@ -0,0 +1,165 @@
require 'minitest/autorun'
require 'yaml'
require 'date'
require_relative '../providers/students_list'
require_relative '../providers/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 = StudentsList.new(TEMP_FILE, YAMLPersistenceStrategy.new)
# Создаём несколько объектов 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
# Создаём новый объект, который загрузит данные из файла
new_list = StudentsList.new(TEMP_FILE, YAMLPersistenceStrategy.new)
new_list.load
# Проверяем, что количество студентов совпадает
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

@ -0,0 +1,48 @@
# Интерфейс стратегии (общий для всех стратегий)
class PaymentStrategy
def pay(amount)
raise NotImplementedError, "Метод pay должен быть реализован в подклассе"
end
end
# Реализация стратегии оплаты картой
class CreditCardPayment < PaymentStrategy
def pay(amount)
puts "Оплата #{amount}с использованием кредитной карты 💳"
end
end
# Реализация стратегии оплаты через SberBank
class SberPayment < PaymentStrategy
def pay(amount)
puts "Оплата #{amount}₽ через Sber ID 🏦"
end
end
# Реализация стратегии оплаты биткоинами
class BitcoinPayment < PaymentStrategy
def pay(amount)
puts "Оплата #{amount}₽ в биткоинах ₿"
end
end
# Контекст использующий стратегию
class PaymentProcessor
def initialize(strategy)
@strategy = strategy
end
def process_payment(amount)
@strategy.pay(amount)
end
end
# === Пример использования ===
card_payment = PaymentProcessor.new(CreditCardPayment.new)
card_payment.process_payment(1000)
paypal_payment = PaymentProcessor.new(SberPayment.new)
paypal_payment.process_payment(2000)
bitcoin_payment = PaymentProcessor.new(BitcoinPayment.new)
bitcoin_payment.process_payment(3000)

@ -0,0 +1,56 @@
class Beverage
# шаблонный метод
def prepare
boil_water
brew
pour_in_cup
add_condiments
end
# общие методы
def boil_water
puts "Кипячение воды..."
end
def pour_in_cup
puts "Наливание в чашку..."
end
def brew
raise NotImplementedError, "Метод 'brew' должен быть определён"
end
def add_condiments
raise NotImplementedError, "Метод 'add_condiments' должен быть определён"
end
end
class Tea < Beverage
def brew
puts "Заваривание чая..."
end
def add_condiments
puts "Добавление лимона..."
end
end
class Coffee < Beverage
def brew
puts "Заваривание кофе..."
end
def add_condiments
puts "Добавление сахара и молока..."
end
end
puts "☕️Приготовление чая:"
tea = Tea.new
tea.prepare
puts "\n☕️Приготовление кофе:"
coffee = Coffee.new
coffee.prepare
Loading…
Cancel
Save