jsonapi.rb 1.6.0 → 2.0.1

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.
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe UsersController, type: :request do
4
+ describe 'GET /users' do
5
+ let!(:user) { }
6
+ let(:params) { }
7
+
8
+ before do
9
+ get(users_path, params: params, headers: jsonapi_headers)
10
+ end
11
+
12
+ context 'with users' do
13
+ let(:first_user) { create_user }
14
+ let(:second_user) { create_user }
15
+ let(:third_user) { create_note.user }
16
+ let(:users) { [first_user, second_user, third_user] }
17
+ let(:user) { users.last }
18
+ let(:note) { third_user.notes.first }
19
+
20
+ context 'returns customers and dasherized first name' do
21
+ let(:params) do
22
+ { upcase: :yes, fields: { unknown: nil } }
23
+ end
24
+
25
+ it do
26
+ expect(response).to have_http_status(:ok)
27
+ expect(response_json['data'].size).to eq(users.size)
28
+
29
+ response_json['data'].each do |item|
30
+ user = users.detect { |u| u.id == item['id'].to_i }
31
+ expect(item).to have_attribute('first_name')
32
+ .with_value(user.first_name.upcase)
33
+ end
34
+ end
35
+ end
36
+
37
+ context 'returns customers included and sparse fields' do
38
+ let(:params) do
39
+ {
40
+ include: 'notes',
41
+ fields: { note: 'title,updated_at' }
42
+ }
43
+ end
44
+
45
+ it do
46
+ expect(response).to have_http_status(:ok)
47
+ expect(response_json['data'].last)
48
+ .to have_relationship(:notes)
49
+ .with_data([
50
+ { 'id' => note.id.to_s, 'type' => 'note' }
51
+ ])
52
+ expect(response_json['included']).to include(
53
+ 'id' => note.id.to_s,
54
+ 'type' => 'note',
55
+ 'relationships' => {},
56
+ 'attributes' => {
57
+ 'title' => note.title,
58
+ 'updated_at' => note.updated_at.as_json
59
+ }
60
+ )
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe UsersController, type: :request do
4
+ describe '#extract_attributes_and_predicate' do
5
+ context 'mixed attributes (and/or)' do
6
+ it 'extracts ANDs' do
7
+ attributes, predicates = JSONAPI::Filtering
8
+ .extract_attributes_and_predicates('attr1_and_attr2_eq')
9
+ expect(attributes).to eq(['attr1', 'attr2'])
10
+ expect(predicates.size).to eq(1)
11
+ expect(predicates[0].name).to eq('eq')
12
+ end
13
+ end
14
+
15
+ context 'mixed predicates' do
16
+ it 'extracts in order' do
17
+ attributes, predicates = JSONAPI::Filtering
18
+ .extract_attributes_and_predicates('attr1_sum_eq')
19
+ expect(attributes).to eq(['attr1'])
20
+ expect(predicates.size).to eq(2)
21
+ expect(predicates[0].name).to eq('sum')
22
+ expect(predicates[1].name).to eq('eq')
23
+ end
24
+ end
25
+ end
26
+
27
+ describe 'GET /users' do
28
+ let!(:user) { }
29
+ let(:params) { }
30
+
31
+ before do
32
+ get(users_path, params: params, headers: jsonapi_headers)
33
+ end
34
+
35
+ context 'with users' do
36
+ let(:first_user) { create_user }
37
+ let(:second_user) { create_note.user }
38
+ let(:third_user) { create_user }
39
+ let(:users) { [first_user, second_user, third_user] }
40
+ let(:user) { users.last }
41
+
42
+ context 'returns filtered users' do
43
+ let(:params) do
44
+ {
45
+ filter: {
46
+ last_name_or_first_name_cont_any: (
47
+ "#{third_user.first_name[0..5]}%,#{self.class.name}"
48
+ )
49
+ }
50
+ }
51
+ end
52
+
53
+ it do
54
+ expect(response).to have_http_status(:ok)
55
+ expect(response_json['data'].size).to eq(1)
56
+ expect(response_json['data'][0]).to have_id(third_user.id.to_s)
57
+ end
58
+
59
+ context 'with a comma' do
60
+ let(:params) do
61
+ third_user.update(first_name: third_user.first_name + ',')
62
+
63
+ {
64
+ filter: { first_name_eq: third_user.first_name }
65
+ }
66
+ end
67
+
68
+ it do
69
+ expect(response).to have_http_status(:ok)
70
+ expect(response_json['data'].size).to eq(1)
71
+ expect(response_json['data'][0]).to have_id(third_user.id.to_s)
72
+ end
73
+ end
74
+ end
75
+
76
+ context 'returns sorted users by notes quantity sum' do
77
+ let(:params) do
78
+ { sort: '-notes_quantity_sum' }
79
+ end
80
+
81
+ it do
82
+ expect(response).to have_http_status(:ok)
83
+ expect(response_json['data'].size).to eq(3)
84
+ expect(response_json['data'][0]).to have_id(second_user.id.to_s)
85
+ end
86
+ end
87
+
88
+ context 'returns sorted users by notes' do
89
+ let(:params) do
90
+ { sort: '-notes_created_at' }
91
+ end
92
+
93
+ it do
94
+ expect(response).to have_http_status(:ok)
95
+ expect(response_json['data'].size).to eq(3)
96
+ expect(response_json['data'][0]).to have_id(second_user.id.to_s)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,246 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe UsersController, type: :request do
4
+ describe 'GET /users' do
5
+ let!(:user) { }
6
+ let(:params) do
7
+ {
8
+ page: { number: 'Nan', size: 'NaN' },
9
+ sort: '-created_at'
10
+ }
11
+ end
12
+
13
+ before do
14
+ get(users_path, params: params, headers: jsonapi_headers)
15
+ end
16
+
17
+ it do
18
+ expect(response_json['data'].size).to eq(0)
19
+ expect(response_json['meta'])
20
+ .to eq(
21
+ 'many' => true,
22
+ 'pagination' => {
23
+ 'current' => 1,
24
+ 'records' => 0
25
+ }
26
+ )
27
+ end
28
+
29
+ context 'with users' do
30
+ let(:first_user) { create_user }
31
+ let(:second_user) { create_user }
32
+ let(:third_user) { create_user }
33
+ let(:users) { [first_user, second_user, third_user] }
34
+ let(:user) { users.last }
35
+
36
+ context 'returns users with pagination links' do
37
+ it do
38
+ expect(response).to have_http_status(:ok)
39
+ expect(response_json['data'].size).to eq(3)
40
+ expect(response_json['data'][0]).to have_id(third_user.id.to_s)
41
+ expect(response_json['data'][1]).to have_id(second_user.id.to_s)
42
+ expect(response_json['data'][2]).to have_id(first_user.id.to_s)
43
+
44
+ expect(response_json).to have_link('self')
45
+ expect(response_json).not_to have_link(:prev)
46
+ expect(response_json).not_to have_link(:next)
47
+ expect(response_json).not_to have_link(:first)
48
+ expect(response_json).not_to have_link(:last)
49
+
50
+ expect(URI.parse(response_json['links']['self']).query)
51
+ .to eq(CGI.unescape(params.to_query))
52
+ end
53
+
54
+ context 'on page 2 out of 3' do
55
+ let(:as_list) { }
56
+ let(:params) do
57
+ {
58
+ page: { number: 2, size: 1 },
59
+ sort: '-created_at',
60
+ as_list: as_list
61
+ }.reject { |_k, _v| _v.blank? }
62
+ end
63
+
64
+ context 'on an array of resources' do
65
+ let(:as_list) { true }
66
+
67
+ it do
68
+ expect(response).to have_http_status(:ok)
69
+ expect(response_json['data'].size).to eq(1)
70
+ expect(response_json['data'][0]).to have_id(second_user.id.to_s)
71
+
72
+ expect(response_json['meta']['pagination']).to eq(
73
+ 'current' => 2,
74
+ 'first' => 1,
75
+ 'prev' => 1,
76
+ 'next' => 3,
77
+ 'last' => 3,
78
+ 'records' => 3
79
+ )
80
+ end
81
+ end
82
+
83
+ it do
84
+ expect(response).to have_http_status(:ok)
85
+ expect(response_json['data'].size).to eq(1)
86
+ expect(response_json['data'][0]).to have_id(second_user.id.to_s)
87
+
88
+ expect(response_json['meta']['pagination']).to eq(
89
+ 'current' => 2,
90
+ 'first' => 1,
91
+ 'prev' => 1,
92
+ 'next' => 3,
93
+ 'last' => 3,
94
+ 'records' => 3
95
+ )
96
+
97
+ expect(response_json).to have_link(:self)
98
+ expect(response_json).to have_link(:prev)
99
+ expect(response_json).to have_link(:first)
100
+ expect(response_json).to have_link(:next)
101
+ expect(response_json).to have_link(:last)
102
+
103
+ qry = CGI.unescape(params.to_query)
104
+ expect(URI.parse(response_json['links']['self']).query).to eq(qry)
105
+
106
+ qry = CGI.unescape(params.deep_merge(page: { number: 2 }).to_query)
107
+ expect(URI.parse(response_json['links']['self']).query).to eq(qry)
108
+
109
+ qry = CGI.unescape(params.deep_merge(page: { number: 1 }).to_query)
110
+ expect(URI.parse(response_json['links']['prev']).query).to eq(qry)
111
+ expect(URI.parse(response_json['links']['first']).query).to eq(qry)
112
+
113
+ qry = CGI.unescape(params.deep_merge(page: { number: 3 }).to_query)
114
+ expect(URI.parse(response_json['links']['next']).query).to eq(qry)
115
+ expect(URI.parse(response_json['links']['last']).query).to eq(qry)
116
+ end
117
+ end
118
+
119
+ context 'on page 3 out of 3' do
120
+ let(:params) do
121
+ {
122
+ page: { number: 3, size: 1 }
123
+ }
124
+ end
125
+
126
+ it do
127
+ expect(response).to have_http_status(:ok)
128
+ expect(response_json['data'].size).to eq(1)
129
+
130
+ expect(response_json['meta']['pagination']).to eq(
131
+ 'current' => 3,
132
+ 'first' => 1,
133
+ 'prev' => 2,
134
+ 'records' => 3
135
+ )
136
+
137
+ expect(response_json).to have_link(:self)
138
+ expect(response_json).to have_link(:prev)
139
+ expect(response_json).to have_link(:first)
140
+ expect(response_json).not_to have_link(:next)
141
+ expect(response_json).not_to have_link(:last)
142
+
143
+ expect(URI.parse(response_json['links']['self']).query)
144
+ .to eq(CGI.unescape(params.to_query))
145
+
146
+ qry = CGI.unescape(params.deep_merge(page: { number: 2 }).to_query)
147
+ expect(URI.parse(response_json['links']['prev']).query).to eq(qry)
148
+
149
+ qry = CGI.unescape(params.deep_merge(page: { number: 1 }).to_query)
150
+ expect(URI.parse(response_json['links']['first']).query).to eq(qry)
151
+ end
152
+ end
153
+
154
+ context 'on paging beyond the last page' do
155
+ let(:as_list) { }
156
+ let(:params) do
157
+ {
158
+ page: { number: 5, size: 1 },
159
+ as_list: as_list
160
+ }.reject { |_k, _v| _v.blank? }
161
+ end
162
+
163
+ context 'on an array of resources' do
164
+ let(:as_list) { true }
165
+
166
+ it do
167
+ expect(response).to have_http_status(:ok)
168
+ expect(response_json['data'].size).to eq(0)
169
+
170
+ expect(response_json['meta']['pagination']).to eq(
171
+ 'current' => 5,
172
+ 'first' => 1,
173
+ 'prev' => 4,
174
+ 'records' => 3
175
+ )
176
+ end
177
+ end
178
+
179
+ it do
180
+ expect(response).to have_http_status(:ok)
181
+ expect(response_json['data'].size).to eq(0)
182
+
183
+ expect(response_json['meta']['pagination']).to eq(
184
+ 'current' => 5,
185
+ 'first' => 1,
186
+ 'prev' => 4,
187
+ 'records' => 3
188
+ )
189
+
190
+ expect(response_json).to have_link(:self)
191
+ expect(response_json).to have_link(:prev)
192
+ expect(response_json).to have_link(:first)
193
+ expect(response_json).not_to have_link(:next)
194
+ expect(response_json).not_to have_link(:last)
195
+
196
+ expect(URI.parse(response_json['links']['self']).query)
197
+ .to eq(CGI.unescape(params.to_query))
198
+
199
+ qry = CGI.unescape(params.deep_merge(page: { number: 4 }).to_query)
200
+ expect(URI.parse(response_json['links']['prev']).query).to eq(qry)
201
+
202
+ qry = CGI.unescape(params.deep_merge(page: { number: 1 }).to_query)
203
+ expect(URI.parse(response_json['links']['first']).query).to eq(qry)
204
+ end
205
+ end
206
+
207
+ context 'on page 1 out of 3' do
208
+ let(:params) do
209
+ {
210
+ page: { size: 1 },
211
+ sort: '-created_at'
212
+ }
213
+ end
214
+
215
+ it do
216
+ expect(response).to have_http_status(:ok)
217
+ expect(response_json['data'].size).to eq(1)
218
+ expect(response_json['data'][0]).to have_id(third_user.id.to_s)
219
+
220
+ expect(response_json['meta']['pagination']).to eq(
221
+ 'current' => 1,
222
+ 'next' => 2,
223
+ 'last' => 3,
224
+ 'records' => 3
225
+ )
226
+
227
+ expect(response_json).not_to have_link(:prev)
228
+ expect(response_json).not_to have_link(:first)
229
+ expect(response_json).to have_link(:next)
230
+ expect(response_json).to have_link(:self)
231
+ expect(response_json).to have_link(:last)
232
+
233
+ expect(URI.parse(response_json['links']['self']).query)
234
+ .to eq(CGI.unescape(params.to_query))
235
+
236
+ qry = CGI.unescape(params.deep_merge(page: { number: 2 }).to_query)
237
+ expect(URI.parse(response_json['links']['next']).query).to eq(qry)
238
+
239
+ qry = CGI.unescape(params.deep_merge(page: { number: 3 }).to_query)
240
+ expect(URI.parse(response_json['links']['last']).query).to eq(qry)
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,87 @@
1
+ require 'bundler/setup'
2
+ require 'simplecov'
3
+
4
+ SimpleCov.start do
5
+ add_group 'Lib', 'lib'
6
+ add_group 'Tests', 'spec'
7
+ end
8
+ SimpleCov.minimum_coverage 90
9
+
10
+ require 'dummy'
11
+ require 'ffaker'
12
+ require 'rspec/rails'
13
+ require 'jsonapi/rspec'
14
+
15
+ RSpec.configure do |config|
16
+ config.include JSONAPI::RSpec
17
+
18
+ config.use_transactional_fixtures = true
19
+ config.mock_with :rspec
20
+ config.filter_run_when_matching :focus
21
+ config.disable_monkey_patching!
22
+
23
+ config.expect_with :rspec do |c|
24
+ c.syntax = :expect
25
+ end
26
+ end
27
+
28
+ module RSpecHelpers
29
+ include Dummy.routes.url_helpers
30
+
31
+ # Helper to return JSONAPI valid headers
32
+ #
33
+ # @return [Hash] the relevant content type &co
34
+ def jsonapi_headers
35
+ { 'Content-Type': Mime[:jsonapi].to_s }
36
+ end
37
+
38
+ # Parses and returns a deserialized JSON
39
+ #
40
+ # @return [Hash]
41
+ def response_json
42
+ JSON.parse(response.body)
43
+ end
44
+
45
+ # Creates an user
46
+ #
47
+ # @return [User]
48
+ def create_user
49
+ User.create!(
50
+ first_name: FFaker::Name.first_name,
51
+ last_name: FFaker::Name.last_name
52
+ )
53
+ end
54
+
55
+ # Creates a note
56
+ #
57
+ # @return [Note]
58
+ def create_note(user = nil)
59
+ Note.create!(
60
+ title: FFaker::Company.name,
61
+ quantity: rand(10),
62
+ user: (user || create_user)
63
+ )
64
+ end
65
+ end
66
+
67
+ module Rails4RequestMethods
68
+ [:get, :post, :put, :delete].each do |method_name|
69
+ define_method(method_name) do |path, named_args|
70
+ super(
71
+ path,
72
+ named_args.delete(:params),
73
+ named_args.delete(:headers)
74
+ )
75
+ end
76
+ end
77
+ end
78
+
79
+ RSpec.configure do |config|
80
+ config.include RSpecHelpers, type: :request
81
+ config.include RSpecHelpers, type: :controller
82
+
83
+ if ::Rails::VERSION::MAJOR == 4
84
+ config.include Rails4RequestMethods, type: :request
85
+ config.include Rails4RequestMethods, type: :controller
86
+ end
87
+ end
metadata CHANGED
@@ -1,31 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stas Suscov
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-09 00:00:00.000000000 Z
11
+ date: 2022-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializer
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
19
+ version: '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: '2.0'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: ransack
28
+ name: rack
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -39,13 +39,13 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rack
42
+ name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
- type: :runtime
48
+ type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
@@ -53,7 +53,21 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: bundler
56
+ name: ransack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: railties
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - ">="
@@ -67,7 +81,7 @@ dependencies:
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: rails
84
+ name: activerecord
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - ">="
@@ -227,18 +241,8 @@ executables: []
227
241
  extensions: []
