subjoin 0.2.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 +15 -0
- data/.travis.yml +23 -0
- data/.yardops +1 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +431 -0
- data/Rakefile +11 -0
- data/lib/subjoin.rb +85 -0
- data/lib/subjoin/attributable.rb +28 -0
- data/lib/subjoin/document.rb +136 -0
- data/lib/subjoin/errors.rb +15 -0
- data/lib/subjoin/identifier.rb +23 -0
- data/lib/subjoin/inclusions.rb +39 -0
- data/lib/subjoin/inheritable.rb +80 -0
- data/lib/subjoin/jsonapi.rb +13 -0
- data/lib/subjoin/link.rb +30 -0
- data/lib/subjoin/linkable.rb +18 -0
- data/lib/subjoin/meta.rb +10 -0
- data/lib/subjoin/metable.rb +21 -0
- data/lib/subjoin/relationship.rb +32 -0
- data/lib/subjoin/resource.rb +93 -0
- data/lib/subjoin/version.rb +4 -0
- data/spec/document_spec.rb +210 -0
- data/spec/identifier_spec.rb +33 -0
- data/spec/inclusions_spec.rb +69 -0
- data/spec/inheritable_resource_spec.rb +89 -0
- data/spec/link_spec.rb +60 -0
- data/spec/meta_spec.rb +11 -0
- data/spec/relationship_spec.rb +64 -0
- data/spec/resource_spec.rb +139 -0
- data/spec/responses/404.json +8 -0
- data/spec/responses/article_example.json +37 -0
- data/spec/responses/compound_example.json +73 -0
- data/spec/responses/links.json +9 -0
- data/spec/responses/meta.json +13 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/subjoin_spec.rb +99 -0
- data/subjoin.gemspec +27 -0
- metadata +168 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Subjoin
|
4
|
+
class ExampleResource < Resource
|
5
|
+
include Inheritable
|
6
|
+
ROOT_URI="http://example.com"
|
7
|
+
end
|
8
|
+
|
9
|
+
class NonStandardUri < ExampleResource
|
10
|
+
TYPE_PATH="nonstandard"
|
11
|
+
end
|
12
|
+
|
13
|
+
class Articles < ExampleResource
|
14
|
+
#TYPE_PATH="articles"
|
15
|
+
end
|
16
|
+
|
17
|
+
class ExamplePerson < ExampleResource
|
18
|
+
TYPE_PATH="people"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe Subjoin::Inheritable do
|
24
|
+
before :each do
|
25
|
+
allow_any_instance_of(Faraday::Connection).
|
26
|
+
to receive(:get).and_return(double(Faraday::Response, :body => ARTICLE))
|
27
|
+
end
|
28
|
+
|
29
|
+
it "has a root uri" do
|
30
|
+
expect(Subjoin::ExampleResource::ROOT_URI).to eq "http://example.com"
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#type_url" do
|
34
|
+
it "is a class method" do
|
35
|
+
expect(Subjoin::ExampleResource::type_url).to eq URI("http://example.com/exampleresource")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe Subjoin::Document do
|
41
|
+
describe "#new" do
|
42
|
+
context "with a single string parameter" do
|
43
|
+
it "maps derived types" do
|
44
|
+
expect_any_instance_of(Faraday::Connection)
|
45
|
+
.to receive(:get).with(URI("http://example.com/articles"), {}, Hash)
|
46
|
+
.and_return(double(Faraday::Response, :body => ARTICLE))
|
47
|
+
Subjoin::Document.new("articles")
|
48
|
+
end
|
49
|
+
|
50
|
+
it "returns objects of the right class" do
|
51
|
+
expect_any_instance_of(Faraday::Connection).
|
52
|
+
to receive(:get).
|
53
|
+
and_return(double(Faraday::Response, :body => COMPOUND))
|
54
|
+
|
55
|
+
expect(Subjoin::Document.new("articles").data.first).
|
56
|
+
to be_an_instance_of Subjoin::Articles
|
57
|
+
end
|
58
|
+
|
59
|
+
it "returns included objects of the right class" do
|
60
|
+
expect_any_instance_of(Faraday::Connection).
|
61
|
+
to receive(:get).
|
62
|
+
and_return(double(Faraday::Response, :body => COMPOUND))
|
63
|
+
expect(Subjoin::Document.new("articles").data.first.
|
64
|
+
rels["author"].first).
|
65
|
+
to be_an_instance_of Subjoin::ExamplePerson
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns Resource objects when there is no mapped type" do
|
69
|
+
expect_any_instance_of(Faraday::Connection).
|
70
|
+
to receive(:get).
|
71
|
+
and_return(double(Faraday::Response, :body => COMPOUND))
|
72
|
+
expect(Subjoin::Document.new("articles").data.first.
|
73
|
+
rels["comments"].first).
|
74
|
+
to be_an_instance_of Subjoin::Resource
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "with two string parameters" do
|
79
|
+
it "maps derived types with the second string as an id" do
|
80
|
+
expect_any_instance_of(Faraday::Connection)
|
81
|
+
.to receive(:get).with(URI("http://example.com/articles/2"), {}, Hash)
|
82
|
+
.and_return(double(Faraday::Response, :body => ARTICLE))
|
83
|
+
Subjoin::Document.new("articles", "2")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
data/spec/link_spec.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Subjoin::Link do
|
4
|
+
context "initialization with a string" do
|
5
|
+
before :each do
|
6
|
+
@link = Subjoin::Link.new(JSON.parse(LINKS)["self"])
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should have an href attribute" do
|
10
|
+
expect(@link.href.to_s).to eq "http://example.com/posts"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should have a nil meta attribute" do
|
14
|
+
expect(@link.meta).to be_nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "initialization with an object" do
|
19
|
+
before :each do
|
20
|
+
@link = Subjoin::Link.new(JSON.parse(LINKS)["related"])
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should have an href attribute" do
|
24
|
+
expect(@link.href.to_s).to eq "http://example.com/articles/1/comments"
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when there is a meta attribute" do
|
28
|
+
it "should have a meta attribute" do
|
29
|
+
expect(@link.meta).to be_an_instance_of Subjoin::Meta
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when there is no meta attribute" do
|
34
|
+
it "should not have a meta attribute" do
|
35
|
+
metaless = JSON.parse(LINKS)["related"]
|
36
|
+
metaless.delete("meta")
|
37
|
+
expect(Subjoin::Link.new(metaless).meta).to be_nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#has_meta?" do
|
43
|
+
it "should be false if there is no meta attribute" do
|
44
|
+
expect(Subjoin::Link.new(JSON.parse(LINKS)["self"]).has_meta?).
|
45
|
+
to eq false
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should be true if there is a meta attribute" do
|
49
|
+
expect(Subjoin::Link.new(JSON.parse(LINKS)["related"]).has_meta?).
|
50
|
+
to eq true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#to_s" do
|
55
|
+
it "should return the href" do
|
56
|
+
@l = Subjoin::Link.new(JSON.parse(LINKS)["self"])
|
57
|
+
expect("#{@l}").to eq "http://example.com/posts"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/spec/meta_spec.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
|
4
|
+
describe Subjoin::Relationship do
|
5
|
+
before :all do
|
6
|
+
@doc = Subjoin::Document.new(JSON.parse(COMPOUND))
|
7
|
+
@auths = @doc.data.first.relationships["author"]
|
8
|
+
end
|
9
|
+
|
10
|
+
it "is linkable" do
|
11
|
+
expect(@auths.links.map{|k,v| v.class}.uniq).to eq [Subjoin::Link]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has the right links" do
|
15
|
+
expect(@auths.links.keys).to eq ["self", "related"]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "has a meta object" do
|
19
|
+
expect(@auths.meta).to be_an_instance_of Subjoin::Meta
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#lookup" do
|
23
|
+
it "returns an Array of resources" do
|
24
|
+
expect(@auths.lookup.map{|o| o.class}.uniq).to eq [Subjoin::Resource]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#linkages" do
|
29
|
+
context "with a single linkage" do
|
30
|
+
it "returns an Array" do
|
31
|
+
expect(@auths.linkages).to be_an_instance_of Array
|
32
|
+
end
|
33
|
+
|
34
|
+
it "returns an array with a single member" do
|
35
|
+
expect(@auths.linkages.count).to eq 1
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns an array of Identifier objects" do
|
39
|
+
expect(@auths.linkages.map{|l| l.class}.uniq).to eq [Subjoin::Identifier]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with an array of linkages" do
|
44
|
+
before :each do
|
45
|
+
@comments = Subjoin::Relationship.new(
|
46
|
+
JSON.parse(ARTICLE)['data']['relationships']['comments'],
|
47
|
+
nil
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "returns an Array" do
|
52
|
+
expect(@comments.linkages).to be_an_instance_of Array
|
53
|
+
end
|
54
|
+
|
55
|
+
it "returns an array with more than 1 member" do
|
56
|
+
expect(@comments.linkages.count).to be > 1
|
57
|
+
end
|
58
|
+
|
59
|
+
it "returns an array of Identifier objects" do
|
60
|
+
expect(@comments.linkages.map{|l| l.class}.uniq).to eq [Subjoin::Identifier]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class Base < Subjoin::Resource
|
4
|
+
def self.root
|
5
|
+
"http://example.com"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Article < Base
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Subjoin::Resource do
|
13
|
+
before :each do
|
14
|
+
allow_any_instance_of(Faraday::Connection).
|
15
|
+
to receive(:get).and_return(double(Faraday::Response, :body => ARTICLE))
|
16
|
+
end
|
17
|
+
|
18
|
+
context "using instance methods to retrieve single resources" do
|
19
|
+
describe "#initialize" do
|
20
|
+
context "passing a URI as a parameter" do
|
21
|
+
it "should get the same a parameter" do
|
22
|
+
expect_any_instance_of(Faraday::Connection)
|
23
|
+
.to receive(:get).
|
24
|
+
with(URI("http://example.com/articles/2"), {}, Hash).
|
25
|
+
and_return(double(Faraday::Response, :body => ARTICLE))
|
26
|
+
|
27
|
+
@articles = Subjoin::Resource.
|
28
|
+
new(URI("http://example.com/articles/2"))
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should raise an error if the response is not a single object" do
|
33
|
+
expect_any_instance_of(Faraday::Connection)
|
34
|
+
.to receive(:get)
|
35
|
+
.and_return(double(Faraday::Response, :body => COMPOUND))
|
36
|
+
expect { Subjoin::Resource.
|
37
|
+
new(URI("http://example.com/articles")) }
|
38
|
+
.to raise_error(Subjoin::UnexpectedTypeError)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "passing a hash as a parameter" do
|
43
|
+
it "should succeed" do
|
44
|
+
expect(Subjoin::Resource.new(JSON.parse(ARTICLE)).id).to eq "1"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "simple methods" do
|
51
|
+
before :each do
|
52
|
+
@article = Subjoin::Resource.new(URI("http://example.com/articles/1"))
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should have an id" do
|
56
|
+
expect(@article.id).to eq "1"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should have a type" do
|
60
|
+
expect(@article.type).to eq "articles"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should have automatic attributes" do
|
64
|
+
expect(@article["title"]).to eq "JSON API paints my bikeshed!"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#links" do
|
69
|
+
before :each do
|
70
|
+
@article = Subjoin::Resource.new(URI("http://example.com/articles/1"))
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with no parameter" do
|
74
|
+
it "returns a Hash of all the links" do
|
75
|
+
expect(@article.links.map{|k,v| v.class}.uniq).to eq [Subjoin::Link]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "with a parameter" do
|
80
|
+
it "returns a link object" do
|
81
|
+
expect(@article.links["self"]).to be_an_instance_of Subjoin::Link
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#rels" do
|
87
|
+
context "when there are included resources" do
|
88
|
+
before :each do
|
89
|
+
allow_any_instance_of(Faraday::Connection).
|
90
|
+
to receive(:get).
|
91
|
+
and_return(double(Faraday::Response, :body => COMPOUND))
|
92
|
+
@article = Subjoin::Document.new(URI("http://example.com/articles/1")).
|
93
|
+
data.first
|
94
|
+
end
|
95
|
+
|
96
|
+
context "called with no parameters" do
|
97
|
+
it "returns a Hash of Arrays of Relationship objects" do
|
98
|
+
expect(@article.rels.map{|k, r| r}.flatten.map{|r| r.class}.uniq).
|
99
|
+
to eq [Subjoin::Resource]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "called with a spec parameter" do
|
104
|
+
it "returns an array" do
|
105
|
+
expect(@article.rels("author").map{|r| r.class}).
|
106
|
+
to eq [Subjoin::Resource]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "when there are no included resources" do
|
112
|
+
before :each do
|
113
|
+
@article = Subjoin::Resource.new(URI("http://example.com/articles/1"))
|
114
|
+
end
|
115
|
+
|
116
|
+
context "called with no spec parameter" do
|
117
|
+
it "returns nil" do
|
118
|
+
expect(@article.rels).to be_nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "called with a spec parameter" do
|
123
|
+
it "returns nil" do
|
124
|
+
expect(@article.rels("author")).to be_nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#meta" do
|
131
|
+
before :each do
|
132
|
+
@article = Subjoin::Resource.new(URI("http://example.com/articles/1"))
|
133
|
+
end
|
134
|
+
|
135
|
+
it "returns a Meta object" do
|
136
|
+
expect(@article.meta).to be_an_instance_of Subjoin::Meta
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
{
|
2
|
+
"data": {
|
3
|
+
"type": "articles",
|
4
|
+
"id": "1",
|
5
|
+
"attributes": {
|
6
|
+
"title": "JSON API paints my bikeshed!"
|
7
|
+
},
|
8
|
+
"links": {
|
9
|
+
"self": "http://example.com/articles/1"
|
10
|
+
},
|
11
|
+
"relationships": {
|
12
|
+
"author": {
|
13
|
+
"links": {
|
14
|
+
"self": "http://example.com/articles/1/relationships/author",
|
15
|
+
"related": "http://example.com/articles/1/author"
|
16
|
+
},
|
17
|
+
"data": { "type": "people", "id": "9" },
|
18
|
+
"meta": {
|
19
|
+
"explanation": "Who wrote the article"
|
20
|
+
}
|
21
|
+
},
|
22
|
+
"comments": {
|
23
|
+
"links": {
|
24
|
+
"self": "http://example.com/articles/1/relationships/comments",
|
25
|
+
"related": "http://example.com/articles/1/comments"
|
26
|
+
},
|
27
|
+
"data": [
|
28
|
+
{ "type": "comments", "id": "5" },
|
29
|
+
{ "type": "comments", "id": "12" }
|
30
|
+
]
|
31
|
+
}
|
32
|
+
},
|
33
|
+
"meta": {
|
34
|
+
"reason": "To have an example meta object"
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,73 @@
|
|
1
|
+
{
|
2
|
+
"data": [{
|
3
|
+
"type": "articles",
|
4
|
+
"id": "1",
|
5
|
+
"attributes": {
|
6
|
+
"title": "JSON API paints my bikeshed!"
|
7
|
+
},
|
8
|
+
"links": {
|
9
|
+
"self": "http://example.com/articles/1"
|
10
|
+
},
|
11
|
+
"relationships": {
|
12
|
+
"author": {
|
13
|
+
"links": {
|
14
|
+
"self": "http://example.com/articles/1/relationships/author",
|
15
|
+
"related": "http://example.com/articles/1/author"
|
16
|
+
},
|
17
|
+
"data": { "type": "people", "id": "9" },
|
18
|
+
"meta": {
|
19
|
+
"explanation": "Who wrote the article"
|
20
|
+
}
|
21
|
+
},
|
22
|
+
"comments": {
|
23
|
+
"links": {
|
24
|
+
"self": "http://example.com/articles/1/relationships/comments",
|
25
|
+
"related": "http://example.com/articles/1/comments"
|
26
|
+
},
|
27
|
+
"data": [
|
28
|
+
{ "type": "comments", "id": "5" },
|
29
|
+
{ "type": "comments", "id": "12" }
|
30
|
+
]
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}],
|
34
|
+
"included": [{
|
35
|
+
"type": "people",
|
36
|
+
"id": "9",
|
37
|
+
"attributes": {
|
38
|
+
"first-name": "Dan",
|
39
|
+
"last-name": "Gebhardt",
|
40
|
+
"twitter": "dgeb"
|
41
|
+
},
|
42
|
+
"links": {
|
43
|
+
"self": "http://example.com/people/9"
|
44
|
+
}
|
45
|
+
}, {
|
46
|
+
"type": "comments",
|
47
|
+
"id": "5",
|
48
|
+
"attributes": {
|
49
|
+
"body": "First!"
|
50
|
+
},
|
51
|
+
"links": {
|
52
|
+
"self": "http://example.com/comments/5"
|
53
|
+
}
|
54
|
+
}, {
|
55
|
+
"type": "comments",
|
56
|
+
"id": "12",
|
57
|
+
"attributes": {
|
58
|
+
"body": "I like XML better"
|
59
|
+
},
|
60
|
+
"links": {
|
61
|
+
"self": "http://example.com/comments/12"
|
62
|
+
}
|
63
|
+
}],
|
64
|
+
"links": {
|
65
|
+
"related": "http://jsonapi.org"
|
66
|
+
},
|
67
|
+
"meta": {
|
68
|
+
"category": "Example response"
|
69
|
+
},
|
70
|
+
"jsonapi": {
|
71
|
+
"version": "1.0"
|
72
|
+
}
|
73
|
+
}
|