student_gem 0.1.1 → 0.1.2
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 +4 -4
- data/lib/data_list.rb +3 -3
- data/lib/data_list_student_short.rb +2 -2
- data/lib/main_controller.rb +117 -0
- data/lib/{view.rb → main_window.rb} +12 -69
- data/lib/modal_controller.rb +74 -0
- data/lib/modal_window.rb +62 -0
- data/lib/student.rb +1 -1
- data/lib/student_list.rb +36 -30
- metadata +17 -15
- data/lib/controller.rb +0 -182
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ccd7efc7cc0b3db277b399f2f3d29200f933364df6ef62fae21c8ef261ccec33
|
|
4
|
+
data.tar.gz: 79ac98a8f24c136eb12d33f4b269303388f5841efa88050900588a813abb796d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 945de714158f048bd8efa84014b359587f592b0192c41449c7c47679dc226ab659405f8ead5e3f50f3567322216bd0e16e1ad7ba415d3c32f215c33f031ec8f2
|
|
7
|
+
data.tar.gz: d2d62790cc68de1110b65b96b39356656dfdb86efa9bb2d31cabad5e3413370e0349c8633236f2b6820e9c231794ca9d42fb817f579d6a3d7b0ee76d4decf42e
|
data/lib/data_list.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require_relative '
|
|
1
|
+
require_relative 'data_table.rb'
|
|
2
2
|
|
|
3
3
|
# класс работы с массивом объектов определенного класса
|
|
4
4
|
class Data_list
|
|
@@ -48,12 +48,12 @@ class Data_list
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# двумерный массив (экземпляр Data Table) объектов и их атрибутов
|
|
51
|
-
def get_data
|
|
51
|
+
def get_data(k, n)
|
|
52
52
|
unless self.class.private_method_defined?(:get_object_data)
|
|
53
53
|
raise(NotImplementedError, "#{self.class} должен реализовать метод #get_object_data")
|
|
54
54
|
end
|
|
55
55
|
table = []
|
|
56
|
-
@selected.each{|number| table << [number + 1].concat(get_object_data(number))}
|
|
56
|
+
@selected.each{|number| table << [(k * n) + number + 1].concat(get_object_data(number))}
|
|
57
57
|
Data_table.new(table)
|
|
58
58
|
end
|
|
59
59
|
|
|
@@ -21,9 +21,9 @@ class Data_list_student_short < Data_list
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# уведомление об изменении состояния подписчикам
|
|
24
|
-
def notify(n)
|
|
24
|
+
def notify(k, n)
|
|
25
25
|
(0...n).each {|number| select(number)}
|
|
26
|
-
data_table = get_data
|
|
26
|
+
data_table = get_data(k, n)
|
|
27
27
|
clear_selected
|
|
28
28
|
|
|
29
29
|
@subscribers.each do |view|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require_relative 'student_list.rb'
|
|
2
|
+
require_relative 'modal_controller.rb'
|
|
3
|
+
|
|
4
|
+
# контроллер основного окна
|
|
5
|
+
class Student_list_controller
|
|
6
|
+
|
|
7
|
+
# константа числа строк на странице
|
|
8
|
+
ROWS_ON_PAGE = 20
|
|
9
|
+
|
|
10
|
+
# инициализация номера страницы
|
|
11
|
+
def initialize(view)
|
|
12
|
+
@view = view
|
|
13
|
+
@student_list = Student_list.new
|
|
14
|
+
@page = 0
|
|
15
|
+
|
|
16
|
+
@data_list_student_short = Data_list_student_short.new(nil)
|
|
17
|
+
@data_list_student_short.subscribe(@view)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# реакция на выделение ячеек таблицы
|
|
22
|
+
def table_rows_onselect(row)
|
|
23
|
+
@data_list_student_short.select(row)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# реакция на снятие выделения
|
|
27
|
+
def table_rows_ondeselect(row)
|
|
28
|
+
@data_list_student_short.reject(row)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# реакция на нажатие кнопки вставки
|
|
33
|
+
def insert_button_onclick
|
|
34
|
+
modal_controller = Insertion_controller.new(@view)
|
|
35
|
+
result = modal_controller.execute
|
|
36
|
+
if result[:status] == :ok
|
|
37
|
+
begin
|
|
38
|
+
@student_list.insert(result[:student])
|
|
39
|
+
rescue => e
|
|
40
|
+
show_error(e)
|
|
41
|
+
end
|
|
42
|
+
refresh_data(@page)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# реакция на нажатие кнопки замены
|
|
47
|
+
def update_button_onclick
|
|
48
|
+
modal_controller = Updation_controller.new(@view)
|
|
49
|
+
id = @data_list_student_short.get_selected.first
|
|
50
|
+
student = @student_list.get_student(id)
|
|
51
|
+
result = modal_controller.execute(old_student: student)
|
|
52
|
+
if result[:status] == :ok
|
|
53
|
+
begin
|
|
54
|
+
@student_list.update(id, result[:student])
|
|
55
|
+
rescue => e
|
|
56
|
+
show_error(e)
|
|
57
|
+
end
|
|
58
|
+
refresh_data(@page)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# реакция на нажатие кнопки удаления
|
|
64
|
+
def delete_button_onclick
|
|
65
|
+
ids = @data_list_student_short.get_selected
|
|
66
|
+
begin
|
|
67
|
+
ids.each {|id| @student_list.delete(id)}
|
|
68
|
+
rescue => e
|
|
69
|
+
show_error(e)
|
|
70
|
+
end
|
|
71
|
+
refresh_data(@page)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# реакция на нажатие кнопки назад
|
|
76
|
+
def previous_button_onclick
|
|
77
|
+
@page -= 1
|
|
78
|
+
refresh_data(@page)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# реакция на нажатие кнопки вперед
|
|
82
|
+
def next_button_onclick
|
|
83
|
+
@page += 1
|
|
84
|
+
refresh_data(@page)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# обновление данных на странице
|
|
89
|
+
def refresh_data(k)
|
|
90
|
+
@page = max_page - 1 if @page >= max_page
|
|
91
|
+
begin
|
|
92
|
+
@student_list.get_k_n_student_short_list(k, ROWS_ON_PAGE, @data_list_student_short)
|
|
93
|
+
max_pages = max_page
|
|
94
|
+
rescue => e
|
|
95
|
+
show_error(e)
|
|
96
|
+
end
|
|
97
|
+
@data_list_student_short.notify(k, ROWS_ON_PAGE)
|
|
98
|
+
|
|
99
|
+
@view.page_count.text = "Страница: #{@page + 1} / #{max_pages}"
|
|
100
|
+
@page == 0 ? @view.previous_button.disable : @view.previous_button.enable
|
|
101
|
+
@page == max_pages - 1 ? @view.next_button.disable : @view.next_button.enable
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def show_error(e)
|
|
106
|
+
error_box = FXMessageBox.new(@view, "Ошибка", e.to_s, opts: MBOX_OK)
|
|
107
|
+
error_box.execute
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
# вычисление максимального числа страниц
|
|
114
|
+
def max_page
|
|
115
|
+
(@student_list.count.to_i + ROWS_ON_PAGE - 1) / ROWS_ON_PAGE
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'fox16'
|
|
2
2
|
include Fox
|
|
3
|
-
require_relative '
|
|
3
|
+
require_relative 'main_controller.rb'
|
|
4
4
|
|
|
5
5
|
# интерфейс подписчика модели
|
|
6
6
|
module View
|
|
@@ -28,7 +28,7 @@ class MainWindow < FXMainWindow
|
|
|
28
28
|
# конструктор страницы и начальное заполнение данными
|
|
29
29
|
def initialize(app)
|
|
30
30
|
super(app, "Таблица студентов", width: 750, height: 500)
|
|
31
|
-
@controller =
|
|
31
|
+
@controller = Student_list_controller.new(self)
|
|
32
32
|
|
|
33
33
|
main_frame = FXVerticalFrame.new(self, LAYOUT_FILL_X | LAYOUT_FILL_Y, padding: 10)
|
|
34
34
|
@table = FXTable.new(main_frame, opts: TABLE_COL_SIZABLE | TABLE_ROW_SIZABLE | LAYOUT_FILL_X | LAYOUT_FILL_Y)
|
|
@@ -144,11 +144,18 @@ class MainWindow < FXMainWindow
|
|
|
144
144
|
|
|
145
145
|
# установка значений в строках таблицы
|
|
146
146
|
def set_table_data(data_table)
|
|
147
|
+
width = 195
|
|
148
|
+
height = 17
|
|
149
|
+
pixel = [255, 0, 0, 255].pack('C*')
|
|
150
|
+
pixels = pixel * (width * height)
|
|
151
|
+
icon = FXIcon.new(app, pix: nil, opts: IMAGE_KEEP, width: width, height: height)
|
|
152
|
+
icon.pixels = pixels
|
|
153
|
+
icon.create
|
|
147
154
|
(0...data_table.get_rows).each do |i|
|
|
148
155
|
@table.setRowText(i, data_table.get_element(i, 0).to_s)
|
|
149
156
|
(1...data_table.get_columns).each do |j|
|
|
150
157
|
if data_table.get_element(i, j).nil?
|
|
151
|
-
@table.
|
|
158
|
+
@table.setItemIcon(i, j - 1, icon)
|
|
152
159
|
else
|
|
153
160
|
@table.setItemText(i, j - 1, data_table.get_element(i, j).to_s)
|
|
154
161
|
end
|
|
@@ -166,74 +173,10 @@ class MainWindow < FXMainWindow
|
|
|
166
173
|
end
|
|
167
174
|
|
|
168
175
|
|
|
169
|
-
# класс модельного окна для добавления и изменения записей
|
|
170
|
-
class ModalWindow < FXDialogBox
|
|
171
|
-
|
|
172
|
-
# геттеры для обращения к текстовым полям и к кнопке "Сохранить"
|
|
173
|
-
attr_reader :last_name, :first_name, :patronymic, :telegram, :email, :phone, :git, :ok_button
|
|
174
|
-
|
|
175
|
-
# конструктор модального окна
|
|
176
|
-
def initialize(owner, controller)
|
|
177
|
-
super(owner, "Создание студента", DECOR_TITLE | DECOR_BORDER, width: 400, height: 350)
|
|
178
|
-
@controller = controller
|
|
179
|
-
|
|
180
|
-
main_frame = FXVerticalFrame.new(self, LAYOUT_FILL_X | LAYOUT_FILL_Y, padding: 10)
|
|
181
|
-
grid = FXMatrix.new(main_frame, 2, MATRIX_BY_COLUMNS | LAYOUT_FILL_X, hSpacing: 10, vSpacing: 8)
|
|
182
|
-
|
|
183
|
-
FXLabel.new(grid, "Фамилия:", opts: LAYOUT_CENTER_Y)
|
|
184
|
-
@last_name = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
185
|
-
FXLabel.new(grid, "Имя:", opts: LAYOUT_CENTER_Y)
|
|
186
|
-
@first_name = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
187
|
-
FXLabel.new(grid, "Отчество:", opts: LAYOUT_CENTER_Y)
|
|
188
|
-
@patronymic = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
189
|
-
FXLabel.new(grid, "Телеграм:", opts: LAYOUT_CENTER_Y)
|
|
190
|
-
@telegram = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
191
|
-
FXLabel.new(grid, "Почта:", opts: LAYOUT_CENTER_Y)
|
|
192
|
-
@email = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
193
|
-
FXLabel.new(grid, "Телефон:", opts: LAYOUT_CENTER_Y)
|
|
194
|
-
@phone = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
195
|
-
FXLabel.new(grid, "Гит:", opts: LAYOUT_CENTER_Y)
|
|
196
|
-
@git = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
197
|
-
|
|
198
|
-
button_frame = FXHorizontalFrame.new(main_frame, LAYOUT_FILL_X | LAYOUT_BOTTOM, padding: 5, hSpacing: 8)
|
|
199
|
-
FXButton.new(button_frame, "Отмена", nil, self, ID_CANCEL, opts: BUTTON_NORMAL | FRAME_RAISED)
|
|
200
|
-
@ok_button = FXButton.new(button_frame, "Сохранить", nil, self, ID_ACCEPT, opts: BUTTON_NORMAL | FRAME_RAISED)
|
|
201
|
-
@ok_button.disable
|
|
202
|
-
|
|
203
|
-
@last_name.connect(SEL_CHANGED) do
|
|
204
|
-
text_fields_onchange
|
|
205
|
-
end
|
|
206
|
-
@first_name.connect(SEL_CHANGED) do
|
|
207
|
-
text_fields_onchange
|
|
208
|
-
end
|
|
209
|
-
@patronymic.connect(SEL_CHANGED) do
|
|
210
|
-
text_fields_onchange
|
|
211
|
-
end
|
|
212
|
-
@telegram.connect(SEL_CHANGED) do
|
|
213
|
-
text_fields_onchange
|
|
214
|
-
end
|
|
215
|
-
@email.connect(SEL_CHANGED) do
|
|
216
|
-
text_fields_onchange
|
|
217
|
-
end
|
|
218
|
-
@phone.connect(SEL_CHANGED) do
|
|
219
|
-
text_fields_onchange
|
|
220
|
-
end
|
|
221
|
-
@git.connect(SEL_CHANGED) do
|
|
222
|
-
text_fields_onchange
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# делегация валидации контроллеру
|
|
227
|
-
def text_fields_onchange
|
|
228
|
-
@controller.text_fields_onchange
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
|
|
234
176
|
# запуск приложения
|
|
177
|
+
script = self
|
|
235
178
|
FXApp.new do |app|
|
|
236
|
-
MainWindow.new(app)
|
|
179
|
+
main = MainWindow.new(app)
|
|
237
180
|
app.create
|
|
238
181
|
app.run
|
|
239
182
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require_relative 'modal_window.rb'
|
|
2
|
+
|
|
3
|
+
# интерфейс контроллера модального окна для стратегии
|
|
4
|
+
class Modal_window_controller
|
|
5
|
+
|
|
6
|
+
# конструктор контроллера модального окна
|
|
7
|
+
def initialize(view)
|
|
8
|
+
@modal = ModalWindow.new(view, self)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# основная работа контроллера
|
|
12
|
+
def execute(old_student: nil)
|
|
13
|
+
fill_text_fields(old_student)
|
|
14
|
+
if @modal.execute(PLACEMENT_OWNER) == 1
|
|
15
|
+
student = Student.from_hash(student_attributes)
|
|
16
|
+
{status: :ok, student: student}
|
|
17
|
+
else
|
|
18
|
+
{status: :cancel}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# общий для контроллера модального окна метод валидации
|
|
24
|
+
def text_fields_onchange
|
|
25
|
+
student = student_attributes
|
|
26
|
+
[
|
|
27
|
+
Student.valid_name?(student["last_name"]),
|
|
28
|
+
Student.valid_name?(student["first_name"]),
|
|
29
|
+
Student.valid_name?(student["patronymic"], true),
|
|
30
|
+
Student.valid_telegram?(student["telegram"]),
|
|
31
|
+
Student.valid_email?(student["email"]),
|
|
32
|
+
Student.valid_phone?(student["phone"]),
|
|
33
|
+
Student.valid_git?(student["git"])
|
|
34
|
+
].all? ? @modal.ok_button.enable : @modal.ok_button.disable
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# сбор атрибутов студента из полей ввода
|
|
38
|
+
def student_attributes
|
|
39
|
+
attributes = {}
|
|
40
|
+
attributes["last_name"] = @modal.last_name.text.empty? ? nil : @modal.last_name.text
|
|
41
|
+
attributes["first_name"] = @modal.first_name.text.empty? ? nil : @modal.first_name.text
|
|
42
|
+
attributes["patronymic"] = @modal.patronymic.text.empty? ? nil : @modal.patronymic.text
|
|
43
|
+
attributes["telegram"] = @modal.telegram.text.empty? ? nil : @modal.telegram.text
|
|
44
|
+
attributes["email"] = @modal.email.text.empty? ? nil : @modal.email.text
|
|
45
|
+
attributes["phone"] = @modal.phone.text.empty? ? nil : @modal.phone.text
|
|
46
|
+
attributes["git"] = @modal.git.text.empty? ? nil : @modal.git.text
|
|
47
|
+
attributes
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# заполнение полей ввода контроллерами
|
|
51
|
+
def fill_text_fields(student)
|
|
52
|
+
raise(NotImplementedError, "#{self.class} должен реализовать метод #fill_text_fields")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# контроллер модального окна при вставке
|
|
57
|
+
class Insertion_controller < Modal_window_controller
|
|
58
|
+
def fill_text_fields(student)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# контроллер модального окна при изменении
|
|
63
|
+
class Updation_controller < Modal_window_controller
|
|
64
|
+
def fill_text_fields(student)
|
|
65
|
+
attributes = student.to_h
|
|
66
|
+
@modal.last_name.text = attributes["last_name"]
|
|
67
|
+
@modal.first_name.text = attributes["first_name"]
|
|
68
|
+
@modal.patronymic.text = attributes["patronymic"]
|
|
69
|
+
@modal.telegram.text = attributes["telegram"]
|
|
70
|
+
@modal.email.text = attributes["email"]
|
|
71
|
+
@modal.phone.text = attributes["phone"]
|
|
72
|
+
@modal.git.text = attributes["git"]
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/modal_window.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# класс модельного окна для добавления и изменения записей
|
|
2
|
+
class ModalWindow < FXDialogBox
|
|
3
|
+
|
|
4
|
+
# геттеры для обращения к текстовым полям и к кнопке "Сохранить"
|
|
5
|
+
attr_reader :last_name, :first_name, :patronymic, :telegram, :email, :phone, :git, :ok_button
|
|
6
|
+
|
|
7
|
+
# конструктор модального окна
|
|
8
|
+
def initialize(owner, controller)
|
|
9
|
+
super(owner, "Создание студента", DECOR_TITLE | DECOR_BORDER, width: 400, height: 350)
|
|
10
|
+
@controller = controller
|
|
11
|
+
|
|
12
|
+
main_frame = FXVerticalFrame.new(self, LAYOUT_FILL_X | LAYOUT_FILL_Y, padding: 10)
|
|
13
|
+
grid = FXMatrix.new(main_frame, 2, MATRIX_BY_COLUMNS | LAYOUT_FILL_X, hSpacing: 10, vSpacing: 8)
|
|
14
|
+
|
|
15
|
+
FXLabel.new(grid, "Фамилия:", opts: LAYOUT_CENTER_Y)
|
|
16
|
+
@last_name = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
17
|
+
FXLabel.new(grid, "Имя:", opts: LAYOUT_CENTER_Y)
|
|
18
|
+
@first_name = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
19
|
+
FXLabel.new(grid, "Отчество:", opts: LAYOUT_CENTER_Y)
|
|
20
|
+
@patronymic = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
21
|
+
FXLabel.new(grid, "Телеграм:", opts: LAYOUT_CENTER_Y)
|
|
22
|
+
@telegram = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
23
|
+
FXLabel.new(grid, "Почта:", opts: LAYOUT_CENTER_Y)
|
|
24
|
+
@email = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
25
|
+
FXLabel.new(grid, "Телефон:", opts: LAYOUT_CENTER_Y)
|
|
26
|
+
@phone = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
27
|
+
FXLabel.new(grid, "Гит:", opts: LAYOUT_CENTER_Y)
|
|
28
|
+
@git = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
29
|
+
|
|
30
|
+
button_frame = FXHorizontalFrame.new(main_frame, LAYOUT_FILL_X | LAYOUT_BOTTOM, padding: 5, hSpacing: 8)
|
|
31
|
+
FXButton.new(button_frame, "Отмена", nil, self, ID_CANCEL, opts: BUTTON_NORMAL | FRAME_RAISED)
|
|
32
|
+
@ok_button = FXButton.new(button_frame, "Сохранить", nil, self, ID_ACCEPT, opts: BUTTON_NORMAL | FRAME_RAISED)
|
|
33
|
+
@ok_button.disable
|
|
34
|
+
|
|
35
|
+
@last_name.connect(SEL_CHANGED) do
|
|
36
|
+
text_fields_onchange
|
|
37
|
+
end
|
|
38
|
+
@first_name.connect(SEL_CHANGED) do
|
|
39
|
+
text_fields_onchange
|
|
40
|
+
end
|
|
41
|
+
@patronymic.connect(SEL_CHANGED) do
|
|
42
|
+
text_fields_onchange
|
|
43
|
+
end
|
|
44
|
+
@telegram.connect(SEL_CHANGED) do
|
|
45
|
+
text_fields_onchange
|
|
46
|
+
end
|
|
47
|
+
@email.connect(SEL_CHANGED) do
|
|
48
|
+
text_fields_onchange
|
|
49
|
+
end
|
|
50
|
+
@phone.connect(SEL_CHANGED) do
|
|
51
|
+
text_fields_onchange
|
|
52
|
+
end
|
|
53
|
+
@git.connect(SEL_CHANGED) do
|
|
54
|
+
text_fields_onchange
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# делегация валидации контроллеру
|
|
59
|
+
def text_fields_onchange
|
|
60
|
+
@controller.text_fields_onchange
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/student.rb
CHANGED
data/lib/student_list.rb
CHANGED
|
@@ -5,11 +5,24 @@ require_relative 'data_list_student_short.rb'
|
|
|
5
5
|
|
|
6
6
|
# класс модели для работы с базой данных с использованием PostgreSQL
|
|
7
7
|
class Student_list
|
|
8
|
-
ACTION_LOGGER = Logger.new(File.join(__dir__, 'log', 'actions.log'))
|
|
8
|
+
ACTION_LOGGER = Logger.new(File.join(__dir__, '..', 'log', 'actions.log'))
|
|
9
9
|
ACTION_LOGGER.level = Logger::INFO
|
|
10
|
-
ERROR_LOGGER = Logger.new(File.join(__dir__, 'log', 'errors.log'))
|
|
10
|
+
ERROR_LOGGER = Logger.new(File.join(__dir__, '..', 'log', 'errors.log'))
|
|
11
11
|
ERROR_LOGGER.level = Logger::ERROR
|
|
12
12
|
|
|
13
|
+
|
|
14
|
+
# конструктор с созданием соединения
|
|
15
|
+
def initialize(dbname: 'student_db', user: 'postgres', password: 'postgres', host: '127.0.0.1', port: 5432)
|
|
16
|
+
@connection = PG.connect(
|
|
17
|
+
dbname: dbname,
|
|
18
|
+
user: user,
|
|
19
|
+
password: password,
|
|
20
|
+
host: host,
|
|
21
|
+
port: port
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
13
26
|
# получение объекта класса Student по ID
|
|
14
27
|
def get_student(id)
|
|
15
28
|
result = sql("SELECT * FROM student WHERE id = #{id}")
|
|
@@ -25,33 +38,35 @@ class Student_list
|
|
|
25
38
|
|
|
26
39
|
# вставка объекта класса в таблицу (новый ID генерируется автоматически)
|
|
27
40
|
def insert(student)
|
|
41
|
+
attributes = student.to_h
|
|
28
42
|
sql("INSERT INTO student (first_name, last_name, patronymic, phone, telegram, email, git)
|
|
29
43
|
VALUES
|
|
30
|
-
('#{
|
|
31
|
-
'#{
|
|
32
|
-
#{
|
|
33
|
-
#{
|
|
34
|
-
#{
|
|
35
|
-
#{
|
|
36
|
-
#{
|
|
37
|
-
|
|
38
|
-
ACTION_LOGGER.info("Создание записи студента: #{
|
|
44
|
+
('#{attributes["first_name"]}',
|
|
45
|
+
'#{attributes["last_name"]}',
|
|
46
|
+
#{attributes["patronymic"].nil? ? "NULL" : "'#{attributes["patronymic"]}'"},
|
|
47
|
+
#{attributes["phone"].nil? ? "NULL" : "'#{attributes["phone"]}'"},
|
|
48
|
+
#{attributes["telegram"].nil? ? "NULL" : "'#{attributes["telegram"]}'"},
|
|
49
|
+
#{attributes["email"].nil? ? "NULL" : "'#{attributes["email"]}'"},
|
|
50
|
+
#{attributes["git"].nil? ? "NULL" : "'#{attributes["git"]}'"})")
|
|
51
|
+
|
|
52
|
+
ACTION_LOGGER.info("Создание записи студента: #{attributes.inspect}")
|
|
39
53
|
end
|
|
40
54
|
|
|
41
55
|
# замена объекта по ID
|
|
42
56
|
def update(id, student)
|
|
57
|
+
attributes = student.to_h
|
|
43
58
|
sql("UPDATE student
|
|
44
59
|
SET
|
|
45
|
-
first_name = '#{
|
|
46
|
-
last_name = '#{
|
|
47
|
-
patronymic = #{
|
|
48
|
-
phone = #{
|
|
49
|
-
telegram = #{
|
|
50
|
-
email = #{
|
|
51
|
-
git = #{
|
|
60
|
+
first_name = '#{attributes["first_name"]}',
|
|
61
|
+
last_name = '#{attributes["last_name"]}',
|
|
62
|
+
patronymic = #{attributes["patronymic"].nil? ? "NULL" : "'#{attributes["patronymic"]}'"},
|
|
63
|
+
phone = #{attributes["phone"].nil? ? "NULL" : "'#{attributes["phone"]}'"},
|
|
64
|
+
telegram = #{attributes["telegram"].nil? ? "NULL" : "'#{attributes["telegram"]}'"},
|
|
65
|
+
email = #{attributes["email"].nil? ? "NULL" : "'#{attributes["email"]}'"},
|
|
66
|
+
git = #{attributes["git"].nil? ? "NULL" : "'#{attributes["git"]}'"}
|
|
52
67
|
WHERE id = #{id}")
|
|
53
68
|
|
|
54
|
-
ACTION_LOGGER.info("Изменение данных студента по ID = #{id}: #{
|
|
69
|
+
ACTION_LOGGER.info("Изменение данных студента по ID = #{id}: #{attributes.inspect}")
|
|
55
70
|
end
|
|
56
71
|
|
|
57
72
|
# удаление объекта по ID
|
|
@@ -71,17 +86,10 @@ class Student_list
|
|
|
71
86
|
private
|
|
72
87
|
|
|
73
88
|
# подключение к базе данных и выполнение запроса
|
|
74
|
-
def sql(command
|
|
89
|
+
def sql(command)
|
|
75
90
|
result = []
|
|
76
91
|
begin
|
|
77
|
-
|
|
78
|
-
dbname: dbname,
|
|
79
|
-
user: user,
|
|
80
|
-
password: password,
|
|
81
|
-
host: host,
|
|
82
|
-
port: port
|
|
83
|
-
)
|
|
84
|
-
result = connection.exec(command)
|
|
92
|
+
result = @connection.exec(command)
|
|
85
93
|
rescue PG::ConnectionBad => e
|
|
86
94
|
s = "Ошибка при попытке установить соединение с базой данных:\n#{e.message}"
|
|
87
95
|
ERROR_LOGGER.error(s)
|
|
@@ -98,8 +106,6 @@ class Student_list
|
|
|
98
106
|
s = "Ошибка на стороне клиента:\n#{e.message}"
|
|
99
107
|
ERROR_LOGGER.error(s)
|
|
100
108
|
raise s
|
|
101
|
-
ensure
|
|
102
|
-
connection&.close
|
|
103
109
|
end
|
|
104
110
|
result.to_a
|
|
105
111
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: student_gem
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- s0195920
|
|
@@ -13,58 +13,60 @@ dependencies:
|
|
|
13
13
|
name: pg
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 1.7.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: 1.7.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: fox16
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- - "
|
|
30
|
+
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version:
|
|
32
|
+
version: 1.6.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
|
-
- - "
|
|
37
|
+
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version:
|
|
39
|
+
version: 1.6.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: logger
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
43
43
|
requirements:
|
|
44
|
-
- - "
|
|
44
|
+
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
46
|
+
version: 1.7.0
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
|
-
- - "
|
|
51
|
+
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version:
|
|
53
|
+
version: 1.7.0
|
|
54
54
|
executables: []
|
|
55
55
|
extensions: []
|
|
56
56
|
extra_rdoc_files: []
|
|
57
57
|
files:
|
|
58
58
|
- lib/checking_attr_writer.rb
|
|
59
|
-
- lib/controller.rb
|
|
60
59
|
- lib/data_list.rb
|
|
61
60
|
- lib/data_list_student_short.rb
|
|
62
61
|
- lib/data_table.rb
|
|
62
|
+
- lib/main_controller.rb
|
|
63
|
+
- lib/main_window.rb
|
|
64
|
+
- lib/modal_controller.rb
|
|
65
|
+
- lib/modal_window.rb
|
|
63
66
|
- lib/student.rb
|
|
64
67
|
- lib/student_ancestor.rb
|
|
65
68
|
- lib/student_list.rb
|
|
66
69
|
- lib/student_short.rb
|
|
67
|
-
- lib/view.rb
|
|
68
70
|
- sql/ddl.sql
|
|
69
71
|
- sql/dml.sql
|
|
70
72
|
homepage: https://fpm-gitlab.kubsu.ru/design-patterns-tests/s0195920
|
data/lib/controller.rb
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
require_relative '../student_list.rb'
|
|
2
|
-
|
|
3
|
-
# общий интерфейс для всех контроллеров
|
|
4
|
-
class Student_list_controller
|
|
5
|
-
|
|
6
|
-
# общий для любого контроллера главного окна фрагмент конструктора
|
|
7
|
-
def initialize(view)
|
|
8
|
-
@view = view
|
|
9
|
-
@student_list = Student_list.new
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# контроллер основного окна
|
|
16
|
-
class Main_window_controller < Student_list_controller
|
|
17
|
-
|
|
18
|
-
# константа числа строк на странице
|
|
19
|
-
ROWS_ON_PAGE = 20
|
|
20
|
-
|
|
21
|
-
# инициализация номера страницы
|
|
22
|
-
def initialize(view)
|
|
23
|
-
super(view)
|
|
24
|
-
@page = 0
|
|
25
|
-
|
|
26
|
-
@data_list_student_short = Data_list_student_short.new(nil)
|
|
27
|
-
@data_list_student_short.subscribe(@view)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# реакция на выделение ячеек таблицы
|
|
32
|
-
def table_rows_onselect(row)
|
|
33
|
-
@data_list_student_short.select(row)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# реакция на снятие выделения
|
|
37
|
-
def table_rows_ondeselect(row)
|
|
38
|
-
@data_list_student_short.reject(row)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# реакция на нажатие кнопки вставки
|
|
43
|
-
def insert_button_onclick
|
|
44
|
-
modal_controller = Insertion_controller.new(@view)
|
|
45
|
-
dialog = modal_controller.create
|
|
46
|
-
if dialog.execute(PLACEMENT_OWNER) == 1
|
|
47
|
-
student = Student.from_hash(modal_controller.student_attributes)
|
|
48
|
-
begin
|
|
49
|
-
result = @student_list.insert(student)
|
|
50
|
-
rescue => e
|
|
51
|
-
show_error(e)
|
|
52
|
-
end
|
|
53
|
-
refresh_data(@page)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# реакция на нажатие кнопки замены
|
|
58
|
-
def update_button_onclick
|
|
59
|
-
modal_controller = Updation_controller.new(@view)
|
|
60
|
-
id = @data_list_student_short.get_selected.first
|
|
61
|
-
dialog = modal_controller.create
|
|
62
|
-
modal_controller.fill_text_fields(id)
|
|
63
|
-
if dialog.execute(PLACEMENT_OWNER) == 1
|
|
64
|
-
student = Student.from_hash(modal_controller.student_attributes)
|
|
65
|
-
begin
|
|
66
|
-
@student_list.update(id, student)
|
|
67
|
-
rescue => e
|
|
68
|
-
show_error(e)
|
|
69
|
-
end
|
|
70
|
-
refresh_data(@page)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# реакция на нажатие кнопки удаления
|
|
76
|
-
def delete_button_onclick
|
|
77
|
-
ids = @data_list_student_short.get_selected
|
|
78
|
-
begin
|
|
79
|
-
ids.each {|id| @student_list.delete(id)}
|
|
80
|
-
rescue => e
|
|
81
|
-
show_error(e)
|
|
82
|
-
end
|
|
83
|
-
refresh_data(@page)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# реакция на нажатие кнопки назад
|
|
88
|
-
def previous_button_onclick
|
|
89
|
-
@page -= 1
|
|
90
|
-
refresh_data(@page)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# реакция на нажатие кнопки вперед
|
|
94
|
-
def next_button_onclick
|
|
95
|
-
@page += 1
|
|
96
|
-
refresh_data(@page)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# обновление данных на странице
|
|
101
|
-
def refresh_data(k)
|
|
102
|
-
@page = max_page - 1 if @page >= max_page
|
|
103
|
-
begin
|
|
104
|
-
@student_list.get_k_n_student_short_list(k, ROWS_ON_PAGE, @data_list_student_short)
|
|
105
|
-
max_pages = max_page
|
|
106
|
-
rescue => e
|
|
107
|
-
show_error(e)
|
|
108
|
-
end
|
|
109
|
-
@data_list_student_short.notify(ROWS_ON_PAGE)
|
|
110
|
-
|
|
111
|
-
@view.page_count.text = "Страница: #{@page + 1} / #{max_pages}"
|
|
112
|
-
@page == 0 ? @view.previous_button.disable : @view.previous_button.enable
|
|
113
|
-
@page == max_pages - 1 ? @view.next_button.disable : @view.next_button.enable
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def show_error(e)
|
|
118
|
-
error_box = FXMessageBox.new(@view, "Ошибка", e.to_s, opts: MBOX_OK)
|
|
119
|
-
error_box.execute
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
private
|
|
124
|
-
|
|
125
|
-
# вычисление максимального числа страниц
|
|
126
|
-
def max_page
|
|
127
|
-
(@student_list.count.to_i + ROWS_ON_PAGE - 1) / ROWS_ON_PAGE
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
# интерфейс контроллера модального окна для стратегии
|
|
133
|
-
class Modal_window_controller < Student_list_controller
|
|
134
|
-
|
|
135
|
-
def create
|
|
136
|
-
@modal = ModalWindow.new(@view, self)
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# общий для контроллера модального окна метод валидации
|
|
140
|
-
def text_fields_onchange
|
|
141
|
-
student = student_attributes
|
|
142
|
-
[
|
|
143
|
-
Student.valid_name?(student["last_name"]),
|
|
144
|
-
Student.valid_name?(student["first_name"]),
|
|
145
|
-
Student.valid_name?(student["patronymic"], true),
|
|
146
|
-
Student.valid_telegram?(student["telegram"]),
|
|
147
|
-
Student.valid_email?(student["email"]),
|
|
148
|
-
Student.valid_phone?(student["phone"]),
|
|
149
|
-
Student.valid_git?(student["git"])
|
|
150
|
-
].all? ? @modal.ok_button.enable : @modal.ok_button.disable
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def student_attributes
|
|
154
|
-
attributes = {}
|
|
155
|
-
attributes["last_name"] = @modal.last_name.text.empty? ? nil : @modal.last_name.text
|
|
156
|
-
attributes["first_name"] = @modal.first_name.text.empty? ? nil : @modal.first_name.text
|
|
157
|
-
attributes["patronymic"] = @modal.patronymic.text.empty? ? nil : @modal.patronymic.text
|
|
158
|
-
attributes["telegram"] = @modal.telegram.text.empty? ? nil : @modal.telegram.text
|
|
159
|
-
attributes["email"] = @modal.email.text.empty? ? nil : @modal.email.text
|
|
160
|
-
attributes["phone"] = @modal.phone.text.empty? ? nil : @modal.phone.text
|
|
161
|
-
attributes["git"] = @modal.git.text.empty? ? nil : @modal.git.text
|
|
162
|
-
attributes
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
# контроллер модального окна при вставке
|
|
167
|
-
class Insertion_controller < Modal_window_controller
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# контроллер модального окна при изменении
|
|
171
|
-
class Updation_controller < Modal_window_controller
|
|
172
|
-
def fill_text_fields(id)
|
|
173
|
-
student = @student_list.get_student(id).to_h
|
|
174
|
-
@modal.last_name.text = student["last_name"]
|
|
175
|
-
@modal.first_name.text = student["first_name"]
|
|
176
|
-
@modal.patronymic.text = student["patronymic"]
|
|
177
|
-
@modal.telegram.text = student["telegram"]
|
|
178
|
-
@modal.email.text = student["email"]
|
|
179
|
-
@modal.phone.text = student["phone"]
|
|
180
|
-
@modal.git.text = student["git"]
|
|
181
|
-
end
|
|
182
|
-
end
|