From: Matt S Trout Date: Tue, 19 Jul 2005 15:12:40 +0000 (+0000) Subject: AutoUpdate, add_constructor, a toy set_sql emulator, object index stubs and destroy... X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=a3018bd384d02955c5a442cf2fd1ac654393a3c8;p=dbsrgits%2FDBIx-Class-Historic.git AutoUpdate, add_constructor, a toy set_sql emulator, object index stubs and destroy warnings. Working on stringify --- diff --git a/lib/DBIx/Class/CDBICompat.pm b/lib/DBIx/Class/CDBICompat.pm index cba2996..fd2de67 100644 --- a/lib/DBIx/Class/CDBICompat.pm +++ b/lib/DBIx/Class/CDBICompat.pm @@ -4,9 +4,14 @@ use strict; use warnings; use base qw/DBIx::Class::CDBICompat::Convenience + DBIx::Class::CDBICompat::ObjIndexStubs + DBIx::Class::CDBICompat::DestroyWarning + DBIx::Class::CDBICompat::Constructor + DBIx::Class::CDBICompat::AutoUpdate DBIx::Class::CDBICompat::AccessorMapping DBIx::Class::CDBICompat::ColumnCase DBIx::Class::CDBICompat::ColumnGroups DBIx::Class::CDBICompat::ImaDBI/; + # DBIx::Class::CDBICompat::Stringify 1; diff --git a/lib/DBIx/Class/CDBICompat/AutoUpdate.pm b/lib/DBIx/Class/CDBICompat/AutoUpdate.pm new file mode 100644 index 0000000..e61e167 --- /dev/null +++ b/lib/DBIx/Class/CDBICompat/AutoUpdate.pm @@ -0,0 +1,38 @@ +package DBIx::Class::CDBICompat::AutoUpdate; + +use strict; +use warnings; + +use base qw/Class::Data::Inheritable/; + +__PACKAGE__->mk_classdata('__AutoCommit'); + +sub set { + my $self = shift; + $self->NEXT::set(@_); + $self->update if ($self->autoupdate && $self->{_in_database}); +} + +sub autoupdate { + my $proto = shift; + ref $proto + ? $proto->_obj_autoupdate(@_) + : $proto->_class_autoupdate(@_) ; +} + +sub _obj_autoupdate { + my ($self, $set) = @_; + my $class = ref $self; + $self->{__AutoCommit} = $set if defined $set; + defined $self->{__AutoCommit} + ? $self->{__AutoCommit} + : $class->_class_autoupdate; +} + +sub _class_autoupdate { + my ($class, $set) = @_; + $class->__AutoCommit($set) if defined $set; + return $class->__AutoCommit; +} + +1; diff --git a/lib/DBIx/Class/CDBICompat/Constructor.pm b/lib/DBIx/Class/CDBICompat/Constructor.pm new file mode 100644 index 0000000..2b4b1e9 --- /dev/null +++ b/lib/DBIx/Class/CDBICompat/Constructor.pm @@ -0,0 +1,17 @@ +package DBIx::Class::CDBICompat::Constructor; + +use strict; +use warnings; + +sub add_constructor { + my ($class, $meth, $sql) = @_; + $class = ref $class if ref $class; + no strict 'refs'; + *{"${class}::${meth}"} = + sub { + my ($class, @args) = @_; + return $class->retrieve_from_sql($sql, @args); + }; +} + +1; diff --git a/lib/DBIx/Class/CDBICompat/Convenience.pm b/lib/DBIx/Class/CDBICompat/Convenience.pm index f0f98cd..63cfa67 100644 --- a/lib/DBIx/Class/CDBICompat/Convenience.pm +++ b/lib/DBIx/Class/CDBICompat/Convenience.pm @@ -13,7 +13,7 @@ sub find_or_create { sub id { my ($self) = @_; die "Can't call id() as a class method" unless ref $self; - my @pk = $self->_ident_value; + my @pk = $self->_ident_values; return (wantarray ? @pk : $pk[0]); } diff --git a/lib/DBIx/Class/CDBICompat/DestroyWarning.pm b/lib/DBIx/Class/CDBICompat/DestroyWarning.pm new file mode 100644 index 0000000..fb5b297 --- /dev/null +++ b/lib/DBIx/Class/CDBICompat/DestroyWarning.pm @@ -0,0 +1,14 @@ +package DBIx::Class::CDBICompat::DestroyWarning; + +use strict; +use warnings; + +sub DESTROY { + my ($self) = @_; + my $class = ref $self; + warn "$class $self destroyed without saving changes to " + .join(', ', keys %{$self->{_dirty_columns} || {}}) + if keys %{$self->{_dirty_columns} || {}}; +} + +1; diff --git a/lib/DBIx/Class/CDBICompat/ImaDBI.pm b/lib/DBIx/Class/CDBICompat/ImaDBI.pm index 37495af..ed08c93 100644 --- a/lib/DBIx/Class/CDBICompat/ImaDBI.pm +++ b/lib/DBIx/Class/CDBICompat/ImaDBI.pm @@ -20,4 +20,19 @@ sub __driver { return $_[0]->_get_dbh->{Driver}->{Name}; } +sub set_sql { + my ($class, $name, $sql) = @_; + my $table = $class->_table_name; + #$sql =~ s/__TABLE__/$table/; + no strict 'refs'; + *{"${class}::sql_${name}"} = + sub { + my $sql = $sql; + my $class = shift; + my $table = $class->_table_name; + $sql =~ s/__TABLE__/$table/; + return $class->_sql_to_sth(sprintf($sql, @_)); + }; +} + 1; diff --git a/lib/DBIx/Class/CDBICompat/ObjIndexStubs.pm b/lib/DBIx/Class/CDBICompat/ObjIndexStubs.pm new file mode 100644 index 0000000..aab0d29 --- /dev/null +++ b/lib/DBIx/Class/CDBICompat/ObjIndexStubs.pm @@ -0,0 +1,10 @@ +package DBIx::Class::CDBICompat::ObjIndexStubs; + +use strict; +use warnings; + +sub remove_from_object_index { } + +sub clear_object_index { } + +1; diff --git a/lib/DBIx/Class/CDBICompat/Stringify.pm b/lib/DBIx/Class/CDBICompat/Stringify.pm new file mode 100644 index 0000000..a3a4c28 --- /dev/null +++ b/lib/DBIx/Class/CDBICompat/Stringify.pm @@ -0,0 +1,18 @@ +package DBIx::Class::CDBICompat::Stringify; + +use strict; +use warnings; + +use overload + '""' => sub { shift->stringify_self }; + +sub stringify_self { + my $self = shift; + #return (ref $self || $self) unless $self; # empty PK + #return ref $self unless $self; + my @cols = $self->columns('Stringify'); + #@cols = $self->primary_column unless @cols; + #return join "/", map { $self->get($_) } @cols; +} + +1; diff --git a/lib/DBIx/Class/PK.pm b/lib/DBIx/Class/PK.pm index a2f5564..1163725 100644 --- a/lib/DBIx/Class/PK.pm +++ b/lib/DBIx/Class/PK.pm @@ -31,8 +31,8 @@ sub retrieve { my $query; if (ref $vals[0] eq 'HASH') { $query = $vals[0]; - } elsif (@pk == 1 && @vals == 1) { - return ($class->retrieve_from_sql($class->_ident_cond, $vals[0]))[0]; + } elsif (@pk == @vals) { + return ($class->retrieve_from_sql($class->_ident_cond, @vals))[0]; } else { $query = {@vals}; } diff --git a/lib/DBIx/Class/Table.pm b/lib/DBIx/Class/Table.pm index 8e5fc33..511f19b 100644 --- a/lib/DBIx/Class/Table.pm +++ b/lib/DBIx/Class/Table.pm @@ -19,6 +19,7 @@ sub new { $new->set($k => $v); } } + $new->{_dirty_columns} = {}; return $new; } @@ -43,36 +44,60 @@ sub update { my ($self) = @_; die "Not in database" unless $self->{_in_database}; my @to_update = keys %{$self->{_dirty_columns} || {}}; + return -1 unless @to_update; my $sth = $self->_get_sth('update', \@to_update, $self->_table_name, $self->_ident_cond); - $sth->execute( (map { $self->{_column_data}{$_} } @to_update), + my $rows = $sth->execute( (map { $self->{_column_data}{$_} } @to_update), $self->_ident_values ); + if ($rows == 0) { + die "Can't update $self: row not found"; + } elsif ($rows > 1) { + die "Can't update $self: updated more than one row"; + } $self->{_dirty_columns} = {}; return $self; } sub delete { - my ($self) = @_; - my $sth = $self->_get_sth('delete', undef, - $self->_table_name, $self->_ident_cond); - $sth->execute($self->_ident_values); - delete $self->{_in_database}; + my $self = shift; + if (ref $self) { + my $sth = $self->_get_sth('delete', undef, + $self->_table_name, $self->_ident_cond); + $sth->execute($self->_ident_values); + $sth->finish; + delete $self->{_in_database}; + } else { + my $query = (ref $_[0] eq 'HASH' ? $_[0] : {@_}); + my ($cond, $param) = $self->_where_from_hash($query); + my $sth = $self->_get_sth('delete', undef, $self->_table_name, $cond); + $sth->execute(@$param); + $sth->finish; + } return $self; } +sub discard_changes { + my ($self) = @_; + $_[0] = $self->retrieve($self->id); +} + sub get { my ($self, $column) = @_; die "Can't fetch data as class method" unless ref $self; - die "No such column '${column}'" unless $self->_columns->{$column}; - return $self->{_column_data}{$column}; + #die "No such column '${column}'" unless $self->_columns->{$column}; + return $self->{_column_data}{$column} if $self->_columns->{$column}; + return shift->SUPER::get(@_); } sub set { my ($self, $column, $value) = @_; - die "No such column '${column}'" unless $self->_columns->{$column}; - die "set_column called for ${column} without value" if @_ < 3; - $self->{_dirty_columns}{$column} = 1; - return $self->{_column_data}{$column} = $value; + #die "No such column '${column}'" unless $self->_columns->{$column}; + #die "set_column called for ${column} without value" if @_ < 3; + if ($self->_columns->{$column}) { + $self->{_dirty_columns}{$column} = 1; + return $self->{_column_data}{$column} = $value; + } + return shift->SUPER::set(@_); } sub _register_columns { @@ -95,7 +120,7 @@ sub set_columns { sub retrieve_from_sql { my ($class, $cond, @vals) = @_; - $cond =~ s/^\s*WHERE//; + $cond =~ s/^\s*WHERE//i; my @cols = $class->_select_columns; my $sth = $class->_get_sth( 'select', \@cols, $class->_table_name, $cond); $sth->execute(@vals); @@ -104,6 +129,7 @@ sub retrieve_from_sql { my $new = $class->new; $new->set($_, shift @row) for @cols; $new->{_in_database} = 1; + $new->{_dirty_columns} = {}; push(@found, $new); } return @found; @@ -112,8 +138,15 @@ sub retrieve_from_sql { sub search { my $class = shift; my $where = ref $_[0] eq "HASH" ? shift: {@_}; - my $cond = join(' AND ', map { "$_ = ?" } keys %$where); - return $class->retrieve_from_sql($cond, values %$where); + my ($cond, $param) = $class->_where_from_hash($where); + return $class->retrieve_from_sql($cond, @{$param}); +} + +sub search_like { + my $class = shift; + my $where = ref $_[0] eq "HASH" ? shift: {@_}; + my ($cond, $param) = $class->_where_from_hash($where, { cmp => 'like' }); + return $class->retrieve_from_sql($cond, @{$param}); } sub _select_columns { @@ -124,7 +157,18 @@ sub copy { my ($self, $changes) = @_; my $new = bless({ _column_data => { %{$self->{_column_data}}} }, ref $self); $new->set($_ => $changes->{$_}) for keys %$changes; - return $new; + return $new->insert; +} + +sub _where_from_hash { + my ($self, $query, $opts) = @_; + my $op = $opts->{'cmp'} || '='; + my $cond = join(' AND ', + map { (defined $query->{$_} + ? "$_ $op ?" + : (do { delete $query->{$_}; "$_ IS NULL"; })); + } keys %$query); + return ($cond, [ values %$query ]); } 1; diff --git a/lib/DBIx/Class/Test/SQLite.pm b/lib/DBIx/Class/Test/SQLite.pm index 28647a3..d23b6bd 100644 --- a/lib/DBIx/Class/Test/SQLite.pm +++ b/lib/DBIx/Class/Test/SQLite.pm @@ -40,8 +40,8 @@ END { unlink $DB if -e $DB } my @DSN = ("dbi:SQLite:dbname=$DB", '', '', { AutoCommit => 1 }); __PACKAGE__->connection(@DSN); -#__PACKAGE__->set_sql(_table_pragma => 'PRAGMA table_info(__TABLE__)'); -#__PACKAGE__->set_sql(_create_me => 'CREATE TABLE __TABLE__ (%s)'); +__PACKAGE__->set_sql(_table_pragma => 'PRAGMA table_info(__TABLE__)'); +__PACKAGE__->set_sql(_create_me => 'CREATE TABLE __TABLE__ (%s)'); =head1 METHODS @@ -62,11 +62,13 @@ sub set_table { sub _create_test_table { my $class = shift; - my @vals = $class->_sql_to_sth( - 'PRAGMA table_info(__TABLE__)')->select_row; - $class->_sql_to_sth( - 'CREATE TABLE '.$class->table.' ('.$class->create_sql.')' - )->execute unless @vals; + my @vals = $class->sql__table_pragma->select_row; + $class->sql__create_me($class->create_sql)->execute unless @vals; +# my @vals = $class->_sql_to_sth( +# 'PRAGMA table_info(__TABLE__)')->select_row; +# $class->_sql_to_sth( +# 'CREATE TABLE '.$class->table.' ('.$class->create_sql.')' +# )->execute unless @vals; } =head2 create_sql (abstract) diff --git a/t/02-Film.t b/t/02-Film.t index 5a8df07..3207b4a 100644 --- a/t/02-Film.t +++ b/t/02-Film.t @@ -159,21 +159,24 @@ eval { Film->create({ Title => 'Mikey and Nicky', Director => 'Elaine May' }); my $new_leaf = Film->create({ Title => 'A New Leaf', Director => 'Elaine May' }); - is(Film->search(Director => 'Elaine May')->count, - 3, "3 Films by Elaine May"); + cmp_ok(Film->search(Director => 'Elaine May'), '==', 3, + "3 Films by Elaine May"); ok(Film->retrieve('Ishtar')->delete, "Ishtar doesn't deserve an entry any more"); ok(!Film->retrieve('Ishtar'), 'Ishtar no longer there'); { my $deprecated = 0; - local $SIG{__WARN__} = sub { $deprecated++ if $_[0] =~ /deprecated/ }; + #local $SIG{__WARN__} = sub { $deprecated++ if $_[0] =~ /deprecated/ }; ok( Film->delete(Director => 'Elaine May'), "In fact, delete all films by Elaine May" ); - is(Film->search(Director => 'Elaine May')->count, + cmp_ok(Film->search(Director => 'Elaine May'), '==', 0, "0 Films by Elaine May"); - is $deprecated, 1, "Got a deprecated warning"; + SKIP: { + skip "No deprecated warnings from DBIx::Class", 1; + is $deprecated, 1, "Got a deprecated warning"; + } } }; is $@, '', "No problems with deletes"; @@ -275,7 +278,8 @@ is($btaste->Director, $orig_director, 'discard_changes()'); } # Change after_update policy -{ +SKIP: { + skip "DBIx::Class compat doesn't handle triggers yet", 4; my $bt = Film->retrieve($btaste->id); $bt->autoupdate(1); @@ -344,7 +348,8 @@ if (0) { } SKIP: { - skip "Scalar::Util::weaken not available", 3; + skip "DBIx::Class doesn't yet have a live objects index", 3; + #skip "Scalar::Util::weaken not available", 3 #if !$Class::DBI::Weaken_Is_Available; # my bad taste is your bad taste