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