[Dbix-class] New module for DBIx::Class (Store many columns in one)
Oleg Pronin
syber.rus at gmail.com
Sun Feb 18 20:05:38 GMT 2007
Skipped content of type multipart/alternative-------------- next part -----=
---------
package DBIx::Class::FrozenColumns;
use base qw/DBIx::Class/;
use strict;
use warnings;
__PACKAGE__->mk_classdata('_frozen_columns' =3D> {});
__PACKAGE__->mk_classdata('_dirty_frozen_columns');
=3Dhead1 NAME
DBIx::Class::FrozenColumns - Store virtual columns inside another column.
=3Dhead1 SYNOPSIS
package Artist;
__PACKAGE__->load_components(qw/FrozenColumns Core/);
__PACKAGE__->add_columns(qw/name description frozen/);
__PACKAGE__->add_frozen_columns(
frozen =3D> qw/biography url img50x50 img100x100/
);
=
$artist->url('http://cpan.org');
$artist->get_column('url');
$artist->get_dirty_columns; # 'url' and 'frozen' are dirty
$artist->update; #updates column 'frozen' (using Storable::freeze)
=
$artistRS->create({
name =3D> 'theodor bastard',
img50x50 =3D> '50x50.gif',
}); #that's ok. 'img50x50' will be stored in 'frozen'
=
my @artists =3D $artistRS->search({
name =3D> '.....',
url =3D> 'http://cpan.org',
}); # Error! no such column 'url'
=
package Artist;
__PACKAGE__->add_frozen_columns(
biography =3D> qw/childhood adolescence youth present/,
);
=
$artist->childhood('bla-bla-bla');
$artist->update; #Updates column 'frozen'.
=3Dhead1 DESCRIPTION
This module allows you to store multiple columns in one. This is useful when
you want to store dynamic number of columns in database or you just don't k=
now
what columns will be stored there. Or when you can't (or don't want) to alt=
er
your tables in database.
Module allows you to transparently use this columns as if they were normal
columns in your table. With one obvious restriction: you cannot search rows=
in a
table and therefore you cannot add relationships using these columns (searc=
h is
needed to build reverse relationship).
Module handles its own dirty column management and will not update the pare=
nt
field unless any columns is changed.
Note: The component needs to be loaded before Core.
Also note that frozen column IS NOT a real column of your result class.
This impose some restrictions on use of this columns such as searching, add=
ing
relationships, has_column, get_columns, etc.
See L</EXTENDED METHODS> for the list of method that will work with frozen
columns (as will methods that use it).
Module unpacks frozen columns only once when you first accessing it and pac=
ks
when you call update.
You can also create frozen columns in another frozen column any level deep.
The only restriction is that they all use the same storing mechanism.
=3Dhead1 METHODS
=3Dhead2 add_frozen_columns
__PACKAGE__->add_frozen_columns ($data_column, @columns)
__PACKAGE__->add_frozen_columns ($hashref)
Adds frozen @columns to your result source class. These columns will be sto=
red in
$data_column using Storable freeze/thaw algorithm.
If $hashref is specified instead, then below params is expected in it:
data_column - same as $data_column
columns - same as @columns
type - class with custom mechanism of storing/restoring frozen c=
ols
See below for more information about L</Custom frozen class>.
=3Dhead2 add_dumped_columns ($data_column, @columns)
Same as L</add_frozen_columns> but uses Data::Dumper mechanism.
=3Dcut
sub add_frozen_columns {
my $this =3D shift;
my ($type, $data_column, @frozen_columns);
if(ref $_[0]) {
my $params =3D shift;
$type =3D $params->{type};
$data_column =3D $params->{data_column};
@frozen_columns =3D @{$params->{columns}||[]};
}
else {
$type =3D 'DBIx::Class::FrozenColumns::Frozen';
($data_column, @frozen_columns) =3D @_;
}
$this->throw_exception('Cannot store frozen columns inside another froz=
en column of different type')
if exists $this->_frozen_columns->{$data_column}
and $this->_frozen_columns->{$data_column}{type} ne $type;
$this->add_column($data_column)
unless $this->has_column($data_column) or $this->_frozen_columns->{$da=
ta_column};
my %frozen_columns =3D %{$this->_frozen_columns};
foreach my $f_column (@frozen_columns) {
$this->throw_exception('Cannot override existing column with frozen=
one')
if $this->has_column($f_column) or $this->_frozen_columns->{$f_col=
umn};
$frozen_columns{$f_column} =3D {
column =3D> $data_column,
type =3D> $type,
};
no strict 'refs';
*{"${this}::$f_column"} =3D sub {
my $self =3D shift;
return $self->get_column($f_column) unless @_;
$self->set_column($f_column, shift);
};
}
$this->_frozen_columns(\%frozen_columns);
}
sub add_dumped_columns {
shift->add_frozen_columns({
type =3D> 'DBIx::Class::FrozenColumns::Dumped',
data_column =3D> shift,
columns =3D> [@_],
});
}
=3Dhead1 EXTENDED METHODS
=3Dhead2 new
Accepts initial values for frozen columns.
$artistRS->new({img50x50 =3D> '50x50.gif'});
=3Dcut
sub new {
my $self =3D shift;
my ($attrs) =3D @_;
my %fattrs;
foreach my $attr (keys %$attrs) {
next unless exists $self->_frozen_columns->{$attr};
$fattrs{$attr} =3D delete $attrs->{$attr};
}
my $ret =3D $self->next::method(@_);
while ( my($k,$v) =3D each %fattrs ) {
$ret->store_column($k, $v);
}
return $ret;
}
=3Dhead2 get_column
=3Dcut
sub get_column {
my ($self, $column) =3D @_;
if (my $frozen_info =3D $self->_frozen_columns->{$column}) {
$self->_ensure_column_unpacked( $frozen_info->{column}, $frozen_inf=
o->{type} );
return $self->get_column( $frozen_info->{column} )->{$column};
}
return $self->next::method($column);
}
=3Dhead2 get_columns
Returns DBIC's get_columns with frozen columns hash.
IMPORTANT: until $row is not in storage this method will return basic get_c=
olumns
result without frozen columns. This is needed for correct work of insert me=
thod.
=3Dcut
sub get_columns {
my $self =3D shift;
return $self->next::method(@_) unless $self->in_storage;
my %data =3D $self->next::method(@_);
foreach my $f_column ( keys %{$self->_frozen_columns} ) {
$data{$f_column} =3D $self->get_column($f_column);
}
return %data;
}
=3Dhead2 store_column
=3Dcut
sub store_column {
my ($self, $column, $value) =3D @_;
if (my $frozen_info =3D $self->_frozen_columns->{$column}) {
$self->_ensure_column_unpacked( $frozen_info->{column}, $frozen_inf=
o->{type} );
return $self->get_column( $frozen_info->{column} )->{$column} =3D $=
value;
}
return $self->next::method($column, $value);
}
=3Dhead2 set_column
=3Dcut
sub set_column {
my ($self, $column, $value) =3D @_;
if (my $frozen_info =3D $self->_frozen_columns->{$column}) {
my $data_column =3D $frozen_info->{column};
my $old =3D $self->get_column($column);
my $ret =3D $self->store_column($column, $value);
if(defined $old ^ defined $ret or (defined $old && $old ne $ret)) {
$self->set_column( $data_column, $self->get_column($data_column=
) );
my $frozen_dirty =3D $self->_dirty_frozen_columns || {};
$frozen_dirty->{$column} =3D 1;
$self->_dirty_frozen_columns($frozen_dirty);
}
return $ret;
}
return $self->next::method($column, $value);
}
=3Dhead2 get_dirty_columns
Returns real and frozen dirty columns.
Note that changing frozen column will result in marking at least 2 columns =
as
dirty.
=3Dcut
sub get_dirty_columns {
my $self =3D shift;
return $self->next::method(@_) unless $self->_dirty_frozen_columns;
my %data =3D $self->next::method(@_);
foreach my $f_column ( keys %{$self->_dirty_frozen_columns} ) {
$data{$f_column} =3D $self->get_column($f_column);
}
return %data;
}
sub _ensure_column_unpacked {
my ($self, $column, $type) =3D @_;
unless ( ref (my $packed =3D $self->get_column($column)) ) {
$self->store_column($column, $type->recover(\$packed));
}
}
=3Dhead2 has_column_loaded
Returns true if data_column of frozen column has loaded.
=3Dcut
sub has_column_loaded {
my ($self, $column) =3D @_;
if (my $frozen_info =3D $self->_frozen_columns->{$column}) {
return $self->has_column_loaded( $frozen_info->{column} );
}
$self->next::method($column);
}
=3Dhead2 is_column_changed
=3Dcut
sub is_column_changed {
my ($self, $column) =3D @_;
if ($self->_frozen_columns->{$column}) {
my $frozen_dirty =3D $self->_dirty_frozen_columns;
return $frozen_dirty && exists $frozen_dirty->{$column};
}
$self->next::method($column);
}
=3Dhead2 is_changed
=3Dcut
sub is_changed {
my $self =3D shift;
if(wantarray) {
my @columns =3D $self->next::method(@_);
my $frozen_dirty =3D $self->_dirty_frozen_columns;
push @columns, keys %$frozen_dirty if $frozen_dirty;
return @columns;
}
return 1 if $self->next::method(@_) or keys %{$self->_dirty_frozen_colu=
mns};
}
=3Dhead2 update
=3Dcut
sub update {
my $self =3D shift;
$self->_dirty_frozen_columns(undef);
$self->next::method(@_);
}
=3Dhead2 insert
=3Dcut
sub insert {
my $self =3D shift;
$self->_dirty_frozen_columns(undef);
$self->next::method(@_);
}
=3Dhead1 Custom frozen class
Such a class must be derived from 'DBIx::Class::FrozenColumns::Base' and is
responsible for fetching and storing frozen columns to/from a real database=
column.
The corresponding methods are 'recover' and 'stringify'.
The best explanation is an expamle:
package DBIx::Class::FrozenColumns::Frozen;
use base qw/DBIx::Class::FrozenColumns::Base/;
=
use strict;
use Storable qw/freeze thaw/;
=
sub stringify {
freeze(shift);
}
=
sub recover {
my ($this, $dataref) =3D @_;
my $data =3D defined $$dataref ? eval {thaw($$dataref)} || {} : {};
bless ($data, ref $this || $this);
}
Information actually stored in database can be used by any other programs a=
s a simple
hash (possibly containing another hashes like itself).
=3Dcut
package DBIx::Class::FrozenColumns::Base;
use strict;
use overload '.' =3D> sub {$_[0]->stringify},
'ne' =3D> sub{1},
fallback =3D> 1;
package DBIx::Class::FrozenColumns::Frozen;
use base qw/DBIx::Class::FrozenColumns::Base/;
use strict;
use Storable qw/freeze thaw/;
sub stringify {
freeze(shift);
}
sub recover {
my ($this, $dataref) =3D @_;
my $data =3D defined $$dataref ? eval {thaw($$dataref)} || {} : {};
bless ($data, ref $this || $this);
}
package DBIx::Class::FrozenColumns::Dumped;
use base qw/DBIx::Class::FrozenColumns::Base/;
use strict;
use Data::Dumper qw/Dumper/;
sub stringify {
local $Data::Dumper::Indent =3D 0;
Dumper(shift);
}
sub recover {
my ($this, $dataref) =3D @_;
our $VAR1;
my $data =3D defined $$dataref ? eval "$$dataref" || {} : {};
bless ($data, ref $this || $this);
}
=3Dhead1 CAVEATS
=3Dover
=3Ditem *
You cannot search rows in a table using frozen columns
=3Ditem *
You cannot add relationships using frozen columns
=3Dback
=3Dhead1 SEE ALSO
L<Storable>, L<Data::Dumper>.
=3Dhead1 AUTHOR
Oleg Pronin <syber.rus at gmail.com>
=3Dhead1 LICENSE
You may distribute this code under the same terms as Perl itself.
=3Dcut
1;
More information about the Dbix-class
mailing list