grape-listing 1.4.9.1 → 2.0.0.pre.beta.2

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: 86be760a79ec587b92567607aaf1980141e8a901d4bcb93a7c36a5b3d8e17305
4
- data.tar.gz: 46d263b7d9ca8dce31760af601a07a6d1a8f280912df685968ef57611f33b7ff
3
+ metadata.gz: 9d7ddb2edcb994044872c61a3c15c2ae58394f620b19d4486b833ae7f5f57e7d
4
+ data.tar.gz: 4bb801340d2f4f73af813e50008f002518ff3826bc67a30b81c9a46a4690b1c2
5
5
  SHA512:
6
- metadata.gz: db17ac06b29aec7864a648d55130f393d1809eeafed7d3423ee93f7757882a991e3fe7756ce5f1ea4794cc3d5d62d1944cfd85e70886e01fa1053f58b21b1af4
7
- data.tar.gz: eb718c3bb664eb6c93b89307d10bdd97990d1bc9d923519e86372e2e0c86f914b8437fecc39732104f3fe7a311e66226b11b58cd9382f7ed68610052961b46c1
6
+ metadata.gz: 1953a65f504b4911a82ae5f3520ea3100dd4b3c1aff9e28e17eecdc03bf6d26e34a0e505a5c70499ffd0e4d246348ea5cedb432176252e95e36016072ba2b639
7
+ data.tar.gz: dc1de49e752f69467e502c34ef2531f9f583115c2563f675cca0f995202bd2f42945ad1a4aa9443b646f4e5c94b14ceb05263397b7013fc8a40c3e7766ff6300
data/README.md CHANGED
@@ -43,9 +43,9 @@ end
43
43
 
44
44
  `fields: %i[...]` - список полей, которые должны присутствовать в списке записей (передается вместо `entity`)
45
45
 
46
- `search: %w[...]` - список полей, по которым должна осуществляться фильтрация (поиск)
46
+ `custom_search: %w[...]` - список параметров, по которым должна осуществляться специальная фильтрация с применением индивидуальных Rails-скоупов
47
47
 
48
- `sortable: %w[...]` - список полей, по которым может осуществляться сортировка
48
+ `custom_sort: %w[...]` - список параметров, по которым может осуществляться специальная сортировка с применением индивидуальных Rails-скоупов
49
49
 
50
50
  `paginate: false` - отдавать результат без пагинации, сразу всей коллекцией (по умолчанию - с пагинацией)
51
51
 
@@ -53,26 +53,187 @@ end
53
53
 
54
54
  Некоторые функции, такие, как поиск (фильтрация), сортировка и формирование эл. таблиц осуществляется путем обработки параметров HTTP запроса.
55
55
 
