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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ae94c2ae8e872f233996072ec628db19b885a8538cafcf7ff3b75a2af99b933
4
- data.tar.gz: 2fd627408f5ad65ae5da8e72fd9b393db0e8cdaf7f2d1b073f02135ce37ea474
3
+ metadata.gz: 8b38840cf61263ac1ee6c8a3a239db1bd1e4e6d852e9ce25919286f2a35b6767
4
+ data.tar.gz: 7160149eb3a72516e113827d8c0ac756322f61160d2b95480f55b91e824daae9
5
5
  SHA512:
6
- metadata.gz: f73355fbbeda03857a8948618625392f69749f4bb968d9c4248662c31703db94cadb3a115013419d24b9fce418edf6db4db8c91dcbd6de855fae22a77a473ce0
7
- data.tar.gz: 1297773a18228ed479111c7cc84fad0a500cfd4af7c87d456e9abe5cc825bdd161716b31b5ffa8ec53c352e0a2b81c8bd94e959c15e552f8ac733a346991e702
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.unscoped.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.2
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-16 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-приложений