paginated 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76e2b8888c610c376b97da4c118067cf4d6d1ac8d63e02fc11971638b9ad0595
4
- data.tar.gz: a5fc2020f8d9a3adf16816e0d03d2e2419ed3128d806d23a5ce3d20a6831e442
3
+ metadata.gz: 8b38840cf61263ac1ee6c8a3a239db1bd1e4e6d852e9ce25919286f2a35b6767
4
+ data.tar.gz: 7160149eb3a72516e113827d8c0ac756322f61160d2b95480f55b91e824daae9
5
5
  SHA512:
6
- metadata.gz: a3aa8e4b873a0498720d727d3517cb9576eb1b97a96884cdf2c8336b92f56a0016db99e1d6c3c49875bede9fc73dbd91d902180b05253e7079d9970504f9b01a
7
- data.tar.gz: 7ad52eb2f7a3b3c3356781d7703f156f14ef4cec04d00dc14e825e0c6dd4ef10b868b9325b18ab2405d740d0ddc2d70072f09d4e91cc12f398548344ed66ce02
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.new.collection()
23
+ Paginated.collection()
24
24
  ```
25
25
 
26
26
  Опции (с примерами):
@@ -40,7 +40,7 @@ Paginated.new.collection()
40
40
  Например:
41
41
 
42
42
  ```
