roar-jsonapi 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/.gitignore +5 -0
- data/.rubocop.yml +37 -0
- data/.travis.yml +14 -0
- data/.yardopts +5 -0
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +11 -0
- data/ISSUE_TEMPLATE.md +20 -0
- data/LICENSE +20 -0
- data/README.markdown +127 -0
- data/Rakefile +12 -0
- data/lib/roar/json/json_api.rb +156 -0
- data/lib/roar/json/json_api/declarative.rb +196 -0
- data/lib/roar/json/json_api/defaults.rb +25 -0
- data/lib/roar/json/json_api/document.rb +104 -0
- data/lib/roar/json/json_api/for_collection.rb +35 -0
- data/lib/roar/json/json_api/member_name.rb +57 -0
- data/lib/roar/json/json_api/meta.rb +56 -0
- data/lib/roar/json/json_api/options.rb +98 -0
- data/lib/roar/json/json_api/version.rb +7 -0
- data/roar-jsonapi.gemspec +25 -0
- data/test/jsonapi/collection_render_test.rb +399 -0
- data/test/jsonapi/fieldsets_options_test.rb +161 -0
- data/test/jsonapi/fieldsets_test.rb +293 -0
- data/test/jsonapi/member_name_test.rb +91 -0
- data/test/jsonapi/post_test.rb +78 -0
- data/test/jsonapi/render_test.rb +281 -0
- data/test/jsonapi/representer.rb +112 -0
- data/test/jsonapi/resource_linkage_test.rb +88 -0
- data/test/test_helper.rb +42 -0
- metadata +132 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
require 'roar/json/json_api'
|
5
|
+
require 'json'
|
6
|
+
require 'jsonapi/representer'
|
7
|
+
|
8
|
+
class MemberNameTest < MiniTest::Spec
|
9
|
+
MemberName = Roar::JSON::JSONAPI::MemberName
|
10
|
+
|
11
|
+
# http://jsonapi.org/format/#document-member-names-reserved-characters
|
12
|
+
UNICODE_RESERVED_CHARACTERS = [
|
13
|
+
"\u002B",
|
14
|
+
"\u002C",
|
15
|
+
"\u002E",
|
16
|
+
"\u005B",
|
17
|
+
"\u005D",
|
18
|
+
"\u0021",
|
19
|
+
"\u0022",
|
20
|
+
"\u0023",
|
21
|
+
"\u0024",
|
22
|
+
"\u0025",
|
23
|
+
"\u0026",
|
24
|
+
"\u0027",
|
25
|
+
"\u0028",
|
26
|
+
"\u0029",
|
27
|
+
"\u002A",
|
28
|
+
"\u002F",
|
29
|
+
"\u003A",
|
30
|
+
"\u003B",
|
31
|
+
"\u003C",
|
32
|
+
"\u003D",
|
33
|
+
"\u003E",
|
34
|
+
"\u003F",
|
35
|
+
"\u0040",
|
36
|
+
"\u005C",
|
37
|
+
"\u005E",
|
38
|
+
"\u0060",
|
39
|
+
"\u007B",
|
40
|
+
"\u007C",
|
41
|
+
"\u007D",
|
42
|
+
"\u007E"
|
43
|
+
].freeze
|
44
|
+
|
45
|
+
describe 'strict (default)' do
|
46
|
+
it 'permits alphanumeric ASCII characters, hyphens' do
|
47
|
+
MemberName.('99 Luftballons').must_equal '99luftballons'
|
48
|
+
MemberName.('Artist').must_equal 'artist'
|
49
|
+
MemberName.('Актер').must_equal ''
|
50
|
+
MemberName.('おまかせ').must_equal ''
|
51
|
+
MemberName.('auf-der-bühne').must_equal 'auf-der-bhne'
|
52
|
+
MemberName.('nouvelle_interprétation').must_equal 'nouvelle-interprtation'
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'does not permit any reserved characters' do
|
56
|
+
MemberName.(UNICODE_RESERVED_CHARACTERS.join).must_equal ''
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'hyphenates underscored words' do
|
60
|
+
MemberName.('playtime_report').must_equal 'playtime-report'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'non-strict' do
|
65
|
+
it 'permits alphanumeric unicode characters, hyphens, underscores and spaces' do
|
66
|
+
MemberName.('99 Luftballons', strict: false).must_equal '99 Luftballons'
|
67
|
+
MemberName.('Artist', strict: false).must_equal 'Artist'
|
68
|
+
MemberName.('Актер', strict: false).must_equal 'Актер'
|
69
|
+
MemberName.('おまかせ', strict: false).must_equal 'おまかせ'
|
70
|
+
MemberName.('auf-der-bühne', strict: false).must_equal 'auf-der-bühne'
|
71
|
+
MemberName.('nouvelle_interprétation', strict: false).must_equal 'nouvelle_interprétation'
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'does not permit any reserved characters' do
|
75
|
+
MemberName.(UNICODE_RESERVED_CHARACTERS.join, strict: false).must_equal ''
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'does not permit hyphens, underscores or spaces at beginning or end' do
|
79
|
+
MemberName.(' 99 Luftballons ', strict: false).must_equal '99 Luftballons'
|
80
|
+
MemberName.('-Artist_', strict: false).must_equal 'Artist'
|
81
|
+
MemberName.('_Актер', strict: false).must_equal 'Актер'
|
82
|
+
MemberName.(' おまかせ', strict: false).must_equal 'おまかせ'
|
83
|
+
MemberName.('-auf-der-bühne', strict: false).must_equal 'auf-der-bühne'
|
84
|
+
MemberName.('nouvelle_interprétation_', strict: false).must_equal 'nouvelle_interprétation'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'preserves underscored words' do
|
88
|
+
MemberName.('playtime_report', strict: false).must_equal 'playtime_report'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'roar/json/json_api'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class JsonapiPostTest < MiniTest::Spec
|
6
|
+
describe 'Parse' do
|
7
|
+
let(:post_article) {
|
8
|
+
%({
|
9
|
+
"data": {
|
10
|
+
"type": "articles",
|
11
|
+
"attributes": {
|
12
|
+
"title": "Ember Hamster"
|
13
|
+
},
|
14
|
+
"relationships": {
|
15
|
+
"author": {
|
16
|
+
"data": {
|
17
|
+
"type": "people",
|
18
|
+
"id": "9",
|
19
|
+
"name": "Celsito"
|
20
|
+
}
|
21
|
+
},
|
22
|
+
"comments": {
|
23
|
+
"data": [{
|
24
|
+
"type": "comment",
|
25
|
+
"id": "2"
|
26
|
+
}, {
|
27
|
+
"type": "comment",
|
28
|
+
"id": "3"
|
29
|
+
}]
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}
|
33
|
+
})
|
34
|
+
}
|
35
|
+
|
36
|
+
subject { ArticleDecorator.new(Article.new(nil, nil, nil, nil, [])).from_json(post_article) }
|
37
|
+
|
38
|
+
it do
|
39
|
+
subject.title.must_equal 'Ember Hamster'
|
40
|
+
subject.author.id.must_equal '9'
|
41
|
+
subject.author.email.must_equal '9@nine.to'
|
42
|
+
# subject.author.name.must_be_nil
|
43
|
+
|
44
|
+
subject.comments.must_equal [Comment.new('2'), Comment.new('3')]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'Parse Simple' do
|
49
|
+
let(:post_article) {
|
50
|
+
%({
|
51
|
+
"data": {
|
52
|
+
"type": "articles",
|
53
|
+
"attributes": {
|
54
|
+
"title": "Ember Hamster"
|
55
|
+
}
|
56
|
+
}
|
57
|
+
})
|
58
|
+
}
|
59
|
+
|
60
|
+
subject { ArticleDecorator.new(Article.new(nil, nil, nil, nil, [])).from_json(post_article) }
|
61
|
+
|
62
|
+
it do
|
63
|
+
subject.title.must_equal 'Ember Hamster'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'Parse Badly Formed Document' do
|
68
|
+
let(:post_article) {
|
69
|
+
%({"title":"Ember Hamster"})
|
70
|
+
}
|
71
|
+
|
72
|
+
subject { ArticleDecorator.new(Article.new(nil, nil, nil, nil, [])).from_json(post_article) }
|
73
|
+
|
74
|
+
it do
|
75
|
+
subject.title.must_be_nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'roar/json/json_api'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class JsonapiRenderTest < MiniTest::Spec
|
6
|
+
let(:article) { Article.new(1, 'Health walk', Author.new(2), Author.new('editor:1'), [Comment.new('comment:1', 'Ice and Snow'), Comment.new('comment:2', 'Red Stripe Skank')]) }
|
7
|
+
let(:decorator) { ArticleDecorator.new(article) }
|
8
|
+
|
9
|
+
it 'renders full document' do
|
10
|
+
json = decorator.to_json
|
11
|
+
json.must_equal_json(%({
|
12
|
+
"data": {
|
13
|
+
"id": "1",
|
14
|
+
"relationships": {
|
15
|
+
"author": {
|
16
|
+
"data": {
|
17
|
+
"id": "2",
|
18
|
+
"type": "authors"
|
19
|
+
},
|
20
|
+
"links": {
|
21
|
+
"self": "/articles/1/relationships/author",
|
22
|
+
"related": "/articles/1/author"
|
23
|
+
}
|
24
|
+
},
|
25
|
+
"editor": {
|
26
|
+
"data": {
|
27
|
+
"id": "editor:1",
|
28
|
+
"type": "editors"
|
29
|
+
},
|
30
|
+
"meta": {
|
31
|
+
"peer-reviewed": false
|
32
|
+
}
|
33
|
+
},
|
34
|
+
"comments": {
|
35
|
+
"data": [{
|
36
|
+
"id": "comment:1",
|
37
|
+
"type": "comments"
|
38
|
+
}, {
|
39
|
+
"id": "comment:2",
|
40
|
+
"type": "comments"
|
41
|
+
}],
|
42
|
+
"links": {
|
43
|
+
"self": "/articles/1/relationships/comments",
|
44
|
+
"related": "/articles/1/comments"
|
45
|
+
},
|
46
|
+
"meta": {
|
47
|
+
"comment-count": 5
|
48
|
+
}
|
49
|
+
}
|
50
|
+
},
|
51
|
+
"attributes": {
|
52
|
+
"title": "Health walk"
|
53
|
+
},
|
54
|
+
"type": "articles",
|
55
|
+
"links": {
|
56
|
+
"self": "http://Article/1"
|
57
|
+
}
|
58
|
+
},
|
59
|
+
"included": [{
|
60
|
+
"id": "2",
|
61
|
+
"type": "authors",
|
62
|
+
"links": {
|
63
|
+
"self": "http://authors/2"
|
64
|
+
}
|
65
|
+
}, {
|
66
|
+
"id": "editor:1",
|
67
|
+
"type": "editors"
|
68
|
+
}, {
|
69
|
+
"id": "comment:1",
|
70
|
+
"attributes": {
|
71
|
+
"body": "Ice and Snow"
|
72
|
+
},
|
73
|
+
"type": "comments",
|
74
|
+
"links": {
|
75
|
+
"self": "http://comments/comment:1"
|
76
|
+
}
|
77
|
+
}, {
|
78
|
+
"id": "comment:2",
|
79
|
+
"attributes": {
|
80
|
+
"body": "Red Stripe Skank"
|
81
|
+
},
|
82
|
+
"type": "comments",
|
83
|
+
"links": {
|
84
|
+
"self": "http://comments/comment:2"
|
85
|
+
}
|
86
|
+
}],
|
87
|
+
"meta": {
|
88
|
+
"reviewers": ["Christian Bernstein"],
|
89
|
+
"reviewer-initials": "C.B."
|
90
|
+
}
|
91
|
+
}))
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'included: false suppresses compound docs' do
|
95
|
+
json = decorator.to_json(included: false)
|
96
|
+
json.must_equal_json(%({
|
97
|
+
"data": {
|
98
|
+
"id": "1",
|
99
|
+
"relationships": {
|
100
|
+
"author": {
|
101
|
+
"data": {
|
102
|
+
"id": "2",
|
103
|
+
"type": "authors"
|
104
|
+
},
|
105
|
+
"links": {
|
106
|
+
"self": "/articles/1/relationships/author",
|
107
|
+
"related": "/articles/1/author"
|
108
|
+
}
|
109
|
+
},
|
110
|
+
"editor": {
|
111
|
+
"data": {
|
112
|
+
"id": "editor:1",
|
113
|
+
"type": "editors"
|
114
|
+
},
|
115
|
+
"meta": {
|
116
|
+
"peer-reviewed": false
|
117
|
+
}
|
118
|
+
},
|
119
|
+
"comments": {
|
120
|
+
"data": [{
|
121
|
+
"id": "comment:1",
|
122
|
+
"type": "comments"
|
123
|
+
}, {
|
124
|
+
"id": "comment:2",
|
125
|
+
"type": "comments"
|
126
|
+
}],
|
127
|
+
"links": {
|
128
|
+
"self": "/articles/1/relationships/comments",
|
129
|
+
"related": "/articles/1/comments"
|
130
|
+
},
|
131
|
+
"meta": {
|
132
|
+
"comment-count": 5
|
133
|
+
}
|
134
|
+
}
|
135
|
+
},
|
136
|
+
"attributes": {
|
137
|
+
"title": "Health walk"
|
138
|
+
},
|
139
|
+
"type": "articles",
|
140
|
+
"links": {
|
141
|
+
"self": "http://Article/1"
|
142
|
+
}
|
143
|
+
},
|
144
|
+
"meta": {
|
145
|
+
"reviewers": ["Christian Bernstein"],
|
146
|
+
"reviewer-initials": "C.B."
|
147
|
+
}
|
148
|
+
}))
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'renders additional meta information if meta option supplied' do
|
152
|
+
hash = decorator.to_hash(meta: {
|
153
|
+
'copyright' => 'Nick Sutterer', 'reviewers' => []
|
154
|
+
})
|
155
|
+
hash['meta']['copyright'].must_equal('Nick Sutterer')
|
156
|
+
hash['meta']['reviewers'].must_equal([])
|
157
|
+
hash['meta']['reviewer-initials'].must_equal('C.B.')
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'does not render additonal meta information if meta option is empty' do
|
161
|
+
hash = decorator.to_hash(meta: {})
|
162
|
+
hash['meta']['copyright'].must_be_nil
|
163
|
+
hash['meta']['reviewers'].must_equal(['Christian Bernstein'])
|
164
|
+
hash['meta']['reviewer-initials'].must_equal('C.B.')
|
165
|
+
end
|
166
|
+
|
167
|
+
describe 'Single Resource Object with simple attributes' do
|
168
|
+
class DocumentSingleResourceObjectDecorator < Roar::Decorator
|
169
|
+
include Roar::JSON::JSONAPI.resource :articles
|
170
|
+
|
171
|
+
attributes do
|
172
|
+
property :title
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
let(:document) {
|
177
|
+
%({
|
178
|
+
"data": {
|
179
|
+
"id": "1",
|
180
|
+
"attributes": {
|
181
|
+
"title": "My Article"
|
182
|
+
},
|
183
|
+
"type": "articles"
|
184
|
+
}
|
185
|
+
})
|
186
|
+
}
|
187
|
+
|
188
|
+
let(:collection_document) {
|
189
|
+
%({
|
190
|
+
"data": [
|
191
|
+
{
|
192
|
+
"type": "articles",
|
193
|
+
"id": "1",
|
194
|
+
"attributes": {
|
195
|
+
"title": "My Article"
|
196
|
+
}
|
197
|
+
}
|
198
|
+
]
|
199
|
+
})
|
200
|
+
}
|
201
|
+
|
202
|
+
it { DocumentSingleResourceObjectDecorator.new(Article.new(1, 'My Article')).to_json.must_equal_json document }
|
203
|
+
it { DocumentSingleResourceObjectDecorator.for_collection.new([Article.new(1, 'My Article')]).to_json.must_equal_json collection_document }
|
204
|
+
end
|
205
|
+
|
206
|
+
describe 'Single Resource Object with complex attributes' do
|
207
|
+
class VisualArtistDecorator < Roar::Decorator
|
208
|
+
include Roar::JSON::JSONAPI.resource :visual_artists
|
209
|
+
|
210
|
+
attributes do
|
211
|
+
property :name
|
212
|
+
collection :known_aliases
|
213
|
+
property :movement
|
214
|
+
collection :noteable_works
|
215
|
+
end
|
216
|
+
|
217
|
+
link(:self) { "http://visual_artists/#{represented.id}" }
|
218
|
+
link(:wikipedia_page) { "https://en.wikipedia.org/wiki/#{represented.name}" }
|
219
|
+
end
|
220
|
+
|
221
|
+
Painter = Struct.new(:id, :name, :known_aliases, :movement, :noteable_works)
|
222
|
+
|
223
|
+
let(:document) {
|
224
|
+
%({
|
225
|
+
"data": {
|
226
|
+
"type": "visual-artists",
|
227
|
+
"id": "p1",
|
228
|
+
"attributes": {
|
229
|
+
"name": "Pablo Picasso",
|
230
|
+
"known-aliases": [
|
231
|
+
"Pablo Ruiz Picasso"
|
232
|
+
],
|
233
|
+
"movement": "Cubism",
|
234
|
+
"noteable-works": [
|
235
|
+
"Kahnweiler",
|
236
|
+
"Guernica"
|
237
|
+
]
|
238
|
+
},
|
239
|
+
"links": {
|
240
|
+
"self": "http://visual_artists/p1",
|
241
|
+
"wikipedia-page": "https://en.wikipedia.org/wiki/Pablo Picasso"
|
242
|
+
}
|
243
|
+
}
|
244
|
+
})
|
245
|
+
}
|
246
|
+
|
247
|
+
let(:collection_document) {
|
248
|
+
%({
|
249
|
+
"data": [
|
250
|
+
{
|
251
|
+
"type": "visual-artists",
|
252
|
+
"id": "p1",
|
253
|
+
"attributes": {
|
254
|
+
"name": "Pablo Picasso",
|
255
|
+
"known-aliases": [
|
256
|
+
"Pablo Ruiz Picasso"
|
257
|
+
],
|
258
|
+
"movement": "Cubism",
|
259
|
+
"noteable-works": [
|
260
|
+
"Kahnweiler",
|
261
|
+
"Guernica"
|
262
|
+
]
|
263
|
+
},
|
264
|
+
"links": {
|
265
|
+
"self": "http://visual_artists/p1",
|
266
|
+
"wikipedia-page": "https://en.wikipedia.org/wiki/Pablo Picasso"
|
267
|
+
}
|
268
|
+
}
|
269
|
+
]
|
270
|
+
})
|
271
|
+
}
|
272
|
+
|
273
|
+
let(:painter) {
|
274
|
+
Painter.new('p1', 'Pablo Picasso', ['Pablo Ruiz Picasso'], 'Cubism',
|
275
|
+
%w(Kahnweiler Guernica))
|
276
|
+
}
|
277
|
+
|
278
|
+
it { VisualArtistDecorator.new(painter).to_json.must_equal_json document }
|
279
|
+
it { VisualArtistDecorator.for_collection.new([painter]).to_json.must_equal_json collection_document }
|
280
|
+
end
|
281
|
+
end
|