student_mvp 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/README.md +35 -0
- data/Rakefile +4 -0
- data/doc/Add_student_presenter.html +238 -0
- data/doc/App_logger.html +386 -0
- data/doc/Base_presenter.html +265 -0
- data/doc/Binary_tree.html +300 -0
- data/doc/Binary_tree_iterator.html +217 -0
- data/doc/Contact_sort_decorator.html +219 -0
- data/doc/DB_client.html +252 -0
- data/doc/Data_list.html +625 -0
- data/doc/Data_list_student_short.html +148 -0
- data/doc/Data_storage_strategy.html +178 -0
- data/doc/Data_table.html +264 -0
- data/doc/Deep_dup.html +152 -0
- data/doc/Edit_contacts_presenter.html +199 -0
- data/doc/Edit_git_presenter.html +192 -0
- data/doc/Edit_student_presenter.html +464 -0
- data/doc/Field_filter_decorator.html +219 -0
- data/doc/Filter.html +148 -0
- data/doc/Filter_decorator.html +209 -0
- data/doc/Full_name_filter_decorator.html +211 -0
- data/doc/Full_name_sort_decorator.html +217 -0
- data/doc/Gemfile.html +98 -0
- data/doc/Git_sort_decorator.html +220 -0
- data/doc/Has_not_field_filter_decorator.html +209 -0
- data/doc/JSON_storage_strategy.html +183 -0
- data/doc/Person.html +511 -0
- data/doc/README_md.html +147 -0
- data/doc/Rakefile.html +94 -0
- data/doc/Replace_student_presenter.html +204 -0
- data/doc/Sort_decorator.html +214 -0
- data/doc/Student.html +755 -0
- data/doc/StudentMvp/Error.html +105 -0
- data/doc/StudentMvp.html +111 -0
- data/doc/Student_list_presenter.html +667 -0
- data/doc/Student_short.html +398 -0
- data/doc/Students_list.html +341 -0
- data/doc/Students_list_DB.html +361 -0
- data/doc/Students_list_file.html +460 -0
- data/doc/Students_list_file_adapter.html +341 -0
- data/doc/Students_list_interface.html +298 -0
- data/doc/YAML_storage_strategy.html +183 -0
- data/doc/bin/setup.html +96 -0
- data/doc/created.rid +44 -0
- data/doc/css/fonts.css +167 -0
- data/doc/css/rdoc.css +662 -0
- data/doc/fonts/Lato-Light.ttf +0 -0
- data/doc/fonts/Lato-LightItalic.ttf +0 -0
- data/doc/fonts/Lato-Regular.ttf +0 -0
- data/doc/fonts/Lato-RegularItalic.ttf +0 -0
- data/doc/fonts/SourceCodePro-Bold.ttf +0 -0
- data/doc/fonts/SourceCodePro-Regular.ttf +0 -0
- data/doc/images/add.png +0 -0
- data/doc/images/arrow_up.png +0 -0
- data/doc/images/brick.png +0 -0
- data/doc/images/brick_link.png +0 -0
- data/doc/images/bug.png +0 -0
- data/doc/images/bullet_black.png +0 -0
- data/doc/images/bullet_toggle_minus.png +0 -0
- data/doc/images/bullet_toggle_plus.png +0 -0
- data/doc/images/date.png +0 -0
- data/doc/images/delete.png +0 -0
- data/doc/images/find.png +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/doc/images/macFFBgHack.png +0 -0
- data/doc/images/package.png +0 -0
- data/doc/images/page_green.png +0 -0
- data/doc/images/page_white_text.png +0 -0
- data/doc/images/page_white_width.png +0 -0
- data/doc/images/plugin.png +0 -0
- data/doc/images/ruby.png +0 -0
- data/doc/images/tag_blue.png +0 -0
- data/doc/images/tag_green.png +0 -0
- data/doc/images/transparent.png +0 -0
- data/doc/images/wrench.png +0 -0
- data/doc/images/wrench_orange.png +0 -0
- data/doc/images/zoom.png +0 -0
- data/doc/index.html +174 -0
- data/doc/js/darkfish.js +114 -0
- data/doc/js/navigation.js +105 -0
- data/doc/js/navigation.js.gz +0 -0
- data/doc/js/search.js +110 -0
- data/doc/js/search_index.js +1 -0
- data/doc/js/search_index.js.gz +0 -0
- data/doc/js/searcher.js +229 -0
- data/doc/js/searcher.js.gz +0 -0
- data/doc/table_of_contents.html +1047 -0
- data/lib/data_access/DB_client/DB_client.rb +26 -0
- data/lib/deep_dup/deep_dup.rb +14 -0
- data/lib/logger/logger.rb +64 -0
- data/lib/models/binary_tree/binary_tree.rb +163 -0
- data/lib/models/binary_tree/binary_tree_iterator.rb +36 -0
- data/lib/models/data_list/data_list.rb +97 -0
- data/lib/models/data_list/data_list_student_short.rb +16 -0
- data/lib/models/data_storage_strategy/JSON_storage_strategy.rb +20 -0
- data/lib/models/data_storage_strategy/YAML_storage_strategy.rb +20 -0
- data/lib/models/data_storage_strategy/data_storage_strategy.rb +11 -0
- data/lib/models/data_table/data_table.rb +52 -0
- data/lib/models/filter/filter.rb +5 -0
- data/lib/models/filter/filter_decorator.rb +14 -0
- data/lib/models/filter/sort_decorator.rb +15 -0
- data/lib/models/filter/student_filters/contact_sort_decorator.rb +50 -0
- data/lib/models/filter/student_filters/field_filter_decorator.rb +34 -0
- data/lib/models/filter/student_filters/full_name_filter_decorator.rb +26 -0
- data/lib/models/filter/student_filters/full_name_sort_decorator.rb +32 -0
- data/lib/models/filter/student_filters/git_sort_decorator.rb +35 -0
- data/lib/models/filter/student_filters/has_not_field_filter_decorator.rb +24 -0
- data/lib/models/person/person.rb +72 -0
- data/lib/models/student/student.rb +204 -0
- data/lib/models/student_short/student_short.rb +98 -0
- data/lib/models/students_list/students_list.rb +32 -0
- data/lib/models/students_list/students_list_DB.rb +95 -0
- data/lib/models/students_list/students_list_file.rb +134 -0
- data/lib/models/students_list/students_list_file_adapter.rb +45 -0
- data/lib/models/students_list/students_list_interface.rb +25 -0
- data/lib/presenters/base_presenters/base_presenter.rb +26 -0
- data/lib/presenters/base_presenters/student_list_presenter.rb +273 -0
- data/lib/presenters/edit_student/add_student_presenter.rb +46 -0
- data/lib/presenters/edit_student/edit_contacts_presenter.rb +36 -0
- data/lib/presenters/edit_student/edit_git_presenter.rb +28 -0
- data/lib/presenters/edit_student/edit_student_presenter.rb +109 -0
- data/lib/presenters/edit_student/replace_student_presenter.rb +37 -0
- data/lib/student_mvp/version.rb +5 -0
- data/lib/student_mvp.rb +8 -0
- data/sig/student_mvp.rbs +4 -0
- metadata +253 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
|
3
|
+
class DB_client
|
4
|
+
private_class_method :new
|
5
|
+
|
6
|
+
def initialize(db_config)
|
7
|
+
raise 'Database configuration is required' unless db_config
|
8
|
+
self.client = Mysql2::Client.new(db_config)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.instance(db_config = nil)
|
12
|
+
@instance ||= new(db_config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def query(query, params=[])
|
16
|
+
self.client.prepare(query).execute(*params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
self.client.close
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
attr_accessor :client
|
25
|
+
@instance = nil
|
26
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class App_logger
|
4
|
+
LOG_FILE_PATH = File.expand_path('../../log/app.log', __FILE__)
|
5
|
+
private_class_method :new
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
ensure_log_directory
|
9
|
+
self.logger = Logger.new(LOG_FILE_PATH)
|
10
|
+
self.logger.formatter = proc do |severity, datetime, progname, msg|
|
11
|
+
"[#{datetime}] #{severity}: #{msg}\n"
|
12
|
+
end
|
13
|
+
setup_log_level
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.instance
|
17
|
+
@instance ||= new
|
18
|
+
end
|
19
|
+
|
20
|
+
def log(severity, message)
|
21
|
+
self.logger.send(severity, message)
|
22
|
+
end
|
23
|
+
|
24
|
+
def info(message)
|
25
|
+
log(:info, message)
|
26
|
+
end
|
27
|
+
|
28
|
+
def debug(message)
|
29
|
+
log(:debug, message)
|
30
|
+
end
|
31
|
+
|
32
|
+
def error(message)
|
33
|
+
log(:error, message)
|
34
|
+
end
|
35
|
+
|
36
|
+
def warn(message)
|
37
|
+
log(:warn, message)
|
38
|
+
end
|
39
|
+
|
40
|
+
def fatal(message)
|
41
|
+
log(:fatal, message)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
attr_accessor :logger
|
46
|
+
|
47
|
+
def ensure_log_directory
|
48
|
+
log_directory = File.dirname(LOG_FILE_PATH)
|
49
|
+
Dir.mkdir(log_directory) unless Dir.exist?(log_directory)
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup_log_level
|
53
|
+
log_mode = ENV['LOG_MODE'] || 'hybrid'
|
54
|
+
|
55
|
+
case log_mode.downcase
|
56
|
+
when 'all'
|
57
|
+
self.logger.level = Logger::DEBUG
|
58
|
+
when 'errors'
|
59
|
+
self.logger.level = Logger::ERROR
|
60
|
+
when 'hybrid'
|
61
|
+
self.logger.level = Logger::INFO
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require_relative './binary_tree_iterator.rb'
|
2
|
+
|
3
|
+
class Binary_tree
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
RED = true
|
7
|
+
BLACK = false
|
8
|
+
|
9
|
+
class Node
|
10
|
+
attr_accessor :value, :left, :right, :color, :parent
|
11
|
+
|
12
|
+
def initialize(value, color = RED)
|
13
|
+
self.value = value
|
14
|
+
self.left = nil
|
15
|
+
self.right = nil
|
16
|
+
self.color = color
|
17
|
+
self.parent = nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :root
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
self.root = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def add(value)
|
28
|
+
new_node = Node.new(value)
|
29
|
+
self.root = insert(self.root, new_node)
|
30
|
+
self.balance(new_node)
|
31
|
+
end
|
32
|
+
|
33
|
+
def each(&block)
|
34
|
+
iterator = Binary_tree_iterator.new(self.root)
|
35
|
+
iterator.each(&block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def find(key)
|
39
|
+
result = nil
|
40
|
+
self.each do |value|
|
41
|
+
if value.key == key
|
42
|
+
result = value
|
43
|
+
break
|
44
|
+
end
|
45
|
+
end
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def insert(root, node)
|
51
|
+
if root.nil?
|
52
|
+
return node
|
53
|
+
elsif node.value < root.value
|
54
|
+
root.left = insert(root.left, node)
|
55
|
+
root.left.parent = root
|
56
|
+
else
|
57
|
+
root.right = insert(root.right, node)
|
58
|
+
root.right.parent = root
|
59
|
+
end
|
60
|
+
root
|
61
|
+
end
|
62
|
+
|
63
|
+
# метод для балансировки узла
|
64
|
+
def balance(node)
|
65
|
+
while node != self.root && node.parent.color == RED
|
66
|
+
if node.parent == node.parent.parent.left
|
67
|
+
balance_with_uncle_on_left(node)
|
68
|
+
else
|
69
|
+
balance_with_uncle_on_right(node)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
self.root.color = BLACK
|
73
|
+
end
|
74
|
+
|
75
|
+
# балансировка, когда дядя находится слева
|
76
|
+
def balance_with_uncle_on_left(node)
|
77
|
+
uncle = node.parent.parent.right
|
78
|
+
|
79
|
+
if uncle&.color == RED # дядя красный
|
80
|
+
recolor(node)
|
81
|
+
node = node.parent.parent # дед
|
82
|
+
else
|
83
|
+
if node == node.parent.right # правый узел
|
84
|
+
node = node.parent # поворот влево
|
85
|
+
left_rotate(node)
|
86
|
+
end
|
87
|
+
rotate_and_recolor(node) # поворот вправо и перекраска
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# балансировка, когда дядя находится справа
|
92
|
+
def balance_with_uncle_on_right(node)
|
93
|
+
uncle = node.parent.parent.left
|
94
|
+
|
95
|
+
if uncle&.color == RED # дядя красный
|
96
|
+
recolor(node)
|
97
|
+
node = node.parent.parent # дед
|
98
|
+
else
|
99
|
+
if node == node.parent.left # левый узел
|
100
|
+
node = node.parent # поворот вправо
|
101
|
+
right_rotate(node)
|
102
|
+
end
|
103
|
+
rotate_and_recolor(node) # поворот вправо и перекраска
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# перекраска дяди и деда
|
108
|
+
def recolor(node)
|
109
|
+
node.parent.color = BLACK # родителя в черный
|
110
|
+
node.parent.parent.color = RED # деда в красный
|
111
|
+
uncle = node.parent.parent.right
|
112
|
+
uncle.color = BLACK if uncle # дядю в черный
|
113
|
+
end
|
114
|
+
|
115
|
+
# поворачиваем узел вправо и перекрашиваем
|
116
|
+
def rotate_and_recolor(node)
|
117
|
+
node.parent.color = BLACK # родителя в черный
|
118
|
+
node.parent.parent.color = RED # деда в красный
|
119
|
+
right_rotate(node.parent.parent) # поворот деда вправо
|
120
|
+
end
|
121
|
+
|
122
|
+
# левое вращение
|
123
|
+
def left_rotate(node)
|
124
|
+
right_child = node.right
|
125
|
+
return if right_child.nil?
|
126
|
+
|
127
|
+
node.right = right_child.left
|
128
|
+
right_child.left.parent = node if right_child.left
|
129
|
+
right_child.parent = node.parent
|
130
|
+
|
131
|
+
if node.parent.nil?
|
132
|
+
self.root = right_child
|
133
|
+
elsif node == node.parent.left
|
134
|
+
node.parent.left = right_child
|
135
|
+
else
|
136
|
+
node.parent.right = right_child
|
137
|
+
end
|
138
|
+
|
139
|
+
right_child.left = node
|
140
|
+
node.parent = right_child
|
141
|
+
end
|
142
|
+
|
143
|
+
# правое вращение
|
144
|
+
def right_rotate(node)
|
145
|
+
left_child = node.left
|
146
|
+
return if left_child.nil?
|
147
|
+
|
148
|
+
node.left = left_child.right
|
149
|
+
left_child.right.parent = node if left_child.right
|
150
|
+
left_child.parent = node.parent
|
151
|
+
|
152
|
+
if node.parent.nil?
|
153
|
+
self.root = left_child
|
154
|
+
elsif node == node.parent.left
|
155
|
+
node.parent.left = left_child
|
156
|
+
else
|
157
|
+
node.parent.right = left_child
|
158
|
+
end
|
159
|
+
|
160
|
+
left_child.right = node
|
161
|
+
node.parent = left_child
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Binary_tree_iterator
|
2
|
+
include Enumerable
|
3
|
+
|
4
|
+
attr_reader :root
|
5
|
+
|
6
|
+
def initialize(root)
|
7
|
+
self.root = root
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(&block)
|
11
|
+
self.enumerator.each(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
attr_writer :root
|
16
|
+
|
17
|
+
def enumerator
|
18
|
+
Enumerator.new do |yielder|
|
19
|
+
stack = []
|
20
|
+
push_left_branch(yielder, self.root, stack)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def push_left_branch(yielder, node, stack)
|
25
|
+
while node
|
26
|
+
yielder << node.value
|
27
|
+
stack.push(node)
|
28
|
+
node = node.left
|
29
|
+
end
|
30
|
+
|
31
|
+
until stack.empty?
|
32
|
+
current = stack.pop
|
33
|
+
push_left_branch(yielder, current.right, stack)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require_relative '../../deep_dup/deep_dup.rb'
|
2
|
+
|
3
|
+
class Data_list
|
4
|
+
include Deep_dup
|
5
|
+
|
6
|
+
attr_accessor :index, :count
|
7
|
+
|
8
|
+
# constructor
|
9
|
+
def initialize(elements)
|
10
|
+
self.data = elements
|
11
|
+
self.selected = []
|
12
|
+
self.index = 1
|
13
|
+
self.observers = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# select element id by number
|
17
|
+
def select(number)
|
18
|
+
raise IndexError, "Index out of bounds" unless self.valid_index?(number)
|
19
|
+
self.selected << number unless self.selected.include?(number)
|
20
|
+
end
|
21
|
+
|
22
|
+
# get selected ids
|
23
|
+
def get_selected
|
24
|
+
ids = []
|
25
|
+
self.selected.each do |key|
|
26
|
+
ids << self.data[key].id
|
27
|
+
end
|
28
|
+
ids
|
29
|
+
end
|
30
|
+
|
31
|
+
# deselect
|
32
|
+
def deselect(number)
|
33
|
+
self.selected.delete(number) if self.selected.include?(number)
|
34
|
+
end
|
35
|
+
|
36
|
+
# clear selected
|
37
|
+
def clear_selected
|
38
|
+
self.selected = []
|
39
|
+
end
|
40
|
+
|
41
|
+
# pattern-method
|
42
|
+
def retrieve_data()
|
43
|
+
result = []
|
44
|
+
result << self.get_names
|
45
|
+
result.concat(self.get_data)
|
46
|
+
Data_table.new(result)
|
47
|
+
end
|
48
|
+
|
49
|
+
# get names (abstract)
|
50
|
+
def get_names
|
51
|
+
raise NotImplementedError, "Not implemented"
|
52
|
+
end
|
53
|
+
|
54
|
+
# get_data
|
55
|
+
def get_data()
|
56
|
+
result = []
|
57
|
+
self.data.each do |key, value|
|
58
|
+
row = build_row(key, value)
|
59
|
+
result.append(row)
|
60
|
+
end
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
64
|
+
# data setter
|
65
|
+
def data=(data)
|
66
|
+
@data = {}
|
67
|
+
data.each do |element|
|
68
|
+
@data[index] = deep_dup(element)
|
69
|
+
self.index += 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def notify
|
74
|
+
observers.each do |observer|
|
75
|
+
observer.set_table_params(self.get_names, self.count)
|
76
|
+
observer.set_table_data(self.retrieve_data)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_observer(observer)
|
81
|
+
self.observers << observer
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
attr_reader :data
|
86
|
+
attr_accessor :selected, :observers
|
87
|
+
|
88
|
+
# validate index
|
89
|
+
def valid_index?(index)
|
90
|
+
self.data.key?(index)
|
91
|
+
end
|
92
|
+
|
93
|
+
# build row method (abstract)
|
94
|
+
def build_row(index, obj)
|
95
|
+
raise NotImplementedError, "Not implemented"
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative '../data_list/data_list.rb'
|
2
|
+
require_relative '../data_table/data_table.rb'
|
3
|
+
|
4
|
+
class Data_list_student_short < Data_list
|
5
|
+
# get_names for student short
|
6
|
+
def get_names
|
7
|
+
["№", "full name", "git", "contact"]
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# build row for student short
|
13
|
+
def build_row(index, obj)
|
14
|
+
[index, obj.full_name, obj.git, obj.get_any_contact]
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative '../student/student.rb'
|
3
|
+
require_relative './data_storage_strategy.rb'
|
4
|
+
|
5
|
+
class JSON_storage_strategy < Data_storage_strategy
|
6
|
+
# read from json file
|
7
|
+
def read(file_path)
|
8
|
+
return [] unless File.exist?(file_path)
|
9
|
+
data = JSON.parse(File.read(file_path), symbolize_names: true) rescue []
|
10
|
+
data.map do |data|
|
11
|
+
Student.new(**data)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# read to json file
|
16
|
+
def write(file_path, students)
|
17
|
+
data = students.map { |student| student.to_h }
|
18
|
+
File.write(file_path, JSON.pretty_generate(data))
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require_relative '../student/student.rb'
|
3
|
+
require_relative './data_storage_strategy.rb'
|
4
|
+
|
5
|
+
class YAML_storage_strategy < Data_storage_strategy
|
6
|
+
# read from yaml file
|
7
|
+
def read(file_path)
|
8
|
+
return [] unless File.exist?(file_path)
|
9
|
+
data = YAML.safe_load(File.read(file_path), permitted_classes: [Date, Symbol]) || []
|
10
|
+
data.map do |student|
|
11
|
+
Student.new_from_hash(student)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# read to yaml file
|
16
|
+
def write(file_path, students)
|
17
|
+
data = students.map { |student| student.to_h }
|
18
|
+
File.write(file_path, data.to_yaml)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative '../../deep_dup/deep_dup.rb'
|
2
|
+
|
3
|
+
class Data_table
|
4
|
+
include Deep_dup
|
5
|
+
|
6
|
+
# constructor
|
7
|
+
def initialize(data)
|
8
|
+
self.data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
# row count
|
12
|
+
def row_count
|
13
|
+
self.data.size
|
14
|
+
end
|
15
|
+
|
16
|
+
# column count
|
17
|
+
def col_count
|
18
|
+
if self.data.empty?
|
19
|
+
return 0
|
20
|
+
end
|
21
|
+
self.data[0].size
|
22
|
+
end
|
23
|
+
|
24
|
+
# get element
|
25
|
+
def get(row, col)
|
26
|
+
raise IndexError, "Row out of bounds" unless self.valid_row?(row)
|
27
|
+
raise IndexError, "Column out of bounds" unless self.valid_col?(col)
|
28
|
+
self.deep_dup(self.data[row][col])
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
attr_reader :data
|
33
|
+
|
34
|
+
# data setter
|
35
|
+
def data=(data)
|
36
|
+
unless data.is_a?(Array) && data.all? {|row| row.is_a?(Array)}
|
37
|
+
raise ArgumentError, "Data must be a two-dimensional array"
|
38
|
+
end
|
39
|
+
|
40
|
+
@data = data.map{ |row| row.map { |element| deep_dup(element) }}
|
41
|
+
end
|
42
|
+
|
43
|
+
# row validation
|
44
|
+
def valid_row?(row)
|
45
|
+
row.between?(0, self.row_count - 1)
|
46
|
+
end
|
47
|
+
|
48
|
+
# column validation
|
49
|
+
def valid_col?(col)
|
50
|
+
col.between?(0, self.col_count - 1)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative './filter_decorator.rb'
|
2
|
+
|
3
|
+
class Sort_decorator < Filter_decorator
|
4
|
+
def initialize(filter, order)
|
5
|
+
super(filter)
|
6
|
+
self.order = order
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply(filtering_obj)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
attr_accessor :order
|
15
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative '../filter_decorator.rb'
|
2
|
+
|
3
|
+
class Contact_sort_decorator < Filter_decorator
|
4
|
+
def initialize(filter, order)
|
5
|
+
super(filter)
|
6
|
+
self.order = order
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply(filtering_obj)
|
10
|
+
if filtering_obj.is_a?(Array)
|
11
|
+
filtered_students = super(filtering_obj)
|
12
|
+
return [] if filtered_students.nil?
|
13
|
+
|
14
|
+
sorted_students = filtered_students.sort_by do |student|
|
15
|
+
self.get_priority(student)
|
16
|
+
end
|
17
|
+
sorted_students.reverse! if self.order == :desc
|
18
|
+
sorted_students
|
19
|
+
else
|
20
|
+
query = super(filtering_obj)
|
21
|
+
"#{query} ORDER BY
|
22
|
+
CASE
|
23
|
+
WHEN telegram IS NOT NULL AND telegram != '' THEN 1
|
24
|
+
WHEN email IS NOT NULL AND email != '' THEN 2
|
25
|
+
WHEN phone_number IS NOT NULL AND phone_number != '' THEN 3
|
26
|
+
ELSE 4
|
27
|
+
END #{self.order_clause}
|
28
|
+
"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
attr_accessor :order
|
34
|
+
|
35
|
+
def order_clause
|
36
|
+
self.order == :asc ? 'ASC' : 'DESC'
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_priority(student)
|
40
|
+
if student.telegram && !student.telegram.empty?
|
41
|
+
1
|
42
|
+
elsif student.email && !student.email.empty?
|
43
|
+
2
|
44
|
+
elsif student.phone_number && !student.phone_number.empty?
|
45
|
+
3
|
46
|
+
else
|
47
|
+
4
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../filter_decorator.rb'
|
2
|
+
|
3
|
+
class Field_filter_decorator < Filter_decorator
|
4
|
+
def initialize(filter, field, value)
|
5
|
+
super(filter)
|
6
|
+
self.field = field
|
7
|
+
self.value = value.strip.downcase
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply(filtering_obj)
|
11
|
+
if filtering_obj.is_a?(Array)
|
12
|
+
super(filtering_obj).select do |student|
|
13
|
+
student_value = student.send(self.field).to_s.downcase
|
14
|
+
if self.value.nil? || self.value.empty?
|
15
|
+
!student_value.nil? && !student_value.empty?
|
16
|
+
else
|
17
|
+
student_value.include?(self.value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
else
|
21
|
+
query = super(filtering_obj)
|
22
|
+
condition = query.include?("WHERE") ? "AND" : "WHERE"
|
23
|
+
if self.value.nil? || self.value.empty?
|
24
|
+
"#{query} #{condition} (#{self.field} IS NOT NULL AND #{self.field} != '')"
|
25
|
+
else
|
26
|
+
"#{query} #{condition} #{self.field} LIKE '%#{self.value}%'"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
attr_accessor :field, :value
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative '../filter_decorator.rb'
|
2
|
+
|
3
|
+
class Full_name_filter_decorator < Filter_decorator
|
4
|
+
def initialize(filter, full_name)
|
5
|
+
super(filter)
|
6
|
+
self.full_name = full_name.strip.downcase unless full_name.nil?
|
7
|
+
end
|
8
|
+
|
9
|
+
def apply(filtering_obj)
|
10
|
+
return super(filtering_obj) if self.full_name.nil? || self.full_name.empty?
|
11
|
+
|
12
|
+
if filtering_obj.is_a?(Array)
|
13
|
+
super(filtering_obj).select do |student|
|
14
|
+
initials = "#{student.get_full_name}"
|
15
|
+
initials.downcase.include?(self.full_name)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
query = super(filtering_obj)
|
19
|
+
condition = query.include?("WHERE") ? "AND" : "WHERE"
|
20
|
+
"#{query} #{condition} CONCAT(LOWER(first_name), ' ', LOWER(LEFT(name, 1)), '.', LOWER(LEFT(patronymic, 1)), '.') LIKE '%#{self.full_name}%'"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
attr_accessor :full_name
|
26
|
+
end
|