|
1 # -*- ruby -*- |
|
2 #encoding: utf-8 |
|
3 |
|
4 require 'sequel/model' |
|
5 |
|
6 require 'thingfish/mixins' |
|
7 require 'thingfish/metastore/pggraph' unless defined?( Thingfish::Metastore::PgGraph ) |
|
8 |
|
9 |
|
10 # A row of metadata describing an asset in a Thingfish store. |
|
11 class Thingfish::Metastore::PgGraph::Node < Sequel::Model( :nodes ) |
|
12 include Thingfish::Normalization |
|
13 |
|
14 # Related resources for this node |
|
15 one_to_many :related_nodes, :key => :id_p, :class => 'Thingfish::Metastore::PgGraph::Edge' |
|
16 |
|
17 # Edge relation if this node is a related resource |
|
18 one_to_one :related_to, :key => :id_c, :class => 'Thingfish::Metastore::PgGraph::Edge' |
|
19 |
|
20 # Allow instances to be created with a primary key |
|
21 unrestrict_primary_key |
|
22 |
|
23 |
|
24 # Dataset methods |
|
25 dataset_module do |
|
26 |
|
27 ### Dataset method: Limit results to metadata which is for a related resource. |
|
28 ### |
|
29 def related |
|
30 return self.join_edges( :rel ).exclude( :rel__id_c => nil ) |
|
31 end |
|
32 |
|
33 |
|
34 ### Dataset method: Limit results to metadata which is not for a related resource. |
|
35 ### |
|
36 def unrelated |
|
37 return self.join_edges( :notrel ).filter( :notrel__id_c => nil ) |
|
38 end |
|
39 |
|
40 |
|
41 ### Dataset method: Limit results to records whose operational or user |
|
42 ### metadata matches the values from the specified +hash+. |
|
43 ### |
|
44 def where_metadata( hash ) |
|
45 ds = self |
|
46 hash.each do |field, value| |
|
47 |
|
48 # Direct DB column |
|
49 # |
|
50 if self.model.metadata_columns.include?( field.to_sym ) |
|
51 ds = ds.where( field.to_sym => value ) |
|
52 |
|
53 # User metadata or edge relationship |
|
54 # |
|
55 else |
|
56 if field.to_sym == :relationship |
|
57 ds = self.join_edges.filter( Sequel.pg_jsonb( :edges__prop ).get_text( field.to_s ) => value ) |
|
58 |
|
59 elsif field.to_sym == :relation |
|
60 ds = self.join_edges.filter( :edges__id_p => value ) |
|
61 |
|
62 else |
|
63 ds = ds.where( self.user_metadata_expr(field) => value ) |
|
64 end |
|
65 end |
|
66 end |
|
67 |
|
68 return ds |
|
69 end |
|
70 |
|
71 |
|
72 ######### |
|
73 protected |
|
74 ######### |
|
75 |
|
76 ### Return a dataset linking related nodes to edges. |
|
77 ### |
|
78 def join_edges( aka=nil ) |
|
79 return self.join_table( :left, :edges, { :id_c => :nodes__id }, { :table_alias => aka } ) |
|
80 end |
|
81 |
|
82 |
|
83 ### Returns a Sequel expression suitable for use as the key of a query against |
|
84 ### the specified user metadata field. |
|
85 def user_metadata_expr( field ) |
|
86 return Sequel.pg_jsonb( :user_metadata ).get_text( field.to_s ) |
|
87 end |
|
88 |
|
89 end # dataset_module |
|
90 |
|
91 |
|
92 ### Return a new Metadata object from the given +oid+ and one-dimensional +hash+ |
|
93 ### used by Thingfish. |
|
94 def self::from_hash( hash ) |
|
95 metadata = Thingfish::Normalization.normalize_keys( hash ) |
|
96 |
|
97 md = new |
|
98 |
|
99 md.format = metadata.delete( 'format' ) |
|
100 md.extent = metadata.delete( 'extent' ) |
|
101 md.created = metadata.delete( 'created' ) |
|
102 md.uploadaddress = metadata.delete( 'uploadaddress' ).to_s |
|
103 |
|
104 md.user_metadata = Sequel.pg_jsonb( metadata ) |
|
105 |
|
106 return md |
|
107 end |
|
108 |
|
109 |
|
110 ### Return the columns of the table that are used for resource metadata. |
|
111 def self::metadata_columns |
|
112 return self.columns - [self.primary_key, :user_metadata] |
|
113 end |
|
114 |
|
115 |
|
116 ### Do some initial attribute setup for new objects. |
|
117 def initialize( * ) |
|
118 super |
|
119 self[ :user_metadata ] ||= Sequel.pg_jsonb({}) |
|
120 end |
|
121 |
|
122 |
|
123 ### Return the metadata as a Hash; overridden from Sequel::Model to |
|
124 ### merge the user and system pairs together. |
|
125 def to_hash |
|
126 hash = self.values.dup |
|
127 |
|
128 hash.delete( :id ) |
|
129 hash.merge!( hash.delete(:user_metadata) ) |
|
130 |
|
131 if related_to = self.related_to |
|
132 hash.merge!( related_to.prop ) |
|
133 hash[ :relation ] = related_to.id_p |
|
134 end |
|
135 |
|
136 return normalize_keys( hash ) |
|
137 end |
|
138 |
|
139 |
|
140 ### Merge new metadata +values+ into the metadata for the resource |
|
141 def merge!( values ) |
|
142 |
|
143 # Extract and set the column-metadata values first |
|
144 self.class.metadata_columns.each do |col| |
|
145 next unless values.key?( col.to_s ) |
|
146 self[ col ] = values.delete( col.to_s ) |
|
147 end |
|
148 |
|
149 self.user_metadata.merge!( values ) |
|
150 end |
|
151 |
|
152 |
|
153 ### Hook creation for new related resources, divert relation data to |
|
154 ### a new edge row. |
|
155 ### |
|
156 def around_save |
|
157 relationship = self.user_metadata.delete( 'relationship' ) |
|
158 relation = self.user_metadata.delete( 'relation' ) |
|
159 |
|
160 super |
|
161 |
|
162 if relation |
|
163 edge = Thingfish::Metastore::PgGraph::Edge.new |
|
164 edge.prop[ 'relationship' ] = relationship |
|
165 edge.id_p = relation |
|
166 edge.id_c = self.id |
|
167 edge.save |
|
168 end |
|
169 end |
|
170 |
|
171 |
|
172 ######### |
|
173 protected |
|
174 ######### |
|
175 |
|
176 ### Proxy method -- fetch a value from the metadata hash if it exists. |
|
177 def method_missing( sym, *args, &block ) |
|
178 return self.user_metadata[ sym.to_s ] || super |
|
179 end |
|
180 |
|
181 end # Thingfish::Metastore::PgGraph::Node |
|
182 |