4 CXGN::JSONProp - an abstract class for implementing different prop objects that store data in json format
8 A subclass needs to implement accessors for each property that is stored as a json key. A BUILD function needs to be implemented that defines some metadata about tables and namespaces that the object has to access.
10 Example implementation of a subclass:
19 BEGIN { extends 'CXGN::JSONProp'; }
21 has 'info_field1' => (isa => 'Str', is => 'rw');
23 has 'info_field2' => (isa => 'Str', is => 'rw');
29 $self->prop_table('projectprop');
30 $self->prop_namespace('Project::Projectprop');
31 $self->prop_primary_key('projectprop_id');
32 $self->prop_type('analysis_metadata_json');
33 $self->cv_name('project_property');
34 $self->allowed_fields( [ qw | info_field1 info_field2 | ] );
35 $self->parent_table('project');
36 $self->parent_primary_key('project_id');
43 Lukas Mueller <lam87@cornell.edu>
51 package CXGN
::JSONProp
;
57 use Bio
::Chado
::Schema
;
58 use CXGN
::People
::Schema
;
61 use SGN
::Model
::Cvterm
;
63 has
'bcs_schema' => ( isa
=> 'Bio::Chado::Schema', is
=> 'rw');
65 has
'people_schema' => ( isa
=> 'CXGN::People::Schema', is
=> 'rw');
67 has
'prop_table' => (isa
=> 'Str', is
=> 'rw', default => 'Set_in_subclass!'); # for example, 'stockprop'
69 has
'prop_namespace' => (isa
=> 'Str', is
=> 'rw', default => 'Set_in_subclass!'); # for example 'Stock::Stockprop'
71 has
'_prop_type_id' => (isa
=> 'Int', is
=> 'rw');
73 has
'prop_primary_key' => (isa
=> 'Str', is
=> 'rw'); # set in subclass
75 has
'prop_type' => (isa
=> 'Str', is
=> 'rw'); # the type given by the type_id, set in subclass
77 has
'cv_name' => (isa
=> 'Str', is
=> 'rw'); # set in subclass
79 has
'allowed_fields' => (isa
=> 'Ref', is
=> 'rw', default => sub { [ qw
| | ] } ); # override in subclass
81 has
'prop_id' => (isa
=> 'Maybe[Int]', is
=> 'rw');
83 has
'parent_table' => (isa
=> 'Str', is
=> 'rw'); # set in subclass
85 has
'parent_primary_key' => (isa
=> 'Str', is
=> 'rw'); # set in subclass
87 has
'parent_id' => (isa
=> 'Maybe[Int]', is
=> 'rw');
90 sub load
{ # must be called from BUILD in subclass
95 print STDERR
"prop_type ".$self->prop_type()." cv_name ".$self->cv_name()."\n";
97 my $cvterm_row = SGN
::Model
::Cvterm
->get_cvterm_row($self->bcs_schema(), $self->prop_type(), $self->cv_name());
99 if (!$cvterm_row) { die "Can't find term ".$self->prop_type()." in cv ".$self->cv_name()."!!!!"; }
101 $self->_prop_type_id($cvterm_row->cvterm_id());
103 print STDERR
"LOAD PROP ID = ".$self->prop_id()."\n";
105 if ($self->prop_id()) {
107 if ($self->prop_table eq 'sp_orderprop') {
108 $rs = $self->people_schema()->resultset($self->prop_namespace())->search( { $self->prop_primary_key() => $self->prop_id() });
110 $rs = $self->bcs_schema()->resultset($self->prop_namespace())->search( { $self->prop_primary_key() => $self->prop_id() });
112 if (my $row = $rs->next()) {
113 if ($row->type_id() == $self->_prop_type_id()) {
114 #print STDERR "ROW VALUE = ".$row->value().", TYPEID=".$row->type_id()." TYPE = ".$self->prop_type()."\n";
115 my $parent_primary_key = $self->parent_primary_key();
116 my $parent_id = $row->$parent_primary_key;
117 $self->parent_id($parent_id);
118 $self->from_json($row->value());
120 print STDERR
"Skipping property unrelated to metadata...\n";
123 die "The object with id ".$self->prop_id()." does not exist. Sorry!";
134 $data = JSON
::Any
->decode($json);
137 print STDERR
"JSON not valid ($json) - ignoring.\n";
140 $self->from_hash($data);
148 my $allowed_fields = $self->allowed_fields();
150 #print STDERR Dumper($hash);
152 foreach my $f (@
$allowed_fields) {
153 if (exists($hash->{$f})) {
154 #print STDERR "Processing $f ($hash->{$f})...\n";
155 $self->$f($hash->{$f});
163 my $allowed_fields = $self->allowed_fields();
165 #print STDERR Dumper($allowed_fields);
168 foreach my $f (@
$allowed_fields) {
169 if (defined($self->$f())) {
170 $data->{$f} = $self->$f();
175 eval { $json = JSON
::Any
->encode($data); };
176 if ($@
) { print STDERR
"Warning! Data is not valid json ($json)\n"; }
182 my $allowed_fields = $self->allowed_fields();
185 foreach my $f (@
$allowed_fields) {
186 if (defined($self->$f())) {
187 $data->{$f} = $self->$f();
193 sub validate
{ # override in subclass
199 # check keys in the info hash...
202 die join("\n", @errors);
209 =head2 get_props($schema, $prop_id)
211 Usage: my @seq_projects = $stock->get_props($schema, $stock_id);
223 my $parent_id = shift;
225 print STDERR
"get_props(): creating object; using parent_id $parent_id\n";
226 my $obj = $class->new( { bcs_schema
=> $schema });
228 my @props = $obj->_retrieve_props($schema, $parent_id);
229 print STDERR
"Props = ".Dumper
(\
@props);
231 foreach my $sp (@props) {
236 $hash = JSON
::Any
->jsonToObj($json);
237 $hash->{prop_id
} = $sp->[0];
238 $hash->{parent_id
} = $sp->[1]
242 print STDERR
"Warning: $json is not valid json in prop ".$sp->[0].".!\n";
251 =head2 filter_props()
253 Usage: my $filtered_props = $JSONPropClass->filter_props({ schema=> $schema, conditions => \%conditions });
254 Desc: This class method can be used to get props that match the provided search criteria
255 Ret: a hash with the results metadata and the matching props:
256 page: current page number
257 maxPage: the number of the last page
258 pageSize: (max) number of results per page
259 total: total number of results
260 results: an arrayref of hashes containing the parent_id, prop_id, all of the prop values, and any specified parent fields
261 Args: schema = Bio::Chado::Schema
262 conditions = (optional, default=unfiltered/all props) a hashref of DBIx where conditions to filter the props by.
263 If you're filtering by a prop value, you should use the form: "value::json->>'prop_name' => 'prop value'"
264 parent_fields = (optional, default=none) an arrayref of the names of fields from the parent table to include in the results
265 NOTE: if a parent field is used in the search conditions, it should also be included here
266 order_by = (optional) the field to sort the results by:
267 order_by => "stockprop_id" // sort by ascending stockprop_id
268 order_by => { "-desc" => "value::json->'timestamp'" } // sort by descending timestamp in the json value
269 page = (optional, default=1) the page number of results to return
270 pageSize = (optional, default=1000) the number of results to return per page
271 Example: my $conditions = {
273 { 'stock.uniquename' => [ 'TEST_SEEDLOT_1', 'TEST_SEEDLOT_2' ] },
274 { 'value::json->>\'timestamp\'' => { '>=' => '2021-06-01 00:00:00' } },
275 { 'value::json->>\'timestamp\'' => { '<=' => '2021-06-30 24:00:00' } },
276 { 'value::json->>\'operator\'' => [ 'dwaring87' ] }
281 { 'value::json->>\'cvterm_id\'' => '78094' },
282 { 'value::json->>\'value\'' => [ 'Successful' ] }
287 { 'value::json->>\'cvterm_id\'' => '78085' },
288 { 'value::json->>\'value\'' => [ 'High', 'Medium' ] }
291 { 'value::json->>\'cvterm_id\'' => '78090' }
294 my $filtered_props = $JSONPropClass->filter_props({
296 conditions => $conditions,
297 parent_fields => ["uniquename"],
298 order_by => { "-desc" => "value::json->>'timestamp'" }
306 my $schema = $args->{schema
};
307 my $conditions = $args->{conditions
};
308 my $parent_fields = $args->{parent_fields
};
309 my $order_by = $args->{order_by
};
310 my $page = $args->{page
} || 1;
311 my $pageSize = $args->{pageSize
} || 1000;
312 my $type_id = $class->_prop_type_id();
314 # Build the search conditions
315 my @all_conditions = ();
316 push(@all_conditions, { 'me.type_id' => $class->_prop_type_id() });
318 push(@all_conditions, $conditions);
321 # Build the filter query using a ResultSet
324 foreach my $f (@
{$class->allowed_fields()}) {
325 push(@s, "value::json->>'$f'");
328 my $props = $schema->resultset($class->prop_namespace())->search(
329 { '-and' => \
@all_conditions },
331 'prefetch' => defined $parent_fields ?
$class->parent_table() : undef,
334 'order_by' => $order_by,
339 my $pager = $props->pager();
340 my $total = $pager->total_entries();
343 my @filtered_props = ();
344 while (my $r = $props->next) {
346 $class->prop_primary_key() => $r->get_column($class->prop_primary_key()),
347 $class->parent_primary_key() => $r->get_column($class->parent_primary_key())
350 $p{$f} = $r->get_column($f);
352 if ( defined $parent_fields ) {
353 my $pt = $class->parent_table();
354 foreach my $pf (@
$parent_fields) {
355 $p{$pf} = $r->$pt->$pf;
358 push(@filtered_props, \
%p);
361 # Return the results and page info
364 maxPage
=> int(ceil
($total/$pageSize)),
366 pageSize
=> $pageSize,
367 results
=> \
@filtered_props
373 =head2 OBJECT METHODS
375 =head2 method store()
378 Desc: creates a sequencing project info in the stockprop
391 ## TO DO: need to check for rank
394 if ($self->prop_id()) {
396 print STDERR
"UPDATING JSONPROP ".$self->to_json()."\n";
397 my $row = $self->bcs_schema()->resultset($self->prop_namespace())->find( { $self->prop_primary_key() => $self->prop_id() } );
399 $row->value($self->to_json());
405 $self->store_by_rank();
411 =head2 method store_by_rank()
413 Usage: $prop->store_by_rank();
414 Desc: store multiple props with the same type_id
428 my $rs = $self->bcs_schema()->resultset($self->prop_namespace())->search( { $self->parent_primary_key() => $self->parent_id(), type_id
=> $self->_prop_type_id() });
431 if ($rs->count() > 0) {
432 $rank = $rs->get_column("rank")->max();
436 my $row = $self->bcs_schema()->resultset($self->prop_namespace())->create( { $self->parent_primary_key()=> $self->parent_id(), value
=> $self->to_json(), type_id
=> $self->_prop_type_id(), rank
=> $rank });
437 my $prop_primary_key = $self->prop_primary_key();
438 $self->prop_id($row->$prop_primary_key);
443 =head2 method store_sp_orderprop()
445 Usage: $prop->store_sp_orderprop();
456 sub store_sp_orderprop
{
458 print STDERR
"PROP ID =".Dumper
($self->prop_id())."\n";
459 if ($self->prop_id()) {
460 print STDERR
"UPDATING JSONPROP ".$self->to_json()."\n";
461 my $row = $self->people_schema()->resultset($self->prop_namespace())->find( { $self->prop_primary_key() => $self->prop_id() } );
463 $row->value($self->to_json());
467 my $row = $self->people_schema()->resultset($self->prop_namespace())->create( { $self->parent_primary_key()=> $self->parent_id(), value
=> $self->to_json(), type_id
=> $self->_prop_type_id(), rank
=> 1});
468 my $prop_primary_key = $self->prop_primary_key();
469 $self->prop_id($row->$prop_primary_key);
474 =head2 method delete()
488 print STDERR
"Prop type id = ".$self->_prop_type_id()."\n";
489 print STDERR
"Parent primary key: ".$self->parent_primary_key()."\n";
490 print STDERR
"Parent id: ".$self->parent_id()."\n";
491 print STDERR
"Prop primary key: ".$self->prop_primary_key()."\n";
492 print STDERR
"Prop ID : ".$self->prop_id()."\n";
494 my $prop = $self->bcs_schema()->resultset($self->prop_namespace())->find({ type_id
=>$self->_prop_type_id(), $self->parent_primary_key() => $self->parent_id(), $self->prop_primary_key() => $self->prop_id() });
497 print STDERR
"Prop not found!\n";
506 =head2 _retrieve_props
509 Desc: Retrieves prop as a list of [prop_id, value]
511 Args: schema, parent_id, prop_type
517 sub _retrieve_props
{
520 my $parent_id = shift;
522 print STDERR
"Getting props for parent_id $parent_id, ".join(",", $self->parent_primary_key(), $self->_prop_type_id(), $self->prop_primary_key())."\n";
525 my $prop_primary_key = $self->prop_primary_key();
526 my $parent_primary_key = $self->parent_primary_key();
530 my $rs = $schema->resultset($self->prop_namespace())->search({ $self->parent_primary_key() => $parent_id, type_id
=> $self->_prop_type_id() }, { order_by
=> {-asc
=> $self->prop_primary_key() } });
532 while (my $r = $rs->next()){
533 push @results, [ $r->$prop_primary_key, $r->$parent_primary_key, $r->value() ];
540 print STDERR
"Searching all ".$self->_prop_type_id()." in namespace ".$self->prop_namespace()." (primary key is ".$prop_primary_key."...\n";
542 my $rs = $schema->resultset($self->prop_namespace())->search({ type_id
=> $self->_prop_type_id() }, { order_by
=> {-asc
=> $prop_primary_key } });
544 while (my $r = $rs->next()){
545 push @results, [ $r->$prop_primary_key(), $r->$parent_primary_key(), $r->value() ];
551 print STDERR
"ERROR $@\n";