fun_with_json_api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +28 -0
  4. data/config/locales/fun_with_json_api.en.yml +13 -0
  5. data/lib/fun_with_json_api/attribute.rb +38 -0
  6. data/lib/fun_with_json_api/attributes/boolean_attribute.rb +24 -0
  7. data/lib/fun_with_json_api/attributes/date_attribute.rb +22 -0
  8. data/lib/fun_with_json_api/attributes/datetime_attribute.rb +20 -0
  9. data/lib/fun_with_json_api/attributes/decimal_attribute.rb +23 -0
  10. data/lib/fun_with_json_api/attributes/float_attribute.rb +20 -0
  11. data/lib/fun_with_json_api/attributes/integer_attribute.rb +20 -0
  12. data/lib/fun_with_json_api/attributes/relationship.rb +73 -0
  13. data/lib/fun_with_json_api/attributes/relationship_collection.rb +99 -0
  14. data/lib/fun_with_json_api/attributes/string_attribute.rb +9 -0
  15. data/lib/fun_with_json_api/controller_methods.rb +12 -0
  16. data/lib/fun_with_json_api/deserializer.rb +70 -0
  17. data/lib/fun_with_json_api/deserializer_class_methods.rb +83 -0
  18. data/lib/fun_with_json_api/deserializer_config_builder.rb +48 -0
  19. data/lib/fun_with_json_api/exception.rb +27 -0
  20. data/lib/fun_with_json_api/exception_payload.rb +29 -0
  21. data/lib/fun_with_json_api/exception_payload_serializer.rb +17 -0
  22. data/lib/fun_with_json_api/exception_serializer.rb +15 -0
  23. data/lib/fun_with_json_api/exceptions/invalid_attribute.rb +13 -0
  24. data/lib/fun_with_json_api/exceptions/invalid_document.rb +12 -0
  25. data/lib/fun_with_json_api/exceptions/invalid_relationship.rb +13 -0
  26. data/lib/fun_with_json_api/exceptions/missing_relationship.rb +15 -0
  27. data/lib/fun_with_json_api/pre_deserializer.rb +61 -0
  28. data/lib/fun_with_json_api/railtie.rb +11 -0
  29. data/lib/fun_with_json_api/version.rb +3 -0
  30. data/lib/fun_with_json_api.rb +24 -0
  31. data/lib/tasks/fun_with_json_api_tasks.rake +4 -0
  32. data/spec/dummy/README.rdoc +28 -0
  33. data/spec/dummy/Rakefile +6 -0
  34. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  35. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  36. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  37. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  38. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  39. data/spec/dummy/bin/bundle +3 -0
  40. data/spec/dummy/bin/rails +4 -0
  41. data/spec/dummy/bin/rake +4 -0
  42. data/spec/dummy/bin/setup +29 -0
  43. data/spec/dummy/config/application.rb +25 -0
  44. data/spec/dummy/config/boot.rb +5 -0
  45. data/spec/dummy/config/database.yml +25 -0
  46. data/spec/dummy/config/environment.rb +5 -0
  47. data/spec/dummy/config/environments/development.rb +41 -0
  48. data/spec/dummy/config/environments/production.rb +80 -0
  49. data/spec/dummy/config/environments/test.rb +42 -0
  50. data/spec/dummy/config/initializers/assets.rb +11 -0
  51. data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
  52. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  53. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  54. data/spec/dummy/config/initializers/inflections.rb +16 -0
  55. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  56. data/spec/dummy/config/initializers/session_store.rb +3 -0
  57. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  58. data/spec/dummy/config/locales/en.yml +23 -0
  59. data/spec/dummy/config/routes.rb +56 -0
  60. data/spec/dummy/config/secrets.yml +22 -0
  61. data/spec/dummy/config.ru +4 -0
  62. data/spec/dummy/log/test.log +37839 -0
  63. data/spec/dummy/public/404.html +67 -0
  64. data/spec/dummy/public/422.html +67 -0
  65. data/spec/dummy/public/500.html +66 -0
  66. data/spec/dummy/public/favicon.ico +0 -0
  67. data/spec/example_spec.rb +64 -0
  68. data/spec/fixtures/active_record.rb +65 -0
  69. data/spec/fun_with_json_api/controller_methods_spec.rb +123 -0
  70. data/spec/fun_with_json_api/deserializer_class_methods_spec.rb +52 -0
  71. data/spec/fun_with_json_api/deserializer_spec.rb +450 -0
  72. data/spec/fun_with_json_api/exception_spec.rb +77 -0
  73. data/spec/fun_with_json_api/pre_deserializer_spec.rb +287 -0
  74. data/spec/fun_with_json_api_spec.rb +55 -0
  75. data/spec/spec_helper.rb +33 -0
  76. 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