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,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe FunWithJsonApi::Exception do
4
+ let(:message) { Faker::Lorem.sentence }
5
+ let(:payload) { FunWithJsonApi::ExceptionPayload.new }
6
+ subject(:instance) { described_class.new(message, payload) }
7
+
8
+ describe '#message' do
9
+ subject { instance.message }
10
+
11
+ it 'should return the developer-only message' do
12
+ expect(subject).to eq message
13
+ end
14
+ end
15
+
16
+ describe '#payload' do
17
+ subject { instance.payload }
18
+
19
+ it 'should wrap the payload in a array' do
20
+ expect(subject).to eq [payload]
21
+ end
22
+
23
+ context 'with an array of payloads' do
24
+ let(:payload) { Array.new(3) { FunWithJsonApi::ExceptionPayload.new } }
25
+
26
+ it 'should return the payload array' do
27
+ expect(subject).to eq payload
28
+ end
29
+ end
30
+ end
31
+
32
+ describe '#http_status' do
33
+ subject { instance.http_status }
34
+
35
+ it 'should default to 400' do
36
+ expect(subject).to eq 400
37
+ end
38
+
39
+ context 'with a payload with a status value' do
40
+ let(:payload) { FunWithJsonApi::ExceptionPayload.new.tap { |p| p.status = '422' } }
41
+
42
+ it 'should return the payload status' do
43
+ expect(subject).to eq 422
44
+ end
45
+ end
46
+
47
+ context 'with mutiple payloads with a single status values' do
48
+ let(:payload) do
49
+ Array.new(3) { FunWithJsonApi::ExceptionPayload.new.tap { |p| p.status = '403' } }
50
+ end
51
+
52
+ it 'should return the common payload status value' do
53
+ expect(subject).to eq 403
54
+ end
55
+ end
56
+
57
+ context 'with mutiple payloads with multiple 400-based status values' do
58
+ let(:payload) do
59
+ Array.new(3) { |i| FunWithJsonApi::ExceptionPayload.new.tap { |p| p.status = "40#{i}" } }
60
+ end
61
+
62
+ it 'should fall back to 400' do
63
+ expect(subject).to eq 400
64
+ end
65
+ end
66
+
67
+ context 'with mutiple payloads with multiple 500-based status values' do
68
+ let(:payload) do
69
+ Array.new(3) { |i| FunWithJsonApi::ExceptionPayload.new.tap { |p| p.status = "50#{i}" } }
70
+ end
71
+
72
+ it 'should fall back to 500' do
73
+ expect(subject).to eq 500
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,287 @@
1
+ require 'spec_helper'
2
+
3
+ describe FunWithJsonApi::PreDeserializer do
4
+ describe '.parse' do
5
+ describe 'document attributes parsing' do
6
+ it 'should pass through attribute values' do
7
+ deserializer = Class.new(FunWithJsonApi::Deserializer) do
8
+ attribute :foo
9
+ end.create
10
+
11
+ document = {
12
+ data: {
13
+ attributes: {
14
+ foo: 'bar'
15
+ }
16
+ }
17
+ }.deep_stringify_keys!
18
+
19
+ expect(FunWithJsonApi::PreDeserializer.parse(document, deserializer)).to eq(
20
+ foo: 'bar'
21
+ )
22
+ end
23
+ it 'should handle renamed attributes' do
24
+ deserializer = Class.new(FunWithJsonApi::Deserializer) do
25
+ attribute :foo, as: :blargh
26
+ end.create
27
+
28
+ document = {
29
+ data: {
30
+ attributes: {
31
+ foo: 'bar'
32
+ }
33
+ }
34
+ }.deep_stringify_keys!
35
+
36
+ expect(FunWithJsonApi::PreDeserializer.parse(document, deserializer)).to eq(
37
+ blargh: 'bar'
38
+ )
39
+ end
40
+ it 'should only return known attributes' do
41
+ deserializer = Class.new(FunWithJsonApi::Deserializer) do
42
+ attribute :foo
43
+ end.create
44
+
45
+ document = {
46
+ data: {
47
+ attributes: {
48
+ foo: 'bar',
49
+ blargh: 'baz'
50
+ }
51
+ }
52
+ }.deep_stringify_keys!
53
+
54
+ expect(FunWithJsonApi::PreDeserializer.parse(document, deserializer)).to eq(
55
+ foo: 'bar'
56
+ )
57
+ end
58
+ end
59
+
60
+ describe 'single relationship parsing' do
61
+ it 'should pass through relationship values with a _id suffix' do
62
+ foo_deserializer_class = Class.new(FunWithJsonApi::Deserializer)
63
+ deserializer = Class.new(FunWithJsonApi::Deserializer) do
64
+ belongs_to :foo, foo_deserializer_class
65
+ end.create
66
+
67
+ document = {
68
+ data: {
69
+ relationships: {
70
+ foo: {
71
+ data: { id: '42', type: 'foos' }
72
+ }
73
+ }
74
+ }
75
+ }.deep_stringify_keys!
76
+
77
+ expect(FunWithJsonApi::PreDeserializer.parse(document, deserializer)).to eq(
78
+ foo_id: '42'
79
+ )
80
+ end
81
+ it 'should handle renamed relationships' do
82
+ foo_deserializer_class = Class.new(FunWithJsonApi::Deserializer)
83
+ deserializer = Class.new(FunWithJsonApi::Deserializer) do
84
+ belongs_to :foo, foo_deserializer_class, as: :blargh
85
+ end.create
86
+
87
+ document = {
88
+ data: {
89
+ relationships: {
90
+ foo: {
91
+ data: { id: '42', type: 'foos' }
92
+ }
93
+ }
94
+ }
95
+ }.deep_stringify_keys!
96
+
97
+ expect(FunWithJsonApi::PreDeserializer.parse(document, deserializer)).to eq(
98
+ blargh_id: '42'
99
+ )
100
+ end
101
+ it 'should only return known relationships' do
102
+ foo_deserializer_class = Class.new(FunWithJsonApi::Deserializer)
103
+ deserializer = Class.new(FunWithJsonApi::Deserializer) do
104
+ belongs_to :foo, foo_deserializer_class
105
+ end.create
106
+
107
+ document = {
108
+ data: {
109
+ relationships: {
110
+ foo: {
111
+ data: { id: '42', type: 'foos' }
112
+ },
113
+ blargh: {
114
+ data: { id: '24', type: 'blarghs' }
115
+ }
116
+ }
117
+ }
118
+ }.deep_stringify_keys!
119
+
120
+ expect(FunWithJsonApi::PreDeserializer.parse(document, deserializer)).to eq(
121
+ foo_id: '42'
122
+ )
123
+ end
124
+ end
125
+
126
+ describe 'relationship collection parsing' do
127
+ it 'should pass through singular relationship values with a _ids suffix' do
128
+ foo_deserializer_class = Class.new(FunWithJsonApi::Deserializer)
129
+ deserializer = Class.new(FunWithJsonApi::Deserializer) do
130
+ has_many :foos, foo_deserializer_class
131
+ end.create
132
+
133
+ document = {
134
+ data: {
135
+ relationships: {
136
+ foos: {
137
+ data: [{ id: '42', type: 'foos' }, { id: '24', type: 'foos' }]
138
+ }
139
+ }
140
+ }
141
+ }.deep_stringify_keys!
142
+
143
+ expect(FunWithJsonApi::PreDeserializer.parse(document, deserializer)).to eq(
144
+ foo_ids: %w(42 24)
145
+ )
146
+ end
147
+ it 'should handle renamed relationships' do
148
+ foo_deserializer_class = Class.new(FunWithJsonApi::Deserializer)
149
+ deserializer = Class.new(FunWithJsonApi::Deserializer) do
150
+ has_many :foos, foo_deserializer_class, as: :blargh
151
+ end.create
152
+
153
+ document = {
154
+ data: {
155
+ relationships: {
156
+ foos: {
157
+ data: [{ id: '42', type: 'foos' }, { id: '11', type: 'foos' }]
158
+ }
159
+ }
160
+ }
161
+ }.deep_stringify_keys!
162
+
163
+ expect(FunWithJsonApi::PreDeserializer.parse(document, deserializer)).to eq(
164
+ blargh_ids: %w(42 11)
165
+ )
166
+ end
167
+ it 'should only return known relationships' do
168
+ foo_deserializer_class = Class.new(FunWithJsonApi::Deserializer)
169
+ deserializer = Class.new(FunWithJsonApi::Deserializer) do
170
+ has_many :foos, foo_deserializer_class
171
+ end.create
172
+
173
+ document = {
174
+ data: {
175
+ relationships: {
176
+ foos: {
177
+ data: [{ id: '42', type: 'foos' }]
178
+ },
179
+ blargh: {
180
+ data: [{ id: '24', type: 'blarghs' }]
181
+ }
182
+ }
183
+ }
184
+ }.deep_stringify_keys!
185
+
186
+ expect(FunWithJsonApi::PreDeserializer.parse(document, deserializer)).to eq(
187
+ foo_ids: ['42']
188
+ )
189
+ end
190
+ end
191
+
192
+ describe 'parser exceptions' do
193
+ it 'should handle an invalid /data values' do
194
+ deserializer = Class.new(FunWithJsonApi::Deserializer).create
195
+ [
196
+ nil,
197
+ {},
198
+ {
199
+ 'data' => nil
200
+ }
201
+ ].each do |document|
202
+ expect do
203
+ FunWithJsonApi::PreDeserializer.parse(document, deserializer)
204
+ end.to raise_error(FunWithJsonApi::Exceptions::InvalidDocument) do |e|
205
+ expect(e.payload.size).to eq 1
206
+
207
+ payload = e.payload.first
208
+ expect(payload.status).to eq '400'
209
+ expect(payload.code).to eq 'invalid_document'
210
+ expect(payload.title).to eq I18n.t('fun_with_json_api.exceptions.invalid_document')
211
+ expect(payload.pointer).to eq '/data'
212
+ expect(payload.detail).to be_kind_of(String)
213
+ end
214
+ end
215
+ end
216
+ it 'should handle invalid /data/attributes values' do
217
+ deserializer = Class.new(FunWithJsonApi::Deserializer).create
218
+ [
219
+ {
220
+ 'data' => { 'attributes' => [] }
221
+ }
222
+ ].each do |document|
223
+ expect do
224
+ FunWithJsonApi::PreDeserializer.parse(document, deserializer)
225
+ end.to raise_error(FunWithJsonApi::Exceptions::InvalidDocument) do |e|
226
+ expect(e.payload.size).to eq 1
227
+
228
+ payload = e.payload.first
229
+ expect(payload.status).to eq '400'
230
+ expect(payload.code).to eq 'invalid_document'
231
+ expect(payload.title).to eq I18n.t('fun_with_json_api.exceptions.invalid_document')
232
+ expect(payload.pointer).to eq '/data/attributes'
233
+ expect(payload.detail).to be_kind_of(String)
234
+ end
235
+ end
236
+ end
237
+ it 'should handle invalid /data/relationships values' do
238
+ deserializer = Class.new(FunWithJsonApi::Deserializer).create
239
+ [
240
+ {
241
+ 'data' => { 'relationships' => [] }
242
+ }
243
+ ].each do |document|
244
+ expect do
245
+ FunWithJsonApi::PreDeserializer.parse(document, deserializer)
246
+ end.to raise_error(FunWithJsonApi::Exceptions::InvalidDocument) do |e|
247
+ expect(e.payload.size).to eq 1
248
+
249
+ payload = e.payload.first
250
+ expect(payload.status).to eq '400'
251
+ expect(payload.code).to eq 'invalid_document'
252
+ expect(payload.title).to eq I18n.t('fun_with_json_api.exceptions.invalid_document')
253
+ expect(payload.pointer).to eq '/data/relationships'
254
+ expect(payload.detail).to be_kind_of(String)
255
+ end
256
+ end
257
+ end
258
+ it 'should handle a invalid relationship value' do
259
+ deserializer = Class.new(FunWithJsonApi::Deserializer).create
260
+ [
261
+ {
262
+ 'data' => {
263
+ 'relationships' => { 'rel' => nil }
264
+ }
265
+ }, {
266
+ 'data' => {
267
+ 'relationships' => { 'rel' => {} }
268
+ }
269
+ }
270
+ ].each do |document|
271
+ expect do
272
+ FunWithJsonApi::PreDeserializer.parse(document, deserializer)
273
+ end.to raise_error(FunWithJsonApi::Exceptions::InvalidDocument) do |e|
274
+ expect(e.payload.size).to eq 1
275
+
276
+ payload = e.payload.first
277
+ expect(payload.status).to eq '400'
278
+ expect(payload.code).to eq 'invalid_document'
279
+ expect(payload.title).to eq I18n.t('fun_with_json_api.exceptions.invalid_document')
280
+ expect(payload.pointer).to eq '/data/relationships/rel'
281
+ expect(payload.detail).to be_kind_of(String)
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe FunWithJsonApi do
4
+ it 'should have a Semantic Versioning compatible VERSION' do
5
+ # based on https://github.com/npm/node-semver/issues/32
6
+ version_regex = /
7
+ \A([0-9]+) # major
8
+ \.([0-9]+) # minor
9
+ \.([0-9]+) # patch
10
+ (?:-([0-9A-Za-z-]+(?:\.[0-9a-z-]+)*))? # build
11
+ (?:\+[0-9a-z-]+)?\z # tag
12
+ /x
13
+ expect(FunWithJsonApi::VERSION).to match(version_regex)
14
+ end
15
+
16
+ describe '.deserialize' do
17
+ context 'with an PostDeserializer' do
18
+ it 'should convert a json api to post params' do
19
+ ARModels::Author.create(id: 9)
20
+ ARModels::Comment.create(id: 5)
21
+ ARModels::Comment.create(id: 12)
22
+
23
+ post_json = {
24
+ 'data': {
25
+ 'type': 'posts',
26
+ 'id': '1',
27
+ 'attributes': {
28
+ 'title': 'Rails is Omakase',
29
+ 'body': 'This is my post body'
30
+ },
31
+ 'relationships': {
32
+ 'author': {
33
+ 'data': { 'type': 'people', 'id': '9' }
34
+ },
35
+ 'comments': {
36
+ 'data': [
37
+ { 'type': 'comments', 'id': '5' },
38
+ { 'type': 'comments', 'id': '12' }
39
+ ]
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ post_params = FunWithJsonApi.deserialize(post_json, ARModels::PostDeserializer)
46
+ expect(post_params).to eq(
47
+ title: 'Rails is Omakase',
48
+ body: 'This is my post body',
49
+ author_id: 9,
50
+ comment_ids: [5, 12]
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,33 @@
1
+ require 'simplecov'
2
+ SimpleCov.minimum_coverage 80
3
+ SimpleCov.start
4
+
5
+ ENV['RAILS_ENV'] ||= 'test'
6
+
7
+ require File.expand_path('../dummy/config/environment.rb', __FILE__)
8
+
9
+ require 'rails'
10
+ require 'action_controller'
11
+ require 'action_controller/test_case'
12
+ require 'action_controller/railtie'
13
+ require 'active_support/json'
14
+ require 'active_support/inflector'
15
+
16
+ require 'fun_with_json_api'
17
+
18
+ require 'fixtures/active_record'
19
+ require 'rspec/rails'
20
+ require 'faker'
21
+ require 'factory_girl_rails'
22
+
23
+ Rails.backtrace_cleaner.remove_silencers!
24
+
25
+ # Load support files
26
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
27
+
28
+ RSpec.configure do |config|
29
+ config.mock_with :rspec
30
+ config.use_transactional_fixtures = true
31
+ config.infer_base_class_for_anonymous_controllers = false
32
+ config.order = 'random'
33
+ end