paginated 1.0.2 → 1.0.4
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 +4 -4
- data/README.md +2 -2
- data/lib/paginated/search.rb +74 -82
- data/lib/paginated/serialization.rb +51 -55
- data/lib/paginated.rb +43 -30
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b38840cf61263ac1ee6c8a3a239db1bd1e4e6d852e9ce25919286f2a35b6767
|
4
|
+
data.tar.gz: 7160149eb3a72516e113827d8c0ac756322f61160d2b95480f55b91e824daae9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8a1f43f03ea4e249ff01d03964a9c7d4b9eab658683d3a570b1698800a2bc83edb329faff24ea284552bfbb3190e6d1884b1d5be17fa2956699c845965c5414
|
7
|
+
data.tar.gz: 2ea45ab22ed18d3d807e2a150c3da237bdc3de2bdc48033a0250acb7593a864a0178ef80e43774de63fa7ae016747757305b033d155fd7fc948c078adc24a547
|
data/README.md
CHANGED
@@ -20,7 +20,7 @@ bundle
|
|
20
20
|
|
21
21
|
Использование с помощью вызова метода `.collection` и передачи в него необходимых опций:
|
22
22
|
```
|
23
|
-
Paginated.
|
23
|
+
Paginated.collection()
|
24
24
|
```
|
25
25
|
|
26
26
|
Опции (с примерами):
|
@@ -40,7 +40,7 @@ Paginated.new.collection()
|
|
40
40
|
Например:
|
41
41
|
|
42
42
|
```
|
43
|
-
Paginated.
|
43
|
+
Paginated.collection(
|
44
44
|
model: 'User',
|
45
45
|
fields: %i[
|
46
46
|
email
|
data/lib/paginated/search.rb
CHANGED
@@ -4,106 +4,98 @@ module Search
|
|
4
4
|
|
5
5
|
private
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
7
|
+
class_methods do
|
8
|
+
def search
|
9
|
+
@search_fields&.each do |field|
|
10
|
+
# коллекция записей для дальнейшей фильтрации
|
11
|
+
list = @objects || @model.unscoped.merge(@scopes)
|
12
|
+
|
13
|
+
# оператор и значение для поиска
|
14
|
+
operator, value = search_operands(field)
|
15
|
+
|
16
|
+
# пропускаем, если параметр для поиска не передан
|
17
|
+
next if value.blank? || value == 'null'
|
18
|
+
|
19
|
+
# кастомный поиск
|
20
|
+
if operator.include?('custom_search') && value.present?
|
21
|
+
# название скоупа из модели, который нужно применить
|
22
|
+
scope_name = operator.split('.')[1]
|
23
|
+
# применение скоупа
|
24
|
+
@objects = list.send(scope_name, value)
|
25
|
+
@objects_count = @objects.count
|
26
|
+
next
|
27
|
+
end
|
28
|
+
|
29
|
+
# форматирование кавычек
|
30
|
+
if operator != 'IN' && !@model.defined_enums.key?(field)
|
31
|
+
value = quotations_formatting(value)
|
32
|
+
end
|
33
|
+
|
34
|
+
if field.include?('.')
|
35
|
+
# нужно приджойнить таблицу
|
36
|
+
join_assoc, field = field.split('.')
|
37
|
+
table = join_assoc.tableize
|
38
|
+
list = list.joins(join_assoc.to_sym)
|
39
|
+
else
|
40
|
+
table = @model.table_name
|
41
|
+
field = field.split('|')[0]
|
42
|
+
end
|
43
|
+
|
44
|
+
query = where_query(table, field, operator, value)
|
45
|
+
@objects = list.where(query)
|
24
46
|
@objects_count = @objects.count
|
25
|
-
next
|
26
47
|
end
|
48
|
+
end
|
27
49
|
|
28
|
-
|
29
|
-
|
30
|
-
value = quotations_formatting(value)
|
31
|
-
end
|
50
|
+
def search_operands(field)
|
51
|
+
value = default_param_value(field)
|
32
52
|
|
33
|
-
|
34
|
-
|
35
|
-
join_association, field = field.split('.')
|
36
|
-
table = join_association.tableize
|
37
|
-
entity = entity.joins(join_association.to_sym)
|
38
|
-
else
|
39
|
-
table = @model.table_name
|
40
|
-
field = field.split('|')[0]
|
41
|
-
end
|
53
|
+
# WHERE query operator
|
54
|
+
operator = field.split('|').size > 1 ? field.split('|').last : '='
|
42
55
|
|
43
|
-
|
44
|
-
|
45
|
-
@objects_count = @objects.count
|
46
|
-
end
|
47
|
-
end
|
56
|
+
# условие для поиска ILIKE
|
57
|
+
value = "%#{value}%" if operator == 'ilike' && value.present?
|
48
58
|
|
49
|
-
|
50
|
-
|
59
|
+
# проверка мульти-поиска по параметру с массивом
|
60
|
+
if operator == 'multi' && !value.blank?
|
61
|
+
operator = 'IN'
|
62
|
+
value = "(#{value.join(', ')})"
|
63
|
+
end
|
51
64
|
|
52
|
-
|
53
|
-
|
65
|
+
# проверка диапазона (MIN/MAX)
|
66
|
+
if %w[min max].include?(operator)
|
67
|
+
range_field = field.split('.').last.split(':')[0].try { |i| i.split('|')[0] }
|
68
|
+
value = @params["#{range_field}_#{operator}"]
|
69
|
+
operator = operator == 'min' ? '>=' : '<='
|
70
|
+
end
|
54
71
|
|
55
|
-
|
56
|
-
|
72
|
+
# check array operator
|
73
|
+
if operator == 'array' && value.present?
|
74
|
+
operator = '='
|
75
|
+
value = "{#{value}}"
|
76
|
+
end
|
57
77
|
|
58
|
-
|
59
|
-
if operator == 'multi' && !value.blank?
|
60
|
-
operator = 'IN'
|
61
|
-
value = "(#{value.join(', ')})"
|
78
|
+
[operator, value]
|
62
79
|
end
|
63
80
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
value = @params["#{range_field}_#{operator}"]
|
68
|
-
operator = operator == 'min' ? '>=' : '<='
|
69
|
-
end
|
81
|
+
def default_param_value(field)
|
82
|
+
# название ключа-поля без оператора (напр. ilike)
|
83
|
+
key = field.split('|')[0]
|
70
84
|
|
71
|
-
|
72
|
-
if operator == 'array' && !value.blank?
|
73
|
-
operator = '='
|
74
|
-
value = "{#{value}}"
|
85
|
+
@params[key]
|
75
86
|
end
|
76
87
|
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
def default_param_value(field)
|
81
|
-
key =
|
82
|
-
if field.include?('|')
|
83
|
-
# название ключа-поля без оператора (напр. ilike)
|
84
|
-
field.split('|')[0]
|
85
|
-
else
|
86
|
-
field
|
87
|
-
end
|
88
|
-
|
89
|
-
@params[key]
|
90
|
-
end
|
91
|
-
|
92
|
-
def where_query(table, field, operator, value)
|
93
|
-
if field.include?('_at')
|
94
|
-
# фильтрация по дате/времени
|
95
|
-
"EXTRACT(epoch FROM #{field}) #{operator} #{value}"
|
96
|
-
else
|
88
|
+
def where_query(table, field, operator, value)
|
97
89
|
"#{table}.#{field} #{operator} #{value}"
|
98
90
|
end
|
99
|
-
end
|
100
91
|
|
101
|
-
|
102
|
-
|
103
|
-
|
92
|
+
def quotations_formatting(string)
|
93
|
+
# удаление одинарных кавычек (напр. внутри строки)
|
94
|
+
value = string.to_s.gsub(/'/, '')
|
104
95
|
|
105
|
-
|
106
|
-
|
96
|
+
# добавление одинарных кавычек вокруг строки
|
97
|
+
"'#{value}'"
|
98
|
+
end
|
107
99
|
end
|
108
100
|
|
109
101
|
end
|
@@ -4,76 +4,72 @@ module Serialization
|
|
4
4
|
|
5
5
|
private
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
7
|
+
class_methods do
|
8
|
+
def serialize
|
9
|
+
# коллекция записей / сущность для применения аггрегирования
|
10
|
+
list = @objects || @model.unscoped.merge(@scopes)
|
11
|
+
|
12
|
+
# применение сортировки и пагинации к коллекции записей
|
13
|
+
@objects = list.offset(@offset).merge(sort_proc).limit(@limit)
|
14
|
+
|
15
|
+
# сериализация с помощью переданных полей или сериалайзера
|
16
|
+
if @fields
|
17
|
+
serialize_with_fields
|
18
|
+
elsif @grape_entity
|
19
|
+
serialize_with_entity
|
17
20
|
end
|
18
|
-
|
19
|
-
# сериализация с помощью переданных полей или сериалайзера
|
20
|
-
if @fields
|
21
|
-
serialize_with_fields
|
22
|
-
elsif @attrs[:entity]
|
23
|
-
serialize_with_entity
|
24
21
|
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def serialize_with_fields
|
28
|
-
# добавление ID в список полей по умолчанию
|
29
|
-
@fields.push(:id)
|
30
22
|
|
31
|
-
|
32
|
-
|
23
|
+
def serialize_with_fields
|
24
|
+
# добавление ID в список полей по умолчанию
|
25
|
+
@fields.push(:id)
|
33
26
|
|
34
|
-
|
35
|
-
|
36
|
-
end
|
27
|
+
# список массивов значений
|
28
|
+
@objects = @objects.map { |i| obtain_fields_values(i) }
|
37
29
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
30
|
+
# трансформация массивов значений в объекты (ключ-значение)
|
31
|
+
@objects = @objects.map { |i| @fields.zip(i).to_h }
|
32
|
+
end
|
42
33
|
|
43
|
-
|
44
|
-
|
45
|
-
# или значение переданного параметра не совпадает с ожидаемым
|
46
|
-
if @sort.blank? || @sort.map { |i| i.split('|')[0] }.exclude?(@params[:sort])
|
47
|
-
field = @order
|
48
|
-
return proc { order("#{field} DESC") }
|
34
|
+
def serialize_with_entity
|
35
|
+
@objects = @objects.map { |i| @grape_entity.represent(i, current_user: @current_user).as_json }
|
49
36
|
end
|
50
37
|
|
51
|
-
|
52
|
-
|
38
|
+
def sort_proc
|
39
|
+
# сортировка не задана явно, не передан параметр
|
40
|
+
# или значение переданного параметра не совпадает с ожидаемым
|
41
|
+
if @sort.blank? || @sort.map { |i| i.split('|')[0] }.exclude?(@params[:sort])
|
42
|
+
field = @order
|
43
|
+
return proc { order("#{field} DESC") }
|
44
|
+
end
|
45
|
+
|
46
|
+
@sort.each do |sort_option|
|
47
|
+
sort, field = sort_option.split('|')
|
53
48
|
|
54
|
-
|
55
|
-
|
49
|
+
# пропускаем, если поле для сортировки не совпадает с переданным параметром
|
50
|
+
next if @params[:sort] != sort
|
56
51
|
|
57
|
-
|
58
|
-
|
52
|
+
# если поле для сортировки совпадает с ключом переданного параметра
|
53
|
+
field = sort if field.blank?
|
59
54
|
|
60
|
-
|
55
|
+
sort_order = "#{@params[:sort_order]} NULLS LAST"
|
61
56
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
57
|
+
order_proc =
|
58
|
+
if field.split('.').count > 1
|
59
|
+
# сортировка по связанной таблице
|
60
|
+
relation, column = field.split('.')
|
61
|
+
proc { eager_load(relation).order("#{relation.tableize}.#{column} #{sort_order}") }
|
62
|
+
else
|
63
|
+
proc { order("#{field} #{sort_order}") }
|
64
|
+
end
|
70
65
|
|
71
|
-
|
66
|
+
return order_proc
|
67
|
+
end
|
72
68
|
end
|
73
|
-
end
|
74
69
|
|
75
|
-
|
76
|
-
|
70
|
+
def obtain_fields_values(record)
|
71
|
+
@fields.map { |i| record.send(i) }
|
72
|
+
end
|
77
73
|
end
|
78
74
|
|
79
75
|
end
|
data/lib/paginated.rb
CHANGED
@@ -6,20 +6,12 @@ class Paginated
|
|
6
6
|
include Search
|
7
7
|
include Serialization
|
8
8
|
|
9
|
-
def collection(**
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@
|
15
|
-
@offset = @params['offset'].to_i
|
16
|
-
@limit = @params['limit'] || 20
|
17
|
-
@order = attrs[:order] || :id
|
18
|
-
@sort = attrs[:sort]
|
19
|
-
@model = attrs[:model].constantize
|
20
|
-
@root = @params.key?('root') ? @params['root'] : false
|
21
|
-
@paginate = @params.key?('paginate') ? @params['paginate'].to_s == 'true' : true
|
22
|
-
@objects_count = records_count(attrs[:model])
|
9
|
+
def self.collection(**args)
|
10
|
+
# обработка переданных аргументов
|
11
|
+
handle_args(**args)
|
12
|
+
|
13
|
+
# подсчет кол-ва записей
|
14
|
+
@objects_count = records_count
|
23
15
|
|
24
16
|
if @objects_count > 0
|
25
17
|
search
|
@@ -28,32 +20,53 @@ class Paginated
|
|
28
20
|
@objects = []
|
29
21
|
end
|
30
22
|
|
31
|
-
|
32
|
-
|
33
|
-
@objects
|
34
|
-
else
|
35
|
-
# результат с пагинацией
|
36
|
-
{ count: @objects_count, objects: @objects }
|
37
|
-
end
|
23
|
+
# результат с пагинацией
|
24
|
+
{ count: @objects_count, objects: @objects }
|
38
25
|
end
|
39
26
|
|
40
|
-
|
27
|
+
def self.handle_args(**args)
|
28
|
+
# обрабатываемая Rails модель
|
29
|
+
@model = args[:model]
|
30
|
+
|
31
|
+
# параметры запроса и текущий пользователь
|
32
|
+
serialization_scope = args[:serialization_scope]
|
33
|
+
@params = serialization_scope.params.stringify_keys
|
34
|
+
@current_user = serialization_scope.current_user
|
35
|
+
|
36
|
+
# поля, по которым возможен поиск
|
37
|
+
@search_fields = args[:search]
|
38
|
+
|
39
|
+
# сериализация - через Grape Entity или список полей
|
40
|
+
@grape_entity = args[:entity]
|
41
|
+
@fields = args[:fields]
|
42
|
+
|
43
|
+
# Rails scopes для применения
|
44
|
+
@scopes = args[:scopes] || {}
|
45
|
+
|
46
|
+
# offset / limit
|
47
|
+
@offset = @params['offset'].to_i
|
48
|
+
@limit = @params['limit'] || 20
|
49
|
+
|
50
|
+
# сортировка
|
51
|
+
@order = args[:order] || :id
|
52
|
+
@sort = args[:sort]
|
53
|
+
end
|
41
54
|
|
42
|
-
def records_count
|
55
|
+
def self.records_count
|
43
56
|
# получение кол-ва записей из кеша
|
44
|
-
cache_key = "#{
|
45
|
-
|
46
|
-
return
|
57
|
+
cache_key = "#{@model}_records_count"
|
58
|
+
cached = Rails.cache.read(cache_key)
|
59
|
+
return cached if cached
|
47
60
|
|
48
61
|
# получение кол-ва записей из запроса к БД
|
49
|
-
|
62
|
+
count = @model.merge(@scopes).count(:id)
|
50
63
|
|
51
64
|
# кеширование кол-ва записей при превышении порогового значения
|
52
|
-
if
|
53
|
-
Rails.cache.write(cache_key,
|
65
|
+
if count >= 1_000
|
66
|
+
Rails.cache.write(cache_key, count, expires_in: 30.minutes)
|
54
67
|
end
|
55
68
|
|
56
|
-
|
69
|
+
count
|
57
70
|
end
|
58
71
|
|
59
72
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paginated
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Павел Бабин
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-02-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -81,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
requirements: []
|
84
|
-
rubygems_version: 3.
|
84
|
+
rubygems_version: 3.4.10
|
85
85
|
signing_key:
|
86
86
|
specification_version: 4
|
87
87
|
summary: Пагинация для Rails-приложений
|