Skip to content

Commit 7ab16ab

Browse files
committed
Make Resource Enumerable to allow easy RDF serialization.
1 parent 5d52d37 commit 7ab16ab

File tree

2 files changed

+97
-39
lines changed

2 files changed

+97
-39
lines changed

lib/json/ld/resource.rb

Lines changed: 26 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,59 @@ module JSON::LD
22
# Simple Ruby reflector class to provide native
33
# access to JSON-LD objects
44
class Resource
5-
# @!attribute [r] attributes
5+
include RDF::Enumerable
6+
67
# @return [Hash<String => Object] Object representation of resource
78
attr_reader :attributes
89

9-
# @!attribute [r] id
1010
# @return [String] ID of this resource
1111
attr_reader :id
1212

13-
# @!attribute [r] context
1413
# @return [JSON::LD::Context] Context associated with this resource
1514
attr_reader :context
1615

16+
##
1717
# Is this resource clean (i.e., saved to mongo?)
1818
#
1919
# @return [Boolean]
2020
def clean?; @clean; end
2121

22+
##
2223
# Is this resource dirty (i.e., not yet saved to mongo?)
2324
#
2425
# @return [Boolean]
2526
def dirty?; !clean?; end
26-
27+
28+
##
2729
# Has this resource been reconciled against a mongo ID?
2830
#
2931
# @return [Boolean]
3032
def reconciled?; @reconciled; end
3133

34+
##
3235
# Has this resource been resolved so that
3336
# all references are to other Resources?
3437
#
3538
# @return [Boolean]
3639
def resolved?; @resolved; end
3740

41+
##
3842
# Anonymous resources have BNode ids or no schema:url
3943
#
4044
# @return [Boolean]
4145
def anonymous?; @anon; end
4246

47+
##
4348
# Is this a stub resource, which has not yet been
4449
# synched or created within the DB?
4550
def stub?; !!@stub; end
4651

52+
##
4753
# Is this a new resource, which has not yet been
4854
# synched or created within the DB?
4955
def new?; !!@new; end
5056

51-
# Manage contexts used by resources.
52-
#
53-
# @param [String] ctx
54-
# @return [JSON::LD::Context]
55-
def self.set_context(ctx)
56-
(@@contexts ||= {})[ctx] = JSON::LD::Context.new.parse(ctx)
57-
end
58-
57+
##
5958
# A new resource from the parsed graph
6059
# @param [Hash{String => Object}] node_definition
6160
# @param [Hash{Symbol => Object}] options
@@ -76,8 +75,7 @@ def self.set_context(ctx)
7675
# This is a stand-in for another resource that has
7776
# not yet been retrieved (or created) from Mongo
7877
def initialize(node_definition, options = {})
79-
@context_name = options[:context]
80-
@context = self.class.set_context(@context_name)
78+
@context = options[:context]
8179
@clean = options.fetch(:clean, false)
8280
@new = options.fetch(:new, true)
8381
@reconciled = options.fetch(:reconciled, !@new)
@@ -91,12 +89,14 @@ def initialize(node_definition, options = {})
9189
@anon = @id.nil? || @id.to_s[0,2] == '_:'
9290
end
9391

92+
##
9493
# Return a hash of this object, suitable for use by for ETag
9594
# @return [Fixnum]
9695
def hash
9796
self.deresolve.hash
9897
end
9998

99+
##
100100
# Reverse resolution of resource attributes.
101101
# Just returns `attributes` if
102102
# resource is unresolved. Otherwise, replaces `Resource`
@@ -134,6 +134,7 @@ def deresolve
134134
compacted.delete_if {|k, v| k == '@context'}
135135
end
136136

137+
##
137138
# Serialize to JSON-LD, minus `@context` using
138139
# a deresolved version of the attributes
139140
#
@@ -143,6 +144,13 @@ def to_json(options = nil)
143144
deresolve.to_json(options)
144145
end
145146

147+
##
148+
# Enumerate over statements associated with this resource
149+
def each(&block)
150+
JSON::LD::API.toRdf(attributes, expandContext: context, &block)
151+
end
152+
153+
##
146154
# Update node references using the provided map.
147155
# This replaces node references with Resources,
148156
# either stub or instantiated.
@@ -186,29 +194,7 @@ def update_obj(obj, reference_map)
186194
self
187195
end
188196