56
- ### Поиск
57
-
58
- Для возможности поиска необходимо передать названия полей, по которым может осуществляться поиск, в виде массива в опции `search`.
59
-
60
- Для осуществления фильтрации необходимо передать параметры в запросе в виде `?field=value`. Для поиска по нескольким полям, параметры должны быть перечислены через `&`, например: `?field_1=value&field_2=value`.
61
-
62
- В общем случае, названия полей соответствуют названиями параметров (например поле `name` и параметр `name`).
63
-
64
- Пример использования с осуществлением поиска по полю `name` с полным совпадением:
65
-
56
+ ### Фильтрация
57
+
58
+ Для фильтрации требуется передать параметры в соответствующем формате.
59
+
60
+ <table>
61
+ <thead>
62
+ <tr>
63
+ <th colspan=3>Строки</th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ <tr>
68
+ <td>Заполнено</td>
69
+ <td>param.is.present</td>
70
+ </tr>
71
+ <tr>
72
+ <td>Не заполнено</td>
73
+ <td>param.not.present</td>
74
+ </tr>
75
+ <tr>
76
+ <td>Содержит</td>
77
+ <td>param.is.contain</td>
78
+ </tr>
79
+ <tr>
80
+ <td>Не содержит</td>
81
+ <td>param.not.contain</td>
82
+ </tr>
83
+ <tr>
84
+ <td>Равно</td>
85
+ <td>param.is.equal</td>
86
+ </tr>
87
+ <tr>
88
+ <td>Не равно</td>
89
+ <td>param.not.equal</td>
90
+ </tr>
91
+ <tr>
92
+ <td>Начинается с</td>
93
+ <td>param.is.start_with</td>
94
+ </tr>
95
+ <tr>
96
+ <td>Заканчивается на</td>
97
+ <td>param.is.end_with</td>
98
+ </tr>
99
+ </tbody>
100
+ <thead>
101
+ <tr>
102
+ <th colspan=3>Числа</th>
103
+ </tr>
104
+ </thead>
105
+ <tbody>
106
+ <tr>
107
+ <td>Заполнено</td>
108
+ <td>param.is.present</td>
109
+ </tr>
110
+ <tr>
111
+ <td>Не заполнено</td>
112
+ <td>param.not.present</td>
113
+ </tr>
114
+ <tr>
115
+ <td>Равно</td>
116
+ <td>param.is.equal</td>
117
+ </tr>
118
+ <tr>
119
+ <td>Не равно</td>
120
+ <td>param.not.equal</td>
121
+ </tr>
122
+ <tr>
123
+ <td>Больше</td>
124
+ <td>param.is.gt</td>
125
+ </tr>
126
+ <tr>
127
+ <td>Больше или равно</td>
128
+ <td>param.is.gte</td>
129
+ </tr>
130
+ <tr>
131
+ <td>Меньше</td>
132
+ <td>param.is.lt</td>
133
+ </tr>
134
+ <tr>
135
+ <td>Меньше или равно</td>
136
+ <td>param.is.lte</td>
137
+ </tr>
138
+ <tr>
139
+ <td>В диапазоне</td>
140
+ <td>param.is.range=[]</td>
141
+ </tr>
142
+ <tr>
143
+ <td>Вне диапазона</td>
144
+ <td>param.not.range=[]</td>
145
+ </tr>
146
+ <tr>
147
+ <td>В перечне</td>
148
+ <td>param.is.in=[]</td>
149
+ </tr>
150
+ <tr>
151
+ <td>Вне перечня</td>
152
+ <td>param.not.in=[]</td>
153
+ </tr>
154
+ </tbody>
155
+ <thead>
156
+ <tr>
157
+ <th colspan=3>Даты</th>
158
+ </tr>
159
+ </thead>
160
+ <tbody>
161
+ <tr>
162
+ <td>Заполнено</td>
163
+ <td>param.is.present</td>
164
+ </tr>
165
+ <tr>
166
+ <td>Не заполнено</td>
167
+ <td>param.not.present</td>
168
+ </tr>
169
+ <tr>
170
+ <td>Равно</td>
171
+ <td>param.is.equal</td>
172
+ </tr>
173
+ <tr>
174
+ <td>Не равно</td>
175
+ <td>param.not.equal</td>
176
+ </tr>
177
+ <tr>
178
+ <td>Больше</td>
179
+ <td>param.is.gt</td>
180
+ </tr>
181
+ <tr>
182
+ <td>Больше или равно</td>
183
+ <td>param.is.gte</td>
184
+ </tr>
185
+ <tr>
186
+ <td>Меньше</td>
187
+ <td>param.is.lt</td>
188
+ </tr>
189
+ <tr>
190
+ <td>Меньше или равно</td>
191
+ <td>param.is.lte</td>
192
+ </tr>
193
+ <tr>
194
+ <td>В диапазоне</td>
195
+ <td>param.is.range=[]</td>
196
+ </tr>
197
+ <tr>
198
+ <td>Вне диапазона</td>
199
+ <td>param.not.range=[]</td>
200
+ </tr>
201
+ <tr>
202
+ <td>В перечне</td>
203
+ <td>param.is.in=[]</td>
204
+ </tr>
205
+ <tr>
206
+ <td>Вне перечня</td>
207
+ <td>param.not.in=[]</td>
208
+ </tr>
209
+ </tbody>
210
+ <thead>
211
+ <tr>
212
+ <th colspan=3>Флаги</th>
213
+ </tr>
214
+ </thead>
215
+ <tbody>
216
+ <tr>
217
+ <td>Равно</td>
218
+ <td>param.is.equal</td>
219
+ </tr>
220
+ <tr>
221
+ <td>Не равно</td>
222
+ <td>param.not.equal</td>
223
+ </tr>
224
+ </tbody>
225
+ </table>
226
+
227
+ Для фильтрации по полям, отсутствующим в таблице, либо для применения специальной фильтрации, не предусмотренной условиями, описанными выше, требуется передать опцию `custom_search` со списком параметров и соответствующим им скоупам Rails:
66
228
  ```ruby
67
229
  get 'users' do
68
230
  listing model: User,
69
231
  entity: UserEntity,
70
- search: %w[name]
232
+ custom_search: %w[
233
+ name|for_name
234
+ ]
71
235
  end
72
236
  ```
