shnaider_code 1.1.5
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/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Documentation.md +33 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +92 -0
- data/lib/shnaider_code/version.rb +5 -0
- data/lib/shnaider_code.rb +10 -0
- data/lib/source/controllers/student_input_form/student_input_form_controller_create.rb +68 -0
- data/lib/source/controllers/student_input_form/student_input_form_controller_edit.rb +78 -0
- data/lib/source/controllers/tab_students_controller.rb +104 -0
- data/lib/source/db_config/config.example.yaml +5 -0
- data/lib/source/db_config/migrations/001_create_table_student.sql +12 -0
- data/lib/source/db_config/mock_data/fill_student.sql +6 -0
- data/lib/source/models/student.rb +125 -0
- data/lib/source/models/student_base.rb +128 -0
- data/lib/source/models/student_short.rb +58 -0
- data/lib/source/repositories/adapters/db_source_adapter.rb +54 -0
- data/lib/source/repositories/adapters/file_source_adapter.rb +37 -0
- data/lib/source/repositories/containers/data_list.rb +74 -0
- data/lib/source/repositories/containers/data_list_student_short.rb +18 -0
- data/lib/source/repositories/containers/data_table.rb +35 -0
- data/lib/source/repositories/data_sources/db_data_source.rb +35 -0
- data/lib/source/repositories/data_sources/file_data_source.rb +77 -0
- data/lib/source/repositories/data_sources/transformers/data_transformer_base.rb +15 -0
- data/lib/source/repositories/data_sources/transformers/data_transformer_json.rb +16 -0
- data/lib/source/repositories/data_sources/transformers/data_transformer_yaml.rb +16 -0
- data/lib/source/repositories/student_repository.rb +37 -0
- data/lib/source/util/logger_holder.rb +29 -0
- data/shnaider_code-1.1.4.gem +0 -0
- data/shnaider_code.gemspec +17 -0
- data/sig/shnaider_code.rbs +4 -0
- metadata +88 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Абстрактный класс с базовым описанием студента
|
|
5
|
+
|
|
6
|
+
class StudentBase
|
|
7
|
+
private_class_method :new
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Валидация имени (также применимо к фамилии и отчеству)
|
|
11
|
+
|
|
12
|
+
def self.valid_name?(name)
|
|
13
|
+
name.match(/(^[А-Я][а-я]+$)|(^[A-Z][a-z]+$)/)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Валидация номера телефона
|
|
18
|
+
|
|
19
|
+
def self.valid_phone?(phone)
|
|
20
|
+
phone.match(/^\+?[78] ?[(-]?\d{3} ?[)-]?[ -]?\d{3}[ -]?\d{2}[ -]?\d{2}$/)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# Валидация имени профиля пользователя
|
|
25
|
+
|
|
26
|
+
def self.valid_profile_name?(profile_name)
|
|
27
|
+
profile_name.match(/^[a-zA-Z0-9_.]+$/)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Валидация email
|
|
32
|
+
|
|
33
|
+
def self.valid_email?(email)
|
|
34
|
+
email.match(/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
attr_writer :id
|
|
40
|
+
attr_reader :phone, :telegram, :email
|
|
41
|
+
|
|
42
|
+
public
|
|
43
|
+
|
|
44
|
+
attr_reader :id, :git
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
# Стандартный конструктор. Принимает именованные параметры:
|
|
48
|
+
# id - Id студента
|
|
49
|
+
# phone - Телефон
|
|
50
|
+
# telegram - Ник в телеграме
|
|
51
|
+
# email - Электронная почта
|
|
52
|
+
# git - Ник на гите
|
|
53
|
+
|
|
54
|
+
def initialize(id: nil, phone: nil, telegram: nil, email: nil, git: nil)
|
|
55
|
+
self.id = id
|
|
56
|
+
self.phone = phone
|
|
57
|
+
self.telegram = telegram
|
|
58
|
+
self.email = email
|
|
59
|
+
self.git = git
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# Возвращает первый доступный контакт пользователя в виде хеша.
|
|
64
|
+
# Пример: {type: :telegram, value: 'xoxolovelylove'}
|
|
65
|
+
|
|
66
|
+
def short_contact
|
|
67
|
+
contact = {}
|
|
68
|
+
%i[telegram email phone].each do |attr|
|
|
69
|
+
attr_val = send(attr)
|
|
70
|
+
next if attr_val.nil?
|
|
71
|
+
|
|
72
|
+
contact[:type] = attr
|
|
73
|
+
contact[:value] = attr_val
|
|
74
|
+
return contact
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
protected
|
|
81
|
+
|
|
82
|
+
def phone=(new_phone)
|
|
83
|
+
raise ArgumentError, "Invalid argument: phone=#{new_phone}" unless new_phone.nil? || StudentBase.valid_phone?(new_phone)
|
|
84
|
+
|
|
85
|
+
@phone = new_phone
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def telegram=(new_telegram)
|
|
89
|
+
raise ArgumentError, "Invalid argument: telegram=#{new_telegram}" unless new_telegram.nil? || StudentBase.valid_profile_name?(new_telegram)
|
|
90
|
+
|
|
91
|
+
@telegram = new_telegram
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def git=(new_git)
|
|
95
|
+
raise ArgumentError, "Invalid argument: git=#{new_git}" unless new_git.nil? || StudentBase.valid_profile_name?(new_git)
|
|
96
|
+
|
|
97
|
+
@git = new_git
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def email=(new_email)
|
|
101
|
+
raise ArgumentError, "Invalid argument: email=#{new_email}" unless new_email.nil? || StudentBase.valid_email?(new_email)
|
|
102
|
+
|
|
103
|
+
@email = new_email
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
public
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# Возвращает true, если у студента есть хотя бы один из контактов
|
|
110
|
+
|
|
111
|
+
def has_contacts?
|
|
112
|
+
!phone.nil? || !telegram.nil? || !email.nil?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
##
|
|
116
|
+
# Возвращает true, если у студента есть гит
|
|
117
|
+
|
|
118
|
+
def has_git?
|
|
119
|
+
!git.nil?
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
##
|
|
123
|
+
# Возвращает true, если у студента есть хотя бы один из контактов и гит
|
|
124
|
+
|
|
125
|
+
def valid?
|
|
126
|
+
has_contacts? && has_git?
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Модель с краткой информацией о студенте
|
|
5
|
+
|
|
6
|
+
class StudentShort < StudentBase
|
|
7
|
+
public_class_method :new
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
attr_writer :last_name_and_initials, :contact
|
|
12
|
+
|
|
13
|
+
public
|
|
14
|
+
|
|
15
|
+
attr_reader :last_name_and_initials, :contact
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Конструктор из объекта класса Student
|
|
19
|
+
|
|
20
|
+
def self.from_student(student)
|
|
21
|
+
raise ArgumentError, 'Student ID is required' if student.id.nil?
|
|
22
|
+
|
|
23
|
+
StudentShort.new(student.id, student.short_info)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Стандартный конструктор. Принимает:
|
|
28
|
+
# id - Числовой id студента
|
|
29
|
+
# info_str - JSON строка с полями last_name_and_initials (обязательно), contact, git, а также полями базового класса
|
|
30
|
+
|
|
31
|
+
def initialize(id, info_str)
|
|
32
|
+
params = JSON.parse(info_str, { symbolize_names: true })
|
|
33
|
+
raise ArgumentError, 'Fields required: last_name_and_initials' if !params.key?(:last_name_and_initials) || params[:last_name_and_initials].nil?
|
|
34
|
+
|
|
35
|
+
self.id = id
|
|
36
|
+
self.last_name_and_initials = params[:last_name_and_initials]
|
|
37
|
+
self.contact = params[:contact]
|
|
38
|
+
self.git = params[:git]
|
|
39
|
+
|
|
40
|
+
options = {}
|
|
41
|
+
options[:id] = id
|
|
42
|
+
options[:git] = git
|
|
43
|
+
options[contact[:type].to_sym] = contact[:value] if contact
|
|
44
|
+
super(**options)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Преобразование объекта в строку
|
|
49
|
+
|
|
50
|
+
def to_s
|
|
51
|
+
result = last_name_and_initials
|
|
52
|
+
%i[id contact git].each do |attr|
|
|
53
|
+
attr_val = send(attr)
|
|
54
|
+
result += ", #{attr}=#{attr_val}" unless attr_val.nil?
|
|
55
|
+
end
|
|
56
|
+
result
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require './LabStudents/repositories/data_sources/db_data_source'
|
|
4
|
+
require './LabStudents/models/student'
|
|
5
|
+
require './LabStudents/models/student_short'
|
|
6
|
+
require './LabStudents/repositories/containers/data_list_student_short'
|
|
7
|
+
|
|
8
|
+
class DBSourceAdapter
|
|
9
|
+
def initialize
|
|
10
|
+
@db = DBDataSource.instance
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def student_by_id(student_id)
|
|
14
|
+
hash = @db.prepare_exec('SELECT * FROM student WHERE id = ?', student_id).first
|
|
15
|
+
return nil if hash.nil?
|
|
16
|
+
|
|
17
|
+
Student.from_hash(hash)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def paginated_short_students(page, count, existing_data_list = nil)
|
|
21
|
+
offset = (page - 1) * count
|
|
22
|
+
students = @db.prepare_exec('SELECT * FROM student LIMIT ?, ?', offset, count)
|
|
23
|
+
slice = students.map { |h| StudentShort.from_student(Student.from_hash(h)) }
|
|
24
|
+
return DataListStudentShort.new(slice) if existing_data_list.nil?
|
|
25
|
+
|
|
26
|
+
existing_data_list.replace_objects(slice)
|
|
27
|
+
existing_data_list
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_student(student)
|
|
31
|
+
template = 'INSERT INTO student(last_name, first_name, father_name, phone, telegram, email, git) VALUES (?, ?, ?, ?, ?, ?, ?)'
|
|
32
|
+
@db.prepare_exec(template, *student_fields(student))
|
|
33
|
+
@db.query('SELECT LAST_INSERT_ID()').first.first[1]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def replace_student(student_id, student)
|
|
37
|
+
template = 'UPDATE student SET last_name=?, first_name=?, father_name=?, phone=?, telegram=?, email=?, git=? WHERE id=?'
|
|
38
|
+
@db.prepare_exec(template, *student_fields(student), student_id)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def remove_student(student_id)
|
|
42
|
+
@db.prepare_exec('DELETE FROM student WHERE id = ?', student_id)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def student_count
|
|
46
|
+
@db.query('SELECT COUNT(id) FROM student').first.first[1]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def student_fields(student)
|
|
52
|
+
[student.last_name, student.first_name, student.father_name, student.phone, student.telegram, student.email, student.git]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class FileSourceAdapter
|
|
4
|
+
def initialize(data_transformer, file_path)
|
|
5
|
+
@file_path = file_path
|
|
6
|
+
@file_source = FileDataSource.new(data_transformer)
|
|
7
|
+
@file_source.load_from_file(file_path)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def student_by_id(student_id)
|
|
11
|
+
@file_source.student_by_id(student_id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def paginated_short_students(page, count, existing_data_list = nil)
|
|
15
|
+
@file_source.paginated_short_students(page, count, existing_data_list)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_student(student)
|
|
19
|
+
added_id = @file_source.add_student(student)
|
|
20
|
+
@file_source.save_to_file(@file_path)
|
|
21
|
+
added_id
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def replace_student(student_id, student)
|
|
25
|
+
@file_source.replace_student(student_id, student)
|
|
26
|
+
@file_source.save_to_file(@file_path)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def remove_student(student_id)
|
|
30
|
+
@file_source.remove_student(student_id)
|
|
31
|
+
@file_source.save_to_file(@file_path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def student_count
|
|
35
|
+
@file_source.student_count
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require './LabStudents/repositories/containers/data_table'
|
|
4
|
+
|
|
5
|
+
class DataList
|
|
6
|
+
# Это "абстрактный" класс
|
|
7
|
+
private_class_method :new
|
|
8
|
+
|
|
9
|
+
attr_writer :objects
|
|
10
|
+
|
|
11
|
+
# Конструктор, принимает массив любых объектов
|
|
12
|
+
def initialize(objects)
|
|
13
|
+
self.objects = objects
|
|
14
|
+
@listeners = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_listener(listener)
|
|
18
|
+
@listeners << listener
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def remove_listener(listener)
|
|
22
|
+
@listeners.delete(listener)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def notify
|
|
26
|
+
@listeners.each { |lst| lst.on_datalist_changed(data_table) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Выбрать элемент по номеру
|
|
30
|
+
def select_element(number)
|
|
31
|
+
self.selected_num = number < objects.size ? number : nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def selected_id
|
|
35
|
+
objects[selected_num].id
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Получить DataTable со всеми элементами.
|
|
39
|
+
def data_table
|
|
40
|
+
result = []
|
|
41
|
+
counter = 0
|
|
42
|
+
objects.each do |obj|
|
|
43
|
+
row = []
|
|
44
|
+
row << counter
|
|
45
|
+
row.push(*table_fields(obj))
|
|
46
|
+
result << row
|
|
47
|
+
counter += 1
|
|
48
|
+
end
|
|
49
|
+
DataTable.new(result)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Добавить элементы в конец списка
|
|
53
|
+
def replace_objects(objects)
|
|
54
|
+
self.objects = objects.dup
|
|
55
|
+
notify
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
protected
|
|
59
|
+
|
|
60
|
+
# Список значений полей для DataTable. Переопределить в наследниках
|
|
61
|
+
def table_fields(_obj)
|
|
62
|
+
[]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Имена атрибутов объектов по порядку. Переопределить в наследниках
|
|
66
|
+
def column_names
|
|
67
|
+
[]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
attr_reader :objects
|
|
73
|
+
attr_accessor :selected_num
|
|
74
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'data_list'
|
|
4
|
+
|
|
5
|
+
class DataListStudentShort < DataList
|
|
6
|
+
# Делаем приватный new предка публичным
|
|
7
|
+
public_class_method :new
|
|
8
|
+
|
|
9
|
+
def column_names
|
|
10
|
+
['Фамилия И. О.', 'Гит', 'Контакт']
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
def table_fields(obj)
|
|
16
|
+
[obj.last_name_and_initials, obj.git, obj.contact]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DataTable
|
|
4
|
+
attr_reader :rows_count, :cols_count
|
|
5
|
+
|
|
6
|
+
# Конструктор, принимает 2D Array
|
|
7
|
+
def initialize(table)
|
|
8
|
+
self.rows_count = table.size
|
|
9
|
+
max_cols = 0
|
|
10
|
+
table.each { |row| max_cols = row.size if row.size > max_cols }
|
|
11
|
+
self.cols_count = max_cols
|
|
12
|
+
self.table = table
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Получить значение в ячейке [row, col]
|
|
16
|
+
def get_item(row, col)
|
|
17
|
+
return nil if row >= rows_count
|
|
18
|
+
return nil if col >= cols_count
|
|
19
|
+
|
|
20
|
+
table[row][col].dup
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_2d_array
|
|
24
|
+
table.dup
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_s
|
|
28
|
+
"DataTable (#{rows_count}x#{cols_count})"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
attr_accessor :table
|
|
34
|
+
attr_writer :rows_count, :cols_count
|
|
35
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mysql2'
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Источник данных из БД
|
|
7
|
+
|
|
8
|
+
class DBDataSource
|
|
9
|
+
private_class_method :new
|
|
10
|
+
@instance_mutex = Mutex.new
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
db_config = YAML.load_file('./LabStudents/db_config/config.example.yaml').transform_keys(&:to_sym)
|
|
14
|
+
@client = Mysql2::Client.new(db_config)
|
|
15
|
+
@client.query_options.merge!(symbolize_keys: true)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.instance
|
|
19
|
+
return @instance if @instance
|
|
20
|
+
|
|
21
|
+
@instance_mutex.synchronize do
|
|
22
|
+
@instance ||= new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@instance
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def prepare_exec(statement, *params)
|
|
29
|
+
@client.prepare(statement).execute(*params)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def query(statement)
|
|
33
|
+
@client.query(statement)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require './LabStudents/models/student'
|
|
4
|
+
require './LabStudents/models/student_short'
|
|
5
|
+
require './LabStudents/repositories/containers/data_list_student_short'
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Источник данных из файла
|
|
9
|
+
|
|
10
|
+
class FileDataSource
|
|
11
|
+
attr_writer :data_transformer
|
|
12
|
+
|
|
13
|
+
def initialize(data_transformer)
|
|
14
|
+
self.students = []
|
|
15
|
+
self.seq_id = 1
|
|
16
|
+
self.data_transformer = data_transformer
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def load_from_file(file_path)
|
|
20
|
+
hash_list = data_transformer.str_to_hash_list(File.read(file_path))
|
|
21
|
+
self.students = hash_list.map { |h| Student.from_hash(h) }
|
|
22
|
+
update_seq_id
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def save_to_file(file_path)
|
|
26
|
+
hash_list = students.map(&:to_hash)
|
|
27
|
+
File.write(file_path, data_transformer.hash_list_to_str(hash_list))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def student_by_id(student_id)
|
|
31
|
+
students.detect { |s| s.id == student_id }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def paginated_short_students(page, count, existing_data_list = nil)
|
|
35
|
+
offset = (page - 1) * count
|
|
36
|
+
slice = students[offset, count].map { |s| StudentShort.from_student(s) }
|
|
37
|
+
|
|
38
|
+
return DataListStudentShort.new(slice) if existing_data_list.nil?
|
|
39
|
+
|
|
40
|
+
existing_data_list.replace_objects(slice)
|
|
41
|
+
existing_data_list
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sorted
|
|
45
|
+
students.sort_by(&:last_name_and_initials)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def add_student(student)
|
|
49
|
+
student.id = seq_id
|
|
50
|
+
students << student
|
|
51
|
+
self.seq_id += 1
|
|
52
|
+
student.id
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def replace_student(student_id, student)
|
|
56
|
+
idx = students.find_index { |s| s.id == student_id }
|
|
57
|
+
students[idx] = student
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def remove_student(student_id)
|
|
61
|
+
students.reject! { |s| s.id == student_id }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def student_count
|
|
65
|
+
students.count
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Метод для актуализации seq_id
|
|
71
|
+
def update_seq_id
|
|
72
|
+
self.seq_id = students.max_by(&:id).id + 1
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
attr_reader :data_transformer
|
|
76
|
+
attr_accessor :students, :seq_id
|
|
77
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DataTransformerBase
|
|
4
|
+
private_class_method :new
|
|
5
|
+
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def str_to_hash_list(str)
|
|
9
|
+
raise NotImplementedError('Should be implemented in child')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def hash_list_to_str(hash_list)
|
|
13
|
+
raise NotImplementedError('Should be implemented in child')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'data_transformer_base'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
class DataTransformerJSON < DataTransformerBase
|
|
7
|
+
public_class_method :new
|
|
8
|
+
|
|
9
|
+
def str_to_hash_list(str)
|
|
10
|
+
JSON.parse(str, { symbolize_names: true })
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def hash_list_to_str(hash_list)
|
|
14
|
+
JSON.pretty_generate(hash_list)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'data_transformer_base'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
class DataTransformerYAML < DataTransformerBase
|
|
7
|
+
public_class_method :new
|
|
8
|
+
|
|
9
|
+
def str_to_hash_list(str)
|
|
10
|
+
YAML.safe_load(str).map { |h| h.transform_keys(&:to_sym) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def hash_list_to_str(hash_list)
|
|
14
|
+
hash_list.map { |h| h.transform_keys(&:to_s) }.to_yaml
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Репозиторий студентов с CRUD операциями.
|
|
5
|
+
|
|
6
|
+
class StudentRepository
|
|
7
|
+
def initialize(data_source_adapter)
|
|
8
|
+
@data_source_adapter = data_source_adapter
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def student_by_id(student_id)
|
|
12
|
+
@data_source_adapter.student_by_id(student_id)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Получить page по счету count элементов (страница начинается с 1)
|
|
17
|
+
|
|
18
|
+
def paginated_short_students(page, count, existing_data_list = nil)
|
|
19
|
+
@data_source_adapter.paginated_short_students(page, count, existing_data_list)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def add_student(student)
|
|
23
|
+
@data_source_adapter.add_student(student)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def replace_student(student_id, student)
|
|
27
|
+
@data_source_adapter.replace_student(student_id, student)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def remove_student(student_id)
|
|
31
|
+
@data_source_adapter.remove_student(student_id)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def student_count
|
|
35
|
+
@data_source_adapter.student_count
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Обертка для хранения объекта Logger
|
|
7
|
+
|
|
8
|
+
class LoggerHolder
|
|
9
|
+
private_class_method :new
|
|
10
|
+
@instance_mutex = Mutex.new
|
|
11
|
+
|
|
12
|
+
attr_reader :logger
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@logger = Logger.new('log.txt')
|
|
16
|
+
|
|
17
|
+
# @logger = Logger.new(STDOUT)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.instance
|
|
21
|
+
return @instance.logger if @instance
|
|
22
|
+
|
|
23
|
+
@instance_mutex.synchronize do
|
|
24
|
+
@instance ||= new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@instance.logger
|
|
28
|
+
end
|
|
29
|
+
end
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/shnaider_code/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "shnaider_code"
|
|
7
|
+
spec.version = ShnaiderCode::VERSION
|
|
8
|
+
spec.authors = ["Shnaider"]
|
|
9
|
+
spec.email = ["nullexp.team@gmail.com"]
|
|
10
|
+
spec.summary = "Student App"
|
|
11
|
+
spec.description = "А gem that allows you to get pass for patterns"
|
|
12
|
+
spec.homepage = "https://github.com/bushmrz/learning_patterns_with_ruby"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
15
|
+
spec.add_dependency 'win32api'
|
|
16
|
+
spec.files = Dir.glob("**/*")
|
|
17
|
+
end
|