228
242
  extra_rdoc_files: []
229
243
  files:
230
- - ".github/ISSUE_TEMPLATE.md"
231
- - ".github/PULL_REQUEST_TEMPLATE.md"
232
- - ".github/workflows/ci.yml"
233
- - ".gitignore"
234
- - ".rspec"
235
- - ".rubocop.yml"
236
- - ".yardstick.yml"
237
- - Gemfile
238
244
  - LICENSE.txt
239
245
  - README.md
240
- - Rakefile
241
- - jsonapi.rb.gemspec
242
246
  - lib/jsonapi.rb
243
247
  - lib/jsonapi/active_model_error_serializer.rb
244
248
  - lib/jsonapi/deserialization.rb
@@ -250,11 +254,18 @@ files:
250
254
  - lib/jsonapi/patches.rb
251
255
  - lib/jsonapi/rails.rb
252
256
  - lib/jsonapi/version.rb
257
+ - spec/deserialization_spec.rb
258
+ - spec/dummy.rb
259
+ - spec/errors_spec.rb
260
+ - spec/fetching_spec.rb
261
+ - spec/filtering_spec.rb
262
+ - spec/pagination_spec.rb
263
+ - spec/spec_helper.rb
253
264
  homepage: https://github.com/stas/jsonapi.rb
