paginated 1.0.8 → 1.0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -5
- data/lib/{grape_extensions → grape}/dsl.rb +5 -4
- data/lib/grape-paginated/args.rb +68 -0
- data/lib/grape-paginated/collection.rb +58 -0
- data/lib/grape-paginated/configuration.rb +14 -0
- data/lib/grape-paginated/search.rb +99 -0
- data/lib/grape-paginated/sorting.rb +12 -0
- data/lib/grape-paginated/spreadsheet.rb +65 -0
- data/lib/paginated.rb +12 -12
- metadata +24 -22
- data/lib/paginated/args_andling.rb +0 -63
- data/lib/paginated/collection.rb +0 -58
- data/lib/paginated/search.rb +0 -99
- data/lib/paginated/sorting.rb +0 -12
- data/lib/paginated/spreadsheet.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c41db6a4f0666aad436a16e28e732045b3d18bbcf19beac25841e2ceb8b9bf5
|
4
|
+
data.tar.gz: a0fa7e1e31f7baa4f49de93135b584dad29ad9206c24c6e9bd3909f0aec44fb9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb4b93b5447a89f7f66abc6863c97e712b142c6d4aa0b7fd7ee400fa5f193927b08ef3bb98f6c7b07521dea0ed65fb5a64b5fd67e51518fd3a12977e4b36e6d4
|
7
|
+
data.tar.gz: bc8faf601587f4aec8a86345f1032712f8356ac38ed4996efa8efb4e984310c0dd6eb0ec2d9e853fa8522646c111206096cb60063aa4c39184ddd32927bc5e85
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
# Paginated
|
1
|
+
# Grape Paginated
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/paginated.svg)](https://badge.fury.io/rb/paginated)
|
2
3
|
|
3
|
-
Гем для пагинации и фильтрации
|
4
|
+
Гем для пагинации и фильтрации записей с возможностью формирования электронных таблиц (XLSX) на базе Grape.
|
4
5
|
|
5
6
|
## Установка
|
6
7
|
|
@@ -24,9 +25,12 @@ bundle
|
|
24
25
|
Например:
|
25
26
|
|
26
27
|
```
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
get 'users' do
|
29
|
+
paginated model: User,
|
30
|
+
entity: UserEntity,
|
31
|
+
search: %w[email name|ilike role|custom.for_role]
|
32
|
+
end
|
33
|
+
|
30
34
|
```
|
31
35
|
|
32
36
|
Опции (с примерами):
|
@@ -70,3 +74,12 @@ paginated model: User,
|
|
70
74
|
- `sort_order` направление, по которому должна осуществляться сортировка (`asc/desc`).
|
71
75
|
|
72
76
|
По умолчанию сортировка осуществляется по `id` записей в направлении `DESC`.
|
77
|
+
|
78
|
+
## Конфигурация
|
79
|
+
|
80
|
+
Добавьте файл конфигурации `config/initializers/grape_paginated.rb` с содержимым:
|
81
|
+
|
82
|
+
```
|
83
|
+
GrapePaginated::Configuration.configure do |config|
|
84
|
+
end
|
85
|
+
```
|
@@ -39,15 +39,16 @@ module Grape
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def paginated_spreadsheet(opts)
|
42
|
-
Dir.mktmpdir do |
|
43
|
-
|
42
|
+
Dir.mktmpdir do |dir|
|
43
|
+
opts[:tempdir] = dir
|
44
|
+
file_path = Paginated.spreadsheet(**opts)
|
44
45
|
|
45
46
|
spreadsheet_file_headers File.basename(file_path)
|
46
|
-
File.
|
47
|
+
File.read(file_path)
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
50
|
-
def spreadsheet_file_headers(filename)
|
51
|
+
def spreadsheet_file_headers(filename)
|
51
52
|
header['Content-Type'] =
|
52
53
|
case File.extname(filename)
|
53
54
|
when '.csv'
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module GrapePaginated
|
2
|
+
module Args
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def handle_args(**args)
|
7
|
+
# обрабатываемая Rails модель
|
8
|
+
@model = args[:model]
|
9
|
+
|
10
|
+
# параметры запроса и текущий пользователь
|
11
|
+
@params = args[:params].stringify_keys
|
12
|
+
@current_user = args[:current_user]
|
13
|
+
|
14
|
+
# поля, по которым возможен поиск
|
15
|
+
@search_fields = args[:search]
|
16
|
+
|
17
|
+
# сериализация - через Grape Entity или список полей
|
18
|
+
@grape_entity = args[:entity]
|
19
|
+
@fields = args[:fields]
|
20
|
+
|
21
|
+
# Rails scopes для применения
|
22
|
+
@scopes = args[:scopes] || {}
|
23
|
+
|
24
|
+
# offset / limit
|
25
|
+
@offset = @params['offset'].to_i
|
26
|
+
@limit = @params['limit'] || 20
|
27
|
+
|
28
|
+
# проверка максимального значения лимита
|
29
|
+
if @limit.to_i > 100
|
30
|
+
raise StandardError.new('Лимит слишком большой')
|
31
|
+
end
|
32
|
+
|
33
|
+
# требуемый список полей для коллекции
|
34
|
+
@only_columns = args[:only_columns]
|
35
|
+
|
36
|
+
# список полей для эл. таблицы
|
37
|
+
@spreadsheet_columns = args[:spreadsheet_columns]
|
38
|
+
|
39
|
+
# сортировка
|
40
|
+
@sort_by = @params['sort_by'] || :id
|
41
|
+
@sort_order = @params['sort_order'] || :desc
|
42
|
+
|
43
|
+
# подсчет кол-ва записей
|
44
|
+
@objects_count = records_count
|
45
|
+
|
46
|
+
# временная директория (для генерации файлов)
|
47
|
+
@tempdir = args[:tempdir]
|
48
|
+
end
|
49
|
+
|
50
|
+
def records_count
|
51
|
+
# получение кол-ва записей из кеша
|
52
|
+
cache_key = "#{@model}_records_count"
|
53
|
+
cached = Rails.cache.read(cache_key)
|
54
|
+
return cached if cached
|
55
|
+
|
56
|
+
# получение кол-ва записей из запроса к БД
|
57
|
+
count = @model.merge(@scopes).count(:id)
|
58
|
+
|
59
|
+
# кеширование кол-ва записей при превышении порогового значения
|
60
|
+
if count >= 1_000
|
61
|
+
Rails.cache.write(cache_key, count, expires_in: 30.minutes)
|
62
|
+
end
|
63
|
+
|
64
|
+
count
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module GrapePaginated
|
2
|
+
module Collection
|
3
|
+
|
4
|
+
def collection
|
5
|
+
if @objects_count > 0
|
6
|
+
search
|
7
|
+
serialize
|
8
|
+
else
|
9
|
+
@objects = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# результат с пагинацией
|
13
|
+
{ count: @objects_count, objects: @objects }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def serialize
|
19
|
+
# коллекция записей ActiveRecord для применения аггрегирования
|
20
|
+
list = @objects || @model.unscoped.merge(@scopes)
|
21
|
+
|
22
|
+
# применение сортировки и пагинации к коллекции записей
|
23
|
+
@objects = list.offset(@offset).merge(sort_proc).limit(@limit)
|
24
|
+
|
25
|
+
# сериализация с помощью переданных полей или сериалайзера
|
26
|
+
if @fields
|
27
|
+
serialize_with_fields
|
28
|
+
elsif @grape_entity
|
29
|
+
serialize_with_entity
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def serialize_with_fields
|
34
|
+
# добавление ID в список полей по умолчанию
|
35
|
+
@fields.push(:id)
|
36
|
+
|
37
|
+
# список массивов значений
|
38
|
+
@objects = @objects.map { |i| obtain_fields_values(i) }
|
39
|
+
|
40
|
+
# трансформация массивов значений в объекты (ключ-значение)
|
41
|
+
@objects = @objects.map { |i| @fields.zip(i).to_h }
|
42
|
+
end
|
43
|
+
|
44
|
+
def serialize_with_entity
|
45
|
+
opts = { current_user: @current_user }
|
46
|
+
|
47
|
+
# требуемый список полей (если был передан)
|
48
|
+
opts[:only] = @only_columns if @only_columns
|
49
|
+
|
50
|
+
@objects = @objects.map { |i| @grape_entity.represent(i, opts).as_json }
|
51
|
+
end
|
52
|
+
|
53
|
+
def obtain_fields_values(record)
|
54
|
+
@fields.map { |i| record.send(i) }
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module GrapePaginated
|
2
|
+
module Search
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def search
|
7
|
+
@search_fields&.each do |field|
|
8
|
+
# коллекция записей для дальнейшей фильтрации
|
9
|
+
list = @objects || @model.unscoped.merge(@scopes)
|
10
|
+
|
11
|
+
# оператор и значение для поиска
|
12
|
+
operator, value = search_operands(field)
|
13
|
+
|
14
|
+
# пропускаем, если параметр для поиска не передан
|
15
|
+
next if value.blank? || value == 'null'
|
16
|
+
|
17
|
+
# кастомный поиск
|
18
|
+
if operator.include?('custom') && value.present?
|
19
|
+
# название скоупа из модели, который нужно применить
|
20
|
+
scope_name = operator.split('.')[1]
|
21
|
+
# применение скоупа
|
22
|
+
@objects = list.send(scope_name, value)
|
23
|
+
@objects_count = @objects.count
|
24
|
+
next
|
25
|
+
end
|
26
|
+
|
27
|
+
# форматирование кавычек
|
28
|
+
if operator != 'IN' && !@model.defined_enums.key?(field)
|
29
|
+
value = quotations_formatting(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
if field.include?('.')
|
33
|
+
# нужно приджойнить таблицу
|
34
|
+
join_assoc, field = field.split('.')
|
35
|
+
table = join_assoc.tableize
|
36
|
+
list = list.joins(join_assoc.to_sym)
|
37
|
+
else
|
38
|
+
table = @model.table_name
|
39
|
+
field = field.split('|')[0]
|
40
|
+
end
|
41
|
+
|
42
|
+
query = where_query(table, field, operator, value)
|
43
|
+
@objects = list.where(query)
|
44
|
+
@objects_count = @objects.count
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def search_operands(field)
|
49
|
+
value = default_param_value(field)
|
50
|
+
|
51
|
+
# WHERE query operator
|
52
|
+
operator = field.split('|').size > 1 ? field.split('|').last : '='
|
53
|
+
|
54
|
+
# условие для поиска ILIKE
|
55
|
+
value = "%#{value}%" if operator == 'ilike' && value.present?
|
56
|
+
|
57
|
+
# проверка мульти-поиска по параметру с массивом
|
58
|
+
if operator == 'multi' && !value.blank?
|
59
|
+
operator = 'IN'
|
60
|
+
value = "(#{value.join(', ')})"
|
61
|
+
end
|
62
|
+
|
63
|
+
# проверка диапазона (MIN/MAX)
|
64
|
+
if %w[min max].include?(operator)
|
65
|
+
range_field = field.split('.').last.split(':')[0].try { |i| i.split('|')[0] }
|
66
|
+
value = @params["#{range_field}_#{operator}"]
|
67
|
+
operator = operator == 'min' ? '>=' : '<='
|
68
|
+
end
|
69
|
+
|
70
|
+
# check array operator
|
71
|
+
if operator == 'array' && value.present?
|
72
|
+
operator = '='
|
73
|
+
value = "{#{value}}"
|
74
|
+
end
|
75
|
+
|
76
|
+
[operator, value]
|
77
|
+
end
|
78
|
+
|
79
|
+
def default_param_value(field)
|
80
|
+
# название ключа-поля без оператора (напр. ilike)
|
81
|
+
key = field.split('|')[0]
|
82
|
+
|
83
|
+
@params[key]
|
84
|
+
end
|
85
|
+
|
86
|
+
def where_query(table, field, operator, value)
|
87
|
+
"#{table}.#{field} #{operator} #{value}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def quotations_formatting(string)
|
91
|
+
# удаление одинарных кавычек (напр. внутри строки)
|
92
|
+
value = string.to_s.gsub("'", '')
|
93
|
+
|
94
|
+
# добавление одинарных кавычек вокруг строки
|
95
|
+
"'#{value}'"
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'fast_excel'
|
2
|
+
|
3
|
+
module GrapePaginated
|
4
|
+
module Spreadsheet
|
5
|
+
|
6
|
+
def spreadsheet
|
7
|
+
# поиск и фильтрация
|
8
|
+
search
|
9
|
+
|
10
|
+
# коллекция записей ActiveRecord для формирования эл. таблицы
|
11
|
+
records = @objects || @model.unscoped.merge(@scopes)
|
12
|
+
|
13
|
+
# ограничение записей
|
14
|
+
records = records.order(:id).offset(@offset).limit(@limit)
|
15
|
+
|
16
|
+
# формирования файла эл. таблицы
|
17
|
+
generate_spreadsheet(records)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def generate_spreadsheet(records)
|
23
|
+
filepath = "#{@tempdir}/#{Time.now.strftime('%Y%m%d_%H%M%S')}.xlsx"
|
24
|
+
|
25
|
+
# создание таблицы и ее конфигурация
|
26
|
+
workbook = FastExcel.open(filepath, constant_memory: true)
|
27
|
+
workbook.default_format.set(
|
28
|
+
font_size: 0,
|
29
|
+
font_family: 'Arial'
|
30
|
+
)
|
31
|
+
|
32
|
+
# стили
|
33
|
+
bold = workbook.bold_format
|
34
|
+
|
35
|
+
# лист
|
36
|
+
worksheet = workbook.add_worksheet('Основной')
|
37
|
+
worksheet.auto_width = true
|
38
|
+
|
39
|
+
# заголовки
|
40
|
+
worksheet.append_row(spreadsheet_titles, bold)
|
41
|
+
|
42
|
+
# содержимое
|
43
|
+
cols = spreadsheet_columns
|
44
|
+
records.each do |record|
|
45
|
+
values = cols.map { |col| record.send(col) }
|
46
|
+
worksheet.append_row(values)
|
47
|
+
end
|
48
|
+
|
49
|
+
# закрытие/сохранение файла
|
50
|
+
workbook.close
|
51
|
+
|
52
|
+
# путь до файла для дальнейшей обработки
|
53
|
+
filepath
|
54
|
+
end
|
55
|
+
|
56
|
+
def spreadsheet_titles
|
57
|
+
@spreadsheet_columns.map { |i| i['title'] }
|
58
|
+
end
|
59
|
+
|
60
|
+
def spreadsheet_columns
|
61
|
+
@spreadsheet_columns.map { |i| i['column'] }
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
data/lib/paginated.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
require '
|
2
|
-
require 'paginated/
|
3
|
-
require 'paginated/
|
4
|
-
require 'paginated/
|
5
|
-
require 'paginated/
|
6
|
-
|
7
|
-
require '
|
1
|
+
require 'grape/dsl'
|
2
|
+
require 'grape-paginated/args'
|
3
|
+
require 'grape-paginated/collection'
|
4
|
+
require 'grape-paginated/configuration'
|
5
|
+
require 'grape-paginated/search'
|
6
|
+
require 'grape-paginated/sorting'
|
7
|
+
require 'grape-paginated/spreadsheet'
|
8
8
|
|
9
9
|
class Paginated
|
10
10
|
|
11
|
-
include
|
12
|
-
include Collection
|
13
|
-
include Search
|
14
|
-
include Sorting
|
15
|
-
include Spreadsheet
|
11
|
+
include GrapePaginated::Args
|
12
|
+
include GrapePaginated::Collection
|
13
|
+
include GrapePaginated::Search
|
14
|
+
include GrapePaginated::Sorting
|
15
|
+
include GrapePaginated::Spreadsheet
|
16
16
|
|
17
17
|
def initialize(**args)
|
18
18
|
handle_args(**args)
|
metadata
CHANGED
@@ -1,71 +1,71 @@
|
|
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.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Павел Бабин
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-03-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: fast_excel
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.5.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.5.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: grape
|
28
|
+
name: grape
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 2.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 2.0.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: grape-entity
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 1.0.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 1.0.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rails
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: '7.0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: '7.0'
|
69
69
|
description: Простая пагинация записей, поиск и формирование эл. таблиц для API на
|
70
70
|
базе Grape
|
71
71
|
email: babin359@gmail.com
|
@@ -74,17 +74,19 @@ extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
76
|
- README.md
|
77
|
-
- lib/
|
77
|
+
- lib/grape-paginated/args.rb
|
78
|
+
- lib/grape-paginated/collection.rb
|
79
|
+
- lib/grape-paginated/configuration.rb
|
80
|
+
- lib/grape-paginated/search.rb
|
81
|
+
- lib/grape-paginated/sorting.rb
|
82
|
+
- lib/grape-paginated/spreadsheet.rb
|
83
|
+
- lib/grape/dsl.rb
|
78
84
|
- lib/paginated.rb
|
79
|
-
- lib/paginated/args_andling.rb
|
80
|
-
- lib/paginated/collection.rb
|
81
|
-
- lib/paginated/search.rb
|
82
|
-
- lib/paginated/sorting.rb
|
83
|
-
- lib/paginated/spreadsheet.rb
|
84
85
|
homepage:
|
85
86
|
licenses:
|
86
87
|
- MIT
|
87
|
-
metadata:
|
88
|
+
metadata:
|
89
|
+
rubygems_mfa_required: 'true'
|
88
90
|
post_install_message:
|
89
91
|
rdoc_options: []
|
90
92
|
require_paths:
|
@@ -93,7 +95,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
95
|
requirements:
|
94
96
|
- - ">="
|
95
97
|
- !ruby/object:Gem::Version
|
96
|
-
version: 3.
|
98
|
+
version: 3.1.0
|
97
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
100
|
requirements:
|
99
101
|
- - ">="
|
@@ -1,63 +0,0 @@
|
|
1
|
-
module ArgsHandling
|
2
|
-
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
private
|
6
|
-
|
7
|
-
def handle_args(**args)
|
8
|
-
# обрабатываемая Rails модель
|
9
|
-
@model = args[:model]
|
10
|
-
|
11
|
-
# параметры запроса и текущий пользователь
|
12
|
-
@params = args[:params].stringify_keys
|
13
|
-
@current_user = args[:current_user]
|
14
|
-
|
15
|
-
# поля, по которым возможен поиск
|
16
|
-
@search_fields = args[:search]
|
17
|
-
|
18
|
-
# сериализация - через Grape Entity или список полей
|
19
|
-
@grape_entity = args[:entity]
|
20
|
-
@fields = args[:fields]
|
21
|
-
|
22
|
-
# Rails scopes для применения
|
23
|
-
@scopes = args[:scopes] || {}
|
24
|
-
|
25
|
-
# offset / limit
|
26
|
-
@offset = @params['offset'].to_i
|
27
|
-
@limit = @params['limit'] || 20
|
28
|
-
|
29
|
-
# требуемый список полей для коллекции
|
30
|
-
@only_columns = args[:only_columns]
|
31
|
-
|
32
|
-
# список полей для эл. таблицы
|
33
|
-
@spreadsheet_columns = args[:spreadsheet_columns]
|
34
|
-
|
35
|
-
# сортировка
|
36
|
-
@sort_by = @params['sort_by'] || :id
|
37
|
-
@sort_order = @params['sort_order'] || :desc
|
38
|
-
|
39
|
-
# подсчет кол-ва записей
|
40
|
-
@objects_count = records_count
|
41
|
-
|
42
|
-
# временная директория (для генерации файлов)
|
43
|
-
@tempdir = args[:tempdir]
|
44
|
-
end
|
45
|
-
|
46
|
-
def records_count
|
47
|
-
# получение кол-ва записей из кеша
|
48
|
-
cache_key = "#{@model}_records_count"
|
49
|
-
cached = Rails.cache.read(cache_key)
|
50
|
-
return cached if cached
|
51
|
-
|
52
|
-
# получение кол-ва записей из запроса к БД
|
53
|
-
count = @model.merge(@scopes).count(:id)
|
54
|
-
|
55
|
-
# кеширование кол-ва записей при превышении порогового значения
|
56
|
-
if count >= 1_000
|
57
|
-
Rails.cache.write(cache_key, count, expires_in: 30.minutes)
|
58
|
-
end
|
59
|
-
|
60
|
-
count
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
data/lib/paginated/collection.rb
DELETED
@@ -1,58 +0,0 @@
|
|
1
|
-
module Collection
|
2
|
-
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
def collection
|
6
|
-
if @objects_count > 0
|
7
|
-
search
|
8
|
-
serialize
|
9
|
-
else
|
10
|
-
@objects = []
|
11
|
-
end
|
12
|
-
|
13
|
-
# результат с пагинацией
|
14
|
-
{ count: @objects_count, objects: @objects }
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def serialize
|
20
|
-
# коллекция записей ActiveRecord для применения аггрегирования
|
21
|
-
list = @objects || @model.unscoped.merge(@scopes)
|
22
|
-
|
23
|
-
# применение сортировки и пагинации к коллекции записей
|
24
|
-
@objects = list.offset(@offset).merge(sort_proc).limit(@limit)
|
25
|
-
|
26
|
-
# сериализация с помощью переданных полей или сериалайзера
|
27
|
-
if @fields
|
28
|
-
serialize_with_fields
|
29
|
-
elsif @grape_entity
|
30
|
-
serialize_with_entity
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def serialize_with_fields
|
35
|
-
# добавление ID в список полей по умолчанию
|
36
|
-
@fields.push(:id)
|
37
|
-
|
38
|
-
# список массивов значений
|
39
|
-
@objects = @objects.map { |i| obtain_fields_values(i) }
|
40
|
-
|
41
|
-
# трансформация массивов значений в объекты (ключ-значение)
|
42
|
-
@objects = @objects.map { |i| @fields.zip(i).to_h }
|
43
|
-
end
|
44
|
-
|
45
|
-
def serialize_with_entity
|
46
|
-
opts = { current_user: @current_user }
|
47
|
-
|
48
|
-
# требуемый список полей (если был передан)
|
49
|
-
opts[:only] = @only_columns if @only_columns
|
50
|
-
|
51
|
-
@objects = @objects.map { |i| @grape_entity.represent(i, opts).as_json }
|
52
|
-
end
|
53
|
-
|
54
|
-
def obtain_fields_values(record)
|
55
|
-
@fields.map { |i| record.send(i) }
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
data/lib/paginated/search.rb
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
module Search
|
2
|
-
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
private
|
6
|
-
|
7
|
-
def search
|
8
|
-
@search_fields&.each do |field|
|
9
|
-
# коллекция записей для дальнейшей фильтрации
|
10
|
-
list = @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') && value.present?
|
20
|
-
# название скоупа из модели, который нужно применить
|
21
|
-
scope_name = operator.split('.')[1]
|
22
|
-
# применение скоупа
|
23
|
-
@objects = list.send(scope_name, value)
|
24
|
-
@objects_count = @objects.count
|
25
|
-
next
|
26
|
-
end
|
27
|
-
|
28
|
-
# форматирование кавычек
|
29
|
-
if operator != 'IN' && !@model.defined_enums.key?(field)
|
30
|
-
value = quotations_formatting(value)
|
31
|
-
end
|
32
|
-
|
33
|
-
if field.include?('.')
|
34
|
-
# нужно приджойнить таблицу
|
35
|
-
join_assoc, field = field.split('.')
|
36
|
-
table = join_assoc.tableize
|
37
|
-
list = list.joins(join_assoc.to_sym)
|
38
|
-
else
|
39
|
-
table = @model.table_name
|
40
|
-
field = field.split('|')[0]
|
41
|
-
end
|
42
|
-
|
43
|
-
query = where_query(table, field, operator, value)
|
44
|
-
@objects = list.where(query)
|
45
|
-
@objects_count = @objects.count
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def search_operands(field)
|
50
|
-
value = default_param_value(field)
|
51
|
-
|
52
|
-
# WHERE query operator
|
53
|
-
operator = field.split('|').size > 1 ? field.split('|').last : '='
|
54
|
-
|
55
|
-
# условие для поиска ILIKE
|
56
|
-
value = "%#{value}%" if operator == 'ilike' && value.present?
|
57
|
-
|
58
|
-
# проверка мульти-поиска по параметру с массивом
|
59
|
-
if operator == 'multi' && !value.blank?
|
60
|
-
operator = 'IN'
|
61
|
-
value = "(#{value.join(', ')})"
|
62
|
-
end
|
63
|
-
|
64
|
-
# проверка диапазона (MIN/MAX)
|
65
|
-
if %w[min max].include?(operator)
|
66
|
-
range_field = field.split('.').last.split(':')[0].try { |i| i.split('|')[0] }
|
67
|
-
value = @params["#{range_field}_#{operator}"]
|
68
|
-
operator = operator == 'min' ? '>=' : '<='
|
69
|
-
end
|
70
|
-
|
71
|
-
# check array operator
|
72
|
-
if operator == 'array' && value.present?
|
73
|
-
operator = '='
|
74
|
-
value = "{#{value}}"
|
75
|
-
end
|
76
|
-
|
77
|
-
[operator, value]
|
78
|
-
end
|
79
|
-
|
80
|
-
def default_param_value(field)
|
81
|
-
# название ключа-поля без оператора (напр. ilike)
|
82
|
-
key = field.split('|')[0]
|
83
|
-
|
84
|
-
@params[key]
|
85
|
-
end
|
86
|
-
|
87
|
-
def where_query(table, field, operator, value)
|
88
|
-
"#{table}.#{field} #{operator} #{value}"
|
89
|
-
end
|
90
|
-
|
91
|
-
def quotations_formatting(string)
|
92
|
-
# удаление одинарных кавычек (напр. внутри строки)
|
93
|
-
value = string.to_s.gsub(/'/, '')
|
94
|
-
|
95
|
-
# добавление одинарных кавычек вокруг строки
|
96
|
-
"'#{value}'"
|
97
|
-
end
|
98
|
-
|
99
|
-
end
|
data/lib/paginated/sorting.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
require 'fast_excel'
|
2
|
-
|
3
|
-
module Spreadsheet
|
4
|
-
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
def spreadsheet
|
8
|
-
search if @objects_count > 0
|
9
|
-
|
10
|
-
# коллекция записей ActiveRecord для формирования эл. таблицы
|
11
|
-
records = @objects || @model.unscoped.merge(@scopes)
|
12
|
-
|
13
|
-
# формирования файла эл. таблицы
|
14
|
-
generate_spreadsheet(records)
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def generate_spreadsheet(records)
|
20
|
-
filepath = "#{@tempdir}/#{Time.now.strftime('%Y%m%d_%H%M%S')}.xlsx"
|
21
|
-
|
22
|
-
# создание таблицы и ее конфигурация
|
23
|
-
workbook = FastExcel.open(filepath, constant_memory: true)
|
24
|
-
workbook.default_format.set(
|
25
|
-
font_size: 0,
|
26
|
-
font_family: 'Arial'
|
27
|
-
)
|
28
|
-
|
29
|
-
# стили
|
30
|
-
bold = workbook.bold_format
|
31
|
-
|
32
|
-
# лист
|
33
|
-
worksheet = workbook.add_worksheet('Основной')
|
34
|
-
worksheet.auto_width = true
|
35
|
-
|
36
|
-
# заголовки
|
37
|
-
worksheet.append_row(spreadsheet_titles, bold)
|
38
|
-
|
39
|
-
# содержимое
|
40
|
-
cols = spreadsheet_columns
|
41
|
-
records.each do |record|
|
42
|
-
values = cols.map { |col| record.send(col) }
|
43
|
-
worksheet.append_row(values)
|
44
|
-
end
|
45
|
-
|
46
|
-
# закрытие/сохранение файла
|
47
|
-
workbook.close
|
48
|
-
|
49
|
-
# путь до файла для дальнейшей обработки
|
50
|
-
filepath
|
51
|
-
end
|
52
|
-
|
53
|
-
def spreadsheet_titles
|
54
|
-
@spreadsheet_columns.map { |i| i['title'] }
|
55
|
-
end
|
56
|
-
|
57
|
-
def spreadsheet_columns
|
58
|
-
@spreadsheet_columns.map { |i| i['column'] }
|
59
|
-
end
|
60
|
-
|
61
|
-
end
|