lib/thingfish/metastore/pggraph/node.rb
changeset 0 3cc90e88c6ab
child 3 5b4ee98d698c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/thingfish/metastore/pggraph/node.rb	Thu Nov 05 10:34:15 2015 -0800
@@ -0,0 +1,182 @@
+# -*- ruby -*-
+#encoding: utf-8
+
+require 'sequel/model'
+
+require 'thingfish/mixins'
+require 'thingfish/metastore/pggraph' unless defined?( Thingfish::Metastore::PgGraph )
+
+
+# A row of metadata describing an asset in a Thingfish store.
+class Thingfish::Metastore::PgGraph::Node < Sequel::Model( :nodes )
+	include Thingfish::Normalization
+
+	# Related resources for this node
+	one_to_many :related_nodes, :key => :id_p, :class => 'Thingfish::Metastore::PgGraph::Edge'
+
+	# Edge relation if this node is a related resource
+	one_to_one :related_to, :key => :id_c, :class => 'Thingfish::Metastore::PgGraph::Edge'
+
+	# Allow instances to be created with a primary key
+	unrestrict_primary_key
+
+
+	# Dataset methods
+	dataset_module do
+
+		### Dataset method: Limit results to metadata which is for a related resource.
+		###
+		def related
+			return self.join_edges( :rel ).exclude( :rel__id_c => nil )
+		end
+
+
+		### Dataset method: Limit results to metadata which is not for a related resource.
+		###
+		def unrelated
+			return self.join_edges( :notrel ).filter( :notrel__id_c => nil )
+		end
+
+
+		### Dataset method: Limit results to records whose operational or user
+		### metadata matches the values from the specified +hash+.
+		###
+		def where_metadata( hash )
+			ds = self
+			hash.each do |field, value|
+
+				# Direct DB column
+				#
+				if self.model.metadata_columns.include?( field.to_sym )
+					ds = ds.where( field.to_sym => value )
+
+				# User metadata or edge relationship
+				#
+				else
+					if field.to_sym == :relationship
+						ds = self.join_edges.filter( Sequel.pg_jsonb( :edges__prop ).get_text( field.to_s ) => value )
+
+					elsif field.to_sym == :relation
+						ds = self.join_edges.filter( :edges__id_p => value )
+
+					else
+						ds = ds.where( self.user_metadata_expr(field) => value )
+					end
+				end
+			end
+
+			return ds
+		end
+
+
+		#########
+		protected
+		#########
+
+		### Return a dataset linking related nodes to edges.
+		###
+		def join_edges( aka=nil )
+			return self.join_table( :left, :edges, { :id_c => :nodes__id }, { :table_alias => aka } )
+		end
+
+
+		### Returns a Sequel expression suitable for use as the key of a query against
+		### the specified user metadata field.
+		def user_metadata_expr( field )
+			return Sequel.pg_jsonb( :user_metadata ).get_text( field.to_s )
+		end
+
+	end # dataset_module
+
+
+	### Return a new Metadata object from the given +oid+ and one-dimensional +hash+
+	### used by Thingfish.
+	def self::from_hash( hash )
+		metadata = Thingfish::Normalization.normalize_keys( hash )
+
+		md = new
+
+		md.format        = metadata.delete( 'format' )
+		md.extent        = metadata.delete( 'extent' )
+		md.created       = metadata.delete( 'created' )
+		md.uploadaddress = metadata.delete( 'uploadaddress' ).to_s
+
+		md.user_metadata = Sequel.pg_jsonb( metadata )
+
+		return md
+	end
+
+
+	### Return the columns of the table that are used for resource metadata.
+	def self::metadata_columns
+		return self.columns - [self.primary_key, :user_metadata]
+	end
+
+
+	### Do some initial attribute setup for new objects.
+	def initialize( * )
+		super
+		self[ :user_metadata ] ||= Sequel.pg_jsonb({})
+	end
+
+
+	### Return the metadata as a Hash; overridden from Sequel::Model to
+	### merge the user and system pairs together.
+	def to_hash
+		hash = self.values.dup
+
+		hash.delete( :id )
+		hash.merge!( hash.delete(:user_metadata) )
+
+		if related_to = self.related_to
+			hash.merge!( related_to.prop )
+			hash[ :relation ] = related_to.id_p
+		end
+
+		return normalize_keys( hash )
+	end
+
+
+	### Merge new metadata +values+ into the metadata for the resource
+	def merge!( values )
+
+		# Extract and set the column-metadata values first
+		self.class.metadata_columns.each do |col|
+			next unless values.key?( col.to_s )
+			self[ col ] = values.delete( col.to_s )
+		end
+
+		self.user_metadata.merge!( values )
+	end
+
+
+	### Hook creation for new related resources, divert relation data to
+	### a new edge row.
+	###
+	def around_save
+		relationship = self.user_metadata.delete( 'relationship' )
+		relation     = self.user_metadata.delete( 'relation' )
+
+		super
+
+		if relation
+			edge = Thingfish::Metastore::PgGraph::Edge.new
+			edge.prop[ 'relationship' ] = relationship
+			edge.id_p = relation
+			edge.id_c = self.id
+			edge.save
+		end
+	end
+
+
+	#########
+	protected
+	#########
+
+	### Proxy method -- fetch a value from the metadata hash if it exists.
+	def method_missing( sym, *args, &block )
+		return self.user_metadata[ sym.to_s ] || super
+	end
+
+end # Thingfish::Metastore::PgGraph::Node
+