thingfish-metastore-pggraph 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aa350287482cc8fd51a482da2133f97e31b01d9d
|
4
|
+
data.tar.gz: ad7d4eeeb9db0849b5fa90b9f76633075fb81134
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b2b3bad22b9291d94bd5c9ce659fd2ce3cedb9956aad6b35d1dee21421d97bbca90ceb96450d91ff58e2f8f1bc1a1c1e15ad2f99444606807b4833e0fb037746
|
7
|
+
data.tar.gz: 30b2e55811bd4ade8794f3407a47476e57175245a9914fc45b0cf529dfaecb139ca2017c224612dc7b8c9bd71381aa4de7537cdb71b8ac3fe12ab7d6d32d275c
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# vim: set nosta noet ts=4 sw=4:
|
2
|
+
|
3
|
+
### The initial Thingfish::Metastore::PgGraph DDL.
|
4
|
+
###
|
5
|
+
Sequel.migration do
|
6
|
+
up do
|
7
|
+
create_table( :nodes ) do
|
8
|
+
uuid :id, primary_key: true
|
9
|
+
text :format, null: false
|
10
|
+
int :extent, null: false
|
11
|
+
timestamptz :created, null: false, default: Sequel.function(:now)
|
12
|
+
inet :uploadaddress, null: false
|
13
|
+
jsonb :user_metadata, null: false, default: '{}'
|
14
|
+
end
|
15
|
+
|
16
|
+
create_table( :edges ) do
|
17
|
+
uuid :id_p, null: false
|
18
|
+
uuid :id_c, null: false
|
19
|
+
jsonb :prop, null: false, default: '{}'
|
20
|
+
|
21
|
+
index :id_p
|
22
|
+
index :id_c
|
23
|
+
|
24
|
+
# Remove relationships when a node is deleted.
|
25
|
+
foreign_key [:id_p], :nodes, name: 'edges_p_fkey', key: :id, on_delete: :cascade, on_update: :cascade
|
26
|
+
foreign_key [:id_c], :nodes, name: 'edges_c_fkey', key: :id, on_delete: :cascade, on_update: :cascade
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add functional index from JSON edge props
|
30
|
+
run "CREATE INDEX relation_idx ON edges ( (prop->>'relationship') )"
|
31
|
+
|
32
|
+
# Disallow a node linking to itself -- no self loops.
|
33
|
+
run 'ALTER TABLE edges ADD CONSTRAINT no_self_edge_chk CHECK ( id_p <> id_c )'
|
34
|
+
|
35
|
+
# Allow only a single link between any two nodes, enforcing relations
|
36
|
+
# to be directional parent->child.
|
37
|
+
run 'CREATE UNIQUE INDEX pair_unique_idx ON edges USING btree ((LEAST(id_p, id_c)), (GREATEST(id_p, id_c)))'
|
38
|
+
end
|
39
|
+
|
40
|
+
down do
|
41
|
+
drop_table( :nodes, cascade: true )
|
42
|
+
drop_table( :edges, cascade: true )
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,311 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'loggability'
|
5
|
+
require 'configurability'
|
6
|
+
require 'sequel'
|
7
|
+
require 'strelka'
|
8
|
+
require 'strelka/mixins'
|
9
|
+
|
10
|
+
require 'thingfish'
|
11
|
+
require 'thingfish/mixins'
|
12
|
+
require 'thingfish/metastore'
|
13
|
+
|
14
|
+
# Toplevel namespace
|
15
|
+
class Thingfish::Metastore::PgGraph < Thingfish::Metastore
|
16
|
+
extend Loggability,
|
17
|
+
Configurability,
|
18
|
+
Strelka::MethodUtilities
|
19
|
+
include Thingfish::Normalization
|
20
|
+
|
21
|
+
|
22
|
+
# Load Sequel extensions/plugins
|
23
|
+
Sequel.extension :migration
|
24
|
+
|
25
|
+
|
26
|
+
# Package version
|
27
|
+
VERSION = '0.1.3'
|
28
|
+
|
29
|
+
# Version control revision
|
30
|
+
REVISION = %q$Revision: 1ad0d5bc5083 $
|
31
|
+
|
32
|
+
# The data directory that contains migration files.
|
33
|
+
#
|
34
|
+
DATADIR = if ENV['THINGFISH_METASTORE_PGGRAPH_DATADIR']
|
35
|
+
Pathname.new( ENV['THINGFISH_METASTORE_PGGRAPH_DATADIR'] )
|
36
|
+
elsif Gem.datadir( 'thingfish-metastore-pggraph' )
|
37
|
+
Pathname.new( Gem.datadir('thingfish-metastore-pggraph') )
|
38
|
+
else
|
39
|
+
Pathname.new( __FILE__ ).dirname.parent.parent.parent +
|
40
|
+
'data' + 'thingfish-metastore-pggraph'
|
41
|
+
end
|
42
|
+
|
43
|
+
# The default config values
|
44
|
+
DEFAULT_CONFIG = {
|
45
|
+
uri: 'postgres:/thingfish',
|
46
|
+
slow_query_seconds: 0.01,
|
47
|
+
}
|
48
|
+
|
49
|
+
|
50
|
+
# Loggability API -- use a separate logger
|
51
|
+
log_as :thingfish_metastore_pggraph
|
52
|
+
|
53
|
+
# Configurability API -- load the `pg_metastore`
|
54
|
+
config_key :pggraph_metastore
|
55
|
+
|
56
|
+
##
|
57
|
+
# The URI of the database to use for the metastore
|
58
|
+
singleton_attr_accessor :uri
|
59
|
+
|
60
|
+
##
|
61
|
+
# The Sequel::Database that's used to access the metastore tables
|
62
|
+
singleton_attr_accessor :db
|
63
|
+
|
64
|
+
##
|
65
|
+
# The number of seconds to consider a "slow" query
|
66
|
+
singleton_attr_accessor :slow_query_seconds
|
67
|
+
|
68
|
+
|
69
|
+
### Set up the metastore database and migrate to the latest version.
|
70
|
+
def self::setup_database
|
71
|
+
Sequel.extension :pg_json_ops
|
72
|
+
|
73
|
+
self.db = Sequel.connect( self.uri )
|
74
|
+
self.db.logger = Loggability[ Thingfish::Metastore::PgGraph ]
|
75
|
+
self.db.extension :pg_streaming
|
76
|
+
self.db.stream_all_queries = true
|
77
|
+
self.db.optimize_model_load = true
|
78
|
+
self.db.sql_log_level = :debug
|
79
|
+
self.db.extension( :pg_json )
|
80
|
+
self.db.log_warn_duration = self.slow_query_seconds
|
81
|
+
|
82
|
+
# Ensure the database is current.
|
83
|
+
#
|
84
|
+
unless Sequel::Migrator.is_current?( self.db, self.migrations_dir.to_s )
|
85
|
+
self.log.info "Installing database schema..."
|
86
|
+
Sequel::Migrator.apply( self.db, self.migrations_dir.to_s )
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
### Tear down the configured metastore database.
|
92
|
+
def self::teardown_database
|
93
|
+
self.log.info "Tearing down database schema..."
|
94
|
+
Sequel::Migrator.apply( self.db, self.migrations_dir.to_s, 0 )
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
### Return the current database migrations directory as a Pathname
|
99
|
+
def self::migrations_dir
|
100
|
+
return DATADIR + 'migrations'
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
### Configurability API -- set up the metastore with the `pg_metastore` section of
|
105
|
+
### the config file.
|
106
|
+
def self::configure( config=nil )
|
107
|
+
config = self.defaults.merge( config || {} )
|
108
|
+
|
109
|
+
self.uri = config[:uri]
|
110
|
+
self.slow_query_seconds = config[:slow_query_seconds]
|
111
|
+
|
112
|
+
self.setup_database
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
### Set up the metastore.
|
117
|
+
def initialize( * ) # :notnew:
|
118
|
+
require 'thingfish/metastore/pggraph/node'
|
119
|
+
require 'thingfish/metastore/pggraph/edge'
|
120
|
+
Thingfish::Metastore::PgGraph::Node.db = self.class.db
|
121
|
+
Thingfish::Metastore::PgGraph::Edge.db = self.class.db
|
122
|
+
@model = Thingfish::Metastore::PgGraph::Node
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
######
|
127
|
+
public
|
128
|
+
######
|
129
|
+
|
130
|
+
##
|
131
|
+
# The Sequel model representing the metadata rows.
|
132
|
+
attr_reader :model
|
133
|
+
|
134
|
+
|
135
|
+
#
|
136
|
+
# :section: Thingfish::Metastore API
|
137
|
+
#
|
138
|
+
|
139
|
+
### Return an Array of all stored oids.
|
140
|
+
def oids
|
141
|
+
return self.each_oid.to_a
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
### Iterate over each of the store's oids, yielding to the block if one is given
|
146
|
+
### or returning an Enumerator if one is not.
|
147
|
+
def each_oid( &block )
|
148
|
+
return self.model.select_map( :id ).each( &block )
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
### Save the +metadata+ Hash for the specified +oid+.
|
153
|
+
def save( oid, metadata )
|
154
|
+
md = self.model.from_hash( metadata )
|
155
|
+
md.id = oid
|
156
|
+
md.save
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
### Fetch the data corresponding to the given +oid+ as a Hash-ish object.
|
161
|
+
def fetch( oid, *keys )
|
162
|
+
metadata = self.model[ oid ] or return nil
|
163
|
+
|
164
|
+
if keys.empty?
|
165
|
+
return metadata.to_hash
|
166
|
+
else
|
167
|
+
keys = normalize_keys( keys )
|
168
|
+
values = metadata.to_hash.values_at( *keys )
|
169
|
+
return Hash[ [keys, values].transpose ]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
### Fetch the value of the metadata associated with the given +key+ for the
|
175
|
+
### specified +oid+.
|
176
|
+
def fetch_value( oid, key )
|
177
|
+
metadata = self.model[ oid ] or return nil
|
178
|
+
key = key.to_sym
|
179
|
+
return metadata[ key ] || metadata.user_metadata[ key ]
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
### Fetch UUIDs related to the given +oid+.
|
184
|
+
def fetch_related_oids( oid )
|
185
|
+
oid = normalize_oid( oid )
|
186
|
+
oid = self.model[ oid ]
|
187
|
+
return [] unless oid
|
188
|
+
return oid.related_nodes.map( &:id_c )
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
### Search the metastore for UUIDs which match the specified +criteria+ and
|
193
|
+
### return them as an iterator.
|
194
|
+
def search( options={} )
|
195
|
+
ds = self.model.naked.select( :id )
|
196
|
+
self.log.debug "Starting search with %p" % [ ds ]
|
197
|
+
|
198
|
+
ds = self.omit_related_resources( ds, options )
|
199
|
+
ds = self.apply_search_criteria( ds, options )
|
200
|
+
ds = self.apply_search_order( ds, options )
|
201
|
+
ds = self.apply_search_direction( ds, options )
|
202
|
+
ds = self.apply_search_limit( ds, options )
|
203
|
+
|
204
|
+
self.log.debug "Dataset for search is: %s" % [ ds.sql ]
|
205
|
+
|
206
|
+
return ds.map {|row| row[:id] }
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
### Update the metadata for the given +oid+ with the specified +values+ hash.
|
211
|
+
def merge( oid, values )
|
212
|
+
values = normalize_keys( values )
|
213
|
+
|
214
|
+
md = self.model[ oid ] or return nil
|
215
|
+
md.merge!( values )
|
216
|
+
md.save
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
### Remove all metadata associated with +oid+ from the Metastore.
|
221
|
+
def remove( oid, *keys )
|
222
|
+
self.model[ id: oid ].destroy
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
### Remove all metadata associated with +oid+ except for the specified +keys+.
|
227
|
+
def remove_except( oid, *keys )
|
228
|
+
keys = normalize_keys( keys )
|
229
|
+
|
230
|
+
md = self.model[ oid ] or return nil
|
231
|
+
md.user_metadata.keep_if {|key,_| keys.include?(key) }
|
232
|
+
md.save
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
### Returns +true+ if the metastore has metadata associated with the specified +oid+.
|
237
|
+
def include?( oid )
|
238
|
+
return self.model.count( id: oid ).nonzero?
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
### Returns the number of objects the store contains.
|
243
|
+
def size
|
244
|
+
return self.model.count
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
#########
|
249
|
+
protected
|
250
|
+
#########
|
251
|
+
|
252
|
+
### Omit related resources from the search dataset +ds+ unless the given
|
253
|
+
### +options+ specify otherwise.
|
254
|
+
def omit_related_resources( ds, options )
|
255
|
+
unless options[:include_related]
|
256
|
+
self.log.debug " omitting entries for related resources"
|
257
|
+
ds = ds.unrelated
|
258
|
+
end
|
259
|
+
return ds
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
### Apply the search :criteria from the specified +options+ to the collection
|
264
|
+
### in +ds+ and return the modified dataset.
|
265
|
+
def apply_search_criteria( ds, options )
|
266
|
+
if (( criteria = options[:criteria] ))
|
267
|
+
criteria.each do |field, value|
|
268
|
+
self.log.debug " applying criteria: %p => %p" % [ field.to_s, value ]
|
269
|
+
ds = ds.where_metadata( field => value )
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
return ds
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
### Apply the search :order from the specified +options+ to the collection in
|
278
|
+
### +ds+ and return the modified dataset.
|
279
|
+
def apply_search_order( ds, options )
|
280
|
+
if options[:order]
|
281
|
+
columns = Array( options[:order] )
|
282
|
+
self.log.debug " ordering results by columns: %p" % [ columns ]
|
283
|
+
ds = ds.order_metadata( columns )
|
284
|
+
end
|
285
|
+
|
286
|
+
return ds
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
### Apply the search :direction from the specified +options+ to the collection
|
291
|
+
### in +ds+ and return the modified dataset.
|
292
|
+
def apply_search_direction( ds, options )
|
293
|
+
ds = ds.reverse if options[:direction] && options[:direction] == 'desc'
|
294
|
+
return ds
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
### Apply the search :limit from the specified +options+ to the collection in
|
299
|
+
### +ds+ and return the modified dataset.
|
300
|
+
def apply_search_limit( ds, options )
|
301
|
+
if (( limit = options[:limit] ))
|
302
|
+
self.log.debug " limiting to %s results" % [ limit ]
|
303
|
+
offset = options[:offset] || 0
|
304
|
+
ds = ds.limit( limit, offset )
|
305
|
+
end
|
306
|
+
|
307
|
+
return ds
|
308
|
+
end
|
309
|
+
|
310
|
+
end # class Thingfish::Metastore::PgGraph
|
311
|
+
|
@@ -0,0 +1,52 @@
|
|
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 representing a relationship between two node objects.
|
11
|
+
###
|
12
|
+
class Thingfish::Metastore::PgGraph::Edge < Sequel::Model( :edges )
|
13
|
+
|
14
|
+
# Related resource associations
|
15
|
+
many_to_one :node, :key => :id_p
|
16
|
+
|
17
|
+
# Dataset methods
|
18
|
+
#
|
19
|
+
dataset_module do
|
20
|
+
#########
|
21
|
+
protected
|
22
|
+
#########
|
23
|
+
|
24
|
+
### Returns a Sequel expression suitable for use as the key of a query against
|
25
|
+
### the specified property field.
|
26
|
+
###
|
27
|
+
def prop_expr( field )
|
28
|
+
return Sequel.pg_jsonb( :prop ).get_text( field.to_s )
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
### Do some initial attribute setup for new objects.
|
34
|
+
###
|
35
|
+
def initialize( * )
|
36
|
+
super
|
37
|
+
self[ :prop ] ||= Sequel.pg_jsonb({})
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
#########
|
42
|
+
protected
|
43
|
+
#########
|
44
|
+
|
45
|
+
### Proxy method -- fetch a value from the edge property hash if it exists.
|
46
|
+
###
|
47
|
+
def method_missing( sym, *args, &block )
|
48
|
+
return self.prop[ sym.to_s ] || super
|
49
|
+
end
|
50
|
+
|
51
|
+
end # Thingfish::Metastore::PgGraph::Edge
|
52
|
+
|
@@ -0,0 +1,202 @@
|
|
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 = ds.join_edges unless ds.joined_dataset?
|
58
|
+
ds = ds.filter( Sequel.pg_jsonb( :edges__prop ).get_text( field.to_s ) => value )
|
59
|
+
|
60
|
+
elsif field.to_sym == :relation
|
61
|
+
ds = ds.join_edges unless ds.joined_dataset?
|
62
|
+
ds = self.join_edges.filter( :edges__id_p => value )
|
63
|
+
|
64
|
+
else
|
65
|
+
ds = ds.where( self.user_metadata_expr(field) => value )
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
return ds
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
### Dataset method: Order results by the specified +columns+.
|
75
|
+
###
|
76
|
+
def order_metadata( *columns )
|
77
|
+
columns.flatten!
|
78
|
+
ds = self
|
79
|
+
columns.each do |column|
|
80
|
+
if Thingfish::Metastore::PgGraph::Node.metadata_columns.include?( column.to_sym )
|
81
|
+
ds = ds.order_append( column.to_sym )
|
82
|
+
else
|
83
|
+
ds = ds.order_append( self.user_metadata_expr(column) )
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
return ds
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
#########
|
93
|
+
protected
|
94
|
+
#########
|
95
|
+
|
96
|
+
### Return a dataset linking related nodes to edges.
|
97
|
+
###
|
98
|
+
def join_edges( aka=nil )
|
99
|
+
return self.join_table( :left, :edges, { :id_c => :nodes__id }, { :table_alias => aka } )
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
### Returns a Sequel expression suitable for use as the key of a query against
|
104
|
+
### the specified user metadata field.
|
105
|
+
def user_metadata_expr( field )
|
106
|
+
return Sequel.pg_jsonb( :user_metadata ).get_text( field.to_s )
|
107
|
+
end
|
108
|
+
|
109
|
+
end # dataset_module
|
110
|
+
|
111
|
+
|
112
|
+
### Return a new Metadata object from the given +oid+ and one-dimensional +hash+
|
113
|
+
### used by Thingfish.
|
114
|
+
def self::from_hash( hash )
|
115
|
+
metadata = Thingfish::Normalization.normalize_keys( hash )
|
116
|
+
|
117
|
+
md = new
|
118
|
+
|
119
|
+
md.format = metadata.delete( 'format' )
|
120
|
+
md.extent = metadata.delete( 'extent' )
|
121
|
+
md.created = metadata.delete( 'created' )
|
122
|
+
md.uploadaddress = metadata.delete( 'uploadaddress' ).to_s
|
123
|
+
|
124
|
+
md.user_metadata = Sequel.pg_jsonb( metadata )
|
125
|
+
|
126
|
+
return md
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
### Return the columns of the table that are used for resource metadata.
|
131
|
+
def self::metadata_columns
|
132
|
+
return self.columns - [self.primary_key, :user_metadata]
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
### Do some initial attribute setup for new objects.
|
137
|
+
def initialize( * )
|
138
|
+
super
|
139
|
+
self[ :user_metadata ] ||= Sequel.pg_jsonb({})
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
### Return the metadata as a Hash; overridden from Sequel::Model to
|
144
|
+
### merge the user and system pairs together.
|
145
|
+
def to_hash
|
146
|
+
hash = self.values.dup
|
147
|
+
|
148
|
+
hash.delete( :id )
|
149
|
+
hash.merge!( hash.delete(:user_metadata) )
|
150
|
+
|
151
|
+
if related_to = self.related_to
|
152
|
+
hash.merge!( related_to.prop )
|
153
|
+
hash[ :relation ] = related_to.id_p
|
154
|
+
end
|
155
|
+
|
156
|
+
return normalize_keys( hash )
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
### Merge new metadata +values+ into the metadata for the resource
|
161
|
+
def merge!( values )
|
162
|
+
|
163
|
+
# Extract and set the column-metadata values first
|
164
|
+
self.class.metadata_columns.each do |col|
|
165
|
+
next unless values.key?( col.to_s )
|
166
|
+
self[ col ] = values.delete( col.to_s )
|
167
|
+
end
|
168
|
+
|
169
|
+
self.user_metadata.merge!( values )
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
### Hook creation for new related resources, divert relation data to
|
174
|
+
### a new edge row.
|
175
|
+
###
|
176
|
+
def around_save
|
177
|
+
relationship = self.user_metadata.delete( 'relationship' )
|
178
|
+
relation = self.user_metadata.delete( 'relation' )
|
179
|
+
|
180
|
+
super
|
181
|
+
|
182
|
+
if relation
|
183
|
+
edge = Thingfish::Metastore::PgGraph::Edge.new
|
184
|
+
edge.prop[ 'relationship' ] = relationship
|
185
|
+
edge.id_p = relation
|
186
|
+
edge.id_c = self.id
|
187
|
+
edge.save
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
#########
|
193
|
+
protected
|
194
|
+
#########
|
195
|
+
|
196
|
+
### Proxy method -- fetch a value from the metadata hash if it exists.
|
197
|
+
def method_missing( sym, *args, &block )
|
198
|
+
return self.user_metadata[ sym.to_s ] || super
|
199
|
+
end
|
200
|
+
|
201
|
+
end # Thingfish::Metastore::PgGraph::Node
|
202
|
+
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thingfish-metastore-pggraph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mahlon E. Smith <mahlon@martini.nu>
|
8
|
+
- Michael Granger <ged@faeriemud.org>
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-11-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thingfish
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0.5'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0.5'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: loggability
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0.11'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0.11'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: configurability
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '2.2'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '2.2'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: sequel
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '4.35'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '4.35'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: pg
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.19'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.19'
|
84
|
+
description: |
|
85
|
+
This is a metadata storage plugin for the Thingfish digital asset
|
86
|
+
manager. It provides persistent storage for uploaded data to PostgreSQL
|
87
|
+
tables.
|
88
|
+
|
89
|
+
It is heavily based on the regular PG metastore, however it differs by
|
90
|
+
storing objects as nodes, and their relations as edges.
|
91
|
+
email: mahlon@martini.nu
|
92
|
+
executables: []
|
93
|
+
extensions: []
|
94
|
+
extra_rdoc_files: []
|
95
|
+
files:
|
96
|
+
- data/thingfish-metastore-pggraph/migrations/20151102_initial.rb
|
97
|
+
- lib/thingfish/metastore/pggraph.rb
|
98
|
+
- lib/thingfish/metastore/pggraph/edge.rb
|
99
|
+
- lib/thingfish/metastore/pggraph/node.rb
|
100
|
+
homepage: https://bitbucket.org/mahlon/thingfish-metastore-pggraph
|
101
|
+
licenses:
|
102
|
+
- BSD-3-Clause
|
103
|
+
metadata: {}
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '2.3'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 2.6.8
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: Graph DDL storage for Thingfish metadata.
|
124
|
+
test_files: []
|