starproxima_library 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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +9 -0
  3. data/Gemfile.lock +69 -0
  4. data/diagrams/add_uthor_seq.png +0 -0
  5. data/diagrams/add_uthor_seq.puml +42 -0
  6. data/diagrams/author.png +0 -0
  7. data/diagrams/author.puml +118 -0
  8. data/diagrams/core.png +0 -0
  9. data/diagrams/core.puml +103 -0
  10. data/diagrams/delete_author.png +0 -0
  11. data/diagrams/delete_author.puml +25 -0
  12. data/diagrams/edit_author.png +0 -0
  13. data/diagrams/edit_author.puml +48 -0
  14. data/diagrams/er.png +0 -0
  15. data/diagrams/er.puml +30 -0
  16. data/diagrams/publisher.png +0 -0
  17. data/diagrams/publisher.puml +118 -0
  18. data/diagrams/requirements.docx +0 -0
  19. data/diagrams/start.png +0 -0
  20. data/diagrams/start.puml +74 -0
  21. data/lib/author/author_db_data_source.rb +90 -0
  22. data/lib/author/controllers/author_controller.rb +57 -0
  23. data/lib/author/controllers/author_input_form_controller_create.rb +44 -0
  24. data/lib/author/controllers/author_input_form_controller_edit.rb +53 -0
  25. data/lib/author/controllers/author_list_controller.rb +107 -0
  26. data/lib/author/ui/author_input_form.rb +69 -0
  27. data/lib/author/ui/author_list_view.rb +170 -0
  28. data/lib/controllers/tab_students_controller.rb +43 -0
  29. data/lib/data_sources/book_db_data_source.rb +43 -0
  30. data/lib/data_sources/db_client.rb +34 -0
  31. data/lib/db_config/config.yaml +5 -0
  32. data/lib/db_config/library_config.yaml +5 -0
  33. data/lib/db_config/migrations/create_db.sql +3 -0
  34. data/lib/db_config/migrations/create_tables.sql +27 -0
  35. data/lib/db_config/mock_data/mock_data.sql +49 -0
  36. data/lib/logger.rb +27 -0
  37. data/lib/main.rb +6 -0
  38. data/lib/models/author.rb +32 -0
  39. data/lib/models/book.rb +31 -0
  40. data/lib/models/publisher.rb +37 -0
  41. data/lib/models/student.rb +102 -0
  42. data/lib/models/student_base.rb +100 -0
  43. data/lib/models/student_short.rb +50 -0
  44. data/lib/publisher/controllers/publisher_input_form_controller_create.rb +44 -0
  45. data/lib/publisher/controllers/publisher_input_form_controller_edit.rb +52 -0
  46. data/lib/publisher/controllers/publisher_list_controller.rb +99 -0
  47. data/lib/publisher/publisher_db_data_source.rb +63 -0
  48. data/lib/publisher/ui/publisher_input_form.rb +69 -0
  49. data/lib/publisher/ui/publisher_list_view.rb +168 -0
  50. data/lib/repositories/adapters/db_source_adapter.rb +54 -0
  51. data/lib/repositories/adapters/file_source_adapter.rb +37 -0
  52. data/lib/repositories/containers/data_list.rb +74 -0
  53. data/lib/repositories/containers/data_list_student_short.rb +18 -0
  54. data/lib/repositories/containers/data_table.rb +35 -0
  55. data/lib/repositories/data_sources/db_data_source.rb +32 -0
  56. data/lib/repositories/data_sources/file_data_source.rb +75 -0
  57. data/lib/repositories/data_sources/transformers/data_transformer_base.rb +15 -0
  58. data/lib/repositories/data_sources/transformers/data_transformer_json.rb +16 -0
  59. data/lib/repositories/data_sources/transformers/data_transformer_yaml.rb +16 -0
  60. data/lib/repositories/student_repository.rb +32 -0
  61. data/lib/state_holders/list_state_notifier.rb +60 -0
  62. data/lib/views/main_window.rb +32 -0
  63. data/lib/views/tab_students.rb +148 -0
  64. data/starproxima_library.gemspec +15 -0
  65. data/test/author_test.rb +51 -0
  66. data/test/book_test.rb +33 -0
  67. data/test/publisher_test.rb +39 -0
  68. data/test/state_notifier_test.rb +80 -0
  69. metadata +123 -0
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './lib/state_holders/list_state_notifier'
4
+ require_relative '../ui/publisher_input_form'
5
+ require_relative 'publisher_input_form_controller_create.rb'
6
+ require_relative 'publisher_input_form_controller_edit'
7
+ require_relative '../publisher_db_data_source'
8
+ require 'win32api'
9
+
10
+ class PublisherListController
11
+
12
+ attr_reader :state_notifier
13
+ def initialize(view)
14
+ @view = view
15
+ @state_notifier = ListStateNotifier.new
16
+ @state_notifier.add_listener(@view)
17
+ @publisher_rep = PublisherDBDataSource.new
18
+
19
+ @sort_columns = %w[PublisherID Name Email]
20
+ @sort_by = @sort_columns.first
21
+
22
+ @email_filter_columns = [nil, true, false]
23
+ @email_filter = @email_filter_columns.first
24
+ end
25
+
26
+
27
+
28
+ def on_view_created
29
+ # begin
30
+ # @student_rep = StudentRepository.new(DBSourceAdapter.new)
31
+ # rescue Mysql2::Error::ConnectionError
32
+ # on_db_conn_error
33
+ # end
34
+ end
35
+
36
+ def show_view
37
+ @view.create.show
38
+ end
39
+
40
+ def show_modal_add
41
+ controller = PublisherInputFormControllerCreate.new(self)
42
+ view = PublisherInputForm.new(controller)
43
+ controller.set_view(view)
44
+ view.create.show
45
+ end
46
+
47
+ def show_modal_edit(current_page, per_page, selected_row)
48
+ # item_num = (current_page - 1) * per_page + selected_row
49
+
50
+ item = @state_notifier.get(selected_row)
51
+
52
+ controller = PublisherInputFormControllerEdit.new(self, item)
53
+ view = PublisherInputForm.new(controller)
54
+ controller.set_view(view)
55
+ view.create.show
56
+ end
57
+
58
+ def delete_selected(current_page, per_page, selected_row)
59
+ begin
60
+ item = @state_notifier.get(selected_row)
61
+ @publisher_rep.delete(item.publisher_id)
62
+ @state_notifier.delete(item)
63
+ rescue
64
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
65
+ api.call(0, "You cannot delete the author because he is associated with some book", "Error", 0)
66
+ end
67
+ end
68
+
69
+ def refresh_data(page, per_page)
70
+ # begin
71
+ # @data_list = @student_rep.paginated_short_students(page, per_page, @data_list)
72
+ # @view.update_student_count(@student_rep.student_count)
73
+ # rescue
74
+ # on_db_conn_error
75
+ # end
76
+ items = @publisher_rep.get_list(per_page, page, @sort_by, 'ASC', @email_filter)
77
+ @state_notifier.set_all(items)
78
+ @view.update_student_count(@publisher_rep.count)
79
+ end
80
+
81
+ def sort(page, per_page, sort_index)
82
+ @sort_by = @sort_columns[sort_index]
83
+ refresh_data(page, per_page)
84
+ end
85
+
86
+ def filter_email(page, per_page, filter_index)
87
+ @email_filter = @email_filter_columns[filter_index]
88
+ refresh_data(page, per_page)
89
+ end
90
+
91
+
92
+ private
93
+
94
+ def on_db_conn_error
95
+ api = Win32API.new('user32', 'MessageBox', ['L', 'P', 'P', 'L'], 'I')
96
+ api.call(0, "No connection to DB", "Error", 0)
97
+ exit(false)
98
+ end
99
+ end
@@ -0,0 +1,63 @@
1
+ require 'mysql2'
2
+ require_relative '../data_sources/db_client'
3
+ require_relative '../models/publisher'
4
+
5
+ class PublisherDBDataSource
6
+ def initialize
7
+ @client = DBClient.instance
8
+ end
9
+
10
+ def add(publisher)
11
+ query = "INSERT INTO Publisher (Name, Email) VALUES ('#{publisher.name}', #{publisher.email.nil? ? 'NULL' : "'#{publisher.email}'"})"
12
+ @client.query(query)
13
+ publisher_id = @client.last_id
14
+ get(publisher_id)
15
+ end
16
+
17
+ def change(publisher)
18
+ query = "UPDATE Publisher SET Name='#{publisher.name}', Email=#{publisher.email.nil? ? 'NULL' : "'#{publisher.email}'"} WHERE PublisherID=#{publisher.publisher_id}"
19
+ @client.query(query)
20
+ get(publisher.publisher_id)
21
+ end
22
+
23
+ def delete(id)
24
+ query = "DELETE FROM Publisher WHERE PublisherID=#{id}"
25
+ @client.query(query)
26
+ end
27
+
28
+ def get(id)
29
+ query = "SELECT * FROM Publisher WHERE PublisherID=#{id}"
30
+ result = @client.query(query).first
31
+ if result
32
+ Publisher.new(result[:'PublisherID'], result[:'Name'], result[:'Email'])
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ def get_list(page_size, page_num, sort_field, sort_direction, has_email = nil)
39
+ offset = (page_num - 1) * page_size
40
+ query = "SELECT * FROM Publisher"
41
+
42
+ if has_email == true
43
+ query += " WHERE Email IS NOT NULL"
44
+ elsif has_email == false
45
+ query += " WHERE Email IS NULL"
46
+ end
47
+
48
+ query += " ORDER BY #{sort_field} #{sort_direction} LIMIT #{page_size} OFFSET #{offset}"
49
+
50
+ results = @client.query(query)
51
+ publishers = []
52
+ results.each do |result|
53
+ publishers << Publisher.new(result[:'PublisherID'], result[:'Name'], result[:'Email'])
54
+ end
55
+ publishers
56
+ end
57
+
58
+ def count
59
+ query = "SELECT COUNT(*) FROM Publisher"
60
+ result = @client.query(query).first
61
+ result[:'COUNT(*)']
62
+ end
63
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+ require_relative '../controllers/publisher_input_form_controller_create.rb'
5
+ require './lib/models/author'
6
+ require 'win32api'
7
+
8
+ class PublisherInputForm
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 = [[:name, 'Название'], [:email, 'Почта']]
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,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'glimmer-dsl-libui'
4
+ require_relative '../controllers/publisher_list_controller'
5
+ require_relative 'publisher_input_form'
6
+
7
+ class PublisherListView
8
+ include Glimmer
9
+
10
+ PAGE_SIZE = 20
11
+
12
+ def initialize
13
+ @controller = PublisherListController.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(publishers)
33
+ @items = []
34
+
35
+ i = 0
36
+ item_num = 0
37
+ publishers.each do |publisher|
38
+ i += 1
39
+ item_num = ((@current_page - 1) * PAGE_SIZE) + i
40
+ @items << Struct.new(:№, :id, :название, :почта).new(item_num, publisher.publisher_id, publisher.name, publisher.email)
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_student_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_email(@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
+ },
102
+ per_page: PAGE_SIZE,
103
+
104
+ )
105
+
106
+ @pages = horizontal_box {
107
+ stretchy false
108
+
109
+ button("<") {
110
+ stretchy true
111
+
112
+ on_clicked do
113
+ @current_page = [@current_page - 1, 1].max
114
+ @controller.refresh_data(@current_page, PAGE_SIZE)
115
+ end
116
+
117
+ }
118
+ @page_label = label("...") { stretchy false }
119
+ button(">") {
120
+ stretchy true
121
+
122
+ on_clicked do
123
+ @current_page = [@current_page + 1, (@total_count / PAGE_SIZE.to_f).ceil].min
124
+ @controller.refresh_data(@current_page, PAGE_SIZE)
125
+ end
126
+ }
127
+ }
128
+ }
129
+
130
+ # Секция 3
131
+ vertical_box {
132
+ stretchy false
133
+
134
+ button('Добавить') {
135
+ stretchy false
136
+
137
+ on_clicked {
138
+ @controller.show_modal_add
139
+ }
140
+ }
141
+ button('Изменить') {
142
+ stretchy false
143
+
144
+ on_clicked {
145
+ @controller.show_modal_edit(@current_page, PAGE_SIZE, @table.selection) unless @table.selection.nil?
146
+ }
147
+ }
148
+ button('Удалить') {
149
+ stretchy false
150
+
151
+ on_clicked {
152
+ @controller.delete_selected(@current_page, PAGE_SIZE, @table.selection) unless @table.selection.nil?
153
+ @controller.refresh_data(@current_page, PAGE_SIZE)
154
+ }
155
+ }
156
+ button('Обновить') {
157
+ stretchy false
158
+
159
+ on_clicked {
160
+ @controller.refresh_data(@current_page, PAGE_SIZE)
161
+ }
162
+ }
163
+ }
164
+ }
165
+ on_create
166
+ root_container
167
+ end
168
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './lib/repositories/data_sources/db_data_source'
4
+ require './lib/models/student'
5
+ require './lib/models/student_short'
6
+ require './lib/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 './lib/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('./lib/db_config/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