package ObjectDB::Schema; use strict; use warnings; require Storable; require Carp; use ObjectDB::Relationship; sub table { @_ > 1 ? $_[0]->{table} = $_[1] : $_[0]->{table} } sub auto_increment { @_ > 1 ? $_[0]->{auto_increment} = $_[1] : $_[0]->{auto_increment}; } sub relationships { @_ > 1 ? $_[0]->{relationships} = $_[1] : $_[0]->{relationships}; } sub class { @_ > 1 ? $_[0]->{class} = $_[1] : $_[0]->{class} } sub columns_map { @_ > 1 ? $_[0]->{columns_map} = $_[1] : $_[0]->{columns_map}; } our %objects; sub new { my $class = shift; my $for_class = shift; my %params = @_; foreach my $parent (_get_parents($for_class)) { if (my $parent_schema = $objects{$parent}) { my $schema = Storable::dclone($parent_schema); $schema->class($for_class); return $schema; } } my %values; my $columns = delete $params{columns}; my $primary_keys = delete $params{primary_keys}; my $unique_keys = delete $params{unique_keys}; my $table = delete $params{table}; my $auto_increment = delete $params{auto_increment}; Carp::croak("No table in $for_class") unless $table; Carp::croak("No columns in $for_class") unless $columns; Carp::croak("No primary keys in $for_class") unless $primary_keys; my @columns_raw = ref $columns ? @{$columns} : ($columns); my @columns = (); $columns = {}; my $prev; while (my $col = shift @columns_raw) { if (ref $col eq 'HASH') { $columns->{$prev} = $col; } else { $columns->{$col} = {}; push @columns, $col; } $prev = $col; } $primary_keys = ref $primary_keys ? $primary_keys : [$primary_keys]; $unique_keys = ref $unique_keys ? $unique_keys : [$unique_keys]; my $self = { class => $for_class, table => $table, auto_increment => $auto_increment, columns_map => $columns, _columns => \@columns, _primary_keys => $primary_keys, _unique_keys => $unique_keys, @_ }; bless $self, $class; # init relationship classes if ($self->relationships && %{$self->relationships}) { foreach my $rel (keys %{$self->relationships}) { $self->relationships->{$rel} = ObjectDB::Relationship->build( name => $rel, %{$self->relationships->{$rel}}, orig_class => $for_class ); } } return $self; } sub is_column { my $self = shift; my ($name) = @_; return unless $name; return exists $self->columns_map->{$name}; } sub is_primary_key { my $self = shift; my ($name) = @_; return 0 unless $name; my @rv = grep { $name eq $_ } $self->primary_keys; return @rv ? 1 : 0; } sub is_auto_increment { my $self = shift; my ($name) = @_; return 0 unless $name; return 0 unless $self->auto_increment; return 0 unless $self->auto_increment eq $name; return 1; } sub is_unique_key { my $self = shift; my ($name) = @_; return 0 unless $name; return 0 unless $self->unique_keys; my @rv = grep { $name eq $_ } $self->unique_keys; return @rv ? 1 : 0; } sub columns { my $self = shift; return @{$self->{_columns}}; } sub primary_keys { my $self = shift; return @{$self->{_primary_keys}}; } sub unique_keys { my $self = shift; return () unless defined $self->{_unique_keys}->[0]; return @{$self->{_unique_keys}}; } sub add_column { my $self = shift; my ($name, $options) = @_; return unless $name; $options ||= {}; $self->columns_map->{$name} = $options; push @{$self->{_columns}}, $name; } sub add_columns { my $self = shift; my $count = 0; while (my ($name, $options) = @_[$count, $count + 1]) { last unless $name; if (ref $options eq 'HASH') { $self->add_column($name, $options); } else { $self->add_column($name); $count++; next; } $count += 2; } } sub add_relationship { my $self = shift; my ($name, $options) = @_; return unless $name && $options; $self->relationships->{$name} = ObjectDB::Relationship->build( %$options, name => $name, orig_class => $self->class ); } sub add_relationships { my $self = shift; my $count = 0; while (my ($name, $options) = @_[$count, $count + 1]) { last unless $name && $options; $self->add_relationship($name, $options); $count += 2; } } sub del_column { my $self = shift; my ($name) = @_; return unless $name && $self->is_column($name); delete $self->columns_map->{$name}; @{$self->{_columns}} = grep { $_ ne $name } $self->columns; } sub _get_parents { my $class = shift; my @parents; no strict 'refs'; # shift our class name foreach my $sub_class (@{"${class}::ISA"}) { push(@parents, _get_parents($sub_class)) if ($sub_class->isa('ObjectDB') && $sub_class ne 'AnyEvent::ORM'); } return $class, @parents; } 1; __END__ =head1 NAME ObjectDB::Schema - Schema class =head1 SYNOPSIS package Article; use strict; use warnings; use base 'ObjectDB'; __PACKAGE__->schema( table => 'article', columns => [qw/id category_id author_id title/], primary_keys => ['id'], auto_increment => 'id', relationships => { author => { type => 'many to one', class => 'Author', map => {author_id => 'id'} }, category => { type => 'many to one', class => 'Category', map => {category_id => 'id'} }, tags => { type => 'many to many', map_class => 'ArticleTagMap', map_from => 'article', map_to => 'tag' }, comments => { type => 'one to many', class => 'Comment', where => [type => 'article'], map => {id => 'master_id'} } } ); 1; =head1 DESCRIPTION =head1 ATTRIBUTES =head2 C =head1 METHODS =head2 C Not called externally, but sets the following attributes: =head3 C __PACKAGE__->schema( table => 'article ... Table name. =head3 C __PACKAGE__->schema( ... columns => [qw/id title/] ... Sets columns. =head3 C __PACKAGE__->schema( ... primary_keys => [qw/id/] ... Sets primary keys. Could be multiple of course. =head3 C __PACKAGE__->schema( ... unique_keys => [qw/title/] ... Sets unique keys. Could be multiple of course. =head3 C __PACKAGE__->schema( ... auto_increment => 'id' ... Sets auto increment key. Is altered on object database insertion. =head3 C __PACKAGE__->schema( ... relationships => { author => { type => 'many to one', class => 'Author', map => {author_id => 'id'} }, ... Sets relationships. For more information see L. =head2 C $article->schema->is_column('title'); # true Returns true when argument is a column. False otherwise. =head2 C $article->schema->is_primary_key('title'); # false Returns true when argument is a primary key. False otherwise. =head2 C $article->schema->is_auto_increment('id'); # true Returns true when argument is an auto increment column. False otherwise. =head2 C $article->schema->is_unique_key('title'); # false Returns true when argument is a unique key. False otherwise. =head2 C my @columns = $article->schema->columns; Returns schema columns. =head2 C my @primary_keys = $article->schema->primary_keys; Returns primary keys. =head2 C my @unique_keys = $article->schema->unique_keys; Returns unique keys. =head2 C __PACKAGE__->add_column(content => {default => 'foo'}); Adds column to the schema. Usually used when you extend existing class and want to add few new columns. =head2 C __PACKAGE__->add_column(qw/content image/); Adds columns to the schema. Uses C. =head2 C __PACKAGE__->add_relationship( comments => { type => 'one to many', class => 'Comment', where => [type => 'article'], map => {id => 'master_id'} } ); Adds relationhips to the schema. Usually used when you extend existing class and want to add few new relationhips. =head2 C __PACKAGE__->add_relationships( comments => { type => 'one to many', class => 'Comment', where => [type => 'article'], map => {id => 'master_id'} }, ... ); Adds relationhips to the schema. Uses C. =head2 C __PACKAGE__->del_column('content'); Deletes column from the schema. Usually used when you extend existing class and want to delete few old columns. =head1 AUTHOR Viacheslav Tykhanovskyi, C. =head1 COPYRIGHT Copyright (C) 2009, Viacheslav Tykhanovskyi. This program is free software, you can redistribute it and/or modify it under the same terms as Perl 5.10. =cut