jsonapi.rb 1.6.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)