shnaider_carproj 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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +10 -0
  3. data/Gemfile.lock +71 -0
  4. data/diagrams/add_owner.jpg +0 -0
  5. data/diagrams/delete_owner.jpg +0 -0
  6. data/diagrams/er.png +0 -0
  7. data/diagrams/owner.jpg +0 -0
  8. data/diagrams/owner.sai2 +0 -0
  9. data/diagrams/start.jpg +0 -0
  10. data/lib/car/controllers/publisher_input_form_controller_create.rb +44 -0
  11. data/lib/car/controllers/publisher_input_form_controller_edit.rb +52 -0
  12. data/lib/car/controllers/publisher_list_controller.rb +99 -0
  13. data/lib/car/tenant_db_data_source.rb +63 -0
  14. data/lib/car/ui/tenant_input_form.rb +69 -0
  15. data/lib/car/ui/tenant_list_view.rb +168 -0
  16. data/lib/controllers/tab_students_controller.rb +43 -0
  17. data/lib/data_sources/car_db_data_source.rb +43 -0
  18. data/lib/data_sources/db_client.rb +34 -0
  19. data/lib/db_config/carshering_config.yaml +5 -0
  20. data/lib/db_config/config.yaml +5 -0
  21. data/lib/db_config/migrations/create_db.sql +3 -0
  22. data/lib/db_config/migrations/create_tables.sql +29 -0
  23. data/lib/db_config/mock_data/mock_data.sql +55 -0
  24. data/lib/models/car.rb +31 -0
  25. data/lib/models/owner.rb +32 -0
  26. data/lib/models/student.rb +102 -0
  27. data/lib/models/student_base.rb +100 -0
  28. data/lib/models/student_short.rb +50 -0
  29. data/lib/models/tenant.rb +40 -0
  30. data/lib/owner/controllers/owner_input_form_controller_create.rb +44 -0
  31. data/lib/owner/controllers/owner_input_form_controller_edit.rb +53 -0
  32. data/lib/owner/controllers/owner_list_controller.rb +107 -0
  33. data/lib/owner/owner_db_data_source.rb +70 -0
  34. data/lib/owner/ui/owner_input_form.rb +69 -0
  35. data/lib/owner/ui/owner_list_view.rb +169 -0
  36. data/lib/repositories/adapters/db_source_adapter.rb +54 -0
  37. data/lib/repositories/adapters/file_source_adapter.rb +37 -0
  38. data/lib/repositories/containers/data_list.rb +74 -0
  39. data/lib/repositories/containers/data_list_student_short.rb +18 -0
  40. data/lib/repositories/containers/data_table.rb +35 -0
  41. data/lib/repositories/data_sources/db_data_source.rb +32 -0
  42. data/lib/repositories/data_sources/file_data_source.rb +75 -0
  43. data/lib/repositories/data_sources/transformers/data_transformer_base.rb +15 -0
  44. data/lib/repositories/data_sources/transformers/data_transformer_json.rb +16 -0
  45. data/lib/repositories/data_sources/transformers/data_transformer_yaml.rb +16 -0
  46. data/lib/repositories/student_repository.rb +32 -0
  47. data/lib/state_holders/list_state_notifier.rb +62 -0
  48. data/lib/tenant/controllers/tenant_input_form_controller_create.rb +44 -0
  49. data/lib/tenant/controllers/tenant_input_form_controller_edit.rb +53 -0
  50. data/lib/tenant/controllers/tenant_list_controller.rb +107 -0
  51. data/lib/tenant/tenant_db_data_source.rb +90 -0
  52. data/lib/tenant/ui/tenant_input_form.rb +69 -0
  53. data/lib/tenant/ui/tenant_list_view.rb +169 -0
  54. data/lib/views/main_window.rb +25 -0
  55. data/lib/views/tab_students.rb +148 -0
  56. data/requirements.docx +0 -0
  57. data/shnaider_carproj.gemspec +15 -0
  58. data/test/car_test.rb +33 -0
  59. data/test/logger.rb +27 -0
  60. data/test/main.rb +5 -0
  61. data/test/owner_test.rb +48 -0
  62. data/test/state_notifier_test.rb +80 -0
  63. data/test/tenant_test.rb +48 -0
  64. metadata +118 -0
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './state_holders/list_state_notifier'
4
+ require_relative '../ui/owner_input_form'
5
+ require_relative 'owner_input_form_controller_create'
6
+ require_relative 'owner_input_form_controller_edit'
7
+ require_relative '../owner_db_data_source'
8
+ require 'win32api'
9
+
10
+ # Класс TenantListController отвечает за управление списком
11
+ # авторов, используя различные методы для обновления данных и
12
+ # взаимодействия с пользовательским интерфейсом.
13
+ class OwnerListController
14
+
15
+ attr_reader :state_notifier
16
+
17
+ def initialize(view)
18
+ LoggerHolder.instance.debug('OwnerListController: initialize')
19
+ @view = view
20
+ @state_notifier = ListStateNotifier.new
21
+ @state_notifier.add_listener(@view)
22
+ @owner_rep = OwnerDBDataSource.new
23
+
24
+ @sort_columns = %w[OwnerID FirstName LastName FatherName]
25
+ @sort_by = @sort_columns.first
26
+
27
+ @father_name_filter_columns = [nil, true, false]
28
+ @father_name_filter = @father_name_filter_columns.first
29
+ end
30
+
31
+ def on_view_created
32
+ # begin
33
+ # @student_rep = StudentRepository.new(DBSourceAdapter.new)
34
+ # rescue Mysql2::Error::ConnectionError
35
+ # on_db_conn_error
36
+ # end
37
+ end
38
+
39
+ def show_view
40
+ @view.create.show
41
+ end
42
+
43
+ def show_modal_add
44
+ LoggerHolder.instance.debug('OwnerListController: show_modal_add')
45
+ controller = OwnerInputFormControllerCreate.new(self)
46
+ view = OwnerInputForm.new(controller)
47
+ controller.set_view(view)
48
+ view.create.show
49
+ end
50
+
51
+ # метод, который создает контроллер TenantInputFormControllerEdit, представление OwnerInputForm, устанавливает связи между ними и показывает модальное окно.
52
+ def show_modal_edit(current_page, per_page, selected_row)
53
+ LoggerHolder.instance.debug('OwnerListController: show_modal_edit')
54
+ # item_num = (current_page - 1) * per_page + selected_row
55
+
56
+ item = @state_notifier.get(selected_row)
57
+
58
+ controller = OwnerInputFormControllerEdit.new(self, item)
59
+ view = OwnerInputForm.new(controller)
60
+ controller.set_view(view)
61
+ view.create.show
62
+ end
63
+
64
+ # метод, который получает выбранный элемент из state_notifier, удаляет его из базы данных и из state_notifier
65
+ def delete_selected(current_page, per_page, selected_row)
66
+ LoggerHolder.instance.debug('OwnerListController: delete_selected')
67
+ begin
68
+ item = @state_notifier.get(selected_row)
69
+ @owner_rep.delete(item.owner_id)
70
+ @state_notifier.delete(item)
71
+ rescue
72
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
73
+ api.call(0, "You cannot delete the owner because he is associated with some car", "Error", 0)
74
+ end
75
+ end
76
+
77
+ # етод, который получает список авторов из базы данных, устанавливает их в state_notifier и обновляет пользовательский интерфейс
78
+ def refresh_data(page, per_page)
79
+ # begin
80
+ # @data_list = @student_rep.paginated_short_students(page, per_page, @data_list)
81
+ # @view.update_student_count(@student_rep.student_count)
82
+ # rescue
83
+ # on_db_conn_error
84
+ # end
85
+ items = @owner_rep.get_list(per_page, page, @sort_by, 'ASC', @father_name_filter)
86
+ @state_notifier.set_all(items)
87
+ @view.update_count(@owner_rep.count)
88
+ end
89
+
90
+ def sort(page, per_page, sort_index)
91
+ @sort_by = @sort_columns[sort_index]
92
+ refresh_data(page, per_page)
93
+ end
94
+
95
+ def filter_father_name(page, per_page, filter_index)
96
+ @father_name_filter = @father_name_filter_columns[filter_index]
97
+ refresh_data(page, per_page)
98
+ end
99
+
100
+ private
101
+
102
+ def on_db_conn_error
103
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
104
+ api.call(0, "No connection to DB", "Error", 0)
105
+ exit(false)
106
+ end
107
+ end
@@ -0,0 +1,70 @@
1
+ require 'mysql2'
2
+ require_relative '../lib/data_sources/db_client'
3
+
4
+ class OwnerDBDataSource
5
+ def initialize
6
+ @client = DBClient.instance
7
+ end
8
+
9
+ # добавляет нового владельца в базу данных, возвращает созданную запись.
10
+ def add(owner)
11
+ query = "INSERT INTO Owner (FirstName, LastName, FatherName) VALUES ('#{owner.first_name}', '#{owner.last_name}', #{owner.father_name.nil? ? 'NULL' : "'#{owner.father_name}'"})"
12
+ @client.query(query)
13
+ owner_id = @client.last_id
14
+ get(owner_id)
15
+ end
16
+
17
+ # изменяет данные об владельце в базе данных, возвращает измененную запись.
18
+ def change(owner)
19
+ query = "UPDATE Owner SET FirstName='#{owner.first_name}', LastName='#{owner.last_name}', FatherName=#{owner.father_name.nil? ? 'NULL' : "'#{owner.father_name}'"} WHERE OwnerID=#{owner.owner_id}"
20
+ @client.query(query)
21
+ get(owner.owner_id)
22
+ end
23
+
24
+ # удаляет запись о владельце из базы данных.
25
+ def delete(id)
26
+ query = "DELETE FROM Owner WHERE OwnerID=#{id}"
27
+ @client.query(query)
28
+ end
29
+
30
+ # возвращает запись об владельце по заданному id.
31
+ def get(id)
32
+ query = "SELECT * FROM Owner WHERE OwnerID=#{id}"
33
+ result = @client.query(query).first
34
+ if result
35
+ Owner.new(result[:'OwnerID'], result[:'FirstName'], result[:'LastName'], result[:'FatherName'])
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ # возвращает список владельцов с учетом фильтра по наличию отчества и сортировки, позволяет задавать количество элементов на странице и номер страницы.
42
+ def get_list(page_size, page_num, sort_field, sort_direction, has_father_name = nil)
43
+ offset = (page_num - 1) * page_size
44
+ query = "SELECT * FROM Owner"
45
+
46
+ if has_father_name == true
47
+ query += " WHERE FatherName IS NOT NULL"
48
+ elsif has_father_name == false
49
+ query += " WHERE FatherName IS NULL"
50
+ end
51
+
52
+ query += " ORDER BY #{sort_field} #{sort_direction} LIMIT #{page_size} OFFSET #{offset}"
53
+ results = @client.query(query)
54
+
55
+ owners = []
56
+ results.each do |result|
57
+ owners << Owner.new(result[:'OwnerID'], result[:'FirstName'], result[:'LastName'], result[:'FatherName'])
58
+ end
59
+
60
+ owners
61
+ end
62
+
63
+ # возвращает количество записей об владельцах в базе данных.
64
+ def count
65
+ query = "SELECT COUNT(*) FROM Owner"
66
+ result = @client.query(query).first
67
+
68
+ result[:'COUNT(*)']
69
+ end
70
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+ require_relative '../controllers/owner_input_form_controller_create'
5
+ require './models/owner'
6
+ require 'win32api'
7
+
8
+ class OwnerInputForm
9
+ include Glimmer
10
+
11
+ def initialize(controller, existing_student = nil)
12
+ @item = existing_student.to_hash unless existing_student.nil?
13
+ @controller = controller
14
+ @entries = {}
15
+ end
16
+
17
+ def on_create
18
+ @controller.on_view_created
19
+ end
20
+
21
+ def create
22
+ @root_container = window('Владелец', 300, 70) {
23
+ resizable false
24
+
25
+ vertical_box {
26
+ @student_form = form {
27
+ stretchy false
28
+
29
+ fields = [[:first_name, 'Имя владельца'], [:last_name, 'Фамилия владельца'], [:father_name, 'Отчество владельца']]
30
+
31
+ fields.each do |field|
32
+ @entries[field[0]] = entry {
33
+ label field[1]
34
+ }
35
+ end
36
+ }
37
+
38
+ button('Сохранить') {
39
+ stretchy false
40
+
41
+ on_clicked {
42
+ values = @entries.transform_values { |v| v.text.force_encoding("utf-8").strip }
43
+ values.transform_values! { |v| v.empty? ? nil : v}
44
+
45
+ @controller.process_fields(values)
46
+ }
47
+ }
48
+ }
49
+ }
50
+ on_create
51
+ @root_container
52
+ end
53
+
54
+ def set_value(field, value)
55
+ return unless @entries.include?(field)
56
+
57
+ @entries[field].text = value
58
+ end
59
+
60
+ def make_readonly(*fields)
61
+ fields.each do |field|
62
+ @entries[field].read_only = true
63
+ end
64
+ end
65
+
66
+ def close
67
+ @root_container.destroy
68
+ end
69
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+ require_relative '../controllers/owner_list_controller'
5
+ require_relative 'owner_input_form'
6
+
7
+ class OwnerListView
8
+ include Glimmer
9
+
10
+ PAGE_SIZE = 20
11
+
12
+ def initialize
13
+ @controller = OwnerListController.new(self)
14
+ @current_page = 1
15
+ @total_count = 0
16
+ end
17
+
18
+ def on_create
19
+ @controller.on_view_created
20
+ @controller.refresh_data(@current_page, PAGE_SIZE)
21
+ end
22
+
23
+ # Метод наблюдателя datalist
24
+ # def on_datalist_changed(new_table)
25
+ # arr = new_table.to_2d_array
26
+ # arr.map do |row|
27
+ # row[3] = [row[3][:value], contact_color(row[3][:type])] unless row[3].nil?
28
+ # end
29
+ # @table.model_array = arr
30
+ # end
31
+
32
+ def update(owners)
33
+ @items = []
34
+
35
+ i = 0
36
+ item_num = 0
37
+ owners.each do |owner|
38
+ i += 1
39
+ item_num = ((@current_page - 1) * PAGE_SIZE) + i
40
+ @items << Struct.new(:№, :id, :имя_владельца, :фамилия_владельца, :отчество_владельца).new(item_num, owner.owner_id, owner.first_name, owner.last_name, owner.father_name)
41
+ end
42
+
43
+ @table.model_array = @items
44
+ @page_label.text = "#{@current_page} / #{(@total_count / PAGE_SIZE.to_f).ceil}"
45
+ end
46
+
47
+ def update_count(new_cnt)
48
+ @total_count = new_cnt
49
+ @page_label.text = "#{@current_page} / #{(@total_count / PAGE_SIZE.to_f).ceil}"
50
+ end
51
+
52
+ def create
53
+
54
+ root_container = horizontal_box {
55
+ # Секция 1
56
+ vertical_box {
57
+ stretchy false
58
+
59
+ vertical_box {
60
+ stretchy false
61
+
62
+ label {
63
+ text 'Отчество'
64
+ }
65
+ combobox { |c|
66
+ items ['Не важно','Есть','Нет']
67
+ selected 0
68
+ on_selected do
69
+ @controller.filter_father_name(@current_page, PAGE_SIZE, c.selected)
70
+ end
71
+ }
72
+
73
+ label {
74
+ text 'Сортировка'
75
+ }
76
+ combobox { |c|
77
+ items ['ID','Имя владельца','Фамилия владельца', 'Отчество владельца']
78
+ selected 0
79
+ on_selected do
80
+ @controller.sort(@current_page, PAGE_SIZE, c.selected)
81
+ end
82
+ }
83
+ }
84
+
85
+
86
+ }
87
+
88
+ # Секция 2
89
+ vertical_box {
90
+ @table = refined_table(
91
+ table_editable: false,
92
+ filter: lambda do |row_hash, query|
93
+ utf8_query = query.force_encoding("utf-8")
94
+ row_hash['Имя владельца'].include?(utf8_query)
95
+ end,
96
+ table_columns: {
97
+ '№' => :text,
98
+ 'ID' => :text,
99
+ 'Имя владельца' => :text,
100
+ 'Фамилия владельца' => :text,
101
+ 'Отчество владельца' => :text,
102
+ },
103
+ per_page: PAGE_SIZE,
104
+
105
+ )
106
+
107
+ @pages = horizontal_box {
108
+ stretchy false
109
+
110
+ button("<") {
111
+ stretchy true
112
+
113
+ on_clicked do
114
+ @current_page = [@current_page - 1, 1].max
115
+ @controller.refresh_data(@current_page, PAGE_SIZE)
116
+ end
117
+
118
+ }
119
+ @page_label = label("...") { stretchy false }
120
+ button(">") {
121
+ stretchy true
122
+
123
+ on_clicked do
124
+ @current_page = [@current_page + 1, (@total_count / PAGE_SIZE.to_f).ceil].min
125
+ @controller.refresh_data(@current_page, PAGE_SIZE)
126
+ end
127
+ }
128
+ }
129
+ }
130
+
131
+ # Секция 3
132
+ vertical_box {
133
+ stretchy false
134
+
135
+ button('Добавить') {
136
+ stretchy false
137
+
138
+ on_clicked {
139
+ @controller.show_modal_add
140
+ }
141
+ }
142
+ button('Изменить') {
143
+ stretchy false
144
+
145
+ on_clicked {
146
+ @controller.show_modal_edit(@current_page, PAGE_SIZE, @table.selection) unless @table.selection.nil?
147
+ }
148
+ }
149
+ button('Удалить') {
150
+ stretchy false
151
+
152
+ on_clicked {
153
+ @controller.delete_selected(@current_page, PAGE_SIZE, @table.selection) unless @table.selection.nil?
154
+ @controller.refresh_data(@current_page, PAGE_SIZE)
155
+ }
156
+ }
157
+ button('Обновить') {
158
+ stretchy false
159
+
160
+ on_clicked {
161
+ @controller.refresh_data(@current_page, PAGE_SIZE)
162
+ }
163
+ }
164
+ }
165
+ }
166
+ on_create
167
+ root_container
168
+ end
169
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './repositories/data_sources/db_data_source'
4
+ require './models/student'
5
+ require './models/student_short'
6
+ require './repositories/containers/data_list_student_short'
7
+
8
+ class DBSourceAdapter
9
+ def initialize
10
+ @db = DBDataSource.instance
11
+ end
12
+
13
+ def student_by_id(student_id)
14
+ hash = @db.prepare_exec('SELECT * FROM student WHERE id = ?', student_id).first
15
+ return nil if hash.nil?
16
+
17
+ Student.from_hash(hash)
18
+ end
19
+
20
+ def paginated_short_students(page, count, existing_data_list = nil)
21
+ offset = (page - 1) * count
22
+ students = @db.prepare_exec('SELECT * FROM student LIMIT ?, ?', offset, count)
23
+ slice = students.map { |h| StudentShort.from_student(Student.from_hash(h)) }
24
+ return DataListStudentShort.new(slice) if existing_data_list.nil?
25
+
26
+ existing_data_list.replace_objects(slice)
27
+ existing_data_list
28
+ end
29
+
30
+ def add_student(student)
31
+ template = 'INSERT INTO student(last_name, first_name, father_name, phone, telegram, email, git) VALUES (?, ?, ?, ?, ?, ?, ?)'
32
+ @db.prepare_exec(template, *student_fields(student))
33
+ @db.query('SELECT LAST_INSERT_ID()').first.first[1]
34
+ end
35
+
36
+ def replace_student(student_id, student)
37
+ template = 'UPDATE student SET last_name=?, first_name=?, father_name=?, phone=?, telegram=?, email=?, git=? WHERE id=?'
38
+ @db.prepare_exec(template, *student_fields(student), student_id)
39
+ end
40
+
41
+ def remove_student(student_id)
42
+ @db.prepare_exec('DELETE FROM student WHERE id = ?', student_id)
43
+ end
44
+
45
+ def student_count
46
+ @db.query('SELECT COUNT(id) FROM student').first.first[1]
47
+ end
48
+
49
+ private
50
+
51
+ def student_fields(student)
52
+ [student.last_name, student.first_name, student.father_name, student.phone, student.telegram, student.email, student.git]
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FileSourceAdapter
4
+ def initialize(data_transformer, file_path)
5
+ @file_path = file_path
6
+ @file_source = FileDataSource.new(data_transformer)
7
+ @file_source.load_from_file(file_path)
8
+ end
9
+
10
+ def student_by_id(student_id)
11
+ @file_source.student_by_id(student_id)
12
+ end
13
+
14
+ def paginated_short_students(page, count, existing_data_list = nil)
15
+ @file_source.paginated_short_students(page, count, existing_data_list)
16
+ end
17
+
18
+ def add_student(student)
19
+ added_id = @file_source.add_student(student)
20
+ @file_source.save_to_file(@file_path)
21
+ added_id
22
+ end
23
+
24
+ def replace_student(student_id, student)
25
+ @file_source.replace_student(student_id, student)
26
+ @file_source.save_to_file(@file_path)
27
+ end
28
+
29
+ def remove_student(student_id)
30
+ @file_source.remove_student(student_id)
31
+ @file_source.save_to_file(@file_path)
32
+ end
33
+
34
+ def student_count
35
+ @file_source.student_count
36
+ end
37
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './repositories/containers/data_table'
4
+
5
+ class DataList
6
+ # Это "абстрактный" класс
7
+ private_class_method :new
8
+
9
+ attr_writer :objects
10
+
11
+ # Конструктор, принимает массив любых объектов
12
+ def initialize(objects)
13
+ self.objects = objects
14
+ @listeners = []
15
+ end
16
+
17
+ def add_listener(listener)
18
+ @listeners << listener
19
+ end
20
+
21
+ def remove_listener(listener)
22
+ @listeners.delete(listener)
23
+ end
24
+
25
+ def notify
26
+ @listeners.each { |lst| lst.on_datalist_changed(data_table) }
27
+ end
28
+
29
+ # Выбрать элемент по номеру
30
+ def select_element(number)
31
+ self.selected_num = number < objects.size ? number : nil
32
+ end
33
+
34
+ def selected_id
35
+ objects[selected_num].id
36
+ end
37
+
38
+ # Получить DataTable со всеми элементами.
39
+ def data_table
40
+ result = []
41
+ counter = 0
42
+ objects.each do |obj|
43
+ row = []
44
+ row << counter
45
+ row.push(*table_fields(obj))
46
+ result << row
47
+ counter += 1
48
+ end
49
+ DataTable.new(result)
50
+ end
51
+
52
+ # Добавить элементы в конец списка
53
+ def replace_objects(objects)
54
+ self.objects = objects.dup
55
+ notify
56
+ end
57
+
58
+ protected
59
+
60
+ # Список значений полей для DataTable. Переопределить в наследниках
61
+ def table_fields(_obj)
62
+ []
63
+ end
64
+
65
+ # Имена атрибутов объектов по порядку. Переопределить в наследниках
66
+ def column_names
67
+ []
68
+ end
69
+
70
+ private
71
+
72
+ attr_reader :objects
73
+ attr_accessor :selected_num
74
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'data_list'
4
+
5
+ class DataListStudentShort < DataList
6
+ # Делаем приватный new предка публичным
7
+ public_class_method :new
8
+
9
+ def column_names
10
+ ['Фамилия И. О.', 'Гит', 'Контакт']
11
+ end
12
+
13
+ protected
14
+
15
+ def table_fields(obj)
16
+ [obj.last_name_and_initials, obj.git, obj.contact]
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DataTable
4
+ attr_reader :rows_count, :cols_count
5
+
6
+ # Конструктор, принимает 2D Array
7
+ def initialize(table)
8
+ self.rows_count = table.size
9
+ max_cols = 0
10
+ table.each { |row| max_cols = row.size if row.size > max_cols }
11
+ self.cols_count = max_cols
12
+ self.table = table
13
+ end
14
+
15
+ # Получить значение в ячейке [row, col]
16
+ def get_item(row, col)
17
+ return nil if row >= rows_count
18
+ return nil if col >= cols_count
19
+
20
+ table[row][col].dup
21
+ end
22
+
23
+ def to_2d_array
24
+ table.dup
25
+ end
26
+
27
+ def to_s
28
+ "DataTable (#{rows_count}x#{cols_count})"
29
+ end
30
+
31
+ private
32
+
33
+ attr_accessor :table
34
+ attr_writer :rows_count, :cols_count
35
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mysql2'
4
+
5
+ class DBDataSource
6
+ private_class_method :new
7
+ @instance_mutex = Mutex.new
8
+
9
+ def initialize
10
+ db_config = YAML.load_file('./db_config/carshering_config.yaml').transform_keys(&:to_sym)
11
+ @client = Mysql2::Client.new(db_config)
12
+ @client.query_options.merge!(symbolize_keys: true)
13
+ end
14
+
15
+ def self.instance
16
+ return @instance if @instance
17
+
18
+ @instance_mutex.synchronize do
19
+ @instance ||= new
20
+ end
21
+
22
+ @instance
23
+ end
24
+
25
+ def prepare_exec(statement, *params)
26
+ @client.prepare(statement).execute(*params)
27
+ end
28
+
29
+ def query(statement)
30
+ @client.query(statement)
31
+ end
32
+ end