189-
# Merge resources
190-
# FIXME: If unreconciled or unresolved resources are merged
191-
# against reconciled/resolved resources, they will appear
192-
# to not match, even if they are really the same thing.
193-
#
194-
# @param [Resource] resource
195-
# @return [Resource] self
196-
def merge(resource)
197-
if attributes.neq?(resource.attributes)
198-
resource.attributes.each do |p, v|
199-
next if p == 'id'
200-
if v.nil? or (v.is_a?(Array) and v.empty?)
201-
attributes.delete(p)
202-
else
203-
attributes[p] = v
204-
end
205-
end
206-
@resolved = @clean = false
207-
end
208-
self
209-
end
210-
211-
#
197+
##
212198
# Override this method to implement save using
213199
# an appropriate storage mechanism.
214200
#
@@ -218,12 +204,14 @@ def merge(resource)
218204
#
219205
# @return [Boolean] true or false if resource not saved
220206
def save
221-
raise NotImplemented
207+
raise NotImplementedError
222208
end
223209

210+
##
224211
# Access individual fields, from subject definition
225212
def property(prop_name); @attributes.fetch(prop_name, nil); end
226213

214+
##
227215
# Access individual fields, from subject definition
228216
def method_missing(method, *args)
229217
property(method.to_s)

spec/resource_spec.rb

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,75 @@
33
require 'spec_helper'
44

55
describe JSON::LD::Resource do
6-
it "is pending"
6+
subject {JSON::LD::Resource.new({'@id' => '_:foo', "http://schema.org/name" => "foo"})}
7+
describe "#initialize" do
8+
specify {expect(subject).not_to be_nil}
9+
specify {expect(subject).to be_a(JSON::LD::Resource)}
10+
specify {expect(subject).not_to be_clean}
11+
specify {expect(subject).to be_anonymous}
12+
specify {expect(subject).to be_dirty}
13+
specify {expect(subject).to be_new}
14+
specify {expect(subject).not_to be_resolved}
15+
specify {expect(subject).not_to be_stub}
16+
context "schema:name property" do
17+
specify {expect(subject.property("http://schema.org/name")).to eq "foo"}
18+
end
19+
20+
describe "compacted with context" do
21+
subject {JSON::LD::Resource.new({'@id' => '_:foo', "http://schema.org/name" => "foo"}, :compact => true, :context => {"@vocab" => "http://schema.org/"})}
22+
specify {expect(subject).not_to be_nil}
23+
specify {expect(subject).to be_a(JSON::LD::Resource)}
24+
specify {expect(subject).not_to be_clean}
25+
specify {expect(subject).to be_anonymous}
26+
specify {expect(subject).to be_dirty}
27+
specify {expect(subject).to be_new}
28+
specify {expect(subject).not_to be_resolved}
29+
specify {expect(subject).not_to be_stub}
30+
its(:name) {should eq "foo"}
31+
end
32+
end
33+
34+
describe "#deresolve" do
35+
it "FIXME"
36+
end
37+
38+
describe "#resolve" do
39+
it "FIXME"
40+
end
41+
42+
describe "#hash" do
43+
specify {subject.hash.should be_a(Fixnum)}
44+
45+
it "returns the hash of the attributes" do
46+
subject.hash.should == subject.deresolve.hash
47+
end
48+
end
49+
50+
describe "#to_json" do
51+
it "has JSON" do
52+
subject.to_json.should be_a(String)
53+
JSON.parse(subject.to_json).should be_a(Hash)
54+
end
55+
it "has same ID" do
56+
JSON.parse(subject.to_json)['@id'].should == subject.id
57+
end
58+
end
59+
60+
describe "#each" do
61+
specify {expect {|b| subject.each(&b)}.to yield_with_args(RDF::Statement)}
62+
end
63+
64+
describe RDF::Enumerable do
65+
specify {expect(subject).to be_enumerable}
66+
67+
it "initializes a graph" do
68+
g = RDF::Graph.new << subject
69+
expect(g.count).to eq 1
70+
expect(g.objects.first).to eq "foo"
71+
end
72+
end
73+
74+
describe "#save" do
75+
specify {expect {subject.save}.to raise_error(NotImplementedError)}
76+
end
777
end

0 commit comments

Comments
 (0)