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.
@@ -0,0 +1,18 @@
1
+ module Subjoin
2
+ # Generically construct and handle {Links} objects
3
+ module Linkable
4
+ attr_reader :links
5
+
6
+ # Load the object's links
7
+ # @param data [Hash] The object's parsed JSON `links` member
8
+ # @return [Hash]
9
+ def load_links(data)
10
+ return nil if data.nil?
11
+ Hash[data.map{|k, v| [k, Link.new(v)]}]
12
+ end
13
+
14
+ def has_links?
15
+ return ! @links.nil?
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module Subjoin
2
+ # Meta object
3
+ # @see http://jsonapi.org/format/#document-meta
4
+ class Meta
5
+ include Attributable
6
+ def initialize(data)
7
+ load_attributes(data)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ module Subjoin
2
+ # Generically handle meta objects
3
+ module Metable
4
+
5
+ # The object's meta attribute
6
+ # @return [Meta]
7
+ attr_reader :meta
8
+
9
+ # Load the object's attributes
10
+ # @param data [Hash] The object's parsed JSON `meta` member
11
+ # @return [Metable,nil]
12
+ def load_meta(data)
13
+ return nil if data.nil?
14
+ Meta.new(data)
15
+ end
16
+
17
+ def has_meta?
18
+ return ! @meta.nil?
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ module Subjoin
2
+ # A related resource link, providing access to resource objects
3
+ # linked in a relationship
4
+ # @see http://jsonapi.org/format/#document-resource-object-related-resource-links
5
+ class Relationship
6
+ include Linkable
7
+ include Metable
8
+
9
+ attr_reader :links, :linkages
10
+ def initialize(data, doc)
11
+ @document = doc
12
+ @links = load_links(data['links'])
13
+ @linkages = load_linkages(data['data'], doc)
14
+ @meta = load_meta(data['meta'])
15
+ end
16
+
17
+ # Resolve available linkages and return related resources
18
+ # @return [Array<Subjoin::Resource>]
19
+ def lookup
20
+ return [] unless @document.has_included?
21
+ @linkages.map{|l| @document.included[l]}
22
+ end
23
+
24
+ private
25
+ def load_linkages(data, doc)
26
+ return [] if data.nil?
27
+ return [Identifier.new(data['type'], data['id'], data['meta'])] if data.is_a? Hash
28
+ data.map{|l| Identifier.new(l['type'], l['id'], l['meta'])}
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,93 @@
1
+ module Subjoin
2
+ # A JSON-API Resource object
3
+ # @see http://jsonapi.org/format/#document-resource-objects
4
+ class Resource
5
+ include Attributable
6
+ #include Keyable
7
+ include Linkable
8
+ include Metable
9
+
10
+ # The relationships specified for the object
11
+ # @return [Hash<Relationship>]
12
+ attr_reader :relationships
13
+
14
+ attr_reader :identifier
15
+
16
+ def initialize(spec, doc = nil)
17
+ @document = doc
18
+ if spec.is_a?(URI)
19
+ data = Subjoin::get(spec)
20
+ elsif spec.is_a?(Hash)
21
+ data = spec
22
+ end
23
+
24
+ if data.has_key?("data")
25
+ data = data["data"]
26
+ end
27
+
28
+ if data.is_a?(Array)
29
+ raise UnexpectedTypeError.new
30
+ end
31
+
32
+ @identifier = Identifier.new(data['type'], data['id'])
33
+
34
+ load_attributes(data['attributes'])
35
+ @links = load_links(data['links'])
36
+ @relationships = load_relationships(data['relationships'], @document)
37
+ @meta = load_meta(data['meta'])
38
+ end
39
+
40
+ # Resource type
41
+ # @return [String]
42
+ def type
43
+ @identifier.type
44
+ end
45
+
46
+ # Resource id
47
+ # @return [String]
48
+ def id
49
+ @identifier.id
50
+ end
51
+
52
+ # Get a related resource or resources. This method resolves the
53
+ # relationship linkages and fetches the included {Subjoin::Resource}
54
+ # objects themselves.
55
+ # @param spec [String] key for the desired resource
56
+ # @param doc [Subjoin::Document] Document in which to look for related
57
+ # resources. By default it is the same document from which the resource
58
+ # came itself.
59
+ # @return [Hash, Array<Subjoin:Resource>, nil] If called with a spec
60
+ # parameter, the return value will be an Array of {Subjoin::Resource}
61
+ # objects corresponding to the key, or nil if that key doesn't exist. If
62
+ # called without a spec parameter, the return value will be a Hash whose
63
+ # keys are the same as {Resource#relationships}, but whose values are
64
+ # Arrays of resolved {Resource} objects. In practice this means that you
65
+ # have a choice of idioms (method vs. hash) since
66
+ #
67
+ # obj.rels("key")
68
+ #
69
+ # and
70
+ #
71
+ # obj.rels["key"]
72
+ #
73
+ # are equivalent
74
+ def rels(spec = nil, doc = @document)
75
+ return nil if doc.nil?
76
+ return nil unless doc.has_included?
77
+
78
+ if spec.nil?
79
+ return Hash[relationships.keys.map{|k| [k, rels(k, doc)]}]
80
+ end
81
+
82
+ relationships[spec].linkages.map{|l| doc.included[l]}
83
+ end
84
+
85
+ private
86
+ def load_relationships(data, doc)
87
+ return {} if data.nil?
88
+
89
+ Hash[data.map{|k, v| [k, Relationship.new(v, doc)]}]
90
+ end
91
+ end
92
+ end
93
+
@@ -0,0 +1,4 @@
1
+ module Subjoin
2
+ # Gem version
3
+ VERSION = "0.2.1"
4
+ end
@@ -0,0 +1,210 @@
1
+ require "spec_helper"
2
+
3
+ describe Subjoin::Document do
4
+ before :all do
5
+ rsp = JSON.parse(COMPOUND)
6
+ @doc = Subjoin::Document.new(rsp)
7
+
8
+ dataless = JSON.parse(COMPOUND)
9
+ dataless.delete("data")
10
+ @nodata = Subjoin::Document.new(dataless)
11
+
12
+ excluded = JSON.parse(COMPOUND)
13
+ excluded.delete("included")
14
+ @noincl = Subjoin::Document.new(excluded)
15
+
16
+ linkless = JSON.parse(COMPOUND)
17
+ linkless.delete("links")
18
+ @nolinks = Subjoin::Document.new(linkless)
19
+
20
+ metaless = JSON.parse(COMPOUND)
21
+ metaless.delete("meta")
22
+ @nometa = Subjoin::Document.new(metaless)
23
+
24
+ versionless = JSON.parse(COMPOUND)
25
+ versionless.delete("jsonapi")
26
+ @noversion = Subjoin::Document.new(versionless)
27
+
28
+ @simple = Subjoin::Document.new(JSON.parse(ARTICLE))
29
+ end
30
+
31
+ describe "#data" do
32
+ context "when there is primary data" do
33
+ it "is an Array" do
34
+ expect(@doc.data).to be_an_instance_of Array
35
+ end
36
+
37
+ it "contains an expected Resource" do
38
+ expect(@doc.data.first["title"]).to eq "JSON API paints my bikeshed!"
39
+ end
40
+ end
41
+
42
+ context "when there is no primary data" do
43
+ it "is nil" do
44
+ expect(@nodata.data).to be_nil
45
+ end
46
+ end
47
+
48
+ context "when there is a single object in primary data" do
49
+ it "is still an Array" do
50
+ expect(@simple.data).to be_an_instance_of Array
51
+ end
52
+
53
+ it "has one element" do
54
+ expect(@simple.data.count).to eq 1
55
+ end
56
+ end
57
+ end
58
+
59
+ describe "#has_data?" do
60
+ context "when there is primary data" do
61
+ it "returns true" do
62
+ expect(@doc.has_data?).to be true
63
+ end
64
+ end
65
+
66
+ context "when there is no primary data" do
67
+ it "returns false" do
68
+ expect(@nodata.has_data?).to be false
69
+ end
70
+ end
71
+ end
72
+
73
+
74
+ describe "#included" do
75
+ context "when there are included resources" do
76
+ it "is an Array" do
77
+ expect(@doc.included).to be_an_instance_of Subjoin::Inclusions
78
+ end
79
+
80
+ it "contains an expected Resource" do
81
+ expect(@doc.included[0].type).to eq "people"
82
+ end
83
+ end
84
+
85
+ context "when there are no included resources" do
86
+ it "is nil" do
87
+ expect(@noincl.included).to be_nil
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "#has_included?" do
93
+ context "when there are included resources" do
94
+ it "returns true" do
95
+ expect(@doc.has_included?).to be true
96
+ end
97
+ end
98
+
99
+ context "when there are no included resources" do
100
+ it "returns false" do
101
+ expect(@noincl.has_included?).to be false
102
+ end
103
+ end
104
+ end
105
+
106
+ describe "#links" do
107
+ context "when there are links" do
108
+ it "is a Hash of Link objects" do
109
+ expect(@doc.links.map{|k, v| v.class}.uniq).to eq [Subjoin::Link]
110
+ end
111
+
112
+ it "has an expected link" do
113
+ expect(@doc.links["related"].to_s).to eq "http://jsonapi.org"
114
+ end
115
+ end
116
+
117
+ context "when there are no links" do
118
+ it "is nil" do
119
+ expect(@nolinks.links).to be_nil
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "#has_links?" do
125
+ context "when there are links" do
126
+ it "returns true" do
127
+ expect(@doc.has_links?).to be true
128
+ end
129
+ end
130
+
131
+ context "when there no links" do
132
+ it "returns false" do
133
+ expect(@nolinks.has_links?).to be false
134
+ end
135
+ end
136
+ end
137
+
138
+ describe "#meta" do
139
+ context "when there is meta information" do
140
+ it "is a Meta object" do
141
+ expect(@doc.meta).to be_an_instance_of(Subjoin::Meta)
142
+ end
143
+
144
+ it "has an expected attribute" do
145
+ expect(@doc.meta["category"]).to eq "Example response"
146
+ end
147
+ end
148
+
149
+ context "when there is no meta information" do
150
+ it "is nil" do
151
+ expect(@nometa.meta).to be nil
152
+ end
153
+ end
154
+ end
155
+
156
+ describe "#has_meta?" do
157
+ context "when there is meta information" do
158
+ it "returns true" do
159
+ expect(@doc.has_meta?).to be true
160
+ end
161
+ end
162
+
163
+ context "when there is no meta information" do
164
+ it "returns false" do
165
+ expect(@nometa.has_meta?).to be false
166
+ end
167
+ end
168
+ end
169
+
170
+ describe "#jsonapi" do
171
+ context "when there is version information" do
172
+ it "is a JsonApi object" do
173
+ expect(@doc.jsonapi).to be_an_instance_of(Subjoin::JsonApi)
174
+ end
175
+
176
+ it "has an expected attribute" do
177
+ expect(@doc.jsonapi.version).to eq "1.0"
178
+ end
179
+ end
180
+
181
+ context "when there is no version information" do
182
+ it "is nil" do
183
+ expect(@noversion.jsonapi).to be nil
184
+ end
185
+ end
186
+ end
187
+
188
+ describe "#has_jsonapi?" do
189
+ context "when there is version information" do
190
+ it "returns true" do
191
+ expect(@doc.has_jsonapi?).to be true
192
+ end
193
+ end
194
+
195
+ context "when there is no version information" do
196
+ it "returns false" do
197
+ expect(@noversion.has_jsonapi?).to be false
198
+ end
199
+ end
200
+ end
201
+
202
+ context "instantiated with a URI" do
203
+ it "succeeds" do
204
+ allow_any_instance_of(Faraday::Connection).
205
+ to receive(:get).and_return(double(Faraday::Response, :body => ARTICLE))
206
+ expect(Subjoin::Document.new(URI("http://example.com/articles"))).
207
+ to be_an_instance_of(Subjoin::Document)
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe Subjoin::Identifier do
4
+ before :each do
5
+ data = JSON.parse(ARTICLE)['data']['relationships']['author']
6
+ @id = Subjoin::Identifier.
7
+ new(data['data']['type'], data['data']['id'], data['meta'])
8
+ end
9
+
10
+ it "has a type" do
11
+ expect(@id.type).to eq "people"
12
+ end
13
+
14
+ it "has an id" do
15
+ expect(@id.id).to eq "9"
16
+ end
17
+
18
+ it "has a Meta object" do
19
+ expect(@id.meta).to be_an_instance_of Subjoin::Meta
20
+ end
21
+
22
+ describe "equality" do
23
+ it "is equal if #type and #id are the same" do
24
+ other = Subjoin::Identifier.new("people", "9")
25
+ expect(@id == other).to be true
26
+ end
27
+
28
+ it "is other not equal" do
29
+ other = Subjoin::Identifier.new("schmeople", "9")
30
+ expect(@id == other).to be false
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,69 @@
1
+ require "spec_helper"
2
+
3
+ describe Subjoin::Inclusions do
4
+ before :all do
5
+ @doc = Subjoin::Document.new(JSON.parse(COMPOUND))
6
+ end
7
+
8
+ describe "#all" do
9
+ it "returns an Array" do
10
+ expect(@doc.included.all).to be_an_instance_of Array
11
+ end
12
+ end
13
+
14
+ describe "#first" do
15
+ it "returns a Resource" do
16
+ expect(@doc.included.first).to be_an_instance_of Subjoin::Resource
17
+ end
18
+ end
19
+
20
+ describe "#[]" do
21
+ context "when passed an Identifier" do
22
+ context "when the Identifier matches something included" do
23
+ it "returns a Resource" do
24
+ id = Subjoin::Identifier.new("people", "9")
25
+ expect(@doc.included[id]).to be_an_instance_of Subjoin::Resource
26
+ end
27
+
28
+ it "returns to expected Resource" do
29
+ id = Subjoin::Identifier.new("people", "9")
30
+ expect(@doc.included[id]['twitter']).to eq "dgeb"
31
+ end
32
+ end
33
+ end
34
+
35
+ context "when passed an Array" do
36
+ context "when the Identifier matches something included" do
37
+ it "returns a Resource" do
38
+ expect(@doc.included[["people", "9"]]).
39
+ to be_an_instance_of Subjoin::Resource
40
+ end
41
+
42
+ it "returns to expected Resource" do
43
+ expect(@doc.included[["people", "9"]]['twitter']).to eq "dgeb"
44
+ end
45
+ end
46
+ end
47
+
48
+ context "when passed a Fixnum" do
49
+ context "when the Identifier matches something included" do
50
+ it "returns a Resource" do
51
+ expect(@doc.included[0]).
52
+ to be_an_instance_of Subjoin::Resource
53
+ end
54
+
55
+ it "returns to expected Resource" do
56
+ expect(@doc.included[["people", "9"]]['twitter']).to eq "dgeb"
57
+ end
58
+ end
59
+ end
60
+
61
+ context "when nothing matched" do
62
+ it "returns nil" do
63
+ expect(@doc.included[9]).
64
+ to be_nil
65
+ end
66
+ end
67
+ end
68
+ end
69
+