diff -r 000000000000 -r 3cc90e88c6ab lib/thingfish/metastore/pggraph/node.rb --- /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 +