Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add Resource class for simple ruby-like management of JSON-LD nodes.

  • Loading branch information...
commit 45e1203b1b853d7a99e2ec222030d679d5a71dd8 1 parent 24a4ebf
@gkellogg authored
Showing with 251 additions and 0 deletions.
  1. +1 −0  lib/json/ld.rb
  2. +243 −0 lib/json/ld/resource.rb
  3. +7 −0 spec/resource_spec.rb
View
1  lib/json/ld.rb
@@ -29,6 +29,7 @@ module LD
autoload :EvaluationContext, 'json/ld/evaluation_context'
autoload :Normalize, 'json/ld/normalize'
autoload :Reader, 'json/ld/reader'
+ autoload :Resource, 'json/ld/resource'
autoload :VERSION, 'json/ld/version'
autoload :Writer, 'json/ld/writer'
View
243 lib/json/ld/resource.rb
@@ -0,0 +1,243 @@
+module JSON::LD
+ # Simple Ruby reflector class to provide native
+ # access to JSON-LD objects
+ class Resource
+ # Object representation of resource
+ #
+ # @attr_reader [Hash<String => Object] attributes
+ attr_reader :attributes
+
+ # ID of this resource
+ #
+ # @attr_reader [String] id
+ attr_reader :id
+
+ # Context associated with this resource
+ #
+ # @attr_reader [JSON::LD::EvaluationContext] context
+ attr_reader :context
+
+ # Is this resource clean (i.e., saved to mongo?)
+ #
+ # @return [Boolean]
+ def clean?; @clean; end
+
+ # Is this resource dirty (i.e., not yet saved to mongo?)
+ #
+ # @return [Boolean]
+ def dirty?; !clean?; end
+
+ # Has this resource been reconciled against a mongo ID?
+ #
+ # @return [Boolean]
+ def reconciled?; @reconciled; end
+
+ # Has this resource been resolved so that
+ # all references are to other Resources?
+ #
+ # @return [Boolean]
+ def resolved?; @resolved; end
+
+ # Anonymous resources have BNode ids or no schema:url
+ #
+ # @return [Boolean]
+ def anonymous?; @anon; end
+
+ # Is this a stub resource, which has not yet been
+ # synched or created within the DB?
+ def stub?; !!@stub; end
+
+ # Is this a new resource, which has not yet been
+ # synched or created within the DB?
+ def new?; !!@new; end
+
+ # Manage contexts used by resources.
+ #
+ # @param [String] ctx
+ # @return [JSON::LD::EvaluationContext]
+ def self.set_context(ctx)
+ (@@contexts ||= {})[ctx] = JSON::LD::EvaluationContext.new.parse(ctx)
+ end
+
+ # A new resource from the parsed graph
+ # @param [Hash{String => Object}] node_definition
+ # @param [Hash{Symbol => Object}] options
+ # @option options [String] :context
+ # Resource context, used for finding
+ # appropriate collection and JSON-LD context.
+ # @option options [Boolean] :clean (false)
+ # @option options [Boolean] :compact (false)
+ # Assume `node_definition` is in expanded form
+ # and compact using `context`.
+ # @option options [Boolean] :reconciled (!new)
+ # node_definition is not based on Mongo IDs
+ # and must be reconciled against Mongo, or merged
+ # into another resource.
+ # @option options [Boolean] :new (true)
+ # This is a new resource, not yet saved to Mongo
+ # @option options [Boolean] :stub (false)
+ # This is a stand-in for another resource that has
+ # not yet been retrieved (or created) from Mongo
+ def initialize(node_definition, options = {})
+ @context_name = options[:context]
+ @context = self.class.set_context(@context_name)
+ @clean = options.fetch(:clean, false)
+ @new = options.fetch(:new, true)
+ @reconciled = options.fetch(:reconciled, !@new)
+ @resolved = false
+ @attributes = if options[:compact]
+ JSON::LD::API.compact(node_definition, @context)
+ else
+ node_definition
+ end
+ @id = @attributes['@id']
+ @anon = @id.nil? || @id.to_s[0,2] == '_:'
+ end
+
+ # Return a hash of this object, suitable for use by for ETag
+ # @return [Fixnum]
+ def hash
+ self.deresolve.hash
+ end
+
+ # Reverse resolution of resource attributes.
+ # Just returns `attributes` if
+ # resource is unresolved. Otherwise, replaces `Resource`
+ # values with node references.
+ #
+ # Result is expanded and re-compacted to get to normalized
+ # representation.
+ #
+ # @return [Hash] deresolved attribute hash
+ def deresolve
+ node_definition = if resolved?
+ deresolved = attributes.keys.inject({}) do |memo, prop|
+ value = attributes[prop]
+ memo[prop] = case value
+ when Resource
+ {'id' => value.id}
+ when Array
+ value.map do |v|
+ v.is_a?(Resource) ? {'id' => v.id} : v
+ end
+ else
+ value
+ end
+ memo
+ end
+ deresolved
+ else
+ attributes
+ end
+
+ compacted = nil
+ JSON::LD::API.expand(node_definition, @context) do |expanded|
+ compacted = JSON::LD::API.compact(expanded, @context)
+ end
+ compacted.delete_if {|k, v| k == '@context'}
+ end
+
+ # Serialize to JSON-LD, minus `@context` using
+ # a deresolved version of the attributes
+ #
+ # @param [Hash] options
+ # @return [String] serizlied JSON representation of resource
+ def to_json(options = nil)
+ deresolve.to_json(options)
+ end
+
+ # Update node references using the provided map.
+ # This replaces node references with Resources,
+ # either stub or instantiated.
+ #
+ # Node references with ids not in the reference_map
+ # will cause stub resources to be added to the map.
+ #
+ # @param [Hash{String => Resource}] reference_map
+ # @return [Resource] self
+ def resolve(reference_map)
+ return if resolved?
+ def update_obj(obj, reference_map)
+ case obj
+ when Array
+ obj.map {|o| update_obj(o, reference_map)}
+ when Hash
+ if obj.node_ref?
+ reference_map[obj['id']] ||= Resource.new(obj,
+ :context => @context_name,
+ :clean => false,
+ :stub => true
+ )
+ else
+ obj.keys.each do |k|
+ obj[k] = update_obj(obj[k], reference_map)
+ end
+ obj
+ end
+ else
+ obj
+ end
+ end
+
+ #$logger.debug "resolve(0): #{attributes.inspect}"
+ @attributes.each do |k, v|
+ next if %w(id type).include?(k)
+ @attributes[k] = update_obj(@attributes[k], reference_map)
+ end
+ #$logger.debug "resolve(1): #{attributes.inspect}"
+ @resolved = true
+ self
+ end
+
+ # Merge resources
+ # FIXME: If unreconciled or unresolved resources are merged
+ # against reconciled/resolved resources, they will appear
+ # to not match, even if they are really the same thing.
+ #
+ # @param [Resource] resource
+ # @return [Resource] self
+ def merge(resource)
+ if attributes.neq?(resource.attributes)
+ resource.attributes.each do |p, v|
+ next if p == 'id'
+ if v.nil? or (v.is_a?(Array) and v.empty?)
+ attributes.delete(p)
+ else
+ attributes[p] = v
+ end
+ end
+ @resolved = @clean = false
+ end
+ self
+ end
+
+ #
+ # Override this method to implement save using
+ # an appropriate storage mechanism.
+ #
+ # Save the object to the Mongo collection
+ # use Upsert to create things that don't exist.
+ # First makes sure that the resource is valid.
+ #
+ # @return [Boolean] true or false if resource not saved
+ def save
+ raise NotImplemented
+ end
+
+ # Access individual fields, from subject definition
+ def property(prop_name); @attributes.fetch(prop_name, nil); end
+
+ # Access individual fields, from subject definition
+ def method_missing(method, *args)
+ property(method.to_s)
+ end
+
+ def inspect
+ "<Resource" +
+ attributes.map do |k, v|
+ "\n #{k}: #{v.inspect}"
+ end.join(" ") +
+ ">"
+ end
+ end
+end
View
7 spec/resource_spec.rb
@@ -0,0 +1,7 @@
+# coding: utf-8
+$:.unshift "."
+require 'spec_helper'
+
+describe JSON::LD::Resource do
+ it "is pending"
+end
Please sign in to comment.
Something went wrong with that request. Please try again.