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.
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