simple_jsonapi_rails 1.0.0
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 +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +132 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +5 -0
- data/Jenkinsfile +92 -0
- data/LICENSE.txt +22 -0
- data/README.md +245 -0
- data/Rakefile +10 -0
- data/lib/simple_jsonapi/errors/active_model_error.rb +21 -0
- data/lib/simple_jsonapi/errors/active_model_error_serializer.rb +15 -0
- data/lib/simple_jsonapi/errors/active_record/record_not_found_serializer.rb +15 -0
- data/lib/simple_jsonapi/rails/action_controller/jsonapi_helper.rb +122 -0
- data/lib/simple_jsonapi/rails/action_controller/request_validator.rb +40 -0
- data/lib/simple_jsonapi/rails/action_controller.rb +44 -0
- data/lib/simple_jsonapi/rails/extensions/routing.rb +57 -0
- data/lib/simple_jsonapi/rails/extensions.rb +13 -0
- data/lib/simple_jsonapi/rails/railtie.rb +34 -0
- data/lib/simple_jsonapi/rails/test_helpers.rb +20 -0
- data/lib/simple_jsonapi/rails/version.rb +5 -0
- data/lib/simple_jsonapi/rails.rb +13 -0
- data/lib/simple_jsonapi_rails.rb +4 -0
- data/simple_jsonapi_rails.gemspec +33 -0
- data/test/action_controller_test.rb +299 -0
- data/test/dummy/.gitignore +23 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/controllers/api_controller.rb +3 -0
- data/test/dummy/app/controllers/orders/relationships/items_controller.rb +10 -0
- data/test/dummy/app/controllers/orders_controller.rb +38 -0
- data/test/dummy/app/models/order.rb +4 -0
- data/test/dummy/app/serializers/order_serializer.rb +6 -0
- data/test/dummy/config/application.rb +13 -0
- data/test/dummy/config/boot.rb +3 -0
- data/test/dummy/config/database.yml +7 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +44 -0
- data/test/dummy/config/environments/test.rb +44 -0
- data/test/dummy/config/initializers/.keep +0 -0
- data/test/dummy/config/locales/en.yml +2 -0
- data/test/dummy/config/puma.rb +56 -0
- data/test/dummy/config/routes.rb +5 -0
- data/test/dummy/config/secrets.yml +24 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/config.ru +5 -0
- data/test/dummy/db/migrate/20170719143227_create_orders.rb +10 -0
- data/test/dummy/db/seeds.rb +7 -0
- data/test/dummy/log/.keep +0 -0
- data/test/dummy/tmp/.keep +0 -0
- data/test/errors/active_model_error_serializer_test.rb +47 -0
- data/test/errors/active_model_error_test.rb +46 -0
- data/test/errors/active_record/record_not_found_serializer_test.rb +33 -0
- data/test/test_helper.rb +35 -0
- metadata +284 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eb87c3e8f49a4ea784c1aafacf83f82dd69f35ee
|
4
|
+
data.tar.gz: 32c513cf1c662eebf42af0a1bc05b3feca0f8ad5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c1f9d71e8dc53a740a449b48e978a4eff6e14a78cb3a8fa23fd235f0109e9250bb2789045b80daf45bde389c09b26ab3d9320832b9a60f72ba3144ed2b05e121
|
7
|
+
data.tar.gz: eaa046434edc39bd0ee5237dd48845a15acf178da2d76b40c9c7545ca0915ba749c811dbb28df3b3902f37a3fad01fc6faeede4228b5972931a4f61eafdfc7c6
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- '*.gemspec'
|
4
|
+
- 'Gemfile*'
|
5
|
+
- 'test/dummy/**/*'
|
6
|
+
TargetRubyVersion: "2.3"
|
7
|
+
|
8
|
+
# Do not modify any of these rules without running your changes past the `@techleads` Slack group.
|
9
|
+
|
10
|
+
# In order to make CodeClimate run in less than 30 minutes (the time-out), we're temporarily turning off
|
11
|
+
# some of the low-risk, high-frequency cops. This should be a temporary solution. We should either
|
12
|
+
# fix all of these issues across the codebase, or tune the cops to only pull up these issues when they
|
13
|
+
# matter (e.g. setting the max line length to 120 instead of 80)
|
14
|
+
|
15
|
+
Style/StringLiterals:
|
16
|
+
Enabled: false # this cop slows rubocop down too much so CodeClimate times out
|
17
|
+
EnforcedStyle: double_quotes
|
18
|
+
|
19
|
+
Style/NumericLiterals:
|
20
|
+
Enabled: false # this cop slows rubocop down too much so CodeClimate times out
|
21
|
+
|
22
|
+
# Permanent rules below
|
23
|
+
|
24
|
+
Layout/AlignParameters:
|
25
|
+
Enabled: false # we haven't reached consensus yet on what this rule should be
|
26
|
+
|
27
|
+
Layout/CaseIndentation:
|
28
|
+
EnforcedStyle: end
|
29
|
+
IndentOneStep: false
|
30
|
+
|
31
|
+
# Layout/ElseAlignment:
|
32
|
+
# Enabled: false # we haven't reached consensus yet on what this rule should be
|
33
|
+
|
34
|
+
# Layout/MultilineMethodCallIndentation:
|
35
|
+
# Enabled: false # we haven't reached consensus yet on what this rule should be
|
36
|
+
|
37
|
+
Layout/MultilineOperationIndentation:
|
38
|
+
Enabled: true
|
39
|
+
EnforcedStyle: indented
|
40
|
+
|
41
|
+
Lint/AmbiguousRegexpLiteral:
|
42
|
+
Enabled: false # we disagree with this rule
|
43
|
+
|
44
|
+
# Lint/EndAlignment:
|
45
|
+
# EnforcedStyleAlignWith: start_of_line
|
46
|
+
|
47
|
+
Metrics/AbcSize:
|
48
|
+
# There are ~500 methods > 20 as of 3/7/18, vs. ~900 methods > 15 (which is the default)
|
49
|
+
Max: 20
|
50
|
+
|
51
|
+
Metrics/BlockLength:
|
52
|
+
Max: 25
|
53
|
+
ExcludedMethods: ["class_methods", "describe", "included"]
|
54
|
+
|
55
|
+
Metrics/ClassLength:
|
56
|
+
# nearly all classes over 200 lines are old and crufty and should be split up
|
57
|
+
Max: 200
|
58
|
+
|
59
|
+
Metrics/CyclomaticComplexity:
|
60
|
+
# There are ~100 methods > 8 as of 3/7/18, vs. 220 > 6 (which is the default)
|
61
|
+
Max: 8
|
62
|
+
|
63
|
+
Metrics/LineLength:
|
64
|
+
# There are 23k lines > 80 chars, 5k > 120, 2k > 140 chars as of 12/21/17
|
65
|
+
Max: 120
|
66
|
+
|
67
|
+
Metrics/MethodLength:
|
68
|
+
# There are ~100 methods > 35 lines as of 12/21/17
|
69
|
+
Max: 35
|
70
|
+
|
71
|
+
Metrics/ModuleLength:
|
72
|
+
# nearly all modules over 200 lines are old and crufty and should be split up
|
73
|
+
Max: 200
|
74
|
+
|
75
|
+
Metrics/PerceivedComplexity:
|
76
|
+
# There are ~100 methods > 8 as of 3/7/18, vs. ~150 methods > 7 (which is the default)
|
77
|
+
Max: 8
|
78
|
+
|
79
|
+
Naming/VariableNumber:
|
80
|
+
EnforcedStyle: snake_case # `condition_info_1` is easier to read than `condition_info1`
|
81
|
+
|
82
|
+
Performance/Casecmp:
|
83
|
+
Enabled: false # we generally prefer the readability of downcase over the performance of casecmp
|
84
|
+
|
85
|
+
Style/Alias:
|
86
|
+
EnforcedStyle: prefer_alias_method
|
87
|
+
|
88
|
+
Style/BracesAroundHashParameters:
|
89
|
+
Enabled: false # using braces can be a good choice, e.g., `assert_equal expected_json, { "foo" => "bar" }`
|
90
|
+
|
91
|
+
Style/ClassAndModuleChildren:
|
92
|
+
EnforcedStyle: compact
|
93
|
+
Exclude: ["share/**/*"]
|
94
|
+
|
95
|
+
Style/CollectionMethods:
|
96
|
+
Enabled: true
|
97
|
+
PreferredMethods:
|
98
|
+
collect: map
|
99
|
+
collect!: map!
|
100
|
+
inject: reduce
|
101
|
+
detect: find
|
102
|
+
find_all: select
|
103
|
+
|
104
|
+
Style/Documentation:
|
105
|
+
Enabled: false # don't require class and method doc comments
|
106
|
+
|
107
|
+
Style/EmptyMethod:
|
108
|
+
EnforcedStyle: expanded
|
109
|
+
|
110
|
+
Style/FrozenStringLiteralComment:
|
111
|
+
EnforcedStyle: never
|
112
|
+
|
113
|
+
Style/GuardClause:
|
114
|
+
Enabled: false # sometimes guard clauses are appropriate and sometimes they aren't
|
115
|
+
|
116
|
+
Style/IfUnlessModifier:
|
117
|
+
Enabled: false # sometimes its clearer to use traditional if/end conditionals, even if they would fit on one line
|
118
|
+
|
119
|
+
Style/NumericPredicate:
|
120
|
+
Enabled: false # we disagree with this rule
|
121
|
+
|
122
|
+
Style/SymbolArray:
|
123
|
+
Enabled: false # undecided
|
124
|
+
|
125
|
+
Style/TrailingCommaInArguments:
|
126
|
+
EnforcedStyleForMultiline: consistent_comma
|
127
|
+
|
128
|
+
Style/TrailingCommaInLiteral:
|
129
|
+
EnforcedStyleForMultiline: consistent_comma
|
130
|
+
|
131
|
+
Style/WordArray:
|
132
|
+
Enabled: false # sometimes %w makes sense and sometimes it doesn't
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Jenkinsfile
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
pipeline {
|
2
|
+
agent none
|
3
|
+
options {
|
4
|
+
timeout(time: 1, unit: 'HOURS')
|
5
|
+
skipDefaultCheckout()
|
6
|
+
}
|
7
|
+
|
8
|
+
stages {
|
9
|
+
stage("Build Ruby") {
|
10
|
+
agent {
|
11
|
+
node {
|
12
|
+
label 'docker'
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
steps {
|
17
|
+
script {
|
18
|
+
with_ruby_build() {
|
19
|
+
script {
|
20
|
+
uid = sh(returnStdout: true, script: 'stat -c %g .').trim()
|
21
|
+
gid = sh(returnStdout: true, script: 'stat -c %u .').trim()
|
22
|
+
}
|
23
|
+
|
24
|
+
sh "chown -R ${uid}:${gid} vendor/bundle/"
|
25
|
+
sh "rm -rf vendor/bundle/ruby/2.3.0/cache"
|
26
|
+
stash name: 'ruby-bundle', includes: 'vendor/bundle/'
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
stage("Test") {
|
33
|
+
steps {
|
34
|
+
script {
|
35
|
+
node('docker') {
|
36
|
+
checkout([
|
37
|
+
$class: 'GitSCM',
|
38
|
+
branches: scm.branches,
|
39
|
+
doGenerateSubmoduleConfigurations: scm.doGenerateSubmoduleConfigurations,
|
40
|
+
extensions: scm.extensions + [[$class: 'CloneOption', noTags: true, reference: '', shallow: true]],
|
41
|
+
userRemoteConfigs: scm.userRemoteConfigs
|
42
|
+
])
|
43
|
+
try {
|
44
|
+
docker.image('ruby:2.3.3').inside() {
|
45
|
+
sh 'rm -rf vendor/bundle'
|
46
|
+
unstash 'ruby-bundle'
|
47
|
+
sh 'bundle install --path=vendor/bundle'
|
48
|
+
|
49
|
+
withEnv([
|
50
|
+
'DISABLE_SPRING=1',
|
51
|
+
'TZ=America/New_York'
|
52
|
+
]) {
|
53
|
+
sh 'bundle exec rake test'
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
finally {
|
58
|
+
junit 'test/reports/'
|
59
|
+
cleanWs()
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
post {
|
68
|
+
failure {
|
69
|
+
script {
|
70
|
+
if (env.BRANCH_NAME == 'master' || env.BRANCH_NAME == 'current') {
|
71
|
+
slackSend (channel: '#plm_website', color: '#FF0000', message: "FAILED ${env.JOB_NAME} [${env.BUILD_NUMBER}] (${env.RUN_DISPLAY_URL})")
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
def with_ruby_build(closure) {
|
79
|
+
docker.image('ruby:2.3.3').inside() {
|
80
|
+
checkout([
|
81
|
+
$class: 'GitSCM',
|
82
|
+
branches: scm.branches,
|
83
|
+
doGenerateSubmoduleConfigurations: scm.doGenerateSubmoduleConfigurations,
|
84
|
+
extensions: scm.extensions + [[$class: 'CloneOption', noTags: true, reference: '', shallow: true]],
|
85
|
+
userRemoteConfigs: scm.userRemoteConfigs
|
86
|
+
])
|
87
|
+
sh 'rm -rf vendor/bundle'
|
88
|
+
sh 'bundle install --path=vendor/bundle'
|
89
|
+
closure()
|
90
|
+
cleanWs()
|
91
|
+
}
|
92
|
+
}
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016-2018 PatientsLikeMe
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
# SimpleJsonapi/Rails
|
2
|
+
|
3
|
+
A library for integrating SimpleJsonapi into a Rails application.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add **simple\_jsonapi\_rails** to your Gemfile and include **SimpleJsonapi::Rails::ActionController** in your API controllers:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# Gemfile
|
11
|
+
gem 'simple_jsonapi_rails'
|
12
|
+
|
13
|
+
# app/controllers/api_controller.rb
|
14
|
+
class ApiController < ApplicationController
|
15
|
+
include SimpleJsonapi::Rails::ActionController
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
## Controllers, parsing, and rendering
|
20
|
+
|
21
|
+
### Index actions
|
22
|
+
|
23
|
+
Render a collection of resources with **`render jsonapi_resources:`**.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class OrdersController < ApiController
|
27
|
+
def index
|
28
|
+
orders = Order.all # search, sort, paginate, etc.
|
29
|
+
render jsonapi_resources: orders
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Any additional parameters will be passed through to the serializer.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class OrdersController < ApiController
|
38
|
+
def index
|
39
|
+
orders = Order.all
|
40
|
+
render jsonapi_resources: orders,
|
41
|
+
serializer: OrderSerializer,
|
42
|
+
fields: jsonapi.fields_params,
|
43
|
+
include: jsonapi.include_params,
|
44
|
+
sort_related: jsonapi.sort_related_params,
|
45
|
+
links: { self: "https://example.com/orders" },
|
46
|
+
meta: { generated_at: Time.current },
|
47
|
+
extras: { current_user: @current_user }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
### Show actions
|
53
|
+
|
54
|
+
Render a single resource with **`render jsonapi_resource:`**. Any additional parameters will be passed through to the serializer.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class OrdersController < ApiController
|
58
|
+
def show
|
59
|
+
order = Order.find(params[:id])
|
60
|
+
render jsonapi_resource: order,
|
61
|
+
serializer: OrderSerializer,
|
62
|
+
...
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
### Create and update actions
|
68
|
+
|
69
|
+
Incoming JSON:API documents can be parsed into a more Rails-friendly structure by calling **`jsonapi_deserialize`** in the controller class.
|
70
|
+
|
71
|
+
`jsonapi_deserialize` converts this incoming document ...
|
72
|
+
|
73
|
+
```json
|
74
|
+
{
|
75
|
+
"data": {
|
76
|
+
"type": "orders",
|
77
|
+
"id": "1",
|
78
|
+
"attributes": {
|
79
|
+
"customer_name": "Jose",
|
80
|
+
"date": "2017-10-01",
|
81
|
+
},
|
82
|
+
"relationships": {
|
83
|
+
"customer": {
|
84
|
+
"data": { "type": "customers", "id": "11" }
|
85
|
+
},
|
86
|
+
"products": {
|
87
|
+
"data": [
|
88
|
+
{ "type": "products", "id": "21" },
|
89
|
+
{ "type": "widgets", "id": "22" },
|
90
|
+
]
|
91
|
+
},
|
92
|
+
},
|
93
|
+
}
|
94
|
+
}
|
95
|
+
```
|
96
|
+
|
97
|
+
... to this hash ...
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
{
|
101
|
+
type: "orders",
|
102
|
+
id: "1",
|
103
|
+
customer_name: "Jose",
|
104
|
+
date: "2017-10-01",
|
105
|
+
customer_type: "customers",
|
106
|
+
customer_id: "11",
|
107
|
+
product_types: ["products", "widgets"],
|
108
|
+
product_ids: ["21", "22"],
|
109
|
+
}
|
110
|
+
```
|
111
|
+
|
112
|
+
... which can then be saved with typical Rails actions.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
class OrdersController < ApiController
|
116
|
+
jsonapi_deserialize :order, only: [:create, :update]
|
117
|
+
|
118
|
+
def create
|
119
|
+
order = Order.create!(order_params)
|
120
|
+
render jsonapi_resource: order, status: :created
|
121
|
+
end
|
122
|
+
|
123
|
+
def update
|
124
|
+
order = Order.find(params[:id]).update!(order_params)
|
125
|
+
render jsonapi_resource: order, status: :ok
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
def order_params
|
130
|
+
params.require(:order).permit(:customer_name, :date)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
Note the use of bang methods (`#create!` and `#update!`). While not necessary, `ActiveRecord::RecordNotFound`, `::RecordInvalid`, and `::RecordNotSaved` exceptions will be rescued and rendered automatically.
|
136
|
+
|
137
|
+
You can also render errors explicitly with **`render jsonapi_errors:`**.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class OrdersController < ApiController
|
141
|
+
jsonapi_deserialize :order, only: [:create, :update]
|
142
|
+
|
143
|
+
def create
|
144
|
+
order = Order.new(order_params)
|
145
|
+
if order.save
|
146
|
+
render jsonapi_resource: order, status: :created
|
147
|
+
else
|
148
|
+
error = SimpleJsonapi::Errors::WrappedError.new(...)
|
149
|
+
render jsonapi_errors: error, status: :unprocessable_entity
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
## Error handling
|
156
|
+
|
157
|
+
In addition to restructuring the incoming JSON, `jsonapi_deserialize` also stores a collection of pointers that can be used to render errors.
|
158
|
+
|
159
|
+
Calling **`jsonapi.pointers`** in a controller action returns the following structure.
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
# OrdersController#create
|
163
|
+
jsonapi.pointers
|
164
|
+
=> {
|
165
|
+
type: "/data/type",
|
166
|
+
id: "/data/id",
|
167
|
+
customer_name: "/data/attributes/customer_name",
|
168
|
+
date: "/data/attributes/date",
|
169
|
+
customer_type: "/data/relationships/customer",
|
170
|
+
customer_id: "/data/relationships/customer",
|
171
|
+
product_types: "/data/relationships/products",
|
172
|
+
product_ids: "/data/relationships/products",
|
173
|
+
}
|
174
|
+
```
|
175
|
+
|
176
|
+
SimpleJsonapi/Rails also provides helpers for rendering several Rails-specific errors.
|
177
|
+
|
178
|
+
**`SimpleJsonapi::Errors::ActiveModelError`** converts an `ActiveModel::Errors` object to an array of serializable errors. This is done automatically in response to `AR::RecordInvalid` or `AR::RecordNotSaved`.
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
SimpleJsonapi::Errors::ActiveModelError.from_errors(order.errors, jsonapi.pointers)
|
182
|
+
=> [
|
183
|
+
<SimpleJsonapi::Errors::ActiveModelError
|
184
|
+
status: "422",
|
185
|
+
code: "unprocessable_entity",
|
186
|
+
title: "Invalid customer_name",
|
187
|
+
detail: "Customer name must be present",
|
188
|
+
source: { pointer: "/data/attributes/customer_name" }
|
189
|
+
>, ...
|
190
|
+
]
|
191
|
+
```
|
192
|
+
|
193
|
+
A serializer is provided for `ActiveRecord::RecordNotFound` errors, so they can be rendered directly.
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
error = ActiveRecord::RecordNotFound.new(...)
|
197
|
+
render jsonapi_errors: error, status: :not_found
|
198
|
+
```
|
199
|
+
|
200
|
+
## Request Validation
|
201
|
+
|
202
|
+
SimpleJsonapi/Rails performs some basic request validations via two `before_action`s,
|
203
|
+
`validate_jsonapi_request_headers`, and `validate_jsonapi_request_body`.
|
204
|
+
|
205
|
+
The `Content-Type` header must be set to `application/vnd+api.json` if there is a response body present. If it is not a
|
206
|
+
head response will be returned with status 415 Unsupported Media Type.
|
207
|
+
|
208
|
+
The `Accept` header, if it present, must also be set to `application/vnd+api.json`. If it is not a head response will be
|
209
|
+
returned with status 406 Not Acceptable.
|
210
|
+
|
211
|
+
## Routing
|
212
|
+
Rails' route mapper works well for most jsonapi resource routes. However, simple_jsonapi_rails does supply helpers method
|
213
|
+
for defining relationship routes, which can be trickier:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
# config/routes.rb
|
217
|
+
resources :orders do
|
218
|
+
jsonapi_to_many_relationship(:orders, :items)
|
219
|
+
jsonapi_to_one_relationship(:orders, :customer)
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
This code generates the following paths and routes/controller action mappings.
|
224
|
+
```
|
225
|
+
orders_relationships_items POST /orders/:order_id/relationships/items(.:format) orders/relationships/items#add
|
226
|
+
orders_relationships_items DELETE /orders/:order_id/relationships/items(.:format) orders/relationships/items#remove
|
227
|
+
orders_relationships_items PATCH /orders/:order_id/relationships/items(.:format) orders/relationships/items#replace
|
228
|
+
|
229
|
+
orders_relationships_customer PATCH /orders/:order_id/customer(.:format) orders/relationships/customer#replace
|
230
|
+
|
231
|
+
```
|
232
|
+
|
233
|
+
## Running tests
|
234
|
+
|
235
|
+
1. Change to the gem's directory
|
236
|
+
2. Run `bundle install`
|
237
|
+
3. Run `bundle exec rake test`
|
238
|
+
|
239
|
+
## Release Process
|
240
|
+
Once pull request is merged to master, on latest master:
|
241
|
+
1. Update CHANGELOG.md. Version: [ major (breaking change: non-backwards
|
242
|
+
compatible release) | minor (new features) | patch (bugfixes) ]
|
243
|
+
2. Update version in lib/global_enforcer/version.rb
|
244
|
+
3. Release by running `bundle exec rake release`
|
245
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module SimpleJsonapi
|
2
|
+
module Errors
|
3
|
+
class ActiveModelError
|
4
|
+
def self.from_errors(errors, pointer_mapping = {})
|
5
|
+
errors.keys.flat_map do |attribute|
|
6
|
+
errors.full_messages_for(attribute).map do |message|
|
7
|
+
new(attribute, message, pointer_mapping[attribute])
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :attribute, :message, :pointer
|
13
|
+
|
14
|
+
def initialize(attribute, message, pointer)
|
15
|
+
@attribute = attribute.to_s
|
16
|
+
@message = message.to_s
|
17
|
+
@pointer = pointer.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SimpleJsonapi
|
2
|
+
module Errors
|
3
|
+
class ActiveModelErrorSerializer < ErrorSerializer
|
4
|
+
status "422"
|
5
|
+
code "unprocessable_entity"
|
6
|
+
|
7
|
+
title { |err| "Invalid #{err.attribute.presence || 'record'}" }
|
8
|
+
detail { |err| err.message }
|
9
|
+
|
10
|
+
source do
|
11
|
+
pointer(if: ->(err) { err.pointer.present? }) { |err| err.pointer }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SimpleJsonapi
|
2
|
+
module Errors
|
3
|
+
module ActiveRecord
|
4
|
+
class RecordNotFoundSerializer < SimpleJsonapi::ErrorSerializer
|
5
|
+
status "404"
|
6
|
+
code "not_found"
|
7
|
+
title "Not found"
|
8
|
+
detail { |ex| ex.message }
|
9
|
+
source do
|
10
|
+
parameter { |ex| ex.primary_key }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|