73
- ```
74
- GET http://localhost:3000/api/users?name=Ivan
75
- ```
76
237
 
77
238
  ### Сортировка
78
239
 
data/lib/grape/dsl.rb CHANGED
@@ -4,13 +4,13 @@ module Grape
4
4
  module DSL
5
5
  module InsideRoute
6
6
 
7
- def listing(model:, entity: nil, fields: nil, scopes: nil, search: nil, sortable: nil, paginate: true, cache: nil, preload: nil)
7
+ def listing(model:, entity: nil, fields: nil, scopes: nil, custom_search: nil, custom_sort: nil, paginate: true, cache: nil, preload: nil)
8
8
  # параметры запроса API
9
9
  request_method = request.env['REQUEST_METHOD']
10
10
  request_uri = request.env['REQUEST_URI']
11
11
 
12
12
  # опции для сервиса
13
- opts = listing_opts(model, entity, fields, scopes, search, sortable, cache, request_method, request_uri, preload)
13
+ opts = listing_opts(model, entity, fields, scopes, custom_search, custom_sort, cache, request_method, request_uri, preload)
14
14
 
15
15
  if params[:spreadsheet]
16
16
  listing_spreadsheet(**opts)
@@ -23,7 +23,7 @@ module Grape
23
23
 
24
24
  private
25
25
 
26
- def listing_opts(model, entity, fields, scopes, search, sortable, cache, request_method, request_uri, preload)
26
+ def listing_opts(model, entity, fields, scopes, custom_search, custom_sort, cache, request_method, request_uri, preload)
27
27
  # стандартные опции