254
265
  licenses:
255
266
  - MIT
256
267
  metadata: {}
257
- post_install_message:
268
+ post_install_message: Install manually `ransack` gem before using `JSONAPI::Filtering`!
258
269
  rdoc_options: []
259
270
  require_paths:
260
271
  - lib
@@ -269,8 +280,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
269
280
  - !ruby/object:Gem::Version
270
281
  version: '0'
271
282
  requirements: []
272
- rubygems_version: 3.1.2
273
- signing_key:
283
+ rubygems_version: 3.3.3
284
+ signing_key:
274
285
  specification_version: 4
275
286
  summary: So you say you need JSON:API support in your API...
276
287
  test_files: []
@@ -1,16 +0,0 @@
1
- ## Expected Behavior
2
-
3
-
4
- ## Actual Behavior
5
-
6
-
7
- ## Steps to Reproduce the Problem
8
-
9
- 1.
10
- 2.
11
- 3.
12
-
13
- ## Specifications
14
-
15
- - Version:
16
- - Ruby version:
@@ -1,17 +0,0 @@
1
- ## What is the current behavior?
2
-
3
- <!-- Please describe the current behavior that you are modifying, or link to a
4
- relevant issue. -->
5
-
6
- ## What is the new behavior?
7
-
8
- <!-- Please describe the behavior or changes that are being added here. -->
9
-
10
- ## Checklist
11
-
12
- Please make sure the following requirements are complete:
13
-
14
- - [ ] Tests for the changes have been added (for bug fixes / features)
15
- - [ ] Docs have been reviewed and added / updated if needed (for bug fixes /
16
- features)
17
- - [ ] All automated checks pass (CI/CD)