Refactor out the JSON collateral code from FilePump/Bundle.pm to make an aspect

that can be used by assets, operations and any other modules in WebGUI.
This commit is contained in:
Colin Kuskie 2009-08-26 15:42:18 +00:00
parent 7fcb339605
commit 18a52b9aa2
6 changed files with 718 additions and 2 deletions

View file

@ -1,4 +1,5 @@
7.7.19
- refactored out JSON collateral module, to use with any module in WebGUI.
- fixed #10825: Text and MediumText fields should be LongText
- fixed #10808: Ask user about using rich edit option causes error in IE6
- fixed #10822: spectre pid sucks

View file

@ -2495,8 +2495,13 @@ sub update {
}
# set the property
if ($propertyDefinition->{serialize}) {
$setPairs{$property} = JSON->new->canonical->encode($value);
}
else {
$setPairs{$property} = $value;
}
$self->{_properties}{$property} = $value;
$setPairs{$property} = $value;
}
# if there's anything to update, then do so

View file

@ -126,7 +126,7 @@ sub addRevision {
# get the default values of each property
foreach my $property (keys %{$definition->{properties}}) {
$defaults{$property} = $definition->{properties}{$property}{defaultValue};
if (ref($defaults{$property}) eq 'ARRAY') {
if (ref($defaults{$property}) eq 'ARRAY' && !$definition->{properties}{$property}{serialize}) {
$defaults{$property} = $defaults{$property}->[0];
}
}

View file

@ -0,0 +1,297 @@
package WebGUI::JSONCollateral;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use Class::C3;
=head1 NAME
Package WebGUI::JSONCollateral
=head1 DESCRIPTION
This is an aspect which allows you to use JSON in the database transparently.
=head1 SYNOPSIS
use Class::C3;
use base qw(WebGUI::JSONCollateral);
$self->setJSONCollateral();
$self->getJSONCollateral();
$self->moveJSONCollateralUp();
$self->moveJSONCollateralDown();
And then wherever you would call $self->SUPER::someMethodName call $self->next::method instead.
Classes that use this Aspect must have an update method that transparently serializes and deserializes data
to and from JSON into perl data structures. See WebGUI::Crud->update, and Asset->update for examples.
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 deleteJSONCollateral ( fieldName, keyName, keyValue )
Deletes a row of collateral data. Returns false if the requested collateral
was not deleted.
=head3 fieldName
The name of the field you wish to delete the data from.
=head3 keyName
The name of a key in the collateral hash. Typically a unique identifier for a given
"row" of collateral data.
=head3 keyValue
Along with keyName, determines which "row" of collateral data to delete.
=cut
sub deleteJSONCollateral {
my $self = shift;
my $fieldName = shift;
my $keyName = shift;
my $keyValue = shift;
my $field = $self->get($fieldName);
my $index = $self->getJSONCollateralDataIndex($field, $keyName, $keyValue);
return if $index == -1;
splice @{ $field }, $index, 1;
$self->update({ $fieldName => $field });
return 1;
}
#-------------------------------------------------------------------
=head2 getJSONCollateral ( fieldName, keyName, keyValue )
Returns a hash reference containing one row of collateral data from a particular
field.
=head3 fieldName
The name of the field you wish to retrieve the data from.
=head3 keyName
The name of a key in the collateral hash. Typically a unique identifier for a given
"row" of collateral data.
=head3 keyValue
Along with keyName, determines which "row" of collateral data to get.
If this is equal to "new", then an empty hashRef will be returned to avoid
strict errors in the caller. If the requested data does not exist in the
collateral array, it also returns an empty hashRef.
=cut
sub getJSONCollateral {
my $self = shift;
my $fieldName = shift;
my $keyName = shift;
my $keyValue = shift;
if ($keyValue eq "new" || $keyValue eq "") {
return {}
}
my $field = $self->get($fieldName);
my $index = $self->getJSONCollateralDataIndex($field, $keyName, $keyValue);
return {} if $index == -1;
my %copy = %{ $field->[$index] };
return \%copy;
}
#-------------------------------------------------------------------
=head2 getJSONCollateralDataIndex ( field, keyName, keyValue )
Returns the index in a set of collateral where an element of the
data (keyName) has a certain value (keyValue). If the criteria
are not found, returns -1.
=head3 field
The collateral data to search
=head3 keyName
The name of a key in the collateral hash.
=head3 keyValue
The value that keyName should have to meet the criteria.
=cut
sub getJSONCollateralDataIndex {
my $self = shift;
my $field = shift;
my $keyName = shift;
my $keyValue = shift;
for (my $index=0; $index <= $#{ $field }; $index++) {
return $index
if (exists($field->[$index]->{$keyName}) && ($field->[$index]->{$keyName} eq $keyValue ));
}
return -1;
}
#-------------------------------------------------------------------
=head2 moveJSONCollateralDown ( fieldName, keyName, keyValue )
Moves a collateral data item down one position, toward the end of the array where the
indices are the highest, swapping the referenced piece of collateral (index) with the collateral
just above it (index+1). For the list of collateral 1,2,3, if called on 2 the resultig
list will be 1,3,2. If called on the last element of the collateral array then it does nothing.
Returns 1 if the move is successful. Returns undef or the empty array otherwise.
=head3 fieldName
A string indicating the field that contains the collateral data.
=head3 keyName
The name of a key in the collateral hash. Typically a unique identifier for a given
"row" of collateral data.
=head3 keyValue
Along with keyName, determines which "row" of collateral data to move.
=cut
sub moveJSONCollateralDown {
my $self = shift;
my $fieldName = shift;
my $keyName = shift;
my $keyValue = shift;
my $field = $self->get($fieldName);
my $index = $self->getJSONCollateralDataIndex($field, $keyName, $keyValue);
return if $index == -1;
return unless (abs($index) < $#{$field});
@{ $field }[$index,$index+1] = @{ $field }[$index+1,$index];
$self->update({ $fieldName => $field });
return 1;
}
#-------------------------------------------------------------------
=head2 moveJSONCollateralUp ( fieldName, keyName, keyValue )
Moves a collateral data item "up" one position, toward the end of the array where the
indices are the lowest, swapping the referenced piece of collateral (index) with the collateral
just below it (index-1). For the list of collateral 1,2,3, if called on 2 the resultig
list will be 2,1,3. If called on the first element of the collateral array then it does nothing.
Returns 1 if the move is successful. Returns undef or the empty array otherwise.
=head3 fieldName
A string indicating the field that contains the collateral data.
=head3 keyName
The name of a key in the collateral hash. Typically a unique identifier for a given
"row" of collateral data.
=head3 keyValue
Along with keyName, determines which "row" of collateral data to move.
=cut
sub moveJSONCollateralUp {
my $self = shift;
my $fieldName = shift;
my $keyName = shift;
my $keyValue = shift;
my $field = $self->get($fieldName);
my $index = $self->getJSONCollateralDataIndex($field, $keyName, $keyValue);
return unless $index > 0; #-1 means that it could not be found, and we cannot move index 0
@{ $field }[$index-1,$index] = @{ $field }[$index,$index-1];
$self->update({ $fieldName => $field });
return 1;
}
#-----------------------------------------------------------------
=head2 setJSONCollateral ( fieldName, keyName, keyValue, properties )
Performs and insert/update of collateral data for any wobject's collateral data.
Returns the id of the data that was set, even if a new row was added to the
data.
=head3 fieldName
The name of the field to insert the data.
=head3 keyName
The name of a key in the collateral hash. Typically a unique identifier for a given
"row" of collateral data.
=head3 keyValue
Along with keyName, determines which "row" of collateral data to set.
The index of the collateral data to set. If the keyValue = "new", then a
new entry will be appended to the end of the collateral array. Otherwise,
the appropriate entry will be overwritten with the new data.
=head3 properties
A hash reference containing the name/value pairs to be inserted into the collateral, using
the criteria mentioned above.
=cut
sub setJSONCollateral {
my $self = shift;
my $fieldName = shift;
my $keyName = shift;
my $keyValue = shift;
my $properties = shift;
##Note, since this returns a reference, it is actually updating
##the object cache directly.
my $field = $self->get($fieldName);
if ($keyValue eq 'new' || $keyValue eq '') {
if ( ! exists $properties->{$keyName}
or ! $self->session->id->valid($properties->{$keyName})) {
$properties->{$keyName} = $self->session->id->generate;
}
push @{ $field }, $properties;
$self->update({$fieldName => $field});
return $properties->{$keyName};
}
my $index = $self->getJSONCollateralDataIndex($field, $keyName, $keyValue);
return if $index == -1;
$field->[$index] = $properties;
$self->update({ $fieldName => $field });
return $keyValue;
}
1;

334
t/Asset/JSONCollateral.t Normal file
View file

@ -0,0 +1,334 @@
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2009 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
use FindBin;
use strict;
use lib "$FindBin::Bin/../lib";
use WebGUI::Test;
use WebGUI::Session;
use WebGUI::Asset;
use WebGUI::AssetVersioning;
use WebGUI::VersionTag;
use WebGUI::Asset::JSONCollateralDummy;
use Test::More;
use Test::Deep;
use Data::Dumper;
my $session = WebGUI::Test->session;
$session->db->write(<<EOSQL);
drop table if exists jsonCollateralDummy
EOSQL
$session->db->write(<<EOSQL);
create table jsonCollateralDummy (
`assetId` varchar(22) character set utf8 collate utf8_bin NOT NULL,
`revisionDate` bigint(20) NOT NULL default '0',
`jsonField` mediumtext,
PRIMARY KEY (`assetId`,`revisionDate`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
EOSQL
plan tests => 39;
my $asset = WebGUI::Asset->getDefault($session)->addChild({
className => 'WebGUI::Asset::JSONCollateralDummy',
title => 'JSON Collateral Test Asset',
});
my $tag = WebGUI::VersionTag->getWorking($session);
$tag->commit;
################################################################
#
# Checking Asset serialization
#
################################################################
isa_ok($asset, 'WebGUI::Asset::JSONCollateralDummy');
cmp_deeply(
$asset->get('jsonField'),
[],
'jsonField set to empty arrayref initially'
);
is(
$session->db->quickScalar(q|select jsonField from jsonCollateralDummy where assetId=? and revisionDate=?|, [$asset->getId, $asset->get('revisionDate')]),
'[]',
'bare JSON arrayref stored in the db'
);
$asset->update({
jsonField => [ { alpha => "aye", beta => "bee", }, ],
});
is(
$session->db->quickScalar(q|select jsonField from jsonCollateralDummy where assetId=? and revisionDate=?|, [$asset->getId, $asset->get('revisionDate')]),
'[{"alpha":"aye","beta":"bee"}]',
'JSON updated in the db'
);
cmp_deeply(
$asset->get('jsonField'),
[ { alpha => "aye", beta => "bee"} ],
'get returns a hash ref with data in it'
);
$asset->update({
jsonField => [ ],
});
################################################################
#
# setJSONCollateral, getJSONCollateral
#
################################################################
my $key1 = $asset->setJSONCollateral('jsonField', 'jsonId', 'new', { first => "one", second => "two", });
ok($session->id->valid($key1), 'setJSONCollateral: returns a valid guid, new, no key in collateral');
my $key2 = $asset->setJSONCollateral('jsonField', 'jsonId', '', { first => "uno", second => "dos", });
ok($session->id->valid($key2), '... returns a valid guid, empty key, no key in collateral');
isnt($key2, $key1, '... returns unique guids each time. Generates GUID if guid is ""');
my $key3 = $session->id->generate();
my $returnedKey;
$returnedKey = $asset->setJSONCollateral('jsonField', 'jsonId', 'new', { first => 'Aye', second => 'Bay', jsonId => $key3});
is($returnedKey, $key3, '... created collateral with set GUID');
$returnedKey = $asset->setJSONCollateral('jsonField', 'jsonId', 'new', { first => 'Aye', second => 'Bay', jsonId => 'notAGUID'});
isnt($returnedKey, 'notAGUID', '... created valid GUID when passed a new one');
my $collateral;
$collateral = $asset->getJSONCollateral('jsonField', 'jsonId', 'notAGUID');
cmp_deeply( $collateral, {}, 'getJSONCollateral returns empty hashref for a non-existant id');
$collateral = $asset->getJSONCollateral('jsonField', 'jsonId', "new");
cmp_deeply( $collateral, {}, '... returns empty hashref for id=new');
$collateral = $asset->getJSONCollateral('jsonField', 'jsonId', "");
cmp_deeply( $collateral, {}, '... returns empty hashref for id=""');
################################################################
#
# Setup for move, delete tests.
#
################################################################
$asset->update({
jsonField => [ ],
});
my $guid1 = $asset->setJSONCollateral('jsonField', 'jsonId', 'new', { first => 'alpha', second => 'beta'});
my $guid2 = $asset->setJSONCollateral('jsonField', 'jsonId', '', { first => 'aee', second => 'bee'});
cmp_deeply(
$asset->get('jsonField'),
[
{ first => 'alpha', second => 'beta', jsonId => $guid1, },
{ first => 'aee', second => 'bee', jsonId => $guid2, },
],
'...checking collateral content, deeply'
);
my $guid3 = $asset->setJSONCollateral('jsonField', 'jsonId', 'new', { first => 'Aye', second => 'Bay', });
my $retVal;
$retVal = $asset->setJSONCollateral('jsonField', 'jsonId', $guid3, { first => 'ahh', second => 'bay', jsonId => $guid3, });
is($retVal, $guid3, 'setJSONCollateral returns GUID when it modifies existing collateral');
cmp_deeply(
$asset->getJSONCollateral('jsonField', 'jsonId', $guid3),
{ first => 'ahh', second => 'bay', jsonId => $guid3 },
'... collateral updated'
);
$retVal = $asset->setJSONCollateral('jsonField', 'jsonId', scalar reverse $guid3, { first => 'ook', second => 'eek'});
ok(!$retVal, '... returns false when it fails');
cmp_deeply(
$asset->getJSONCollateral('jsonField', 'jsonId', $guid3),
{ first => 'ahh', second => 'bay', jsonId => $guid3 },
'... collateral not updated'
);
cmp_deeply(
[ map { $_->{jsonId} } @{ $asset->get('jsonField') } ],
[
$guid1,
$guid2,
$guid3,
],
'...checking collateral order, ready for moving collateral'
);
################################################################
#
# getJSONCollateralDataIndex
#
################################################################
is(
$asset->getJSONCollateralDataIndex($asset->get('jsonField'), 'jsonId', $guid1),
0,
'getJSONCollateralDataIndex: guid1 in the correct position'
);
is(
$asset->getJSONCollateralDataIndex($asset->get('jsonField'), 'jsonId', scalar reverse($guid1)),
-1,
'... returns -1 when it cannot be found'
);
is(
$asset->getJSONCollateralDataIndex($asset->get('jsonField'), 'JSONID', $guid1),
-1,
'... returns -1 when it cannot be found'
);
################################################################
#
# moveJSONCollateralDown
#
################################################################
cmp_deeply(
[ map { $_->{jsonId} } @{ $asset->get('jsonField') } ],
[
$guid1,
$guid2,
$guid3,
],
'...checking collateral order, ready for moving collateral'
);
$retVal = $asset->moveJSONCollateralDown('jsonField', 'jsonId', $guid3);
ok(!$retVal, 'moveJSONCollateralDown returned false');
cmp_deeply(
[ map { $_->{jsonId} } @{ $asset->get('jsonField') } ],
[
$guid1,
$guid2,
$guid3,
],
'...order did not change. Cannot move last one down'
);
$retVal = $asset->moveJSONCollateralDown('jsonField', 'jsonId', scalar reverse $guid3);
ok(!$retVal, '... returned false again (nonexistant guid)');
cmp_deeply(
[ map { $_->{jsonId} } @{ $asset->get('jsonField') } ],
[
$guid1,
$guid2,
$guid3,
],
'...order did not change. Cannot move an entry that cannot be found'
);
$retVal = $asset->moveJSONCollateralDown('jsonField', 'jsonId', $guid1);
ok($retVal, '... returned true');
cmp_deeply(
[ map { $_->{jsonId} } @{ $asset->get('jsonField') } ],
[
$guid2,
$guid1,
$guid3,
],
'...order changed'
);
################################################################
#
# moveJSONCollateralUp
#
################################################################
$retVal = $asset->moveJSONCollateralUp('jsonField', 'jsonId', $guid2);
ok(!$retVal, 'moveJSONCollateralUp returned false');
cmp_deeply(
[ map { $_->{jsonId} } @{ $asset->get('jsonField') } ],
[
$guid2,
$guid1,
$guid3,
],
'...order did not change. Cannot move first one up'
);
$retVal = $asset->moveJSONCollateralUp('jsonField', 'jsonId', scalar reverse $guid3);
ok(!$retVal, '... returned false again (nonexistant guid)');
cmp_deeply(
[ map { $_->{jsonId} } @{ $asset->get('jsonField') } ],
[
$guid2,
$guid1,
$guid3,
],
'...order did not change. Cannot move an entry that cannot be found'
);
$retVal = $asset->moveJSONCollateralUp('jsonField', 'jsonId', $guid3);
ok($retVal, '... returned true');
cmp_deeply(
[ map { $_->{jsonId} } @{ $asset->get('jsonField') } ],
[
$guid2,
$guid3,
$guid1,
],
'...order changed'
);
################################################################
#
# deleteJSONCollateral
#
################################################################
$retVal = $asset->deleteJSONCollateral('jsonField', 'jsonId', scalar reverse $guid3);
ok(!$retVal, 'deleteJSONCollateral returns false with an invalid id');
cmp_deeply(
[ map { $_->{jsonId} } @{ $asset->get('jsonField') } ],
[
$guid2,
$guid3,
$guid1,
],
'...nothing was deleted'
);
$retVal = $asset->deleteJSONCollateral('jsonField', 'jsonId', $guid3);
ok($retVal, '... delete was successful');
cmp_deeply(
[ map { $_->{jsonId} } @{ $asset->get('jsonField') } ],
[
$guid2,
$guid1,
],
'...collateral was removed'
);
diag $asset->getId;
$tag->rollback;
$asset->purge;
$session->db->write(<<EOSQL);
drop table jsonCollateralDummy
EOSQL

View file

@ -0,0 +1,79 @@
package WebGUI::Asset::JSONCollateralDummy;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use Tie::IxHash;
use Class::C3;
use base qw/WebGUI::JSONCollateral WebGUI::Asset/;
=head1 NAME
Package WebGUI::Asset::JSONCollateral
=head1 DESCRIPTION
A dummy module for testing the JSON Collateral aspect. The module really doesn't
do anything, except provide suport modules for testing.
The module inherits directly from WebGUI::Asset.
=head1 SYNOPSIS
use WebGUI::Asset::JSONCollateralDummy;
=head1 METHODS
These methods are available from this class:
=cut
#-------------------------------------------------------------------
=head2 definition ( )
=cut
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift || [];
my %properties;
tie %properties, 'Tie::IxHash';
%properties = (
jsonField => {
label => 'jsonField',
hoverHelp => 'Not really needed, it is for internal data in this test case',
fieldType => 'textarea',
serialize => 1,
defaultValue => [],
noFormPost => 1,
},
);
push(@{$definition}, {
assetName=>'JSON Collateral Dummy',
tableName=>'jsonCollateralDummy',
autoGenerateForms=>1,
className=>'WebGUI::Asset::JSONCollateralDummy',
icon=>'assets.gif',
properties=>\%properties
}
);
return $class->next::method($session, $definition);
}
1;
#vim:ft=perl