ActiveRecord-Simple

 view release on metacpan or  search on metacpan

lib/ActiveRecord/Simple.pm  view on Meta::CPAN

sub auto_load {
   my ($class) = @_;

    my $table_name = class_to_table_name($class);

    # 0. check the name
    my $table_info_sth = $class->dbh->table_info('', '%', $table_name, 'TABLE');
    $table_info_sth->fetchrow_hashref or croak "Can't find table '$table_name' in the database";

    # 1. columns list
    my $column_info_sth = $class->dbh->column_info(undef, undef, $table_name, undef);
    my $cols = $column_info_sth->fetchall_arrayref({});

    my @columns = ();
    push @columns, $_->{COLUMN_NAME} for @$cols;

    # 2. Primary key
    my $primary_key_sth = $class->dbh->primary_key_info(undef, undef, $table_name);
    my $primary_key_data = $primary_key_sth->fetchrow_hashref;
    my $primary_key = ($primary_key_data) ? $primary_key_data->{COLUMN_NAME} : undef;

    $class->table_name($table_name) if $table_name;
    $class->primary_key($primary_key) if $primary_key;
    $class->columns(@columns) if @columns;
}

sub connect {
    my ($class, $dsn, $username, $password, $options) = @_;

    eval { require DBIx::Connector };

lib/ActiveRecord/Simple.pm  view on Meta::CPAN


    #return unless $self->dbh;
    croak "Undefined database handler" unless $self->dbh;

    croak 'Object is read-only'
        if exists $self->{read_only} && $self->{read_only} == 1;

    my $save_param = {};
    my $fields = $self->_get_columns;

    my $pkey = ($self->can('_get_primary_key')) ? $self->_get_primary_key : undef;

    FIELD:
    for my $field (@$fields) {
        next FIELD if defined $pkey && $field eq $pkey && !$self->{$pkey};
        next FIELD if ref $field && ref $field eq 'HASH';
        $save_param->{$field} = $self->{$field};
    }

    ### Get additional fields from related objects:
    for my $field (keys %$self) {

lib/ActiveRecord/Simple.pm  view on Meta::CPAN

    my $result;
    if ($self->{isin_database}) {
        $result = $self->_update($save_param);
    }
    else {
        $result = $self->_insert($save_param);
    }
    $self->{need_to_save} = 0 if $result;
    delete $self->{SQL} if $result;

    return (defined $result) ? $self : undef;
}

sub update {
    my ($self, $params) = @_;

    my $fields = $self->_get_columns();
    FIELD:
    for my $field (@$fields) {
        next FIELD if ! exists $params->{$field};
        next FIELD if ! $params->{$field};

lib/ActiveRecord/Simple.pm  view on Meta::CPAN

    my $table_name = _what_is_the_table_name($self);
    my $pkey = $self->_get_primary_key;

    return unless $self->{$pkey};

    my $sql = qq{
        DELETE FROM "$table_name" WHERE $pkey = ?
    };
    $sql .= ' CASCADE ' if $param && $param->{cascade};

    my $res = undef;
    $sql = ActiveRecord::Simple::Utils::quote_sql_stmt($sql, $self->dbh->{Driver}{Name});

    if ( $self->dbh->do($sql, undef, $self->{$pkey}) ) {
        $self->{isin_database} = undef;
        delete $self->{$pkey};

        $res = 1;
    }

    return $res;
}

sub is_defined {
    my ($self) = @_;

lib/ActiveRecord/Simple.pm  view on Meta::CPAN


    my $related_class = _get_related_class($relation);

    #eval { load $related_class }; ### TODO: check module is loaded
    #load $related_class;

    #load $related_class unless is_loaded $related_class;
    #mark_as_loaded $related_class;
    load_module $related_class;

    my $rel_type = undef;
    while (my ($rel_key, $rel_opts) = each %{ $related_class->_get_relations }) {
        next if $class ne _get_related_class($rel_opts);
        $rel_type = $rel_opts->{type};
    }

    croak 'Oops! Looks like related class ' . $related_class . ' has no relations with ' . $class unless $rel_type;

    $type .= $rel_type;

    return $type;
}

sub _get_related_subclass {
    my ($relation) = @_;

    return undef if !ref $relation->{class};

    my $subclass;
    if (ref $relation->{class} eq 'HASH') {
        $subclass = (keys %{ $relation->{class} })[0];
    }
    elsif (ref $relation->{class} eq 'ARRAY') {
        $subclass = $relation->{class}[0];
    }

    return $subclass;

lib/ActiveRecord/Simple.pm  view on Meta::CPAN


sub _insert {
    my ($self, $param) = @_;

    return unless $self->dbh && $param;

    #my $table_name  = $self->_table_name;
    my $table_name = _what_is_the_table_name($self);
    my @field_names  = grep { defined $param->{$_} } sort keys %$param;
    my $primary_key = ($self->can('_get_primary_key')) ? $self->_get_primary_key :
                      ($self->can('_get_secondary_key')) ? $self->_get_secondary_key : undef;

    my $field_names_str = join q/, /, map { q/"/ . $_ . q/"/ } @field_names;

    my (@bind, @values_list);
    for (@field_names) {
        if (ref $param->{$_} eq 'SCALAR') {
            push @values_list, ${ $param->{$_} };
        }
        else {
            push @values_list, '?';

lib/ActiveRecord/Simple.pm  view on Meta::CPAN

    my $pkey_val;
    my $sql_stm = qq{
        INSERT INTO "$table_name" ($field_names_str)
        VALUES ($values)
    };

    if ( $self->dbh->{Driver}{Name} eq 'Pg' ) {
        if ($primary_key) {
            $sql_stm .= ' RETURINIG ' . $primary_key if $primary_key;
            $sql_stm = ActiveRecord::Simple::Utils::quote_sql_stmt($sql_stm, $self->dbh->{Driver}{Name});
            $pkey_val = $self->dbh->selectrow_array($sql_stm, undef, @bind);
        }
        else {
            my $sth = $self->dbh->prepare(
                ActiveRecord::Simple::Utils::quote_sql_stmt($sql_stm, $self->dbh->{Driver}{Name})
            );

            $sth->execute(@bind);
        }
    }
    else {

lib/ActiveRecord/Simple.pm  view on Meta::CPAN

        );
        $sth->execute(@bind);

        if ( $primary_key && defined $self->{$primary_key} ) {
            $pkey_val = $self->{$primary_key};
        }
        else {
            $pkey_val =
                exists $sth->{mysql_insertid} # mysql only
                    ? $sth->{mysql_insertid}
                    : $self->dbh->last_insert_id(undef, undef, $table_name, undef);
        }
    }

    if (defined $primary_key && $self->can($primary_key) && $pkey_val) {
        #$self->$primary_key($pkey_val);
        $self->{$primary_key} = $pkey_val;
    }
    $self->{isin_database} = 1;

    return $pkey_val;

lib/ActiveRecord/Simple.pm  view on Meta::CPAN


sub _update {
    my ($self, $param) = @_;

    return unless $self->dbh && $param;

    #my $table_name      = $self->_table_name;
    my $table_name = _what_is_the_table_name($self);
    my @field_names     = sort keys %$param;
    my $primary_key     = ($self->can('_get_primary_key')) ? $self->_get_primary_key :
                          ($self->can('_get_secondary_key')) ? $self->_get_secondary_key : undef;

    my (@set_list, @bind);
    for (@field_names) {
        if (ref $param->{$_} eq 'SCALAR') {
            push @set_list, $_ . ' = ' . ${ $param->{$_} };
        }
        else {
            push @set_list, "$_ = ?";
            push @bind, $param->{$_};
        }

lib/ActiveRecord/Simple.pm  view on Meta::CPAN


    my $sql_stm = ActiveRecord::Simple::Utils::quote_sql_stmt(
        qq{
            UPDATE "$table_name" SET $setstring
            WHERE
                $primary_key = ?
        },
        $self->dbh->{Driver}{Name}
    );

    return $self->dbh->do($sql_stm, undef, @bind);
}

sub _mk_rw_accessors {
    my ($class, $fields) = @_;

    return unless $fields;
    return if $class->can('_make_columns_accessors') && $class->_make_columns_accessors == 0;

    $class->_mk_accessors($fields, 'rw');
}

lib/ActiveRecord/Simple.pm  view on Meta::CPAN

    #eval { load $class }; ### TODO: check class has been loaded 
    #load $class unless is_loaded $class;
    #mark_as_loaded $class;
    load_module $class;


    my $table_name = _what_is_the_table_name($class);
    
    $table_name =~ s/s$// if $what_key eq 'foreign_key';

    return ($what_key eq 'foreign_key') ? "$table_name\_id" : undef;
}

sub _delete_keys {
    my ($self, $rx) = @_;

    map { delete $self->{$_} if $_ =~ $rx } keys %$self;
}

sub _append_relation {
    my ($class, $rel_name, $rel_hashref) = @_;

lib/ActiveRecord/Simple.pm  view on Meta::CPAN

    no strict 'refs';
    RELATION_NAME:
    for my $relation_name ( keys %{ $relations }) {
        my $pkg_method_name = $class . '::' . $relation_name;
        next RELATION_NAME if $class->can($pkg_method_name); ### FIXME: orrrr $relation_name???

        my $relation           = $relations->{$relation_name};
        my $full_relation_type = _get_relation_type($class, $relation);
        my $related_class      = _get_related_class($relation);

        ### TODO: check for error if returns undef
        my $pk = $relation->{params}{pk};
        my $fk = $relation->{params}{fk};

        my $instance_name = "relation_instance_$relation_name";

        if (grep { $full_relation_type eq $_ } qw/one_to_many one_to_one one_to_only/) {
            *{$pkg_method_name} = sub {
                my ($self, @args) = @_;
                if (@args) {
                    my $object = shift @args;

lib/ActiveRecord/Simple.pm  view on Meta::CPAN

                        my $via_table = $relation->{via_table};

                        OBJECT:
                        for my $object (@args) {
                            next OBJECT if !blessed $object;

                            my $pk2_name = $object->_get_primary_key;
                            my $pk2 = $object->{$pk2_name};

                            my $sql = qq/INSERT INTO "$via_table" ("$fk1", "$fk2") VALUES (?, ?)/;
                            $self->dbh->do($sql, undef, $pk1, $pk2);
                        }
                    }

                    return $self;
                }
                # else

                if (!$self->{$instance_name}) {
                    $self->{$instance_name} = $related_class->_find_many_to_many({
                        root_class => $class,

lib/ActiveRecord/Simple/Connect.pm  view on Meta::CPAN


use DBI;


my $self;

sub new {
	my ($class, $dsn, $username, $password, $params) = @_;

	if (!$self) {
		$self = { dbh => undef };
		if ($dsn) {
			$self->{dsn} = $dsn;
			$self->{username} = $username if $username;
			$self->{password} = $password if $password;
			$self->{connection_parameters} = $params if $params;
		}

		bless $self, $class;
	}

lib/ActiveRecord/Simple/Find.pm  view on Meta::CPAN


our $MAXIMUM_LIMIT = 100_000_000_000;


sub new {
    my ($self_class, $class, @param) = @_;

    #my $self = $class->new();
    my $self = bless { class => $class } => $self_class;

    my $table_name = ($self->{class}->can('_get_table_name'))  ? $self->{class}->_get_table_name  : undef;
    my $pkey       = ($self->{class}->can('_get_primary_key')) ? $self->{class}->_get_primary_key : undef;

    croak 'can not get table_name for class ' . $self->{class} unless $table_name;
    #croak 'can not get primary_key for class ' . $self->{class} unless $pkey;

    $self->{prep_select_fields} //= [];
    $self->{prep_select_from}   //= [];
    $self->{prep_select_where}  //= [];

    my ($fields, $from, $where);

lib/ActiveRecord/Simple/Find.pm  view on Meta::CPAN

        $fields = qq/"$table_name".*/;
        $from   = qq/"$table_name"/;
        $where  = qq/"$table_name"."$pkey" = ?/;

        $self->{BIND} = \@param
    }
    elsif (!ref $param[0] && scalar @param == 0) {
        $fields = qq/"$table_name".*/;
        $from   = qq/"$table_name"/;

        $self->{BIND} = undef;
    }
    elsif (ref $param[0] && ref $param[0] eq 'HASH') {
        # find many by params
        my ($bind, $condition_pairs) = $self->_parse_hash($param[0]);

        my $where_str = join q/ AND /, @$condition_pairs;

        $fields = qq/"$table_name".*/;
        $from   = qq/"$table_name"/;
        $where  = $where_str;

lib/ActiveRecord/Simple/Find.pm  view on Meta::CPAN

        $self->{BIND} = $bind;
    }
    elsif (ref $param[0] && ref $param[0] eq 'ARRAY') {
        # find many by primary keys
        my $whereinstr = join ', ', @{ $param[0] };

        $fields = qq/"$table_name".*/;
        $from   = qq/"$table_name"/;
        $where  = qq/"$table_name"."$pkey" IN ($whereinstr)/;

        $self->{BIND} = undef;
    }
    else {
        # find many by condition
        my $wherestr = shift @param;

        $fields = qq/"$table_name".*/;
        $from   = qq/"$table_name"/;
        $where  = $wherestr;

        $self->{BIND} = \@param;

lib/ActiveRecord/Simple/Find.pm  view on Meta::CPAN


    scalar @fields > 0 or croak 'Not defined fields for method "only"';
    ref $self or croak 'Create an object abstraction before using the modifiers. Use methods like `find`, `first`, `last` at the beginning';

    if ($self->{class}->can('_get_primary_key')) {
        my $pk = $self->{class}->_get_primary_key;
        push @fields, $pk if ! grep { $_ eq $pk } @fields;
    }

    my $table_name = $self->{class}->_get_table_name;
    my $mixins = $self->{class}->can('_get_mixins') ? $self->{class}->_get_mixins : undef;

    my @filtered_prep_select_fields =
        grep { $_ ne qq/"$table_name".*/ } @{ $self->{prep_select_fields} };
    for my $fld (@fields) {
        if ($mixins && grep { $_ eq $fld } keys %$mixins) {
            my $mixin = $mixins->{$fld}->($self->{class});
            $mixin .= qq/ AS $fld/ unless $mixin =~ /as\s+\w+$/i;
            push @filtered_prep_select_fields, $mixin;
        }
        else {

lib/ActiveRecord/Simple/Find.pm  view on Meta::CPAN


    $self->{SQL} .= ' LIMIT ' .  ($self->{prep_limit}  // $MAXIMUM_LIMIT);
    $self->{SQL} .= ' OFFSET '.  ($self->{prep_offset} // 0);

    return $self;
}

sub _parse_hash {
    my ($self, $param_hash) = @_;
    my $class = $self->{class};
    my $table_name = ($self->{class}->can('_get_table_name'))  ? $self->{class}->_get_table_name  : undef;
    my ($bind, $condition_pairs) = ([],[]);
    for my $param_name (keys %{ $param_hash }) {
        if (ref $param_hash->{$param_name} eq 'ARRAY' and !ref $param_hash->{$param_name}[0]) {
            my $instr = join q/, /, map { '?' } @{ $param_hash->{$param_name} };
            push @$condition_pairs, qq/"$table_name"."$param_name" IN ($instr)/;
            push @$bind, @{ $param_hash->{$param_name} };
        }
        elsif (ref $param_hash->{$param_name}) {
            next if !$class->can('_get_relations');
            my $relation = $class->_get_relations->{$param_name} or next;

lib/ActiveRecord/Simple/QueryManager.pm  view on Meta::CPAN

        bless $row, $self->{caller};
        push @list, $row;
    }

    return \@list;
}

sub sql_fetch_row {
    my ($self, $sql, @bind) = @_;

    my $row = $self->{caller}->dbh->selectrow_hashref($sql, undef, @bind);
    $self->{caller}->_mk_ro_accessors([keys %$row]);
    bless $row, $self->{caller};

    return $row;
}

1;

__END__;

t/07-auto_load.t  view on Meta::CPAN


use Test::More;


ok my $customer = Customer->new();
eval { $customer->first_name };
ok ! $@, 'loaded accessor `first_name`';
eval { $customer->id };
ok ! $@, 'loaded accessor `id`';
eval { $customer->foo };
ok $@, 'error load undefined accessor';

is(Customer->_get_table_name, 'customer', 'loaded table name');
is(Customer->_get_primary_key, 'id', 'loaded primary key');


done_testing();


t/09-find.t  view on Meta::CPAN


eval { Customer->objects->get(1)->fetch };
ok $@, 'fetch after get causes die';

ok my $cnt = Customer->objects->find->count, 'count';
is $cnt, 6;

ok my $exists = Customer->objects->find({ first_name => 'Bob' })->exists, 'exists';

ok(!Customer->objects->find({ first_name => 'Not Found' })->exists);
is(Customer->objects->find({ first_name => 'Not Found' })->exists, undef);

ok my $first = Customer->objects->find->first, 'first';
is_deeply $first, $Bob;

ok my $last = Customer->objects->find->last, 'last';
is $last->id, 6;

ok my $customized = Customer->objects->find({ first_name => 'Bob' })->only('id')->fetch, 'only';
is $customized->id, 1;
ok !$customized->first_name;

t/09-find.t  view on Meta::CPAN

ok $first = Customer->objects->find->only('id')->first, 'first->only';
is $first->id, 1;
ok !$first->first_name;

ok $last = Customer->objects->find->last, 'last';
is $last->id, 6;

ok my @list = Customer->objects->find->order_by('id')->desc->fetch, 'order_by, desc';
is $list[4]->id, 2;

undef @list;
ok @list = Customer->objects->find->order_by('id')->asc->fetch, 'order_by, asc';
is $list[4]->id, 5;

ok @list = Customer->objects->find->limit(2)->fetch, 'limit';
is scalar @list, 2;

ok @list = Customer->objects->find->order_by('id')->offset(2)->fetch, 'offset';
is $list[0]->id, 3;

is scalar @list, 4;

undef @list;

$Bill = Customer->objects->find({ first_name => 'Bill' });
ok $Bill->upload;

@list = Customer->objects->find->order_by('first_name')->desc->order_by('id')->asc->fetch;
is $list[0]->first_name, 'Lady', 'order_by does work';

undef @list;

@list = Customer->objects->find->group_by('first_name', 'age')->fetch;
is scalar @list, 5, 'group_by, got 4 objects';

my $count = Customer->objects->find->count;
is $count, 6, 'simple count, got 5';
undef $count;

$count = Customer->objects->find({ first_name => 'Bob' })->count;
is $count, 2, 'count, got 2 Bob\'s';
undef $count;

my @count = Customer->objects->find->group_by('first_name')->count;
is_deeply \@count, [{first_name => '', count => 1}, {first_name => 'Bill', count => 1}, {first_name => 'Bob', count => 2}, {first_name => 'John', count => 1}, {first_name => 'Lady', count => 1}];

@count = Customer->objects->find({ first_name => 'Bob' })->group_by('second_name')->count;
is_deeply \@count, [{second_name => 'Dylan', count => 1}, {second_name => 'Marley', count => 1}], 'count when find by first_name, group by second_name';

$Bill = Customer->objects->find(3)->fetch;
is_deeply $Bill->to_hash, {
	first_name => 'Bill',
	second_name => 'Clinton',
	age => 50,
	email => 'mynameisbill@gmail.com',
	id => 3,
	mixin => undef,
}, 'got undefined mixin in the hash';

$Bill = Customer->objects->find(3)->only('id', 'mixin')->fetch;
is_deeply $Bill->to_hash({ only_defined_fields => 1 }), { id => 3, mixin => 3 }, 'got defined mixin';

done_testing();



( run in 2.756 seconds using v1.01-cache-2.11-cpan-d80b1682f3f )