fun_with_json_api 0.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +28 -0
- data/config/locales/fun_with_json_api.en.yml +13 -0
- data/lib/fun_with_json_api/attribute.rb +38 -0
- data/lib/fun_with_json_api/attributes/boolean_attribute.rb +24 -0
- data/lib/fun_with_json_api/attributes/date_attribute.rb +22 -0
- data/lib/fun_with_json_api/attributes/datetime_attribute.rb +20 -0
- data/lib/fun_with_json_api/attributes/decimal_attribute.rb +23 -0
- data/lib/fun_with_json_api/attributes/float_attribute.rb +20 -0
- data/lib/fun_with_json_api/attributes/integer_attribute.rb +20 -0
- data/lib/fun_with_json_api/attributes/relationship.rb +73 -0
- data/lib/fun_with_json_api/attributes/relationship_collection.rb +99 -0
- data/lib/fun_with_json_api/attributes/string_attribute.rb +9 -0
- data/lib/fun_with_json_api/controller_methods.rb +12 -0
- data/lib/fun_with_json_api/deserializer.rb +70 -0
- data/lib/fun_with_json_api/deserializer_class_methods.rb +83 -0
- data/lib/fun_with_json_api/deserializer_config_builder.rb +48 -0
- data/lib/fun_with_json_api/exception.rb +27 -0
- data/lib/fun_with_json_api/exception_payload.rb +29 -0
- data/lib/fun_with_json_api/exception_payload_serializer.rb +17 -0
- data/lib/fun_with_json_api/exception_serializer.rb +15 -0
- data/lib/fun_with_json_api/exceptions/invalid_attribute.rb +13 -0
- data/lib/fun_with_json_api/exceptions/invalid_document.rb +12 -0
- data/lib/fun_with_json_api/exceptions/invalid_relationship.rb +13 -0
- data/lib/fun_with_json_api/exceptions/missing_relationship.rb +15 -0
- data/lib/fun_with_json_api/pre_deserializer.rb +61 -0
- data/lib/fun_with_json_api/railtie.rb +11 -0
- data/lib/fun_with_json_api/version.rb +3 -0
- data/lib/fun_with_json_api.rb +24 -0
- data/lib/tasks/fun_with_json_api_tasks.rake +4 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config/application.rb +25 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +56 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/log/test.log +37839 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/example_spec.rb +64 -0
- data/spec/fixtures/active_record.rb +65 -0
- data/spec/fun_with_json_api/controller_methods_spec.rb +123 -0
- data/spec/fun_with_json_api/deserializer_class_methods_spec.rb +52 -0
- data/spec/fun_with_json_api/deserializer_spec.rb +450 -0
- data/spec/fun_with_json_api/exception_spec.rb +77 -0
- data/spec/fun_with_json_api/pre_deserializer_spec.rb +287 -0
- data/spec/fun_with_json_api_spec.rb +55 -0
- data/spec/spec_helper.rb +33 -0
- metadata +275 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The page you were looking for doesn't exist (404)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body>
|
58
|
+
<!-- This file lives in public/404.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>The page you were looking for doesn't exist.</h1>
|
62
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
63
|
+
</div>
|
64
|
+
<p>If you are the application owner check the logs for more information.</p>
|
65
|
+
</div>
|
66
|
+
</body>
|
67
|
+
</html>
|
@@ -0,0 +1,67 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body>
|
58
|
+
<!-- This file lives in public/422.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>The change you wanted was rejected.</h1>
|
62
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
63
|
+
</div>
|
64
|
+
<p>If you are the application owner check the logs for more information.</p>
|
65
|
+
</div>
|
66
|
+
</body>
|
67
|
+
</html>
|
@@ -0,0 +1,66 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<style>
|
7
|
+
body {
|
8
|
+
background-color: #EFEFEF;
|
9
|
+
color: #2E2F30;
|
10
|
+
text-align: center;
|
11
|
+
font-family: arial, sans-serif;
|
12
|
+
margin: 0;
|
13
|
+
}
|
14
|
+
|
15
|
+
div.dialog {
|
16
|
+
width: 95%;
|
17
|
+
max-width: 33em;
|
18
|
+
margin: 4em auto 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
div.dialog > div {
|
22
|
+
border: 1px solid #CCC;
|
23
|
+
border-right-color: #999;
|
24
|
+
border-left-color: #999;
|
25
|
+
border-bottom-color: #BBB;
|
26
|
+
border-top: #B00100 solid 4px;
|
27
|
+
border-top-left-radius: 9px;
|
28
|
+
border-top-right-radius: 9px;
|
29
|
+
background-color: white;
|
30
|
+
padding: 7px 12% 0;
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
32
|
+
}
|
33
|
+
|
34
|
+
h1 {
|
35
|
+
font-size: 100%;
|
36
|
+
color: #730E15;
|
37
|
+
line-height: 1.5em;
|
38
|
+
}
|
39
|
+
|
40
|
+
div.dialog > p {
|
41
|
+
margin: 0 0 1em;
|
42
|
+
padding: 1em;
|
43
|
+
background-color: #F7F7F7;
|
44
|
+
border: 1px solid #CCC;
|
45
|
+
border-right-color: #999;
|
46
|
+
border-left-color: #999;
|
47
|
+
border-bottom-color: #999;
|
48
|
+
border-bottom-left-radius: 4px;
|
49
|
+
border-bottom-right-radius: 4px;
|
50
|
+
border-top-color: #DADADA;
|
51
|
+
color: #666;
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
53
|
+
}
|
54
|
+
</style>
|
55
|
+
</head>
|
56
|
+
|
57
|
+
<body>
|
58
|
+
<!-- This file lives in public/500.html -->
|
59
|
+
<div class="dialog">
|
60
|
+
<div>
|
61
|
+
<h1>We're sorry, but something went wrong.</h1>
|
62
|
+
</div>
|
63
|
+
<p>If you are the application owner check the logs for more information.</p>
|
64
|
+
</div>
|
65
|
+
</body>
|
66
|
+
</html>
|
File without changes
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Example' do
|
4
|
+
xit 'prints out the thing' do
|
5
|
+
params = {
|
6
|
+
id: 'zorglub',
|
7
|
+
title: 'Ember Hamster',
|
8
|
+
published_at: '2016-03-12',
|
9
|
+
enabled_at: '2016-03-12T17:19:28+11:00',
|
10
|
+
promoted: false,
|
11
|
+
times_read: '414',
|
12
|
+
author_id: nil,
|
13
|
+
editor_id: '9',
|
14
|
+
comment_ids: %w(1 2)
|
15
|
+
}
|
16
|
+
|
17
|
+
puts "attributes: #{ArticleDeserializer.attributes.map(&:name).inspect}"
|
18
|
+
|
19
|
+
deserializer = ArticleDeserializer.create
|
20
|
+
deserialized_params = deserializer.sanitize_params(params)
|
21
|
+
puts "title: #{deserialized_params[:title].inspect}"
|
22
|
+
puts "published_at: #{deserialized_params[:published_at].inspect}"
|
23
|
+
puts "enabled_at: #{deserialized_params[:enabled_at].inspect}"
|
24
|
+
puts "times_read: #{deserialized_params[:times_read].inspect}"
|
25
|
+
puts deserialized_params.inspect
|
26
|
+
|
27
|
+
expect(deserializer.type).to eq 'articles'
|
28
|
+
expect(deserializer.resource_class).to eq Article
|
29
|
+
end
|
30
|
+
|
31
|
+
xit 'deserializes params' do
|
32
|
+
document_data = {
|
33
|
+
# id: 'zorglub',
|
34
|
+
type: 'articles',
|
35
|
+
attributes: {
|
36
|
+
title: 'Ember Hamster',
|
37
|
+
date: '2016-03-12',
|
38
|
+
enabled_at: '2016-03-12T13:45:40+09:30',
|
39
|
+
promoted: false,
|
40
|
+
times_read: '414'
|
41
|
+
},
|
42
|
+
relationships: {
|
43
|
+
author: { data: nil },
|
44
|
+
editor: { data: { id: 'demouser', type: 'persons' } },
|
45
|
+
comments: { data: [{ id: '1', type: 'comments' }, { id: '2', type: 'comments' }] }
|
46
|
+
}
|
47
|
+
}
|
48
|
+
document = { data: document_data }.as_json
|
49
|
+
|
50
|
+
document_params = FunWithJsonApi.deserialize(document, ArticleDeserializer)
|
51
|
+
expect(document_params).to eq(
|
52
|
+
# id: Article.find(code: 'zorglub').id,
|
53
|
+
title: 'Ember Hamster',
|
54
|
+
published_at: Date.new(2016, 03, 12),
|
55
|
+
enabled_at: DateTime.new(2016, 03, 12, 4, 15, 40, 0),
|
56
|
+
promoted: false,
|
57
|
+
times_read: 414,
|
58
|
+
author_id: nil,
|
59
|
+
editor_id: Person.find_by!(username: 'demouser').id,
|
60
|
+
comment_ids: [1, 2]
|
61
|
+
)
|
62
|
+
puts "deserialized: #{document_params.inspect}"
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
4
|
+
ActiveRecord::Schema.define do
|
5
|
+
create_table :posts, force: true do |t|
|
6
|
+
t.string :title
|
7
|
+
t.text :body
|
8
|
+
t.references :author
|
9
|
+
t.timestamps null: false
|
10
|
+
end
|
11
|
+
create_table :authors, force: true do |t|
|
12
|
+
t.string :name
|
13
|
+
t.string :code
|
14
|
+
t.timestamps null: false
|
15
|
+
end
|
16
|
+
create_table :comments, force: true do |t|
|
17
|
+
t.text :contents
|
18
|
+
t.references :author
|
19
|
+
t.references :post
|
20
|
+
t.timestamp null: false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ARModels
|
25
|
+
class Post < ActiveRecord::Base
|
26
|
+
has_many :comments
|
27
|
+
belongs_to :author
|
28
|
+
end
|
29
|
+
|
30
|
+
class Comment < ActiveRecord::Base
|
31
|
+
belongs_to :post
|
32
|
+
belongs_to :author
|
33
|
+
end
|
34
|
+
|
35
|
+
class Author < ActiveRecord::Base
|
36
|
+
has_many :posts
|
37
|
+
end
|
38
|
+
|
39
|
+
class PostDeserializer < FunWithJsonApi::Deserializer
|
40
|
+
resource_class Post
|
41
|
+
|
42
|
+
attribute :title
|
43
|
+
attribute :body
|
44
|
+
|
45
|
+
has_many :comments, -> { CommentDeserializer }
|
46
|
+
belongs_to :author, -> { AuthorDeserializer }
|
47
|
+
end
|
48
|
+
|
49
|
+
class CommentDeserializer < FunWithJsonApi::Deserializer
|
50
|
+
resource_class Comment
|
51
|
+
|
52
|
+
attribute :contents
|
53
|
+
|
54
|
+
belongs_to :author, -> { AuthorDeserializer }
|
55
|
+
end
|
56
|
+
|
57
|
+
class AuthorDeserializer < FunWithJsonApi::Deserializer
|
58
|
+
type 'person'
|
59
|
+
resource_class Author
|
60
|
+
|
61
|
+
attribute :name
|
62
|
+
|
63
|
+
has_many :posts, -> { PostDeserializer }
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FunWithJsonApi::ControllerMethods, type: :controller do
|
4
|
+
describe '#render_fun_with_json_api_exception' do
|
5
|
+
context 'with a controller that raises an exception' do
|
6
|
+
controller do
|
7
|
+
include FunWithJsonApi::ControllerMethods
|
8
|
+
|
9
|
+
rescue_from FunWithJsonApi::Exception, with: :render_fun_with_json_api_exception
|
10
|
+
|
11
|
+
attr_accessor :fun_with_json_api_exception
|
12
|
+
|
13
|
+
def index
|
14
|
+
raise fun_with_json_api_exception
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:payload) { [] }
|
19
|
+
let(:exception) { FunWithJsonApi::Exception.new('document is invalid!', payload) }
|
20
|
+
before { controller.fun_with_json_api_exception = exception }
|
21
|
+
|
22
|
+
context 'with a full exception payload' do
|
23
|
+
let(:payload) do
|
24
|
+
FunWithJsonApi::ExceptionPayload.new(
|
25
|
+
id: '123',
|
26
|
+
status: '403',
|
27
|
+
code: 'text_exception',
|
28
|
+
title: 'Test Example Title',
|
29
|
+
detail: 'Test Example Details',
|
30
|
+
pointer: '/data/attributes/example',
|
31
|
+
parameter: 'example'
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns a json api error status' do
|
36
|
+
get :index
|
37
|
+
expect(response.status).to eq 403
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'renders the exception payload as a json api errors response ' do
|
41
|
+
get :index
|
42
|
+
expect(JSON.parse(response.body)).to eq(
|
43
|
+
'errors' => [
|
44
|
+
{
|
45
|
+
'id' => '123',
|
46
|
+
'status' => '403',
|
47
|
+
'code' => 'text_exception',
|
48
|
+
'title' => 'Test Example Title',
|
49
|
+
'detail' => 'Test Example Details',
|
50
|
+
'source' => {
|
51
|
+
'pointer' => '/data/attributes/example',
|
52
|
+
'parameter' => 'example'
|
53
|
+
}
|
54
|
+
}
|
55
|
+
]
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'when the exception has multiple payloads' do
|
61
|
+
let(:payload) do
|
62
|
+
[
|
63
|
+
FunWithJsonApi::ExceptionPayload.new(
|
64
|
+
status: '403',
|
65
|
+
code: 'text_exception'
|
66
|
+
),
|
67
|
+
FunWithJsonApi::ExceptionPayload.new(
|
68
|
+
status: '403',
|
69
|
+
detail: 'This is an example'
|
70
|
+
)
|
71
|
+
]
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns a json api error status' do
|
75
|
+
get :index
|
76
|
+
expect(response.status).to eq 403
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'renders all exception payload items ' do
|
80
|
+
get :index
|
81
|
+
expect(JSON.parse(response.body)).to eq(
|
82
|
+
'errors' => [
|
83
|
+
{
|
84
|
+
'status' => '403',
|
85
|
+
'code' => 'text_exception'
|
86
|
+
},
|
87
|
+
{
|
88
|
+
'status' => '403',
|
89
|
+
'detail' => 'This is an example'
|
90
|
+
}
|
91
|
+
]
|
92
|
+
)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'with a partial ExceptionPayload' do
|
97
|
+
let(:payload) do
|
98
|
+
FunWithJsonApi::ExceptionPayload.new(
|
99
|
+
status: '422',
|
100
|
+
code: 'text_exception'
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'returns a json api error status' do
|
105
|
+
get :index
|
106
|
+
expect(response.status).to eq 422
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'only renders the non-nil params ' do
|
110
|
+
get :index
|
111
|
+
expect(JSON.parse(response.body)).to eq(
|
112
|
+
'errors' => [
|
113
|
+
{
|
114
|
+
'status' => '422',
|
115
|
+
'code' => 'text_exception'
|
116
|
+
}
|
117
|
+
]
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|