43
- Paginated.new.collection(
43
+ Paginated.collection(
44
44
  model: 'User',
45
45
  fields: %i[
46
46
  email
@@ -4,106 +4,98 @@ module Search
4
4
 
5
5
  private
6
6
 
7
- def search
8
- @attrs[:search]&.each do |field|
9
- # коллекция записей для дальнейшей фильтрации
10
- entity = @objects || @model.unscoped.merge(@scopes)
11
-
12
- # оператор и значение для поиска
13
- operator, value = search_operands(field)
14
-
15
- # пропускаем, если параметр для поиска не передан
16
- next if value.blank? || value == 'null'
17
-
18
- # кастомный поиск
19
- if operator.include?('custom_search') && value.present?
20
- # название скоупа из модели, который нужно применить
21
- scope_name = operator.split('.')[1]
22
- # применение скоупа
23
- @objects = entity.send(scope_name, value)
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
- if operator != 'IN' && !@model.defined_enums.key?(field)
30
- value = quotations_formatting(value)
31
- end
50
+ def search_operands(field)
51
+ value = default_param_value(field)
32
52
 
33
- if field.include?('.')
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
- query = where_query(table, field, operator, value)
44
- @objects = entity.where(query)
45
- @objects_count = @objects.count
46
- end
47
- end
56
+ # условие для поиска ILIKE
57
+ value = "%#{value}%" if operator == 'ilike' && value.present?
48
58
 
49
- def search_operands(field)
50
- value = default_param_value(field)
59
+ # проверка мульти-поиска по параметру с массивом
60
+ if operator == 'multi' && !value.blank?
61
+ operator = 'IN'
62
+ value = "(#{value.join(', ')})"
63
+ end
51
64
 
52
- # WHERE query operator
53
- operator = field.split('|').size > 1 ? field.split('|').last : '='
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
- # check for ILIKE operator
56
- value = "%#{value}%" if operator == 'ilike' && value.present?
72
+ # check array operator
73
+ if operator == 'array' && value.present?
74
+ operator = '='
75
+ value = "{#{value}}"
76
+ end
57
77
 
58
- # check if multi-filter search enabled
59
- if operator == 'multi' && !value.blank?
60
- operator = 'IN'
61
- value = "(#{value.join(', ')})"
78
+ [operator, value]
62
79
  end
63
80
 
64
- # проверка диапазона (MIN/MAX)
65
- if %w[min max].include?(operator)
66
- range_field = field.split('.').last.split(':')[0].try { |p| p.split('|')[0] }
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
- # check array operator
72
- if operator == 'array' && !value.blank?
73
- operator = '='
74
- value = "{#{value}}"
85
+ @params[key]
75
86
  end
76
87
 
77
- [operator, value]
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
- def quotations_formatting(string)
102
- # удаление одинарных кавычек (напр. внутри строки)
103
- value = string.to_s.gsub(/'/, '')
92
+ def quotations_formatting(string)
93
+ # удаление одинарных кавычек (напр. внутри строки)
94
+ value = string.to_s.gsub(/'/, '')
104
95
 
105
- # добавление одинарных кавычек вокруг строки
106
- "'#{value}'"
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
- def serialize
8
- # коллекция записей / сущность для применения аггрегирования
9
- entity = @objects || @model.unscoped.merge(@scopes)
10
-
11
- # применение сортировки и пагинации (если необходимо) к коллекции записей
12
- @objects =
13
- if @paginate
14
- entity.offset(@offset).merge(sort_proc).limit(@limit)
15
- else
16
- entity.merge(sort_proc)
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
- @objects = @objects.map { |i| obtain_fields_values(i) }
23
+ def serialize_with_fields
24
+ # добавление ID в список полей по умолчанию
25
+ @fields.push(:id)
33
26
 
34
- # трансформация массивов значений в объекты (ключ-значение)
35
- @objects = @objects.map { |i| @fields.zip(i).to_h }
36
- end
27
+ # список массивов значений
28
+ @objects = @objects.map { |i| obtain_fields_values(i) }
37
29
 
38
- def serialize_with_entity
39
- entity = @attrs[:entity].constantize
40
- @objects = @objects.map { |i| entity.represent(i, current_user: @current_user).as_json }
41
- end
30
+ # трансформация массивов значений в объекты (ключ-значение)
31
+ @objects = @objects.map { |i| @fields.zip(i).to_h }
32
+ end
42
33
 
43
- def sort_proc
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
- @sort.each do |sort_option|
52
- sort, field = sort_option.split('|')
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
- next if @params[:sort] != sort
49
+ # пропускаем, если поле для сортировки не совпадает с переданным параметром
50
+ next if @params[:sort] != sort
56
51
 
57
- # если поле для сортировки совпадает с ключом переданного параметра
58
- field = sort if field.blank?
52
+ # если поле для сортировки совпадает с ключом переданного параметра
53
+ field = sort if field.blank?
59
54
 
60
- sort_order = "#{@params[:sort_order]} NULLS LAST"
55
+ sort_order = "#{@params[:sort_order]} NULLS LAST"
61
56
 
62
- order_proc =
63
- if field.split('.').count > 1
64
- # сортировка по связанной таблице
65
- relation, column = field.split('.')
66
- proc { eager_load(relation).order("#{relation.tableize}.#{column} #{sort_order}") }
67
- else
68
- proc { order("#{field} #{sort_order}") }
69
- end
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
- return order_proc
66
+ return order_proc
67
+ end
72
68
  end
73
- end
74
69
 
75
- def obtain_fields_values(object)
76
- @fields.map { |f| object.send(f) }
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(**attrs)
10
- @attrs = attrs
11
- @params = attrs[:serialization_scope].params.stringify_keys
12
- @current_user = attrs[:serialization_scope].current_user
13
- @fields = attrs[:fields]
14
- @scopes = attrs[:scopes] || {}
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
- if @root
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
- private
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(model_name)
55
+ def self.records_count
43
56
  # получение кол-ва записей из кеша
44
- cache_key = "#{model_name}_records_count"
45
- cached_value = Rails.cache.read(cache_key)
46
- return cached_value if cached_value
57
+ cache_key = "#{@model}_records_count"
58
+ cached = Rails.cache.read(cache_key)
59
+ return cached if cached
47
60
 
48
61
  # получение кол-ва записей из запроса к БД
49
- db_value = @model.merge(@scopes).count(:id)
62
+ count = @model.merge(@scopes).count(:id)
50
63
 
51
64
  # кеширование кол-ва записей при превышении порогового значения
52
- if db_value >= 1_000
53
- Rails.cache.write(cache_key, db_value, expires_in: 30.minutes)
65
+ if count >= 1_000
66
+ Rails.cache.write(cache_key, count, expires_in: 30.minutes)
54
67
  end
55
68
 
56
- db_value
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.3
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: 2023-11-17 00:00:00.000000000 Z
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.0.6
84
+ rubygems_version: 3.4.10
85
85
  signing_key:
86
86
  specification_version: 4
87
87
  summary: Пагинация для Rails-приложений