rails_api_kit 1.0.0 → 1.0.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 +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +2 -4
- data/lib/api_kit/fetching.rb +1 -3
- data/lib/api_kit/pagination.rb +13 -2
- data/lib/api_kit/rails_app.rb +71 -11
- data/lib/api_kit/version.rb +1 -1
- data/spec/dummy.rb +57 -0
- data/spec/filtering_spec.rb +29 -1
- data/spec/pagination_spec.rb +46 -0
- data/spec/sparse_fields_type_key_spec.rb +87 -0
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 398056ff21cfe533837881bebaf65d87fff8c5abba87d5cdf8a9c708d55c90ef
|
|
4
|
+
data.tar.gz: 0b9de9c9f63c4312b898a03d5b8b142a57f82f7a353588f656a0b3cc8da2d290
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d965e94656b77eec9b146898305aed13c67aa744c589e101065f87a828433e1a99e9677145d79285b0e630d7c26d31c0f1e0c63365b281f84007477e235a1afe
|
|
7
|
+
data.tar.gz: 45b408f991bbf95bc9341396249e2d12900a906cc0359120a6e1877607c4c41f1898fb79862db405fc13a874087ddca3aecafee5014beabf40b6421d841ad079
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# RailsApiKit
|
|
2
2
|
|
|
3
3
|
A lightweight Rails toolkit for building standardized, structured API responses with serialization, error handling, filtering, sorting, and pagination.
|
|
4
4
|
|
|
5
|
-
> Building clean, consistent APIs shouldn't be rocket science. ApiKit provides simple, powerful modules to get you up and running quickly.
|
|
6
|
-
|
|
7
5
|
## Features
|
|
8
6
|
|
|
9
7
|
ApiKit offers a collection of lightweight modules that integrate seamlessly with your Rails controllers:
|
|
@@ -294,7 +292,7 @@ This gem leverages these excellent libraries:
|
|
|
294
292
|
|
|
295
293
|
## Contributing
|
|
296
294
|
|
|
297
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
295
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ismailakbudak/rails_api_kit
|
|
298
296
|
|
|
299
297
|
This project follows the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
300
298
|
|
data/lib/api_kit/fetching.rb
CHANGED
|
@@ -12,9 +12,7 @@ module ApiKit
|
|
|
12
12
|
|
|
13
13
|
result = []
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
model_name ||= serializer_class.name.demodulize.delete_suffix("Serializer").underscore
|
|
17
|
-
end
|
|
15
|
+
model_name ||= ApiKit::RailsApp.serializer_name(serializer_class)
|
|
18
16
|
|
|
19
17
|
keys = []
|
|
20
18
|
params[:fields].each do |k, v|
|
data/lib/api_kit/pagination.rb
CHANGED
|
@@ -49,7 +49,9 @@ module ApiKit
|
|
|
49
49
|
*api_path_parameters.keys.map(&:to_s)
|
|
50
50
|
).as_json.with_indifferent_access
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
# A blank `page=` passed `api_pagination_params` as "no page"; links build a fresh one.
|
|
53
|
+
page = original_params[:page]
|
|
54
|
+
original_params[:page] = page.is_a?(Hash) ? page.dup : {}
|
|
53
55
|
original_url = "?"
|
|
54
56
|
|
|
55
57
|
pagination.each do |page_name, number|
|
|
@@ -132,7 +134,16 @@ module ApiKit
|
|
|
132
134
|
#
|
|
133
135
|
# @return [Array] with the offset, limit and the current page number
|
|
134
136
|
def api_pagination_params
|
|
135
|
-
|
|
137
|
+
page = params[:page]
|
|
138
|
+
# `page=` names no page: the defaults, like a missing `page`.
|
|
139
|
+
page = nil if page.is_a?(String) && page.blank?
|
|
140
|
+
# `page=2` (or a JSON array / number) is not `page[number]=2`: a client error like
|
|
141
|
+
# `params.expect`, not a 500 from #slice.
|
|
142
|
+
unless page.nil? || page.is_a?(ActionController::Parameters) || page.is_a?(Hash)
|
|
143
|
+
raise ActionController::ParameterMissing, :page
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
pagination = page.try(:slice, :number, :size) || {}
|
|
136
147
|
per_page = api_page_size(pagination)
|
|
137
148
|
num = [ 1, pagination[:number].to_f.to_i ].max
|
|
138
149
|
|
data/lib/api_kit/rails_app.rb
CHANGED
|
@@ -75,6 +75,7 @@ module ApiKit
|
|
|
75
75
|
options[:fields] = api_fields(serializer_class, ApiKit::RailsApp.fetch_name(many, resource))
|
|
76
76
|
options[:adapter] = :attributes
|
|
77
77
|
options[:each_serializer] = serializer_class
|
|
78
|
+
ApiKit::RailsApp.assign_collection_root!(options, resource, serializer_class) if many
|
|
78
79
|
data = ActiveModelSerializers::SerializableResource.new(resource, options).as_json
|
|
79
80
|
result[:data] = data
|
|
80
81
|
result.to_json
|
|
@@ -107,6 +108,7 @@ module ApiKit
|
|
|
107
108
|
options[:adapter] = :attributes
|
|
108
109
|
options[:each_serializer] = serializer_class
|
|
109
110
|
if many
|
|
111
|
+
ApiKit::RailsApp.assign_collection_root!(options, resource, serializer_class)
|
|
110
112
|
data = ActiveModelSerializers::SerializableResource.new(resource, options).as_json
|
|
111
113
|
else
|
|
112
114
|
data = ActiveModelSerializers::SerializableResource.new([ resource ], options).as_json[0]
|
|
@@ -137,21 +139,79 @@ module ApiKit
|
|
|
137
139
|
"#{klass.name}Serializer".constantize
|
|
138
140
|
end
|
|
139
141
|
|
|
140
|
-
# Resolves the
|
|
142
|
+
# Resolves the model name used as the sparse-fieldset type key
|
|
143
|
+
#
|
|
144
|
+
# Mirrors `ActiveModel::Serializer#json_key`, which AMS uses to look a
|
|
145
|
+
# type up in the fieldset: `object.class.model_name.to_s.underscore`.
|
|
146
|
+
# NOT `model_name.singular` — that tr()s the namespace separator to an
|
|
147
|
+
# underscore (`manufacturing_work_order`), so for a namespaced model the
|
|
148
|
+
# key never matched, the primary type went unconstrained, and AMS fell
|
|
149
|
+
# through to the pluralised collection key whose value is an empty list:
|
|
150
|
+
# every attribute of the primary resource was silently dropped.
|
|
141
151
|
#
|
|
142
152
|
# @param many [Boolean] indicates whether the resource is a collection
|
|
143
153
|
# @param resource [Object] serialized resource or collection
|
|
144
|
-
# @return [String, nil]
|
|
154
|
+
# @return [String, nil] model name when available
|
|
145
155
|
def self.fetch_name(many, resource)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
record = many ? collection_model(resource) : resource
|
|
157
|
+
model_name = record&.model_name
|
|
158
|
+
model_name && model_name.to_s.underscore
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Root key for a collection AMS cannot infer one for
|
|
162
|
+
#
|
|
163
|
+
# `AMS::CollectionSerializer#json_key` reads the root from its first
|
|
164
|
+
# element, or from a named collection (`ActiveRecord::Relation` answers
|
|
165
|
+
# `#name` through its klass). An EMPTY plain Array offers neither, so it
|
|
166
|
+
# raises `CannotInferRootKeyError` the moment sparse fieldsets are
|
|
167
|
+
# requested. Aggregation endpoints that render POROs hit exactly that.
|
|
168
|
+
#
|
|
169
|
+
# Returns nil whenever AMS can infer the key itself, so a collection that
|
|
170
|
+
# already works keeps its own root and item serializers keep their
|
|
171
|
+
# `json_key`.
|
|
172
|
+
#
|
|
173
|
+
# @param resource [Object] the collection being serialized
|
|
174
|
+
# @param serializer_class [Class, NilClass] serializer for its members
|
|
175
|
+
# @return [String, nil] pluralised root key, or nil to leave it to AMS
|
|
176
|
+
def self.collection_root(resource, serializer_class)
|
|
177
|
+
return nil unless resource.respond_to?(:empty?) && resource.empty?
|
|
178
|
+
return nil if resource.respond_to?(:name)
|
|
179
|
+
|
|
180
|
+
name = serializer_name(serializer_class)
|
|
181
|
+
name && name.pluralize
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Sets the collection root only when AMS could not work one out
|
|
185
|
+
#
|
|
186
|
+
# @param options [Hash] render options, mutated in place
|
|
187
|
+
# @param resource [Object] the collection being serialized
|
|
188
|
+
# @param serializer_class [Class, NilClass] serializer for its members
|
|
189
|
+
# @return [NilClass]
|
|
190
|
+
def self.assign_collection_root!(options, resource, serializer_class)
|
|
191
|
+
return if options[:root]
|
|
192
|
+
|
|
193
|
+
root = collection_root(resource, serializer_class)
|
|
194
|
+
options[:root] = root if root
|
|
195
|
+
nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Resolves the type name a serializer class stands for
|
|
199
|
+
#
|
|
200
|
+
# @param serializer_class [Class, NilClass] e.g. `V1::UserSerializer`
|
|
201
|
+
# @return [String, nil] e.g. `"user"`
|
|
202
|
+
def self.serializer_name(serializer_class)
|
|
203
|
+
name = serializer_class && serializer_class.name
|
|
204
|
+
name && name.demodulize.delete_suffix("Serializer").underscore
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Resolves the record a collection's model name should come from
|
|
208
|
+
#
|
|
209
|
+
# @param resource [Object] the collection
|
|
210
|
+
# @return [Object, NilClass] a record responding to `model_name`
|
|
211
|
+
def self.collection_model(resource)
|
|
212
|
+
return resource if resource.is_a?(ActiveRecord::Relation)
|
|
213
|
+
|
|
214
|
+
resource.respond_to?(:first) ? resource.first : nil
|
|
155
215
|
end
|
|
156
216
|
end
|
|
157
217
|
end
|
data/lib/api_kit/version.rb
CHANGED
data/spec/dummy.rb
CHANGED
|
@@ -28,6 +28,12 @@ ActiveRecord::Schema.define do
|
|
|
28
28
|
t.integer :quantity
|
|
29
29
|
t.timestamps
|
|
30
30
|
end
|
|
31
|
+
|
|
32
|
+
create_table :inventory_items, force: true do |t|
|
|
33
|
+
t.string :name
|
|
34
|
+
t.integer :quantity
|
|
35
|
+
t.timestamps
|
|
36
|
+
end
|
|
31
37
|
end
|
|
32
38
|
|
|
33
39
|
|
|
@@ -94,6 +100,32 @@ class UserSerializer < ActiveModel::Serializer
|
|
|
94
100
|
end
|
|
95
101
|
end
|
|
96
102
|
|
|
103
|
+
# Namespaced model — `model_name.to_s.underscore` is slash-separated
|
|
104
|
+
# (`inventory/item`) while `model_name.singular` is not
|
|
105
|
+
# (`inventory_item`). The sparse-fieldset type key is the former.
|
|
106
|
+
module Inventory
|
|
107
|
+
class Item < ApplicationRecord
|
|
108
|
+
self.table_name = 'inventory_items'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class ItemSerializer < ActiveModel::Serializer
|
|
112
|
+
attributes :id, :name, :quantity
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# A row of an aggregation endpoint: a PORO, not an AR record, rendered as
|
|
117
|
+
# a plain Array. An empty one gives AMS nothing to infer a root key from.
|
|
118
|
+
class ReportRow
|
|
119
|
+
include ActiveModel::Model
|
|
120
|
+
include ActiveModel::Serialization
|
|
121
|
+
|
|
122
|
+
attr_accessor :label, :total
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class ReportRowSerializer < ActiveModel::Serializer
|
|
126
|
+
attributes :label, :total
|
|
127
|
+
end
|
|
128
|
+
|
|
97
129
|
class MyUserSerializer < UserSerializer
|
|
98
130
|
attribute :full_name
|
|
99
131
|
|
|
@@ -110,6 +142,8 @@ class Dummy < Rails::Application
|
|
|
110
142
|
scope defaults: { format: :api } do
|
|
111
143
|
resources :users, only: [ :index, :show ]
|
|
112
144
|
resources :notes, only: [ :update ]
|
|
145
|
+
resources :inventory_items, only: [ :index ]
|
|
146
|
+
resources :reports, only: [ :index ]
|
|
113
147
|
end
|
|
114
148
|
end
|
|
115
149
|
end
|
|
@@ -170,6 +204,29 @@ class UsersController < BaseApplicationController
|
|
|
170
204
|
end
|
|
171
205
|
end
|
|
172
206
|
|
|
207
|
+
class InventoryItemsController < BaseApplicationController
|
|
208
|
+
include ApiKit::Fetching
|
|
209
|
+
|
|
210
|
+
def index
|
|
211
|
+
render api: Inventory::Item.all, serializer_class: Inventory::ItemSerializer
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
class ReportsController < BaseApplicationController
|
|
216
|
+
include ApiKit::Fetching
|
|
217
|
+
|
|
218
|
+
def index
|
|
219
|
+
rows =
|
|
220
|
+
if params[:empty]
|
|
221
|
+
[]
|
|
222
|
+
else
|
|
223
|
+
[ ReportRow.new(label: 'first', total: 1) ]
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
render api: rows, serializer_class: ReportRowSerializer
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
173
230
|
class NotesController < ActionController::Base
|
|
174
231
|
include ApiKit::Fetching
|
|
175
232
|
include ApiKit::Errors
|
data/spec/filtering_spec.rb
CHANGED
|
@@ -40,11 +40,18 @@ RSpec.describe UsersController, type: :request do
|
|
|
40
40
|
let(:user) { users.last }
|
|
41
41
|
|
|
42
42
|
context 'returns filtered users' do
|
|
43
|
+
# FFaker names are random, so filter on a token that no other user
|
|
44
|
+
# can match by accident
|
|
45
|
+
let(:name_prefix) { 'Filterable' }
|
|
46
|
+
let(:filterable_name) { "#{name_prefix}#{third_user.id}" }
|
|
47
|
+
|
|
43
48
|
let(:params) do
|
|
49
|
+
third_user.update(first_name: filterable_name)
|
|
50
|
+
|
|
44
51
|
{
|
|
45
52
|
filter: {
|
|
46
53
|
last_name_or_first_name_cont_any: (
|
|
47
|
-
"#{
|
|
54
|
+
"#{name_prefix},#{self.class.name}"
|
|
48
55
|
)
|
|
49
56
|
}
|
|
50
57
|
}
|
|
@@ -56,6 +63,27 @@ RSpec.describe UsersController, type: :request do
|
|
|
56
63
|
expect(response_json['data'][0]['id']).to eql(third_user.id)
|
|
57
64
|
end
|
|
58
65
|
|
|
66
|
+
# `cont` escapes LIKE wildcards, `matches` passes them through
|
|
67
|
+
context 'with a wildcard' do
|
|
68
|
+
let(:params) do
|
|
69
|
+
third_user.update(first_name: filterable_name)
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
filter: {
|
|
73
|
+
last_name_or_first_name_matches_any: (
|
|
74
|
+
"#{name_prefix}%,#{self.class.name}"
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it do
|
|
81
|
+
expect(response).to have_http_status(:ok)
|
|
82
|
+
expect(response_json['data'].size).to eq(1)
|
|
83
|
+
expect(response_json['data'][0]['id']).to eql(third_user.id)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
59
87
|
context 'with a comma' do
|
|
60
88
|
let(:params) do
|
|
61
89
|
third_user.update(first_name: third_user.first_name + ',')
|
data/spec/pagination_spec.rb
CHANGED
|
@@ -332,4 +332,50 @@ RSpec.describe UsersController, type: :request do
|
|
|
332
332
|
end
|
|
333
333
|
end
|
|
334
334
|
end
|
|
335
|
+
|
|
336
|
+
describe 'GET /users with a scalar page' do
|
|
337
|
+
# `page=2` is not `page[number]=2`: ParameterMissing, a client error, not a 500 from
|
|
338
|
+
# String#slice. This controller has no ApiKit::Errors, so Rails answers 400; with it, 422.
|
|
339
|
+
[ 'page=2', 'page[]=2' ].each do |query|
|
|
340
|
+
it "answers #{query} with a client error" do
|
|
341
|
+
get("#{users_path}?#{query}", headers: api_headers)
|
|
342
|
+
|
|
343
|
+
expect(response).to have_http_status(:bad_request)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
describe 'GET /users with a blank page' do
|
|
349
|
+
# `page=` names no page: the defaults, like a missing `page`.
|
|
350
|
+
[ 'page=', 'page=%20' ].each do |query|
|
|
351
|
+
it "answers #{query} with the first page" do
|
|
352
|
+
get("#{users_path}?#{query}", headers: api_headers)
|
|
353
|
+
|
|
354
|
+
expect(response).to have_http_status(:ok)
|
|
355
|
+
expect(response_json.dig('meta', 'pagination', 'current')).to eq(1)
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
describe '#api_pagination_params with a typed non-hash page' do
|
|
361
|
+
# A JSON body reaches it typed: an array or a number is malformed too, never a #slice error.
|
|
362
|
+
let(:controller_class) do
|
|
363
|
+
Class.new do
|
|
364
|
+
include ApiKit::Pagination
|
|
365
|
+
|
|
366
|
+
attr_reader :params
|
|
367
|
+
|
|
368
|
+
def initialize(params)
|
|
369
|
+
@params = ActionController::Parameters.new(params)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
[ [], 2 ].each do |page|
|
|
375
|
+
it "raises ParameterMissing for #{page.inspect}" do
|
|
376
|
+
expect { controller_class.new(page: page).send(:api_pagination_params) }
|
|
377
|
+
.to raise_error(ActionController::ParameterMissing)
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
335
381
|
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
# The sparse-fieldset type key AMS looks a serializer up by is
|
|
4
|
+
# `model_name.to_s.underscore`. api_kit has to derive the same string when it
|
|
5
|
+
# injects the fallback "everything for the primary type" entry, or AMS falls
|
|
6
|
+
# through to the pluralised collection key — whose value is an empty list.
|
|
7
|
+
RSpec.describe 'sparse fieldsets', type: :request do
|
|
8
|
+
describe 'GET /inventory_items' do
|
|
9
|
+
let!(:item) { Inventory::Item.create!(name: 'Bolt', quantity: 7) }
|
|
10
|
+
|
|
11
|
+
before { get(inventory_items_path, params: params, headers: api_headers) }
|
|
12
|
+
|
|
13
|
+
context 'when fields are sent for the primary type' do
|
|
14
|
+
let(:params) { { fields: { 'inventory/item' => 'id,name' } } }
|
|
15
|
+
|
|
16
|
+
it 'trims the primary type to the requested attributes' do
|
|
17
|
+
expect(response).to have_http_status(:ok)
|
|
18
|
+
expect(response_json['data'].first.keys).to contain_exactly('id', 'name')
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context 'when fields are sent only for another type' do
|
|
23
|
+
let(:params) { { fields: { user: 'id' } } }
|
|
24
|
+
|
|
25
|
+
it 'leaves the namespaced primary type fully populated' do
|
|
26
|
+
expect(response).to have_http_status(:ok)
|
|
27
|
+
expect(response_json['data'].first.keys)
|
|
28
|
+
.to contain_exactly('id', 'name', 'quantity')
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'without any fields param' do
|
|
33
|
+
let(:params) { {} }
|
|
34
|
+
|
|
35
|
+
it 'returns every attribute' do
|
|
36
|
+
expect(response).to have_http_status(:ok)
|
|
37
|
+
expect(response_json['data'].first.keys)
|
|
38
|
+
.to contain_exactly('id', 'name', 'quantity')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# `AMS::CollectionSerializer#json_key` infers the root from the first element,
|
|
44
|
+
# or from a collection that answers `#name` (an `ActiveRecord::Relation` does,
|
|
45
|
+
# through its klass). An empty plain Array answers neither, and raises
|
|
46
|
+
# `CannotInferRootKeyError` as soon as a fieldset has to be built.
|
|
47
|
+
describe 'GET /reports' do
|
|
48
|
+
before { get(reports_path, params: params, headers: api_headers) }
|
|
49
|
+
|
|
50
|
+
context 'with rows and a fields param' do
|
|
51
|
+
let(:params) { { fields: { report_row: 'label' } } }
|
|
52
|
+
|
|
53
|
+
it 'trims the PORO row to the requested attributes' do
|
|
54
|
+
expect(response).to have_http_status(:ok)
|
|
55
|
+
expect(response_json['data'].first.keys).to contain_exactly('label')
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context 'with rows and no fields param' do
|
|
60
|
+
let(:params) { {} }
|
|
61
|
+
|
|
62
|
+
it 'returns every attribute' do
|
|
63
|
+
expect(response).to have_http_status(:ok)
|
|
64
|
+
expect(response_json['data'].first.keys)
|
|
65
|
+
.to contain_exactly('label', 'total')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context 'with no rows and a fields param' do
|
|
70
|
+
let(:params) { { empty: true, fields: { report_row: 'label' } } }
|
|
71
|
+
|
|
72
|
+
it 'renders an empty collection instead of failing to infer a root' do
|
|
73
|
+
expect(response).to have_http_status(:ok)
|
|
74
|
+
expect(response_json['data']).to eq([])
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context 'with no rows and no fields param' do
|
|
79
|
+
let(:params) { { empty: true } }
|
|
80
|
+
|
|
81
|
+
it 'renders an empty collection' do
|
|
82
|
+
expect(response).to have_http_status(:ok)
|
|
83
|
+
expect(response_json['data']).to eq([])
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_api_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ismail Akbudak
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ransack
|
|
@@ -244,9 +244,10 @@ files:
|
|
|
244
244
|
- spec/fetching_spec.rb
|
|
245
245
|
- spec/filtering_spec.rb
|
|
246
246
|
- spec/pagination_spec.rb
|
|
247
|
+
- spec/sparse_fields_type_key_spec.rb
|
|
247
248
|
- spec/spec_helper.rb
|
|
248
249
|
- spec/support/api_kit_rspec.rb
|
|
249
|
-
homepage: https://github.com/
|
|
250
|
+
homepage: https://github.com/ismailakbudak/rails_api_kit
|
|
250
251
|
licenses:
|
|
251
252
|
- MIT
|
|
252
253
|
metadata: {}
|
|
@@ -265,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
265
266
|
- !ruby/object:Gem::Version
|
|
266
267
|
version: '0'
|
|
267
268
|
requirements: []
|
|
268
|
-
rubygems_version: 3.
|
|
269
|
+
rubygems_version: 3.4.19
|
|
269
270
|
signing_key:
|
|
270
271
|
specification_version: 4
|
|
271
272
|
summary: A lightweight toolkit for building JSON:API compliant APIs with Rails.
|