grape-listing 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/paginated.svg)](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: []
|