grape-listing 1.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 +83 -0
- data/lib/extensions/object.rb +7 -0
- data/lib/grape/dsl.rb +55 -0
- data/lib/grape-listing.rb +1 -0
- data/lib/grape_listing/configuration.rb +14 -0
- data/lib/grape_listing.rb +7 -0
- data/lib/grape_listing_service.rb +27 -0
- data/lib/listing_service/args_handling.rb +65 -0
- data/lib/listing_service/collection.rb +58 -0
- data/lib/listing_service/search.rb +99 -0
- data/lib/listing_service/sorting.rb +12 -0
- data/lib/listing_service/spreadsheet.rb +77 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f7ada8749162c0e09f2a0dad2712dbd31e3296d302266449e4c5edd7cea9b1d6
|
4
|
+
data.tar.gz: 51afe0e601ad9e8f5d22d4c0d07031078cc9b6d1161cfd092a15dfb18265cd8b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff72f5cf03c2394347d8745e8c68429d85f793ce64f29eddc7d808f6b75fb81c4a4cb6d12bf5af8f707a999e2fe90d5269b4b310ebd6794819d65d0bfbeb7fe0
|
7
|
+
data.tar.gz: 98e6cec6b17f9efa1fcbd1890a3d376539d2662240e6dfddb107ae729a96b1f8e2aea569f2adb183fb52639945159a3dcc7b0b24ce7e5ada098d055c2d1b1066
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Grape Listing
|
2
|
+
[](https://badge.fury.io/rb/paginated)
|
3
|
+
|
4
|
+
Гем для пагинации и фильтрации записей с возможностью формирования электронных таблиц (XLSX) на базе Grape.
|
5
|
+
|
6
|
+
## Установка
|
7
|
+
|
8
|
+
Добавить в Gemfile
|
9
|
+
|
10
|
+
```
|
11
|
+
gem 'grape-listing'
|
12
|
+
```
|
13
|
+
|
14
|
+
И выполнить:
|
15
|
+
|
16
|
+
```
|
17
|
+
bundle
|
18
|
+
```
|
19
|
+
|
20
|
+
## Использование
|
21
|
+
|
22
|
+
Использование с помощью вызова метода-хелпера `listing` и передачи в него необходимых опций.
|
23
|
+
|
24
|
+
|
25
|
+
Например:
|
26
|
+
|
27
|
+
```
|
28
|
+
get 'users' do
|
29
|
+
listing model: User,
|
30
|
+
entity: UserEntity,
|
31
|
+
search: %w[email name|ilike role|custom.for_role]
|
32
|
+
end
|
33
|
+
|
34
|
+
```
|
35
|
+
|
36
|
+
Опции (с примерами):
|
37
|
+
|
38
|
+
`model: User` - модель, записи которой нужно обработать.
|
39
|
+
|
40
|
+
`entity: UserEntity` - класс Grape Entity, который обработает каждую запись (передается вместо `fields`).
|
41
|
+
|
42
|
+
`scopes: proc {...}` - блок кода для применения
|
43
|
+
|
44
|
+
`fields: %i[...]` - список полей, которые должны присутствовать в списке записей (передается вместо `entity`).
|
45
|
+
|
46
|
+
`search: %w[...]` - список полей, по которым должна осуществляться фильтрация (поиск).
|
47
|
+
|
48
|
+
## Параметры HTTP запроса
|
49
|
+
|
50
|
+
Некоторые функции, такие, как поиск (фильтрация), сортировка и формирование эл. таблиц осуществляется путем обработки параметров HTTP запроса.
|
51
|
+
|
52
|
+
### Поиск
|
53
|
+
|
54
|
+
Для поиска по полям необходимо передать их в запросе в виде `?field=value`. Для поиска по нескольким полям, параметры должны быть перечислены через `&`, например: `?field_1=value&field_2=value`.
|
55
|
+
|
56
|
+
### Сортировка
|
57
|
+
|
58
|
+
Для сортировки выдачи необходимо передать в параметрах запроса:
|
59
|
+
|
60
|
+
- `sort_by` - название поля, по которому должна осуществляться сортировка.
|
61
|
+
|
62
|
+
- `sort_order` направление, по которому должна осуществляться сортировка (`asc/desc`).
|
63
|
+
|
64
|
+
По умолчанию сортировка осуществляется по `id` записей в направлении `DESC`.
|
65
|
+
|
66
|
+
### Ограничение полей
|
67
|
+
|
68
|
+
При передаче параметра `columns[]=` результаты в ответе ограничиваются переданным массив колонок.
|
69
|
+
|
70
|
+
### Формирование эл. таблиц
|
71
|
+
|
72
|
+
При передаче параметра `spreadsheet=true` происходит формирование эл. таблицы в виде XLSX файла с учетом всех остальных переданных параметров. Параметр `columns` при этом является обязательным.
|
73
|
+
|
74
|
+
Заголовки эл. таблицы будут взяты из описаний полей таблицы БД с соответствующими названиями. Значения будут сформированы путем обработки переданного списка колонок как методов.
|
75
|
+
|
76
|
+
## Конфигурация
|
77
|
+
|
78
|
+
Добавьте файл конфигурации `config/initializers/grape_listing.rb` с содержимым:
|
79
|
+
|
80
|
+
```
|
81
|
+
GrapeListing.configure do |config|
|
82
|
+
end
|
83
|
+
```
|
data/lib/grape/dsl.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Grape
|
2
|
+
module DSL
|
3
|
+
module InsideRoute
|
4
|
+
|
5
|
+
def listing(model:, entity:, scopes: nil, search: nil)
|
6
|
+
opts = listing_opts(model, entity, scopes, search)
|
7
|
+
|
8
|
+
if params[:spreadsheet]
|
9
|
+
listing_spreadsheet(**opts)
|
10
|
+
else
|
11
|
+
GrapeListingService.paginated(**opts)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def listing_opts(model, entity, scopes, search)
|
18
|
+
# стандартные опции
|
19
|
+
opts = { model:, entity:, scopes:, search:, params:, current_user: }
|
20
|
+
|
21
|
+
# требуемый список полей (+стандартные) для фильтрации в Grape Entity
|
22
|
+
if params[:columns] && !params[:spreadsheet]
|
23
|
+
opts[:only_columns] = params[:columns] + %w[id deleted_at]
|
24
|
+
end
|
25
|
+
|
26
|
+
opts
|
27
|
+
end
|
28
|
+
|
29
|
+
def listing_spreadsheet(opts)
|
30
|
+
Dir.mktmpdir do |dir|
|
31
|
+
opts[:tempdir] = dir
|
32
|
+
file_path = GrapeListingService.spreadsheet(**opts)
|
33
|
+
|
34
|
+
spreadsheet_file_headers File.basename(file_path)
|
35
|
+
File.read(file_path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def spreadsheet_file_headers(filename)
|
40
|
+
header['Content-Type'] =
|
41
|
+
case File.extname(filename)
|
42
|
+
when '.csv'
|
43
|
+
'application/csv'
|
44
|
+
when '.xlsx'
|
45
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
46
|
+
end
|
47
|
+
|
48
|
+
header['Access-Control-Expose-Headers'] = 'Content-Disposition'
|
49
|
+
header['Content-Disposition'] = "attachment; filename=#{Addressable::URI.escape(filename)}"
|
50
|
+
env['api.format'] = :binary
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'grape_listing'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'listing_service/args_handling'
|
2
|
+
require 'listing_service/collection'
|
3
|
+
require 'listing_service/search'
|
4
|
+
require 'listing_service/sorting'
|
5
|
+
require 'listing_service/spreadsheet'
|
6
|
+
|
7
|
+
class GrapeListingService
|
8
|
+
|
9
|
+
include GrapeListing::ArgsHandling
|
10
|
+
include GrapeListing::Collection
|
11
|
+
include GrapeListing::Search
|
12
|
+
include GrapeListing::Sorting
|
13
|
+
include GrapeListing::Spreadsheet
|
14
|
+
|
15
|
+
def initialize(**args)
|
16
|
+
handle_args(**args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.paginated(**args)
|
20
|
+
new(**args).paginated
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.spreadsheet(**args)
|
24
|
+
new(**args).spreadsheet
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module GrapeListing
|
2
|
+
module ArgsHandling
|
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
|
+
@sort_by = @params['sort_by'] || :id
|
38
|
+
@sort_order = @params['sort_order'] || :desc
|
39
|
+
|
40
|
+
# подсчет кол-ва записей
|
41
|
+
@objects_count = records_count
|
42
|
+
|
43
|
+
# временная директория (для генерации файлов)
|
44
|
+
@tempdir = args[:tempdir]
|
45
|
+
end
|
46
|
+
|
47
|
+
def records_count
|
48
|
+
# получение кол-ва записей из кеша
|
49
|
+
cache_key = "#{@model}_records_count"
|
50
|
+
cached = Rails.cache.read(cache_key)
|
51
|
+
return cached if cached
|
52
|
+
|
53
|
+
# получение кол-ва записей из запроса к БД
|
54
|
+
count = @model.merge(@scopes).count(:id)
|
55
|
+
|
56
|
+
# кеширование кол-ва записей при превышении порогового значения
|
57
|
+
if count >= 1_000
|
58
|
+
Rails.cache.write(cache_key, count, expires_in: 30.minutes)
|
59
|
+
end
|
60
|
+
|
61
|
+
count
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module GrapeListing
|
2
|
+
module Collection
|
3
|
+
|
4
|
+
def paginated
|
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 GrapeListing
|
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,77 @@
|
|
1
|
+
require 'fast_excel'
|
2
|
+
|
3
|
+
module GrapeListing
|
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_chain(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
|
+
model_cols = @model.columns.to_h { |i| [i.name, i] }
|
58
|
+
|
59
|
+
@params['columns'].map do |column|
|
60
|
+
db_col = model_cols[column]
|
61
|
+
|
62
|
+
db_col.comment || column
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def spreadsheet_columns
|
67
|
+
entity_doc = @grape_entity.documentation
|
68
|
+
|
69
|
+
@params['columns'].map do |column|
|
70
|
+
doc = entity_doc[column.to_sym]
|
71
|
+
|
72
|
+
doc && doc[:sheet_method] ? doc[:sheet_method].split('.') : column
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grape-listing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Павел Бабин
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-03-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: fast_excel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.5.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.5.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: grape
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: grape-entity
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.0.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '7.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '7.0'
|
69
|
+
description: Простая пагинация, поиск и формирование эл. таблиц для API на базе Grape
|
70
|
+
email: babin359@gmail.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- README.md
|
76
|
+
- lib/extensions/object.rb
|
77
|
+
- lib/grape-listing.rb
|
78
|
+
- lib/grape/dsl.rb
|
79
|
+
- lib/grape_listing.rb
|
80
|
+
- lib/grape_listing/configuration.rb
|
81
|
+
- lib/grape_listing_service.rb
|
82
|
+
- lib/listing_service/args_handling.rb
|
83
|
+
- lib/listing_service/collection.rb
|
84
|
+
- lib/listing_service/search.rb
|
85
|
+
- lib/listing_service/sorting.rb
|
86
|
+
- lib/listing_service/spreadsheet.rb
|
87
|
+
homepage:
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
metadata:
|
91
|
+
rubygems_mfa_required: 'true'
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 3.1.0
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubygems_version: 3.0.6
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Формирование списков записей для API на базе Grape
|
111
|
+
test_files: []
|