28
28
  opts = {
29
29
  model:,
@@ -31,8 +31,8 @@ module Grape
31
31
  entity:,
32
32
  fields:,
33
33
  scopes:,
34
- search:,
35
- sortable:,
34
+ custom_search:,
35
+ custom_sort:,
36
36
  params:,
37
37
  current_user:,
38
38
  cache:,
@@ -1,3 +1,3 @@
1
1
  module GrapeListing
2
- VERSION = '1.4.9.1'.freeze
2
+ VERSION = '2.0.0-beta.2'.freeze
3
3
  end
@@ -6,6 +6,7 @@ module GrapeListing
6
6
  def handle_args(**args)
7
7
  # обрабатываемая модель
8
8
  @model = args[:model]
9
+ @model_cols = @model.columns_hash
9
10
 
10
11
  # список связей, необходимых для подгрузки eager load
11
12
  @preload = args[:preload] || []
@@ -19,7 +20,17 @@ module GrapeListing
19
20
  @current_user = args[:current_user]
20
21
 
21
22
  # поля, по которым возможен поиск
22
- @search_fields = args[:search]
23
+ @search_params = @params.except('offset', 'limit', 'sort_by', 'sort_order')
24
+
25
+ # настройки кастомного поиска
26
+ if args[:custom_search]
27
+ @custom_search = args[:custom_search].map { |i| i.split('|') }.to_h
28
+ end
29
+
30
+ # настройки кастомной сортировки
31
+ if args[:custom_sort]
32
+ @custom_sort = args[:custom_sort].map { |i| i.split('|') }.to_h
33
+ end
23
34
 
24
35
  # сериализация - через Grape Entity или список полей
25
36
  @grape_entity = args[:entity]
@@ -40,9 +51,6 @@ module GrapeListing
40
51
  # требуемый список полей для коллекции
41
52
  @only_columns = args[:only_columns]
42
53
 
43
- # сортировка
44
- @sortable = args[:sortable]
45
-
46
54
  # временная директория (для генерации файлов)
47
55
  @tempdir = args[:tempdir]
48
56
 
@@ -10,8 +10,8 @@ module GrapeListing
10
10
 
11
11
  # результат с пагинацией
12
12
  {
13
- sort_by: @params['sort_by'] || 'id',
14
- sort_order: @sort_order || 'desc',
13
+ sort_by: @sort_by,
14
+ sort_order: @sort_order,
15
15
  count: @objects_count,
16
16
  objects: @objects
17
17
  }
@@ -1,90 +1,315 @@
1
1
  module GrapeListing
2
2
  module Search
3
3
 
4
+ # условия
5
+ IS = 'is'.freeze
6
+ NOT = 'not'.freeze
7
+
8
+ # операторы
9
+ PRESENT = 'present'.freeze
10
+ CONTAIN = 'contain'.freeze
11
+ EQUAL = 'equal'.freeze
12
+ START_WITH = 'start_with'.freeze
13
+ END_WITH = 'end_with'.freeze
14
+ GT = 'gt'.freeze
15
+ GTE = 'gte'.freeze
16
+ LT = 'lt'.freeze
17
+ LTE = 'lte'.freeze
18
+ RANGE = 'range'.freeze
19
+ IN = 'in'.freeze
20
+
4
21
  private
5
22
 
6
23
  def search
7
- @search_fields&.each do |field|
24
+ @search_params&.each do |param, value|
8
25
  # коллекция записей для дальнейшей фильтрации
9
26
  list = @objects || @model.preload(@preload).merge(@scopes)
10
27
 
11
- # оператор и значение для поиска
12
- operator, value = search_operands(field)
13
-
14
- # пропускаем, если параметр для поиска не передан
15
- next if value.nil? || value == '' || value == 'null'
28
+ # обработка параметра
29
+ column, condition, operator = param.split('.')
30
+ next unless [column, condition, operator].all?
16
31
 
17
- # кастомный поиск
18
- if operator.include?('custom') && value.present?
19
- # название скоупа из модели, который нужно применить
20
- scope_name = operator.split('.')[1]
21
- # применение скоупа
22
- @objects = list.send(scope_name, value).distinct
32
+ # применение кастомного скоупа (при наличии)
33
+ custom_scope = @custom_search[column]
34
+ if custom_scope
35
+ @objects = list.send(custom_scope, value)
23
36
  next
24
37
  end
25
38
 
26
- # форматирование кавычек
27
- if operator != 'IN'
28
- value = quotations_formatting(value)
29
- end
39
+ # формирование запроса для фильтрации
40
+ type = @model_cols[column].type
41
+ subject = "#{@model.table_name}.#{column}"
30
42
 
31
- if field.include?('.')
32
- # нужно приджойнить таблицу
33
- join_assoc, field = field.split('.')
34
- table = join_assoc.tableize
35
- list = list.joins(join_assoc.to_sym)
36
- else
37
- table = @model.table_name
38
- field = field.split('|')[0]
43
+ clause = case type
44
+ when :string, :text
45
+ string_clause(subject, condition, operator, value)
46
+ when :integer, :float
47
+ number_clause(subject, condition, operator, value)
48
+ when :date, :datetime
49
+ date_clause(subject, condition, operator, value)
50
+ when :boolean
51
+ boolean_clause(subject, condition, operator, value)
39
52
  end
40
53
 
41
- query = where_query(table, field, operator, value)
42
- @objects = list.where(query)
54
+ # применение запроса для фильтрации
55
+ @objects = list.where(clause)
56
+ end
57
+ end
58
+
59
+ def string_clause(subject, condition, operator, value)
60
+ case operator
61
+ when PRESENT
62
+ string_present(subject, condition)
63
+ when CONTAIN
64
+ string_contain(subject, condition, value)
65
+ when EQUAL
66
+ string_equal(subject, condition, value)
67
+ when START_WITH
68
+ string_start(subject, condition, value)
69
+ when END_WITH
70
+ string_end(subject, condition, value)
71
+ end
72
+ end
73
+
74
+ def number_clause(subject, condition, operator, value)
75
+ case operator
76
+ when PRESENT
77
+ number_present(subject, condition)
78
+ when EQUAL
79
+ number_equal(subject, condition, value)
80
+ when GT
81
+ number_gt(subject, condition, value)
82
+ when GTE
83
+ number_gte(subject, condition, value)
84
+ when LT
85
+ number_lt(subject, condition, value)
86
+ when LTE
87
+ number_lte(subject, condition, value)
88
+ when RANGE
89
+ number_range(subject, condition, value)
90
+ when IN
91
+ number_in(subject, condition, value)
92
+ end
93
+ end
94
+
95
+ def date_clause(subject, condition, operator, value)
96
+ case operator
97
+ when PRESENT
98
+ date_present(subject, condition)
99
+ when EQUAL
100
+ date_equal(subject, condition, value)
101
+ when GT
102
+ date_gt(subject, condition, value)
103
+ when GTE
104
+ date_gte(subject, condition, value)
105
+ when LT
106
+ date_lt(subject, condition, value)
107
+ when LTE
108
+ date_lte(subject, condition, value)
109
+ when RANGE
110
+ date_range(subject, condition, value)
111
+ when IN
112
+ date_in(subject, condition, value)
113
+ end
114
+ end
115
+
116
+ def boolean_clause(subject, condition, operator, value)
117
+ case operator
118
+ when EQUAL
119
+ boolean_equal(subject, condition, value)
120
+ end
121
+ end
122
+
123
+ def string_present(subject, condition)
124
+ case condition
125
+ when IS
126
+ "#{subject} IS NOT NULL AND #{subject} != ''"
127
+ when NOT
128
+ "#{subject} IS NULL OR #{subject} = ''"
129
+ end
130
+ end
131
+
132
+ def string_contain(subject, condition, value)
133
+ case condition
134
+ when IS
135
+ "#{subject} ILIKE %#{value}%"
136
+ when NOT
137
+ "#{subject} NOT ILIKE %#{value}%"
138
+ end
139
+ end
140
+
141
+ def string_equal(subject, condition, value)
142
+ case condition
143
+ when IS
144
+ "#{subject} = '#{value}'"
145
+ when NOT
146
+ "#{subject} != '#{value}'"
147
+ end
148
+ end
149
+
150
+ def string_start(subject, condition, value)
151
+ case condition
152
+ when IS
153
+ "#{subject} ILIKE #{value}%"
154
+ when NOT
155
+ "#{subject} NOT ILIKE #{value}%"
156
+ end
157
+ end
158
+
159
+ def string_end(subject, condition, value)
160
+ case condition
161
+ when IS
162
+ "#{subject} ILIKE %#{value}"
163
+ when NOT
164
+ "#{subject} NOT ILIKE %#{value}"
165
+ end
166
+ end
167
+
168
+ def number_present(subject, condition)
169
+ case condition
170
+ when IS
171
+ "#{subject} IS NOT NULL"
172
+ when NOT
173
+ "#{subject} IS NULL"
174
+ end
175
+ end
176
+
177
+ def number_equal(subject, condition, value)
178
+ case condition
179
+ when IS
180
+ "#{subject} = #{value}"
181
+ when NOT
182
+ "#{subject} != #{value}"
43
183
  end
44
184
  end
45
185
 
46
- def search_operands(field)
47
- value = default_param_value(field)
186
+ def number_gt(subject, condition, value)
187
+ case condition
188
+ when IS
189
+ "#{subject} > #{value}"
190
+ end
191
+ end
48
192
 
49
- # WHERE query operator
50
- operator = field.split('|').size > 1 ? field.split('|').last : '='
193
+ def number_gte(subject, condition, value)
194
+ case condition
195
+ when IS
196
+ "#{subject} >= #{value}"
197
+ end
198
+ end
51
199
 
52
- # условие для поиска ILIKE
53
- value = "%#{value}%" if operator == 'ilike' && value.present?
200
+ def number_lt(subject, condition, value)
201
+ case condition
202
+ when IS
203
+ "#{subject} < #{value}"
204
+ end
205
+ end
54
206
 
55
- # проверка мульти-поиска по параметру с массивом
56
- if value.is_a?(Array) && operator.exclude?('custom')
57
- operator = 'IN'
58
- value = "(#{value.map { |i| "'#{i}'" }.join(', ')})"
207
+ def number_lte(subject, condition, value)
208
+ case condition
209
+ when IS
210
+ "#{subject} <= #{value}"
59
211
  end
212
+ end
60
213
 
61
- # проверка диапазона (MIN/MAX)
62
- if %w[min max].include?(operator)
63
- range_field = field.split('.').last.split(':')[0].try { |i| i.split('|')[0] }
64
- value = @params["#{range_field}_#{operator}"]
65
- operator = operator == 'min' ? '>=' : '<='
214
+ def number_range(subject, condition, value)
215
+ min, max = value
216
+
217
+ case condition
218
+ when IS
219
+ "#{subject} >= #{min} AND #{subject} <= #{max}"
220
+ when NOT
221
+ "#{subject} < #{min} OR #{subject} > #{max}"
66
222
  end
223
+ end
224
+
225
+ def number_in(subject, condition, value)
226
+ opts = value.join(',')
67
227
 
68
- [operator, value]
228
+ case condition
229
+ when IS
230
+ "#{subject} IN (#{opts})"
231
+ when NOT
232
+ "#{subject} NOT IN (#{opts})"
233
+ end
69
234
  end
70
235
 
71
- def default_param_value(field)
72
- # название ключа-поля без оператора (напр. ilike)
73
- key = field.split('|')[0]
236
+ def date_present(subject, condition)
237
+ case condition
238
+ when IS
239
+ "#{subject} IS NOT NULL"
240
+ when NOT
241
+ "#{subject} IS NULL"
242
+ end
243
+ end
74
244
 
75
- @params[key]
245
+ def date_equal(subject, condition, value)
246
+ case condition
247
+ when IS
248
+ "#{subject} = '#{value}'"
249
+ when NOT
250
+ "#{subject} != '#{value}'"
251
+ end
76
252
  end
77
253
 
78
- def where_query(table, field, operator, value)
79
- "#{table}.#{field} #{operator} #{value}"
254
+ def date_gt(subject, condition, value)
255
+ case condition
256
+ when IS
257
+ "#{subject} > '#{value}'"
258
+ end
80
259
  end
81
260
 
82
- def quotations_formatting(string)
83
- # удаление одинарных кавычек (напр. внутри строки)
84
- value = string.to_s.gsub("'", '')
261
+ def date_gte(subject, condition, value)
262
+ case condition
263
+ when IS
264
+ "#{subject} >= '#{value}'"
265
+ end
266
+ end
85
267
 
86
- # добавление одинарных кавычек вокруг строки
87
- "'#{value}'"
268
+ def date_lt(subject, condition, value)
269
+ case condition
270
+ when IS
271
+ "#{subject} < '#{value}'"
272
+ end
273
+ end
274
+
275
+ def date_lte(subject, condition, value)
276
+ case condition
277
+ when IS
278
+ "#{subject} <= '#{value}'"
279
+ end
280
+ end
281
+
282
+ def date_range(subject, condition, value)
283
+ min, max = value
284
+
285
+ case condition
286
+ when IS
287
+ "#{subject} >= '#{min}' AND #{subject} <= '#{max}'"
288
+ when NOT
289
+ "#{subject} < '#{min}' OR #{subject} > '#{max}'"
290
+ end
291
+ end
292
+
293
+ def date_in(subject, condition, value)
294
+ opts = value.join(',')
295
+
296
+ case condition
297
+ when IS
298
+ "#{subject} IN (#{opts})"
299
+ when NOT
300
+ "#{subject} NOT IN (#{opts})"
301
+ end
302
+ end
303
+
304
+ def boolean_equal(subject, condition, value)
305
+ flag = value.to_s == 'true' ? 't' : 'f'
306
+
307
+ case condition
308
+ when IS
309
+ "#{subject} = #{flag}"
310
+ when NOT
311
+ "#{subject} != #{flag}"
312
+ end
88
313
  end
89
314
 
90
315
  end
@@ -4,28 +4,18 @@ module GrapeListing
4
4
  private
5
5
 
6
6
  def sort_proc
7
- # проверка необходимости нестандартной сортировки
8
- if !@sortable.is_a?(Array) || @sortable.empty? || custom_sorting_field.blank?
9
- return proc { order('id desc') }
10
- end
11
-
7
+ @sort_by = @params['sort_by'] || 'id'
12
8
  @sort_order = @params['sort_order'] || 'desc'
13
- sorting_scope = custom_sorting_field.split('|')[1]
9
+ custom_scope = @custom_sort[@sort_by]
14
10
 
15
- if sorting_scope
16
- direction = @sort_order
17
- proc { send(sorting_scope, direction) }
11
+ if custom_scope
12
+ proc { send(custom_scope, @sort_order) }
18
13
  else
19
14
  nulls_order = @sort_order.to_s.downcase == 'desc' ? 'LAST' : 'FIRST'
20
- query = Arel.sql("#{custom_sorting_field} #{@sort_order} NULLS #{nulls_order}")
15
+ query = Arel.sql("#{@sort_by} #{@sort_order} NULLS #{nulls_order}")
21
16
  proc { order(query) }
22
17
  end
23
18
  end
24
19
 
25
- # поиск поля для индивидуальной сортировки среди допустимых
26
- def custom_sorting_field
27
- @custom_sorting_field ||= @sortable.select { |i| i.split('|')[0] == @params['sort_by'] }.first
28
- end
29
-
30
20
  end
31
21
  end
@@ -97,7 +97,6 @@ module GrapeListing
97
97
  def spreadsheet_titles
98
98
  values = []
99
99
  entity_doc = @grape_entity.documentation
100
- model_cols = @model.columns_hash
101
100
 
102
101
  spreadsheet_cols.each do |column|
103
102
  # исключение: колонка с ИД
@@ -107,7 +106,7 @@ module GrapeListing
107
106
  end
108
107
 
109
108
  # поиск описания среди комментариев к таблице БД
110
- db_col = model_cols[column.to_s]
109
+ db_col = @model_cols[column.to_s]
111
110
  if db_col&.comment&.present?
112
111
  values << db_col.comment
113
112
  next
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape-listing
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.9.1
4
+ version: 2.0.0.pre.beta.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Павел Бабин
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-14 00:00:00.000000000 Z
11
+ date: 2025-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -116,9 +116,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
116
  version: 3.1.0
117
117
  required_rubygems_version: !ruby/object:Gem::Requirement
118
118
  requirements:
119
- - - ">="
119
+ - - ">"
120
120
  - !ruby/object:Gem::Version
121
- version: '0'
121
+ version: 1.3.1
122
122
  requirements: []
123
123
  rubygems_version: 3.3.3
124
124
  signing_key: