Skip to content

Commit 8a4eee0

Browse files
committed
Add support for @reverse in frames.
1 parent 005cc68 commit 8a4eee0

File tree

2 files changed

+60
-26
lines changed

2 files changed

+60
-26
lines changed

lib/json/ld/frame.rb

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,13 @@ def frame(state, subjects, frame, options = {})
4949
next
5050
end
5151

52-
# Note: In order to treat each top-level match as a
53-
# compartmentalized result, clear the unique embedded subjects map
54-
# when the property is None, which only occurs at the top-level.
52+
# Note: In order to treat each top-level match as a compartmentalized result, clear the unique embedded subjects map when the property is None, which only occurs at the top-level.
5553
state = state.merge(uniqueEmbeds: {}) if property.nil?
5654

5755
output = {'@id' => id}
5856
state[:link][id] = output
5957

60-
# if embed is @never or if a circular reference would be created
61-
# by an embed, the subject cannot be embedded, just add the
62-
# reference; note that a circular reference won't occur when the
63-
# embed flag is `@link` as the above check will short-circuit
64-
# before reaching this point
58+
# if embed is @never or if a circular reference would be created by an embed, the subject cannot be embedded, just add the reference; note that a circular reference won't occur when the embed flag is `@link` as the above check will short-circuit before reaching this point
6559
if flags[:embed] == '@never' || creates_circular_reference(subject, state[:subjectStack])
6660
add_frame_output(parent, property, output)
6761
next
@@ -124,9 +118,7 @@ def frame(state, subjects, frame, options = {})
124118

125119
# handle defaults in order
126120
frame.keys.kw_sort.reject {|p| p.start_with?('@')}.each do |prop|
127-
# if omit default is off, then include default values for
128-
# properties that appear in the next frame but are not in
129-
# the matching subject
121+
# if omit default is off, then include default values for properties that appear in the next frame but are not in the matching subject
130122
n = frame[prop].first || {}
131123
omit_default_on = get_frame_flag(n, options, :omitDefault)
132124
if !omit_default_on && !output[prop]
@@ -136,6 +128,18 @@ def frame(state, subjects, frame, options = {})
136128
end
137129
end
138130

131+
# If frame has @reverse, embed identified nodes having this subject as a value of the associated property.
132+
frame.fetch('@reverse', {}).each do |reverse_prop, subframe|
133+
state[:subjects].each do |r_id, node|
134+
if Array(node[reverse_prop]).any? {|v| v['@id'] == id}
135+
# Node has property referencing this subject
136+
# recurse into reference
137+
(output['@reverse'] ||= {})[reverse_prop] ||= []
138+
frame(state, [r_id], subframe, options.merge(parent: output['@reverse'][reverse_prop]))
139+
end
140+
end
141+
end
142+
139143
# add output to parent
140144
add_frame_output(parent, property, output)
141145

@@ -206,13 +210,9 @@ def filter_subjects(state, subjects, frame, flags)
206210
##
207211
# Returns true if the given node matches the given frame.
208212
#
209-
# Matches either based on explicit type inclusion where the node
210-
# has any type listed in the frame. If the frame has empty types defined
211-
# matches nodes not having a @type. If the frame has a type of {} defined
212-
# matches nodes having any type defined.
213+
# Matches either based on explicit type inclusion where the node has any type listed in the frame. If the frame has empty types defined matches nodes not having a @type. If the frame has a type of {} defined matches nodes having any type defined.
213214
#
214-
# Otherwise, does duck typing, where the node must have all of the properties
215-
# defined in the frame.
215+
# Otherwise, does duck typing, where the node must have all of the properties defined in the frame.
216216
#
217217
# @param [Hash{String => Object}] subject the subject to check.
218218
# @param [Hash{String => Object}] frame the frame to check.
@@ -252,8 +252,7 @@ def filter_subject(subject, frame, flags)
252252
next
253253
end
254254

255-
# all properties must match to be a duck unless a @default is
256-
# specified
255+
# all properties must match to be a duck unless a @default is specified
257256
has_default = v.is_a?(Array) && v.length == 1 && v.first.is_a?(Hash) && v.first.has_key?('@default')
258257
return false if flags[:requireAll] && !has_default
259258
end
@@ -270,8 +269,7 @@ def validate_frame(state, frame)
270269
frame.is_a?(Hash) || (frame.is_a?(Array) && frame.first.is_a?(Hash) && frame.length == 1)
271270
end
272271

273-
# Checks the current subject stack to see if embedding the given subject
274-
# would cause a circular reference.
272+
# Checks the current subject stack to see if embedding the given subject would cause a circular reference.
275273
#
276274
# @param subject_to_embed the subject to embed.
277275
# @param subject_stack the current stack of subjects.
@@ -283,6 +281,7 @@ def creates_circular_reference(subject_to_embed, subject_stack)
283281
end
284282
end
285283

284+
##
286285
# Gets the frame flag value for the given flag name.
287286
#
288287
# @param frame the frame.
@@ -369,10 +368,7 @@ def add_frame_output(parent, property, output)
369368
end
370369
end
371370

372-
# Creates an implicit frame when recursing through subject matches. If
373-
# a frame doesn't have an explicit frame for a particular property, then
374-
# a wildcard child frame will be created that uses the same flags that
375-
# the parent frame used.
371+
# Creates an implicit frame when recursing through subject matches. If a frame doesn't have an explicit frame for a particular property, then a wildcard child frame will be created that uses the same flags that the parent frame used.
376372
#
377373
# @param [Hash] flags the current framing flags.
378374
# @return [Array<Hash>] the implicit frame.

spec/frame_spec.rb

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@
512512
end
513513
end
514514

515-
describe "@reverse", skip:true do
515+
describe "@reverse" do
516516
{
517517
"embed matched frames with @reverse" => {
518518
frame: {
@@ -550,6 +550,44 @@
550550
}]
551551
}
552552
},
553+
"embed matched frames with reversed property" => {
554+
frame: {
555+
"@context" => {
556+
"ex" => "http://example.org/",
557+
"excludes" => {"@reverse" => "ex:includes"}
558+
},
559+
"@type" => "ex:Type1",
560+
"excludes" => {}
561+
},
562+
input: [
563+
{
564+
"@context" => {"ex" => "http://example.org/"},
565+
"@id" => "ex:Sub1",
566+
"@type" => "ex:Type1"
567+
},
568+
{
569+
"@context" => {"ex" => "http://example.org/"},
570+
"@id" => "ex:Sub2",
571+
"@type" => "ex:Type2",
572+
"ex:includes" => {"@id" => "ex:Sub1"}
573+
},
574+
],
575+
output:{
576+
"@context" => {
577+
"ex" => "http://example.org/",
578+
"excludes" => {"@reverse" => "ex:includes"}
579+
},
580+
"@graph" => [{
581+
"@id" => "ex:Sub1",
582+
"@type" => "ex:Type1",
583+
"excludes" => {
584+
"@id" => "ex:Sub2",
585+
"@type" => "ex:Type2",
586+
"ex:includes" => {"@id" => "ex:Sub1"}
587+
}
588+
}]
589+
}
590+
},
553591
}.each do |title, params|
554592
it title do
555593
begin

0 commit comments

Comments
 (0)