student_gem 0.1.0
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/checking_attr_writer.rb +25 -0
- data/controller.rb +176 -0
- data/data_list.rb +88 -0
- data/data_list_student_short.rb +55 -0
- data/data_table.rb +23 -0
- data/student.rb +142 -0
- data/student_ancestor.rb +45 -0
- data/student_list.rb +106 -0
- data/student_short.rb +33 -0
- data/view.rb +230 -0
- metadata +88 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f0c586a241d8469b845e81cf3eb2b1fb54e58a5419a91c3d9eae380072396a35
|
|
4
|
+
data.tar.gz: 1af65ec25b4dbbe942fc3e814d2101d3a6668ce341b7b55f3ab48726b4df46cc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: de730a7273ca23713304362557fb813ed4d1cf2441485dcf30de5d7b5d3e99a4538c5835c59838b10c0fccb5d0b2b2df86d1fbfaea1a7d6cf4fb3b6ab8e7a3d6
|
|
7
|
+
data.tar.gz: bc1fd8f93e58c8c76725eeadbec5f61b1870164321188df13f457f776cf9499848e79df81e35f5361d58da332c7cf53aea2aee4bd2c0f071fa0b805e27e6777a
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module CheckingAttrWriter
|
|
2
|
+
|
|
3
|
+
# переопределение сеттера для валидации
|
|
4
|
+
def attr_writer(*attrs)
|
|
5
|
+
attrs.each do |attr|
|
|
6
|
+
validation = "valid_#{attr}?".to_sym
|
|
7
|
+
define_method("#{attr}=") do |value|
|
|
8
|
+
unless self.class.respond_to?(validation, true)
|
|
9
|
+
raise(NotImplementedError, "#{self.class} должен реализовать метод #{validation}")
|
|
10
|
+
end
|
|
11
|
+
if self.class.send(validation, value)
|
|
12
|
+
instance_variable_set("@#{attr}", value)
|
|
13
|
+
else
|
|
14
|
+
raise(ArgumentError)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def attr_accessor(*attrs)
|
|
21
|
+
attr_reader *attrs
|
|
22
|
+
attr_writer *attrs
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
data/controller.rb
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require_relative 'student_list.rb'
|
|
2
|
+
|
|
3
|
+
class Student_list_controller
|
|
4
|
+
|
|
5
|
+
# общий для любого контроллера главного окна фрагмент конструктора
|
|
6
|
+
def initialize(view)
|
|
7
|
+
@view = view
|
|
8
|
+
@student_list = Student_list.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Main_window_controller < Student_list_controller
|
|
14
|
+
|
|
15
|
+
# константа числа строк на странице
|
|
16
|
+
ROWS_ON_PAGE = 20
|
|
17
|
+
|
|
18
|
+
# инициализация номера страницы
|
|
19
|
+
def initialize(view)
|
|
20
|
+
super(view)
|
|
21
|
+
@page = 0
|
|
22
|
+
|
|
23
|
+
@data_list_student_short = Data_list_student_short.new(nil)
|
|
24
|
+
@data_list_student_short.subscribe(@view)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# реакция на выделение ячеек таблицы
|
|
29
|
+
def table_rows_onselect(row)
|
|
30
|
+
@data_list_student_short.select(row)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# реакция на снятие выделения
|
|
34
|
+
def table_rows_ondeselect(row)
|
|
35
|
+
@data_list_student_short.reject(row)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# реакция на нажатие кнопки вставки
|
|
40
|
+
def insert_button_onclick
|
|
41
|
+
modal_controller = Insertion_controller.new(@view)
|
|
42
|
+
dialog = modal_controller.create
|
|
43
|
+
if dialog.execute(PLACEMENT_OWNER) == 1
|
|
44
|
+
student = Student.from_hash(modal_controller.student_attributes)
|
|
45
|
+
begin
|
|
46
|
+
result = @student_list.insert(student)
|
|
47
|
+
rescue => e
|
|
48
|
+
show_error(e)
|
|
49
|
+
end
|
|
50
|
+
refresh_data(@page)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# реакция на нажатие кнопки замены
|
|
55
|
+
def update_button_onclick
|
|
56
|
+
modal_controller = Updation_controller.new(@view)
|
|
57
|
+
id = @data_list_student_short.get_selected.first
|
|
58
|
+
dialog = modal_controller.create
|
|
59
|
+
modal_controller.fill_text_fields(id)
|
|
60
|
+
if dialog.execute(PLACEMENT_OWNER) == 1
|
|
61
|
+
student = Student.from_hash(modal_controller.student_attributes)
|
|
62
|
+
begin
|
|
63
|
+
@student_list.update(id, student)
|
|
64
|
+
rescue => e
|
|
65
|
+
show_error(e)
|
|
66
|
+
end
|
|
67
|
+
refresh_data(@page)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# реакция на нажатие кнопки удаления
|
|
73
|
+
def delete_button_onclick
|
|
74
|
+
ids = @data_list_student_short.get_selected
|
|
75
|
+
begin
|
|
76
|
+
ids.each {|id| @student_list.delete(id)}
|
|
77
|
+
rescue => e
|
|
78
|
+
show_error(e)
|
|
79
|
+
end
|
|
80
|
+
refresh_data(@page)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# реакция на нажатие кнопки назад
|
|
85
|
+
def previous_button_onclick
|
|
86
|
+
@page -= 1
|
|
87
|
+
refresh_data(@page)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# реакция на нажатие кнопки вперед
|
|
91
|
+
def next_button_onclick
|
|
92
|
+
@page += 1
|
|
93
|
+
refresh_data(@page)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# обновление данных на странице
|
|
98
|
+
def refresh_data(k)
|
|
99
|
+
@page = max_page - 1 if @page >= max_page
|
|
100
|
+
begin
|
|
101
|
+
@student_list.get_k_n_student_short_list(k, ROWS_ON_PAGE, @data_list_student_short)
|
|
102
|
+
max_pages = max_page
|
|
103
|
+
rescue => e
|
|
104
|
+
show_error(e)
|
|
105
|
+
end
|
|
106
|
+
@data_list_student_short.notify(ROWS_ON_PAGE)
|
|
107
|
+
|
|
108
|
+
@view.page_count.text = "Страница: #{@page + 1} / #{max_pages}"
|
|
109
|
+
@page == 0 ? @view.previous_button.disable : @view.previous_button.enable
|
|
110
|
+
@page == max_pages - 1 ? @view.next_button.disable : @view.next_button.enable
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def show_error(e)
|
|
115
|
+
error_box = FXMessageBox.new(@view, "Ошибка", e.to_s, opts: MBOX_OK)
|
|
116
|
+
error_box.execute
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# вычисление максимального числа страниц
|
|
123
|
+
def max_page
|
|
124
|
+
(@student_list.count.to_i + ROWS_ON_PAGE - 1) / ROWS_ON_PAGE
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Modal_window_controller < Student_list_controller
|
|
130
|
+
|
|
131
|
+
def create
|
|
132
|
+
@modal = ModalWindow.new(@view, self)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# общий для контроллера модального окна метод валидации
|
|
136
|
+
def text_fields_onchange
|
|
137
|
+
student = student_attributes
|
|
138
|
+
[
|
|
139
|
+
Student.valid_name?(student["last_name"]),
|
|
140
|
+
Student.valid_name?(student["first_name"]),
|
|
141
|
+
Student.valid_name?(student["patronymic"], true),
|
|
142
|
+
Student.valid_telegram?(student["telegram"]),
|
|
143
|
+
Student.valid_email?(student["email"]),
|
|
144
|
+
Student.valid_phone?(student["phone"]),
|
|
145
|
+
Student.valid_git?(student["git"])
|
|
146
|
+
].all? ? @modal.ok_button.enable : @modal.ok_button.disable
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def student_attributes
|
|
150
|
+
attributes = {}
|
|
151
|
+
attributes["last_name"] = @modal.last_name.text.empty? ? nil : @modal.last_name.text
|
|
152
|
+
attributes["first_name"] = @modal.first_name.text.empty? ? nil : @modal.first_name.text
|
|
153
|
+
attributes["patronymic"] = @modal.patronymic.text.empty? ? nil : @modal.patronymic.text
|
|
154
|
+
attributes["telegram"] = @modal.telegram.text.empty? ? nil : @modal.telegram.text
|
|
155
|
+
attributes["email"] = @modal.email.text.empty? ? nil : @modal.email.text
|
|
156
|
+
attributes["phone"] = @modal.phone.text.empty? ? nil : @modal.phone.text
|
|
157
|
+
attributes["git"] = @modal.git.text.empty? ? nil : @modal.git.text
|
|
158
|
+
attributes
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
class Insertion_controller < Modal_window_controller
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class Updation_controller < Modal_window_controller
|
|
166
|
+
def fill_text_fields(id)
|
|
167
|
+
student = @student_list.get_student(id).to_h
|
|
168
|
+
@modal.last_name.text = student["last_name"]
|
|
169
|
+
@modal.first_name.text = student["first_name"]
|
|
170
|
+
@modal.patronymic.text = student["patronymic"]
|
|
171
|
+
@modal.telegram.text = student["telegram"]
|
|
172
|
+
@modal.email.text = student["email"]
|
|
173
|
+
@modal.phone.text = student["phone"]
|
|
174
|
+
@modal.git.text = student["git"]
|
|
175
|
+
end
|
|
176
|
+
end
|
data/data_list.rb
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
require_relative 'data_table.rb'
|
|
2
|
+
|
|
3
|
+
class Data_list
|
|
4
|
+
|
|
5
|
+
# сеттер, учитывающий то, что атрибуты не могут меняться
|
|
6
|
+
def list=(list)
|
|
7
|
+
if list.nil? || check_names?(list)
|
|
8
|
+
@list = list
|
|
9
|
+
@selected = []
|
|
10
|
+
else
|
|
11
|
+
raise ArgumentError
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# конструктор, запрещающий создание непосредственного экземпляра Data_list
|
|
16
|
+
def initialize(list)
|
|
17
|
+
if self.instance_of?(Data_list)
|
|
18
|
+
raise(NotImplementedError, "#{self.class} не может создавать экземпляры, требуется наследник")
|
|
19
|
+
end
|
|
20
|
+
self.list = list
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# выделение номера элемента
|
|
24
|
+
def select(number)
|
|
25
|
+
if number < @list.size
|
|
26
|
+
@selected << number
|
|
27
|
+
@selected = @selected.uniq.sort
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# удаление элемента из выделенных
|
|
32
|
+
def reject(number)
|
|
33
|
+
@selected.delete(number)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# массив ID выделенных элементов
|
|
37
|
+
def get_selected
|
|
38
|
+
@selected.map {|number| @list[number].id}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# массив имен атрибутов
|
|
42
|
+
def get_names
|
|
43
|
+
unless self.class.private_method_defined?(:get_class_names)
|
|
44
|
+
raise(NotImplementedError, "#{self.class} должен реализовать метод #get_class_names")
|
|
45
|
+
end
|
|
46
|
+
["№ по порядку"].concat(get_class_names)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# двумерный массив (экземпляр Data Table) объектов и их атрибутов
|
|
50
|
+
def get_data
|
|
51
|
+
unless self.class.private_method_defined?(:get_object_data)
|
|
52
|
+
raise(NotImplementedError, "#{self.class} должен реализовать метод #get_object_data")
|
|
53
|
+
end
|
|
54
|
+
table = []
|
|
55
|
+
@selected.each{|number| table << [number + 1].concat(get_object_data(number))}
|
|
56
|
+
Data_table.new(table)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# очищение выделения
|
|
60
|
+
def clear_selected
|
|
61
|
+
@selected = []
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# защищенный метод проверки атрибутов класса
|
|
66
|
+
protected
|
|
67
|
+
|
|
68
|
+
def check_names?(list)
|
|
69
|
+
true if list.empty?
|
|
70
|
+
unless self.class.private_method_defined?(:check_class_names)
|
|
71
|
+
raise(NotImplementedError, "#{self.class} должен реализовать метод #check_class_names")
|
|
72
|
+
end
|
|
73
|
+
[list[0].respond_to?(:id)].concat(check_class_names(list)).all?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# приватные методы для работы с атрибутами конкретного класса
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def get_class_names
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def get_object_data(number)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def check_class_names(list)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require_relative 'data_list.rb'
|
|
2
|
+
|
|
3
|
+
class Data_list_student_short < Data_list
|
|
4
|
+
|
|
5
|
+
# инициализация массива подписчиков
|
|
6
|
+
def initialize(list)
|
|
7
|
+
super(list)
|
|
8
|
+
@subscribers = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# подписка
|
|
13
|
+
def subscribe(subscriber)
|
|
14
|
+
@subscribers << subscriber
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# отписка
|
|
18
|
+
def unsubscribe(subscriber)
|
|
19
|
+
@subscribers.delete(subscriber)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# уведомление об изменении состояния подписчикам
|
|
23
|
+
def notify(n)
|
|
24
|
+
(0...n).each {|number| select(number)}
|
|
25
|
+
data_table = get_data
|
|
26
|
+
clear_selected
|
|
27
|
+
|
|
28
|
+
@subscribers.each do |view|
|
|
29
|
+
view.set_table_params(get_names, data_table.get_rows)
|
|
30
|
+
view.set_table_data(data_table)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# имена атрибутов класса StudentShort
|
|
38
|
+
def get_class_names
|
|
39
|
+
["Фамилия ИО", "Контакт", "Гит"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# значения атрибутов класса StudentShort
|
|
43
|
+
def get_object_data(number)
|
|
44
|
+
[@list[number].last_name_initials, @list[number].contact, @list[number].git]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# проверка наличия атрибутов класса StudentShort
|
|
48
|
+
def check_class_names(list)
|
|
49
|
+
[
|
|
50
|
+
list[0].class.method_defined?(:last_name_initials),
|
|
51
|
+
list[0].class.method_defined?(:contact),
|
|
52
|
+
list[0].class.method_defined?(:git)
|
|
53
|
+
]
|
|
54
|
+
end
|
|
55
|
+
end
|
data/data_table.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class Data_table
|
|
2
|
+
|
|
3
|
+
# конструктор
|
|
4
|
+
def initialize(table)
|
|
5
|
+
@table = table
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# получение значения атрибута объекта по номеру строки и столбца
|
|
9
|
+
def get_element(row, column)
|
|
10
|
+
@table[row][column]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# количество атрибутов (столбцов)
|
|
14
|
+
def get_columns
|
|
15
|
+
@table[0].size
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# количество объектов (строк)
|
|
19
|
+
def get_rows
|
|
20
|
+
@table.size
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
data/student.rb
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
require_relative 'student_ancestor.rb'
|
|
2
|
+
require_relative 'checking_attr_writer.rb'
|
|
3
|
+
|
|
4
|
+
class Student < StudentAncestor
|
|
5
|
+
|
|
6
|
+
# методы класса
|
|
7
|
+
|
|
8
|
+
# валидация полей
|
|
9
|
+
def Student.valid_name?(name, can_be_nil = false)
|
|
10
|
+
self.validation?(name, /^[A-Z][a-z]+$/, can_be_nil) || self.validation?(name, /^[А-ЯЁ][а-яё]+$/, can_be_nil)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def Student.valid_email?(email)
|
|
14
|
+
self.validation?(email, /^\S+@\S+\.\S+$/, true)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def Student.valid_phone?(phone)
|
|
18
|
+
self.validation?((phone.scan(/[\+\d]/).join unless phone.nil?), /^(\+7|8)\d{10}$/, true)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def Student.valid_telegram?(telegram)
|
|
22
|
+
self.validation?(telegram, /^@\w{5,}$/, true)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def Student.valid_git?(git)
|
|
26
|
+
self.validation?(git, /^https:\/\/git(hu|la)b\.com\/[\w-]+$/, true)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# методы объекта
|
|
31
|
+
|
|
32
|
+
# геттеры и сеттеры с встроенной валидацией
|
|
33
|
+
singleton_class.prepend(CheckingAttrWriter)
|
|
34
|
+
|
|
35
|
+
attr_accessor :first_name, :last_name, :patronymic, :git
|
|
36
|
+
|
|
37
|
+
# получение контакта и его типа
|
|
38
|
+
def contact
|
|
39
|
+
if !@telegram.nil?
|
|
40
|
+
"telegram - #{@telegram}"
|
|
41
|
+
elsif !@email.nil?
|
|
42
|
+
"email - #{@email}"
|
|
43
|
+
elsif !@phone.nil?
|
|
44
|
+
"phone - #{@phone}"
|
|
45
|
+
else
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# установка контактов
|
|
51
|
+
def contact=(contacts)
|
|
52
|
+
@telegram = Student.valid_telegram?(contacts[:telegram]) ? contacts[:telegram] : raise(ArgumentError)
|
|
53
|
+
@email = Student.valid_email?(contacts[:email]) ? contacts[:email] : raise(ArgumentError)
|
|
54
|
+
@phone = Student.valid_phone?(contacts[:phone]) ? contacts[:phone] : raise(ArgumentError)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# конструктор
|
|
59
|
+
def initialize(last_name:, first_name:, patronymic: nil, id: nil, phone: nil, telegram: nil, email: nil, git: nil)
|
|
60
|
+
self.first_name = first_name
|
|
61
|
+
self.last_name = last_name
|
|
62
|
+
self.patronymic = patronymic
|
|
63
|
+
@id = id
|
|
64
|
+
self.contact = { telegram:telegram, email:email, phone:phone }
|
|
65
|
+
self.git = git
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# конструктор, принимающий хеш
|
|
69
|
+
def self.from_hash(hash)
|
|
70
|
+
new(
|
|
71
|
+
first_name: hash["first_name"],
|
|
72
|
+
last_name: hash["last_name"],
|
|
73
|
+
patronymic: hash["patronymic"],
|
|
74
|
+
id: hash["id"],
|
|
75
|
+
telegram: hash["telegram"],
|
|
76
|
+
phone: hash["phone"],
|
|
77
|
+
email: hash["email"],
|
|
78
|
+
git: hash["git"]
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Фамилия ИО
|
|
83
|
+
def last_name_initials
|
|
84
|
+
"#{@last_name} #{@first_name[0]}.#{patronymic.nil? ? "" : " #{patronymic[0]}."}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# полная информация о содержимом всех полей
|
|
89
|
+
def to_s
|
|
90
|
+
str = "#{id.nil? ? "" : "ID - #{id}, "}"
|
|
91
|
+
str += "last name - #{last_name}, first name - #{first_name}"
|
|
92
|
+
str += "#{patronymic.nil? ? "" : ", patronymic - #{patronymic}"}"
|
|
93
|
+
str += "#{@phone.nil? ? "" : ", phone - #{@phone}"}"
|
|
94
|
+
str += "#{@email.nil? ? "" : ", email - #{@email}"}"
|
|
95
|
+
str += "#{@telegram.nil? ? "" : ", telegram - #{@telegram}"}"
|
|
96
|
+
str += "#{git.nil? ? "" : ", git - #{git}"}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def to_h
|
|
100
|
+
hash = {}
|
|
101
|
+
hash["last_name"] = @last_name
|
|
102
|
+
hash["first_name"] = @first_name
|
|
103
|
+
hash["patronymic"] = @patronymic
|
|
104
|
+
hash["telegram"] = @telegram
|
|
105
|
+
hash["email"] = @email
|
|
106
|
+
hash["phone"] = @phone
|
|
107
|
+
hash["git"] = @git
|
|
108
|
+
hash["id"] = @id
|
|
109
|
+
hash
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# сравнение студентов по полям фамилии, имени и отчества
|
|
114
|
+
include Comparable
|
|
115
|
+
|
|
116
|
+
def <=>(other)
|
|
117
|
+
return nil unless other.is_a?(Student)
|
|
118
|
+
[self.last_name, self.first_name, self.patronymic] <=> [other.last_name, other.first_name, other.patronymic]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# общий метод валидации с проверкой на nil
|
|
123
|
+
def Student.validation?(string, pattern, can_be_nil = false)
|
|
124
|
+
pattern === string || (string.nil? if can_be_nil) ? true : false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# новые методы валидации для attr_writer
|
|
128
|
+
def Student.valid_first_name?(name)
|
|
129
|
+
Student.valid_name?(name, false)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def Student.valid_last_name?(name)
|
|
133
|
+
Student.valid_name?(name, false)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def Student.valid_patronymic?(name)
|
|
137
|
+
Student.valid_name?(name, true)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private_class_method :validation?, :valid_last_name?, :valid_first_name?, :valid_patronymic?
|
|
141
|
+
|
|
142
|
+
end
|
data/student_ancestor.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
class StudentAncestor
|
|
2
|
+
|
|
3
|
+
# геттеры
|
|
4
|
+
attr_reader :id, :git
|
|
5
|
+
|
|
6
|
+
# конструктор, запрещающий создание непосредственного экземпляра StudentAncestor
|
|
7
|
+
def initialize
|
|
8
|
+
if self.instance_of?(StudentAncestor)
|
|
9
|
+
raise(NotImplementedError, "#{self.class} не может создавать экземпляры, требуется наследник")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# краткая информация
|
|
14
|
+
def short_info
|
|
15
|
+
str = "#{id.nil? ? "" : "ID - #{id}, "}"
|
|
16
|
+
str += "name - #{last_name_initials}"
|
|
17
|
+
str += "#{contact.nil? ? "" : ", #{contact}"}"
|
|
18
|
+
str += "#{git.nil? ? "" : ", git - #{git}"}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# проверка наличия гита
|
|
22
|
+
def has_git?
|
|
23
|
+
git.nil? ? false : true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# проверка наличия контакта
|
|
27
|
+
def has_contact?
|
|
28
|
+
contact.nil? ? false : true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# абстрактные методы
|
|
33
|
+
def last_name_initials
|
|
34
|
+
raise(NotImplementedError, "#{self.class} должен реализовать метод #last_name_initials")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def contact
|
|
38
|
+
raise(NotImplementedError, "#{self.class} должен реализовать метод #contact")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_s
|
|
42
|
+
raise(NotImplementedError, "#{self.class} должен реализовать метод #to_s")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
data/student_list.rb
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'pg'
|
|
2
|
+
require 'logger'
|
|
3
|
+
require_relative 'student.rb'
|
|
4
|
+
require_relative 'data_list_student_short.rb'
|
|
5
|
+
|
|
6
|
+
class Student_list
|
|
7
|
+
ACTION_LOGGER = Logger.new(File.join(__dir__, 'log', 'actions.log'))
|
|
8
|
+
ACTION_LOGGER.level = Logger::INFO
|
|
9
|
+
ERROR_LOGGER = Logger.new(File.join(__dir__, 'log', 'errors.log'))
|
|
10
|
+
ERROR_LOGGER.level = Logger::ERROR
|
|
11
|
+
|
|
12
|
+
# получение объекта класса Student по ID
|
|
13
|
+
def get_student(id)
|
|
14
|
+
result = sql("SELECT * FROM student WHERE id = #{id}")
|
|
15
|
+
Student.from_hash(result.first)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# получить k-й список n объектов класса StudentShort для сохранения в формате Data_list_student_short
|
|
19
|
+
def get_k_n_student_short_list(k, n, target)
|
|
20
|
+
result = sql("SELECT * FROM student LIMIT #{n} OFFSET #{n * k}")
|
|
21
|
+
students = result.map {|hash| Student.from_hash(hash)}
|
|
22
|
+
target.list = students
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# вставка объекта класса в таблицу (новый ID генерируется автоматически)
|
|
26
|
+
def insert(student)
|
|
27
|
+
sql("INSERT INTO student (first_name, last_name, patronymic, phone, telegram, email, git)
|
|
28
|
+
VALUES
|
|
29
|
+
('#{student.to_h["first_name"]}',
|
|
30
|
+
'#{student.to_h["last_name"]}',
|
|
31
|
+
#{student.to_h["patronymic"].nil? ? "NULL" : "'#{student.to_h["patronymic"]}'"},
|
|
32
|
+
#{student.to_h["phone"].nil? ? "NULL" : "'#{student.to_h["phone"]}'"},
|
|
33
|
+
#{student.to_h["telegram"].nil? ? "NULL" : "'#{student.to_h["telegram"]}'"},
|
|
34
|
+
#{student.to_h["email"].nil? ? "NULL" : "'#{student.to_h["email"]}'"},
|
|
35
|
+
#{student.to_h["git"].nil? ? "NULL" : "'#{student.to_h["git"]}'"})")
|
|
36
|
+
|
|
37
|
+
ACTION_LOGGER.info("Создание записи студента: #{student.to_h.inspect}")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# замена объекта по ID
|
|
41
|
+
def update(id, student)
|
|
42
|
+
sql("UPDATE student
|
|
43
|
+
SET
|
|
44
|
+
first_name = '#{student.to_h["first_name"]}',
|
|
45
|
+
last_name = '#{student.to_h["last_name"]}',
|
|
46
|
+
patronymic = #{student.to_h["patronymic"].nil? ? "NULL" : "'#{student.to_h["patronymic"]}'"},
|
|
47
|
+
phone = #{student.to_h["phone"].nil? ? "NULL" : "'#{student.to_h["phone"]}'"},
|
|
48
|
+
telegram = #{student.to_h["telegram"].nil? ? "NULL" : "'#{student.to_h["telegram"]}'"},
|
|
49
|
+
email = #{student.to_h["email"].nil? ? "NULL" : "'#{student.to_h["email"]}'"},
|
|
50
|
+
git = #{student.to_h["git"].nil? ? "NULL" : "'#{student.to_h["git"]}'"}
|
|
51
|
+
WHERE id = #{id}")
|
|
52
|
+
|
|
53
|
+
ACTION_LOGGER.info("Изменение данных студента по ID = #{id}: #{student.to_h.inspect}")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# удаление объекта по ID
|
|
57
|
+
def delete(id)
|
|
58
|
+
sql("DELETE FROM student WHERE id = #{id}")
|
|
59
|
+
|
|
60
|
+
ACTION_LOGGER.info("Удаление записи студента по ID = #{id}")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# количество объектов в таблице
|
|
64
|
+
def count
|
|
65
|
+
result = sql("SELECT count(*) FROM student")
|
|
66
|
+
result.first["count"]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# подключение к базе данных и выполнение запроса
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def sql(command, dbname: 'student_db', user: 'postgres', password: 'postgres', host: '127.0.0.1', port: 5432)
|
|
74
|
+
result = []
|
|
75
|
+
begin
|
|
76
|
+
connection = PG.connect(
|
|
77
|
+
dbname: dbname,
|
|
78
|
+
user: user,
|
|
79
|
+
password: password,
|
|
80
|
+
host: host,
|
|
81
|
+
port: port
|
|
82
|
+
)
|
|
83
|
+
result = connection.exec(command)
|
|
84
|
+
rescue PG::ConnectionBad => e
|
|
85
|
+
s = "Ошибка при попытке установить соединение с базой данных:\n#{e.message}"
|
|
86
|
+
ERROR_LOGGER.error(s)
|
|
87
|
+
raise s
|
|
88
|
+
rescue PG::UniqueViolation => e
|
|
89
|
+
s = "Студент с указанным контактом уже существует:\n#{e.message}"
|
|
90
|
+
ERROR_LOGGER.error(s)
|
|
91
|
+
raise s
|
|
92
|
+
rescue PG::ServerError => e
|
|
93
|
+
s = "Ошибка на стороне сервера:\n#{e.message}"
|
|
94
|
+
ERROR_LOGGER.error(s)
|
|
95
|
+
raise s
|
|
96
|
+
rescue PG::Error => e
|
|
97
|
+
s = "Ошибка на стороне клиента:\n#{e.message}"
|
|
98
|
+
ERROR_LOGGER.error(s)
|
|
99
|
+
raise s
|
|
100
|
+
ensure
|
|
101
|
+
connection&.close
|
|
102
|
+
end
|
|
103
|
+
result.to_a
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end
|
data/student_short.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require_relative 'student.rb'
|
|
2
|
+
|
|
3
|
+
class StudentShort < StudentAncestor
|
|
4
|
+
|
|
5
|
+
# альтернативный конструктор, принимающий экземпляр класса студент
|
|
6
|
+
def StudentShort.from_student(student)
|
|
7
|
+
if student.id.nil?
|
|
8
|
+
raise(ArgumentError)
|
|
9
|
+
else
|
|
10
|
+
new(id: student.id, last_name_initials: student.last_name_initials, contact: student.contact, git: student.git)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# геттеры
|
|
16
|
+
attr_reader :last_name_initials, :contact
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# конструктор
|
|
20
|
+
def initialize(id:, last_name_initials:, contact: nil, git: nil)
|
|
21
|
+
@id = id
|
|
22
|
+
@last_name_initials = last_name_initials
|
|
23
|
+
@contact = contact
|
|
24
|
+
@git = git
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# полная информация о содержимом всех полей
|
|
29
|
+
def to_s
|
|
30
|
+
short_info
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
data/view.rb
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
require 'fox16'
|
|
2
|
+
include Fox
|
|
3
|
+
require_relative 'controller.rb'
|
|
4
|
+
|
|
5
|
+
module View
|
|
6
|
+
|
|
7
|
+
def set_table_params(column_names, whole_entities_count)
|
|
8
|
+
raise(NotImplementedError, "#{self.class} должен реализовать метод #set_table_params")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def set_table_data(data_table)
|
|
12
|
+
raise(NotImplementedError, "#{self.class} должен реализовать метод #set_table_data")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class MainWindow < FXMainWindow
|
|
18
|
+
|
|
19
|
+
include View
|
|
20
|
+
|
|
21
|
+
attr_reader :page_count, :previous_button, :next_button, :insert_button, :update_button
|
|
22
|
+
|
|
23
|
+
# конструктор страницы и начальное заполнение данными
|
|
24
|
+
def initialize(app)
|
|
25
|
+
super(app, "Таблица студентов", width: 750, height: 500)
|
|
26
|
+
@controller = Main_window_controller.new(self)
|
|
27
|
+
|
|
28
|
+
main_frame = FXVerticalFrame.new(self, LAYOUT_FILL_X | LAYOUT_FILL_Y, padding: 10)
|
|
29
|
+
@table = FXTable.new(main_frame, opts: TABLE_COL_SIZABLE | TABLE_ROW_SIZABLE | LAYOUT_FILL_X | LAYOUT_FILL_Y)
|
|
30
|
+
@table.editable = false
|
|
31
|
+
|
|
32
|
+
button_frame = FXHorizontalFrame.new(main_frame, LAYOUT_FILL_X | LAYOUT_BOTTOM, padding: 5, hSpacing: 8)
|
|
33
|
+
@insert_button = FXButton.new(button_frame, "Добавить", opts: BUTTON_NORMAL)
|
|
34
|
+
@update_button = FXButton.new(button_frame, "Изменить", opts: BUTTON_NORMAL)
|
|
35
|
+
@delete_button = FXButton.new(button_frame, "Удалить", opts: BUTTON_NORMAL)
|
|
36
|
+
@update_button.disable
|
|
37
|
+
@delete_button.disable
|
|
38
|
+
|
|
39
|
+
pages_frame = FXHorizontalFrame.new(button_frame, LAYOUT_RIGHT, padding: 5, hSpacing: 8)
|
|
40
|
+
@previous_button = FXButton.new(pages_frame, "<--", opts: BUTTON_NORMAL)
|
|
41
|
+
@page_count = FXLabel.new(pages_frame, "")
|
|
42
|
+
@next_button = FXButton.new(pages_frame, "-->", opts: BUTTON_NORMAL)
|
|
43
|
+
|
|
44
|
+
@table.connect(SEL_SELECTED) do |sender, sel, pos|
|
|
45
|
+
table_rows_onselect(pos.row)
|
|
46
|
+
end
|
|
47
|
+
@table.connect(SEL_DESELECTED) do |sender, sel, pos|
|
|
48
|
+
table_rows_ondeselect(pos.row)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
@insert_button.connect(SEL_COMMAND) do
|
|
52
|
+
insert_button_onclick
|
|
53
|
+
end
|
|
54
|
+
@update_button.connect(SEL_COMMAND) do
|
|
55
|
+
update_button_onclick
|
|
56
|
+
end
|
|
57
|
+
@delete_button.connect(SEL_COMMAND) do
|
|
58
|
+
delete_button_onclick
|
|
59
|
+
end
|
|
60
|
+
@previous_button.connect(SEL_COMMAND) do
|
|
61
|
+
previous_button_onclick
|
|
62
|
+
end
|
|
63
|
+
@next_button.connect(SEL_COMMAND) do
|
|
64
|
+
next_button_onclick
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
refresh_data(0)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# делегация обработки событий контроллеру
|
|
72
|
+
def table_rows_onselect(row)
|
|
73
|
+
@controller.table_rows_onselect(row)
|
|
74
|
+
buttons_update
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def table_rows_ondeselect(row)
|
|
78
|
+
@controller.table_rows_ondeselect(row)
|
|
79
|
+
buttons_update
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def insert_button_onclick
|
|
83
|
+
@controller.insert_button_onclick
|
|
84
|
+
table_update
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def update_button_onclick
|
|
88
|
+
@controller.update_button_onclick
|
|
89
|
+
table_update
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def delete_button_onclick
|
|
93
|
+
@controller.delete_button_onclick
|
|
94
|
+
table_update
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def previous_button_onclick
|
|
98
|
+
@controller.previous_button_onclick
|
|
99
|
+
table_update
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def next_button_onclick
|
|
103
|
+
@controller.next_button_onclick
|
|
104
|
+
table_update
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def refresh_data(k)
|
|
108
|
+
@controller.refresh_data(k)
|
|
109
|
+
table_update
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# обновление данных на окне после изменений в таблице
|
|
114
|
+
def table_update
|
|
115
|
+
@table.update
|
|
116
|
+
@page_count.update
|
|
117
|
+
buttons_update
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# изменение доступности кнопок при выделении строк
|
|
121
|
+
def buttons_update
|
|
122
|
+
selected_count = 0
|
|
123
|
+
(0...@table.numRows).each{|i| selected_count += 1 if @table.rowSelected?(i)}
|
|
124
|
+
selected_count == 1 ? @update_button.enable : @update_button.disable
|
|
125
|
+
selected_count >= 1 ? @delete_button.enable : @delete_button.disable
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# установка заголовков и количества строк таблицы
|
|
130
|
+
def set_table_params(column_names, whole_entities_count)
|
|
131
|
+
@table.setTableSize(whole_entities_count, 3)
|
|
132
|
+
|
|
133
|
+
@table.cornerButton.text = column_names.first
|
|
134
|
+
column_names[1...column_names.size].each_with_index do |name, index|
|
|
135
|
+
@table.setColumnText(index, name)
|
|
136
|
+
@table.setColumnWidth(index, 200)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# установка значений в строках таблицы
|
|
141
|
+
def set_table_data(data_table)
|
|
142
|
+
(0...data_table.get_rows).each do |i|
|
|
143
|
+
@table.setRowText(i, data_table.get_element(i, 0).to_s)
|
|
144
|
+
(1...data_table.get_columns).each do |j|
|
|
145
|
+
if data_table.get_element(i, j).nil?
|
|
146
|
+
@table.setItemText(i, j - 1, "[NULL]")
|
|
147
|
+
else
|
|
148
|
+
@table.setItemText(i, j - 1, data_table.get_element(i, j).to_s)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# включение видимости окна при его регистрации в ОС
|
|
156
|
+
def create
|
|
157
|
+
super
|
|
158
|
+
show(PLACEMENT_SCREEN)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class ModalWindow < FXDialogBox
|
|
165
|
+
|
|
166
|
+
attr_reader :last_name, :first_name, :patronymic, :telegram, :email, :phone, :git, :ok_button, :controller
|
|
167
|
+
|
|
168
|
+
def initialize(owner, controller)
|
|
169
|
+
super(owner, "Создание студента", DECOR_TITLE | DECOR_BORDER, width: 400, height: 350)
|
|
170
|
+
@controller = controller
|
|
171
|
+
|
|
172
|
+
main_frame = FXVerticalFrame.new(self, LAYOUT_FILL_X | LAYOUT_FILL_Y, padding: 10)
|
|
173
|
+
grid = FXMatrix.new(main_frame, 2, MATRIX_BY_COLUMNS | LAYOUT_FILL_X, hSpacing: 10, vSpacing: 8)
|
|
174
|
+
|
|
175
|
+
FXLabel.new(grid, "Фамилия:", opts: LAYOUT_CENTER_Y)
|
|
176
|
+
@last_name = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
177
|
+
FXLabel.new(grid, "Имя:", opts: LAYOUT_CENTER_Y)
|
|
178
|
+
@first_name = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
179
|
+
FXLabel.new(grid, "Отчество:", opts: LAYOUT_CENTER_Y)
|
|
180
|
+
@patronymic = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
181
|
+
FXLabel.new(grid, "Телеграм:", opts: LAYOUT_CENTER_Y)
|
|
182
|
+
@telegram = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
183
|
+
FXLabel.new(grid, "Почта:", opts: LAYOUT_CENTER_Y)
|
|
184
|
+
@email = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
185
|
+
FXLabel.new(grid, "Телефон:", opts: LAYOUT_CENTER_Y)
|
|
186
|
+
@phone = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
187
|
+
FXLabel.new(grid, "Гит:", opts: LAYOUT_CENTER_Y)
|
|
188
|
+
@git = FXTextField.new(grid, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_FILL_X | LAYOUT_CENTER_Y)
|
|
189
|
+
|
|
190
|
+
button_frame = FXHorizontalFrame.new(main_frame, LAYOUT_FILL_X | LAYOUT_BOTTOM, padding: 5, hSpacing: 8)
|
|
191
|
+
FXButton.new(button_frame, "Отмена", nil, self, ID_CANCEL, opts: BUTTON_NORMAL | FRAME_RAISED)
|
|
192
|
+
@ok_button = FXButton.new(button_frame, "Сохранить", nil, self, ID_ACCEPT, opts: BUTTON_NORMAL | FRAME_RAISED)
|
|
193
|
+
@ok_button.disable
|
|
194
|
+
|
|
195
|
+
@last_name.connect(SEL_CHANGED) do
|
|
196
|
+
text_fields_onchange
|
|
197
|
+
end
|
|
198
|
+
@first_name.connect(SEL_CHANGED) do
|
|
199
|
+
text_fields_onchange
|
|
200
|
+
end
|
|
201
|
+
@patronymic.connect(SEL_CHANGED) do
|
|
202
|
+
text_fields_onchange
|
|
203
|
+
end
|
|
204
|
+
@telegram.connect(SEL_CHANGED) do
|
|
205
|
+
text_fields_onchange
|
|
206
|
+
end
|
|
207
|
+
@email.connect(SEL_CHANGED) do
|
|
208
|
+
text_fields_onchange
|
|
209
|
+
end
|
|
210
|
+
@phone.connect(SEL_CHANGED) do
|
|
211
|
+
text_fields_onchange
|
|
212
|
+
end
|
|
213
|
+
@git.connect(SEL_CHANGED) do
|
|
214
|
+
text_fields_onchange
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def text_fields_onchange
|
|
219
|
+
@controller.text_fields_onchange
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# запуск приложения
|
|
226
|
+
FXApp.new do |app|
|
|
227
|
+
MainWindow.new(app)
|
|
228
|
+
app.create
|
|
229
|
+
app.run
|
|
230
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: student_gem
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- s0195920
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: pg
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: fox16
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: logger
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
executables: []
|
|
55
|
+
extensions: []
|
|
56
|
+
extra_rdoc_files: []
|
|
57
|
+
files:
|
|
58
|
+
- checking_attr_writer.rb
|
|
59
|
+
- controller.rb
|
|
60
|
+
- data_list.rb
|
|
61
|
+
- data_list_student_short.rb
|
|
62
|
+
- data_table.rb
|
|
63
|
+
- student.rb
|
|
64
|
+
- student_ancestor.rb
|
|
65
|
+
- student_list.rb
|
|
66
|
+
- student_short.rb
|
|
67
|
+
- view.rb
|
|
68
|
+
homepage: https://fpm-gitlab.kubsu.ru/design-patterns-tests/s0195920
|
|
69
|
+
licenses: []
|
|
70
|
+
metadata: {}
|
|
71
|
+
rdoc_options: []
|
|
72
|
+
require_paths:
|
|
73
|
+
- lib
|
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '0'
|
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0'
|
|
84
|
+
requirements: []
|
|
85
|
+
rubygems_version: 3.6.7
|
|
86
|
+
specification_version: 4
|
|
87
|
+
summary: CRUD-приложение для управления базой данных студентов
|
|
88
|
+
test_files: []
|