view release on metacpan or search on metacpan
Revision history for Perl module ActiveRecord::Simple
0.21 2013-01-16
First public release.
0.25 2013-02-10
* Many bug fixes
* Improved relations
+ Added test suite (see sandbox)
+ Added relation type many-to-many
0.30 2013-07-10
* Minor bug bixes
+ Added ordering methods "order_by", "asc" and "desc"
0.31 2013-07-11
* Fixes
0.32 2013-07-12
* Fixed typos
0.33 2013-07-12
+ [EXPERIMENTAL] Added a new method "use_smart_saving".
0.34 2013-08-21
+ Added tracing queries
+ Added some tests
* Minor fixes
0.35 2013-08-26
+ Added new "update-on-destroy" feature.
0.40 2013-10-23
+ Added methods: limit, offset
+ Method "find" now works with no arguments
(returns all records from db)
- Deleted method "get_all"
* Bugfixes
* Method find() with primary key goes to be named get()
* Tests fixes
* Improved documentation
0.41 2013-10-24
* Code cleanup
* Bux fixes
* Typo fixes
0.50 2013-11-02
+ Added ability to change relation instance into object
+ Added some tests
* Fixed "save" method
* Many fixes and huge code improvements
* Typo fixes
0.51 2013-11-11
+ Added method's "last" & "first"
+ Added new class ActiveRecord::Simple::Tutorial with pod-documentation
+ Implemented schema-loader script called "mars"
+ Added method "count"
+ Added method "exists"
+ Added possibility to creating read-only objects: fetch({ read_only => 1 })
+ Added new syntax to method "fetch"
+ Added possibility to select only specific fields ("only")
* Improved tests
* Improved documentation
* Fixed bugs
0.52 2013-11-26
* Fixed the `fetch` behavior
* Fixed tests
0.53 2014-05-12
+ Added method "increment" (thnx @lifeofguenter)
+ Added method "decrement" (thnx @lifeofguenter)
0.60.0 2014-05-19
+ Added new relationships aliases
+ Added generic relations
* Improved increment/decrement methods
* Migrated to semver
0.61.0 2014-09-17
+ Added schema builder (with method "fields")
+ Added fields validation (only when "fields" method is used)
+ Added class method "as_sql"
+ Added PACKAGE method "index"
0.64 2014-09-19
+ Improved method "columns"
+ Improved relationship framework
* Fixed bugs
0.65 2014-09-26
+ Dependancy on SQL::Translator now is optional (thanks to @kberov)
+ Added Credits - list of contributors (see README)
* Fixed bugs
* Improved tests
0.70 2015-08-14
+ Added ARS_TRACE
+ Created method "update" for quick objects update
+ New mars command "--upload"
* Improved `find` and `count` methods, now you can use find({ id => [1, 2, 3] }) as '.. where id in (1, 2, 3)'
* Improved error handling
* Method `new` now takes simple hashes (not only hashrefs)
* Improved documentation
0.80 2016-01-05
+ Added method "abstract"
+ Added method "select"
+ Added method "update"
+ Added method "abstract"
+ Added method "next"
+ Added "where in ... " condition to find
+ Added method "connect"
* Improved error handling
* Improved "new" method
+ Added LEFT JOIN
* Optimization of data fetch
* Improved documentation
+ Added package method "load_info"
+ "Smart accessors"
+ Added cookbook
* Improved tests
0.84 2016-07-13
+ Added scalarref as an argument of accessor to set not-quoted data (e.g. to send database-specific functions)
+ Now multiobject accessor is a "ARS::Find" object too, you can use such method as "order_by", "desc" etc.
* Small bugfixes
0.90 2016-07-16
+ Added "smart accessors" to methods "new", "find", "count".
* Small fixes
* Improved documentation
0.91 2017-08-19
+ New behavior of method "fetch"
+ Use DBIx::Connector if it's in the system
+ Added "group_by" method (thanks to @reindeer)
* Imrpoved many-to-many objects manipulations
* "asc", "desc" in every columns (thanks to @reindeer)
* New API for "count" method (thanks to @reindeer)
* New API for "last", "first" methods
* Fixed connection bug
* Renamed method "load_info" to "autoload"
* Renamed "use_smart_saving" to "autosave"
* Fixed typos
0.92 2017-08-20
* Improve "next" method in favor less memory usage
* Fixes
0.93 2017-08-21
+ Method "next" not takes a number of given objects. Default is 1
* Fixed auto_load error
* Fixed error messages
* Different fixes
0.95 2017-09-22
+ Method "sql_fetch_all"
+ Method "sql_fetch_row"
0.96 2017-09-25
+ Method "all"
+ Mixins now can get $class
+ Added class Meta.pm, access via method "META" in ARS::Model
* Improved relations
* Fixed "auto_load" behavior
0.97 2017-10-13
- Method "fields"
- Classes
- SQL::Traslator things
1.00 2018-01-02
* New faster accessors
* Improved tests
* Updated sandbox
+ New option "make_columns_accessors"
- script/mars
- "abstract" method
- "select" method
* Improved documentation
1.10 2018-01-13
* Fixed bugs
+ new class ActiveRecord::Simple::QueryManager
+ new "objects" method
- method "find" [DEPRECATED]
- method "all" [DEPRECATED]
- method "get" [DEPRECATED]
1.11 2018-01-17
* Improvement of module loading
Changes
lib/ActiveRecord/Simple.pm
lib/ActiveRecord/Simple/Connect.pm
lib/ActiveRecord/Simple/Find.pm
lib/ActiveRecord/Simple/Utils.pm
lib/ActiveRecord/Simple/QueryManager.pm
Makefile.PL
MANIFEST This list of files
README.md
README
t/00-load.t
t/01-boilerplate.t
t/02-pod.t
t/03-pod-coverage.t
t/04-manifest.t
t/05-accessors.t
t/06-no-accessors.t
t/07-auto_load.t
t/08-basic.t
t/09-find.t
t/10-relations.t
t/11-crud-methods.t
t/12-connect.t
t/13-init.t
t/14-smart-accessors.t
t/15-sql-row.t
META.yml Module YAML meta-data (added by MakeMaker)
META.json Module JSON meta-data (added by MakeMaker)
{
"abstract" : "unknown",
"author" : [
"shootnix <shootnix@cpan.org>"
],
"dynamic_config" : 1,
"generated_by" : "ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.150010",
"license" : [
"perl_5"
],
"meta-spec" : {
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
"version" : 2
},
"name" : "ActiveRecord-Simple",
"no_index" : {
"directory" : [
"t",
"inc"
]
},
"prereqs" : {
"build" : {
"requires" : {
"ExtUtils::MakeMaker" : "0",
"Test::More" : "0"
}
},
"configure" : {
"requires" : {
"ExtUtils::MakeMaker" : "0"
}
},
"runtime" : {
"requires" : {
"DBD::SQLite" : "0",
"DBI" : "0",
"Module::Load" : "0",
"Scalar::Util" : "0",
"perl" : "5.010"
}
}
},
"release_status" : "stable",
"resources" : {
"repository" : {
"url" : "https://github.com/shootnix/activerecord-simple"
}
},
"version" : "1.11",
"x_serialization_backend" : "JSON::PP version 2.97000"
}
---
abstract: unknown
author:
- 'shootnix <shootnix@cpan.org>'
build_requires:
ExtUtils::MakeMaker: '0'
Test::More: '0'
configure_requires:
ExtUtils::MakeMaker: '0'
dynamic_config: 1
generated_by: 'ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.150010'
license: perl
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: '1.4'
name: ActiveRecord-Simple
no_index:
directory:
- t
- inc
requires:
DBD::SQLite: '0'
DBI: '0'
Module::Load: '0'
Scalar::Util: '0'
perl: '5.010'
resources:
repository: https://github.com/shootnix/activerecord-simple
version: '1.11'
x_serialization_backend: 'CPAN::Meta::YAML version 0.018'
Makefile.PL view on Meta::CPAN
use 5.006;
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile1(
NAME => 'ActiveRecord::Simple',
AUTHOR => q{shootnix <shootnix@cpan.org>},
VERSION_FROM => 'lib/ActiveRecord/Simple.pm',
LICENSE => 'perl',
PL_FILES => {},
PREREQ_PM => {
'Module::Load' => 0,
'DBI' => 0,
'DBD::SQLite' => 0,
'Scalar::Util' => 0,
},
MIN_PERL_VERSION => '5.010',
META_MERGE => {
resources => {
repository => 'https://github.com/shootnix/activerecord-simple',
},
},
TEST_REQUIRES => {
'Test::More' => 0,
},
dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
clean => { FILES => 'ActiveRecord::Simple-*' },
);
sub WriteMakefile1 { #Compatibility code for old versions of EU::MM. Written by Alexandr Ciornii, version 0.23. Added by eumm-upgrade.
my %params=@_;
my $eumm_version=$ExtUtils::MakeMaker::VERSION;
$eumm_version=eval $eumm_version;
die "EXTRA_META is deprecated" if exists $params{EXTRA_META};
die "License not specified" if not exists $params{LICENSE};
if ($params{AUTHOR} and ref($params{AUTHOR}) eq 'ARRAY' and $eumm_version < 6.5705) {
$params{META_ADD}->{author}=$params{AUTHOR};
$params{AUTHOR}=join(', ',@{$params{AUTHOR}});
}
if ($params{TEST_REQUIRES} and $eumm_version < 6.64) {
$params{BUILD_REQUIRES}={ %{$params{BUILD_REQUIRES} || {}} , %{$params{TEST_REQUIRES}} };
delete $params{TEST_REQUIRES};
}
if ($params{BUILD_REQUIRES} and $eumm_version < 6.5503) {
#EUMM 6.5502 has problems with BUILD_REQUIRES
$params{PREREQ_PM}={ %{$params{PREREQ_PM} || {}} , %{$params{BUILD_REQUIRES}} };
delete $params{BUILD_REQUIRES};
}
delete $params{CONFIGURE_REQUIRES} if $eumm_version < 6.52;
delete $params{MIN_PERL_VERSION} if $eumm_version < 6.48;
delete $params{META_MERGE} if $eumm_version < 6.46;
delete $params{META_ADD} if $eumm_version < 6.46;
delete $params{LICENSE} if $eumm_version < 6.31;
delete $params{AUTHOR} if $] < 5.005;
delete $params{ABSTRACT_FROM} if $] < 5.005;
delete $params{BINARY_LOCATION} if $] < 5.005;
WriteMakefile(%params);
}
ActiveRecord::Simple
====================
ActiveRecord::Simple - Simple to use lightweight implementation of ActiveRecord pattern.
It is fast, don't have any dependencies and realy easy to use.
The basic setup of your package should be:
package Model::Foo;
use base 'ActiveRecord::Simple';
__PACKAGE__->table_name('foo');
__PACKAGE__->columns('id', 'bar', 'baz');
__PACKAGE__->primary_key('id');
1;
And then, you can use your package in a program:
use Foo;
my $foo = Foo->new({ bar => 'value', baz => 'value' });
$foo->save();
# or
my $foo = Foo->get(1);
print $foo->bar;
# or
$foo->bar('new value')->save();
print $foo->bar;
See pod documentation of the module for more information about using
ActiveRecord::Simple.
What we've got?
===============
Flexible search
Person->find(1); # by ID
Person->find([1, 2, 3]); # by several ID's
Person->find({ name => 'Foo' }); # by parameters
Person->find({ city => City->find({name => 'Paris'})->fetch }); # parameters as an objects
Person->find('name = ? OR lastname = ?', 'Foo', 'Bar'); # by condition
Person->last; # last object in the database
Person->first; # first object
Easy fetch
# Just one object:
my $bill = Person->find({ name => 'Bill' })->fetch;
# Only 3 objects:
my @list = Person->find('age > ?', 21)->fetch(3);
# All objects:
my @list = Person->find->fetch;
# Even more:
while (my $person = Person->find->fetch) {
print $person->name, "\n";
}
Simple ordering:
Person->find->order_by('name');
Person->find->order_by('name', 'last_name');
Person->find->order_by('name')->desc;
Limit, Offset:
Person->find->limit(3);
Person->find->offset(10);
Person->find->limit(3)->offset(12);
Left joins:
my $person = Person->find->with('misc_info')->fetch;
print $person->name;
print $person->misc_info->zip;
And, of course, all of this together:
my $new_customer =
Person->find
->only('name')
->order_by('date_register')
->desc
->limit(1)
->with('misc_info', 'payment_info')
->fetch;
print $new_customer->name;
print $new_customer->misc_info->zip;
print $new_customer->payment_info->last_payment;
Also one-to-one, one-to-many, many-to-one and many-to-many relations, smart_saving and even more.
And, of course, you don't need use "table_name", "primary_key" etc. Just use this:
__PACKAGE__->load_info(); ### All info will be loaded from database automatically.
Check it out!
INSTALLATION
============
To install this module, run the following commands:
$ perl Makefile.PL
$ make
$ make test
$ make install
or:
$ sudo cpan ActiveRecord::Simple
SUPPORT AND DOCUMENTATION
=========================
After installing, you can find documentation for this module with the
perldoc command.
perldoc ActiveRecord::Simple
Feel free to join us at google groups:
https://groups.google.com/forum/#!forum/activerecord-simple
Also the github page:
http://shootnix.github.io/activerecord-simple/
LICENSE AND COPYRIGHT
=====================
Copyright (C) 2013-2018 shootnix
This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
CREDITS
=======
@shootnix
@kberov
@chorny
@lifeofguenter
@neilbowers
@dsteinbrunner
@reindeer
@grinya007
ActiveRecord::Simple
====================
ActiveRecord::Simple - Simple to use lightweight implementation of ActiveRecord pattern.
It is fast, don't have any dependencies and realy easy to use.
The basic setup of your package should be:
package Model::Foo;
use base 'ActiveRecord::Simple';
__PACKAGE__->table_name('foo');
__PACKAGE__->columns('id', 'bar', 'baz');
__PACKAGE__->primary_key('id');
1;
And then, you can use your package in a program:
use Foo;
my $foo = Foo->new({ bar => 'value', baz => 'value' });
$foo->save();
# or
my $foo = Foo->get(1);
print $foo->bar;
# or
$foo->bar('new value')->save();
print $foo->bar;
See pod documentation of the module for more information about using
ActiveRecord::Simple.
What we've got?
===============
Flexible search
Person->find(1); # by ID
Person->find([1, 2, 3]); # by several ID's
Person->find({ name => 'Foo' }); # by parameters
Person->find({ city => City->find({name => 'Paris'})->fetch }); # parameters as an objects
Person->find('name = ? OR lastname = ?', 'Foo', 'Bar'); # by condition
Person->last; # last object in the database
Person->first; # first object
Easy fetch
# Just one object:
my $bill = Person->find({ name => 'Bill' })->fetch;
# Only 3 objects:
my @list = Person->find('age > ?', 21)->fetch(3);
# All objects:
my @list = Person->find->fetch;
# Even more:
while (my $person = Person->find->fetch) {
print $person->name, "\n";
}
Simple ordering:
Person->find->order_by('name');
Person->find->order_by('name', 'last_name');
Person->find->order_by('name')->desc;
Limit, Offset:
Person->find->limit(3);
Person->find->offset(10);
Person->find->limit(3)->offset(12);
Left joins:
my $person = Person->find->with('misc_info')->fetch;
print $person->name;
print $person->misc_info->zip;
And, of course, all of this together:
my $new_customer =
Person->find
->only('name')
->order_by('date_register')
->desc
->limit(1)
->with('misc_info', 'payment_info')
->fetch;
print $new_customer->name;
print $new_customer->misc_info->zip;
print $new_customer->payment_info->last_payment;
Also one-to-one, one-to-many, many-to-one and many-to-many relations, smart_saving and even more.
And, of course, you don't need use "table_name", "primary_key" etc. Just use this:
__PACKAGE__->load_info(); ### All info will be loaded from database automatically.
Check it out!
INSTALLATION
============
To install this module, run the following commands:
$ perl Makefile.PL
$ make
$ make test
$ make install
or:
$ sudo cpan ActiveRecord::Simple
SUPPORT AND DOCUMENTATION
=========================
After installing, you can find documentation for this module with the
perldoc command.
perldoc ActiveRecord::Simple
Feel free to join us at google groups:
https://groups.google.com/forum/#!forum/activerecord-simple
Also the github page:
http://shootnix.github.io/activerecord-simple/
LICENSE AND COPYRIGHT
=====================
Copyright (C) 2013-2018 shootnix
This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
CREDITS
=======
@shootnix
@kberov
@chorny
@lifeofguenter
@neilbowers
@dsteinbrunner
@reindeer
@grinya007
@manwar
lib/ActiveRecord/Simple.pm view on Meta::CPAN
package ActiveRecord::Simple;
use 5.010;
use strict;
use warnings;
our $VERSION = '1.11';
use utf8;
use Carp;
use Scalar::Util qw/blessed/;
use ActiveRecord::Simple::QueryManager;
use ActiveRecord::Simple::Utils qw/all_blessed class_to_table_name load_module/;
use ActiveRecord::Simple::Connect;
our $connector;
my $qm = ActiveRecord::Simple::QueryManager->new();
sub new {
my $class = shift;
my $params = (scalar @_ > 1) ? {@_} : $_[0];
# relations
$class->_init_relations if $class->can('_get_relations');
return bless $params || {}, $class;
}
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 };
$options->{HandleError} = sub {
my ($error_message, $DBI_st) = @_;
$error_message or return;
croak $error_message;
} if ! exists $options->{HandleError};
if ($@) {
$connector = ActiveRecord::Simple::Connect->new($dsn, $username, $password, $options);
$connector->db_connect;
}
else {
$connector = DBIx::Connector->new($dsn, $username, $password, $options);
}
return 1;
}
sub belongs_to {
my ($class, $rel_name, $rel_class, $params) = @_;
my $new_relation = {
class => $rel_class,
type => 'one',
};
my $primary_key = $params->{pk} ||
$params->{primary_key} ||
_guess(primary_key => $class);
my $foreign_key = $params->{fk} ||
$params->{foreign_key} ||
_guess(foreign_key => $rel_class);
$new_relation->{params} = {
pk => $primary_key,
fk => $foreign_key,
};
$class->_append_relation($rel_name => $new_relation);
#$class->_mk_relations_accessors;
}
sub has_many {
my ($class, $rel_name, $rel_class, $params) = @_;
my $new_relation = {
class => $rel_class,
type => 'many',
};
$params ||= {};
my $primary_key = $params->{pk} ||
$params->{primary_key} ||
_guess(primary_key => $class);
my $foreign_key = $params->{fk} ||
$params->{foreign_key} ||
_guess(foreign_key => $class);
$new_relation->{params} = {
pk => $primary_key,
fk => $foreign_key,
};
$new_relation->{via_table} = $params->{via} if $params->{via};
$class->_append_relation($rel_name => $new_relation);
#$class->_mk_relations_accessors;
}
sub has_one {
my ($class, $rel_name, $rel_class, $params) = @_;
my $new_relation = {
class => $rel_class,
type => 'only',
};
$params ||= {};
#my ($primary_key, $foreign_key);
my $primary_key = $params->{pk} ||
$params->{primary_key} ||
_guess(primary_key => $class);
my $foreign_key = $params->{fk} ||
$params->{foreign_key} ||
_guess(foreign_key => $class);
$new_relation->{params} = {
pk => $primary_key,
fk => $foreign_key,
};
$class->_append_relation($rel_name => $new_relation);
#$class->_mk_relations_accessors;
}
sub generic {
my ($class, $rel_name, $rel_class, $key) = @_;
my $new_relation = {
class => $rel_class,
type => 'generic',
key => $key
};
return $class->_append_relation($rel_name => $new_relation);
$class->_mk_relations_accessors;
}
sub columns {
my ($class, @columns_list) = @_;
croak "Error: array-ref no longer supported for 'columns' method, sorry"
if scalar @columns_list == 1 && ref $columns_list[0] eq 'ARRAY';
$class->_mk_attribute_getter('_get_columns', \@columns_list);
$class->_mk_rw_accessors(\@columns_list) unless $class->can('_make_columns_accessors') && $class->_make_columns_accessors == 0;
}
sub make_columns_accessors {
my ($class, $flag) = @_;
$flag //= 1; # default value
$class->_mk_attribute_getter('_make_columns_accessors', $flag);
}
sub mixins {
my ($class, %mixins) = @_;
$class->_mk_attribute_getter('_get_mixins', \%mixins);
$class->_mk_ro_accessors([keys %mixins]);
}
sub primary_key {
my ($class, $primary_key) = @_;
$class->_mk_attribute_getter('_get_primary_key', $primary_key);
}
sub secondary_key {
my ($class, $key) = @_;
$class->_mk_attribute_getter('_get_secondary_key', $key);
}
sub table_name {
my ($class, $table_name) = @_;
$class->_mk_attribute_getter('_get_table_name', $table_name);
}
sub relations {
my ($class, $relations) = @_;
$class->_mk_attribute_getter('_get_relations', $relations);
}
sub dbh {
my ($self, $dbh) = @_;
if ($dbh) {
if ($connector) {
$connector->dbh($dbh);
}
else {
$connector = ActiveRecord::Simple::Connect->new();
$connector->dbh($dbh);
}
}
return $connector->dbh;
}
sub objects {
$qm->{caller} = shift;
return $qm;
}
sub save {
my ($self) = @_;
#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) {
next unless ref $self->{$field};
next unless $self->can('_get_relations');
next unless grep { $_ eq $field } keys %{ $self->_get_relations };
my $relation = $self->_get_relations->{$field} or next;
next unless $relation->{type} && $relation->{type} eq 'one';
my $fk = $relation->{params}{fk};
my $pk = $relation->{params}{pk};
$save_param->{$fk} = $self->{$field}->$pk;
}
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};
$self->{$field} = $params->{$field};
}
return $self;
}
# param:
# cascade => 1
sub delete {
my ($self, $param) = @_;
return unless $self->dbh;
#my $table_name = $self->_table_name;
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) = @_;
return grep { defined $self->{$_} } @{ $self->_get_columns };
}
# param:
# only_defined_fields => 1
### TODO: refactor this
sub to_hash {
my ($self, $param) = @_;
my $field_names = $self->_get_columns;
push @$field_names, keys %{ $self->_get_mixins } if $self->can('_get_mixins');
my $attrs = {};
for my $field (@$field_names) {
next if ref $field;
if ( $param && $param->{only_defined_fields} ) {
$attrs->{$field} = $self->{$field} if defined $self->{$field};
}
else {
$attrs->{$field} = $self->{$field};
}
}
return $attrs;
}
sub increment {
my ($self, @fields) = @_;
FIELD:
for my $field (@fields) {
next FIELD if not exists $self->{$field};
$self->{$field} += 1;
}
return $self;
}
sub decrement {
my ($self, @fields) = @_;
FIELD:
for my $field (@fields) {
next FIELD if not exists $self->{$field};
$self->{$field} -= 1;
}
return $self;
}
#### Find ####
sub find {
$qm->{caller} = shift;
carp
q/[DEPRECATED] This method is DEPRECATED since version 1.10. '/ .
q/Please, use "find" via "objects" method: / . $qm->{caller} . q/->objects->find/;
$qm->find(@_);
}
sub all {
$qm->{caller} = shift;
carp
q/[DEPRECATED] This method is DEPRECATED since version 1.10. '/ .
q/Please, use "all" via "objects" method: / . $qm->{caller} . q/->objects->all/;
$qm->all();
}
sub get {
$qm->{caller} = shift;
carp
q/[DEPRECATED] This method is DEPRECATED since version 1.10. '/ .
q/Please, use "get" via "objects" method: / . $qm->{caller} . q/->objects->get/;
$qm->get(@_);
}
sub exists {
my $first_arg = shift;
my ($class, @search_criteria);
if (ref $first_arg) {
# FIXME: Ugly solution, need some beautifulness =)
# object method
$class = ref $first_arg;
if ($class eq 'ActiveRecord::Simple::Find') {
return $first_arg->exists;
}
else {
return ActiveRecord::Simple::Find->new($class, $first_arg->to_hash({ only_defined_fields => 1 }))->exists;
}
}
else {
carp '[DEPRECATED] This way of using method "exists" is deprecated. Please, see documentation to know how does it work now.';
$class = $first_arg;
@search_criteria = @_;
return (defined $class->find(@search_criteria)->fetch) ? 1 : 0;
}
}
sub _find_many_to_many { ActiveRecord::Simple::Find->_find_many_to_many(shift, @_) }
sub DESTROY {}
### Private
sub _get_primary_key_value {
my ($self) = @_;
croak "Sory, you can call method '_get_primary_key_value' on unblessed scalar."
unless blessed $self;
my $pk = $self->_get_primary_key;
return $self->$pk;
}
sub _get_relation_type {
my ($class, $relation) = @_;
my $type = $relation->{type};
$type .= '_to_';
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;
}
sub _get_related_class {
my ($relation) = @_;
return $relation->{class} if !ref $relation->{class};
my $related_class;
if (ref $relation->{class} eq 'HASH') {
$related_class = ( %{ $relation->{class} } )[1]
}
elsif (ref $relation->{class} eq 'ARRAY') {
$related_class = $relation->{class}[1];
}
return $related_class;
}
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, '?';
push @bind, $param->{$_};
}
}
my $values = join q/, /, @values_list;
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 {
my $sth = $self->dbh->prepare(
ActiveRecord::Simple::Utils::quote_sql_stmt($sql_stm, $self->dbh->{Driver}{Name})
);
$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;
}
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->{$_};
}
}
my $setstring = join q/, /, @set_list;
push @bind, $self->{$primary_key};
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');
}
sub _mk_ro_accessors {
my ($class, $fields) = @_;
return unless $fields;
return if $class->can('_make_columns_accessors') && $class->_make_columns_accessors == 0;
$class->_mk_accessors($fields, 'ro');
}
sub _mk_accessors {
my ($class, $fields, $type) = @_;
$type ||= 'rw';
my $code_string = q//;
METHOD_NAME:
for my $method_name (@$fields) {
next METHOD_NAME if $class->can($method_name);
$code_string .= "sub $method_name {\n";
if ($type eq 'rw') {
$code_string .= "if (\@_ > 1) { \$_[0]->{$method_name} = \$_[1]; return \$_[0] }\n";
}
elsif ($type eq 'ro') {
$code_string .= "die 'Object is read-only, sorry' if \@_ > 1;\n";
}
$code_string .= "return \$_[0]->{$method_name};\n }\n";
}
eval "package $class;\n $code_string" if $code_string;
say $@ if $@;
}
sub _guess {
my ($what_key, $class) = @_;
return 'id' if $what_key eq 'primary_key';
#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) = @_;
if ($class->can('_get_relations')) {
my $relations = $class->_get_relations();
$relations->{$rel_name} = $rel_hashref;
$class->relations($relations);
}
else {
$class->relations({ $rel_name => $rel_hashref });
}
return $rel_hashref;
}
sub _mk_attribute_getter {
my ($class, $method_name, $return) = @_;
return if $class->can($method_name);
eval "package $class; \n sub $method_name { \$return }";
}
sub _init_relations {
my ($class) = @_;
my $relations = $class->_get_relations;
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;
croak "Using unblessed scalar as an object reference"
unless blessed $object;
$object->save() if ! exists $object->{isin_database} && !$object->{isin_database} == 1;
#$self->$fk($object->$pk);
$self->{$fk} = $object->{$pk};
$self->{$instance_name} = $object;
return $self;
}
# else
if (!$self->{$instance_name}) {
$self->{$instance_name} = $related_class->objects->get($self->{$fk}) // $related_class;
}
return $self->{$instance_name};
}
}
elsif ($full_relation_type eq 'only_to_one') {
*{$pkg_method_name} = sub {
my ($self, @args) = @_;
if (!$self->{$instance_name}) {
$self->{$instance_name} = $related_class->find("$fk = ?", $self->{$pk})->fetch;
}
return $self->{$instance_name};
}
}
elsif ($full_relation_type eq 'many_to_one') {
*{$pkg_method_name} = sub {
my ($self, @args) = @_;
if (@args) {
unless (all_blessed(\@args)) {
return $related_class->find(@args)->left_join($self->_get_table_name);
}
OBJECT:
for my $object (@args) {
next OBJECT if !blessed $object;
my $pk = $self->_get_primary_key;
#$object->$fk($self->$pk)->save;
$object->{$fk} = $self->{$pk};
$object->save();
}
return $self;
}
# else
return $related_class->new() if not $self->can('_get_primary_key');
if (!$self->{$instance_name}) {
$self->{$instance_name} = $related_class->objects->find("$fk = ?", $self->{$pk});
}
return $self->{$instance_name};
}
}
elsif ($full_relation_type eq 'many_to_many') {
*{$pkg_method_name} = sub {
my ($self, @args) = @_;
if (@args) {
my $related_subclass = _get_related_subclass($relation);
unless (all_blessed(\@args)) {
return $related_class->_find_many_to_many({
root_class => $class,
via_table => $relation->{via_table},
m_class => $related_subclass,
self => $self,
where_statement => \@args,
});
}
if (defined $related_subclass) {
my ($fk1, $fk2);
$fk1 = $fk;
RELATED_CLASS_RELATION:
for my $related_class_relation (values %{ $related_class->_get_relations }) {
next RELATED_CLASS_RELATION
unless _get_related_subclass($related_class_relation)
&& $related_subclass eq _get_related_subclass($related_class_relation);
$fk2 = $related_class_relation->{params}{fk};
}
my $pk1_name = $self->_get_primary_key;
my $pk1 = $self->{$pk1_name};
defined $pk1 or croak 'You are trying to create relations between unsaved objects. Save your ' . $class . ' object first';
OBJECT:
for my $object (@args) {
next OBJECT if !blessed $object;
my $pk2_name = $object->_get_primary_key;
my $pk2 = $object->{$pk2_name};
$related_subclass->new($fk1 => $pk1, $fk2 => $pk2)->save;
}
}
else {
my ($fk1, $fk2);
$fk1 = $fk;
$fk2 = class_to_table_name($related_class) . '_id';
my $pk1_name = $self->_get_primary_key;
my $pk1 = $self->{$pk1_name};
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,
m_class => _get_related_subclass($relation),
via_table => $relation->{via_table},
self => $self,
});
}
return $self->{$instance_name};
}
}
elsif ($full_relation_type eq 'generic_to_generic') {
*{$pkg_method_name} = sub {
my ($self, @args) = @_;
if (!$self->{$instance_name}) {
my %find_attrs;
while (my ($k, $v) = each %{ $relation->{key} }) {
$find_attrs{$v} = $self->{$k};
}
$self->{$instance_name} = $related_class->find(\%find_attrs);
}
return $self->{$instance_name};
}
}
}
use strict 'refs';
}
sub _what_is_the_table_name {
my $class = ref $_[0] ? ref $_[0] : $_[0];
croak 'Invalid data class' if $class =~ /^ActiveRecord::Simple/;
my $table_name =
$class->can('_get_table_name') ?
$class->_get_table_name
: class_to_table_name($class);
return $table_name;
}
1;
__END__;
=head1 NAME
ActiveRecord::Simple - Simple to use lightweight implementation of ActiveRecord pattern.
=head1 DESCRIPTION
ActiveRecord::Simple is a simple lightweight implementation of ActiveRecord
pattern. It's fast, very simple and very light.
=head1 SYNOPSIS
package Model;
use parent 'ActiveRecord::Simple';
# connect to the database:
__PACKAGE__->connect($dsn, $opts);
package Customer;
use parent 'Model';
__PACKAGE__->table_name('customer');
__PACKAGE__->columns(qw/id first_name last_login/);
__PACKAGE__->primary_key('id');
__PACKAGE__->has_many(purchases => 'Purchase');
package Purchase;
use parent 'Model';
__PACKAGE__->auto_load(); ### load table_name, columns and primary key from the database automatically
__PACKAGE__->belongs_to(customer => 'Customer');
package main;
# get customer with id = 1:
my $customer = Customer->objects->find({ id => 1 })->fetch();
# or (the same):
my $customer = Customer->objects->get(1);
print $customer->first_name; # print first name
$customer->last_login(\'NOW()'); # to use built-in database function just send it as a SCALAR ref
$customer->save(); # save in the database
# get all purchases of $customer:
my @purchases = Purchase->objects->find(customer => $customer)->fetch();
# or (the same):
my @purchases = $customer->purchases->fetch();
# order, group and limit:
my @purchases = $customer->purchases->order_by('paid')->desc->group_by('kind')->limit(10)->fetch();
=head1 CLASS METHODS
L<ActiveRecord::Simple> implements the following class methods.
=head2 new
Object's constructor.
my $log = Log->new(message => 'hello', level => 'info');
=head2 connect
Connect to the database, uses DBIx::Connector if installed, if it's not - L<ActiveRecord::Simple::Connect>.
__PACKAGE__->connect($dsn, $username, $password, $options);
=head2 dbh
Access to the database handler. Undef if it's not connected.
__PACKAGE__->dbh->do('SELECT 1');
=head2 table_name
Set table name.
__PACKAGE__->table_name('log');
=head2 columns
Set columns. Make accessors if make_columns_accessors not 0 (default is 1)
__PACKAGE__->columns('id', 'time');
=head2 primary_key
Set primary key. Optional parameter.
__PACKAGE__->primary_key('id');
=head2 secondary_key
Set secondary key.
__PACKAGE__->secondary_key('time');
=head2 auto_load
Load table_name, columns and primary_key from table_info (automatically from database).
__PACKAGE__->auto_load();
=head2 has_many
Create a ralation to another table (many-to-many, many-to-one).
Customer->has_many(purchases => 'Purchase');
# if you need to set a many-to-many relation, you have to
# specify a third table using "via" key:
Pizza->has_many(toppings => 'Topping', { via => 'pizza_topping' });
=head2 belongs_to
Create a relation to another table (one-to-many, one-to-one). Foreign key is an optional
parameter, default is <table tane>_id.
Purchase->belongs_to(customer => 'Customer');
# or
Purchase->belong_to(customer => 'Customer', { fk => 'customer_id' });
=head2 has_one
Create a relation to another table (one-to-one).
Customer->has_one(address => 'Address');
=head2 generic
Create a relation without foreign keys:
Meal->generic(critical_t => 'Weather', { t_max => 't' });
=head2 make_columns_accessors
Set to 0 before method 'columns' if you don't want to make accessors to columns:
__PACKAGE__->make_columns_accessors(0);
__PACKAGE__->columns('id', 'time'); # now you can't get $log->id and $log->time, only $log->{id} and $log->{time};
=head2 mixins
Create calculated fields
Purchase->mixins(
sum_amount => sub {
return 'SUM(amount)'
}
);
# and then
my $purchase = Purchase->find({ id => 1 })->fields('id', 'title', 'amount', 'sum_amount')->fetch;
=head2 relations
Make a relation. The method is aoutdated.
=head2 objects
Returns instance of L<ActiveRecord::Simple::QueryManager>.
=head2 find [DEPRECATED]
Returns L<ActiveRecord::Simple::Find> object.
my $finder = Customer->find(); # it's like ActiveRecord::Simple::Find->new();
$finder->order_by('id');
my @customers = $finder->fetch;
=head2 all [DEPRECATED]
Same as __PACKAGE__->find->fetch;
=head2 get [DEPRECATED]
Get object by primary_key
my $customer = Customer->get(1);
# same as Customer->find({ id => 1 })->fetch;
=head2 count
Get number of rows
my $cnt = Customer->count('age > ?', 21);
=head2 exists
Check if row is exists in the database
warn "Got Barak!"
if Customer->exists({ name => 'Barak Obama' })
=head1 OBJECT METHODS
L<ActiveRecord::Simple> implements the following object methods.
=head2 is_defined
Check object is defined
=head2 save
Save object to the database
=head2 delete
Delete object from the database
=head2 update
Update object using hashref
$user->update({ last_login => \'NOW()' });
=head2 to_hash
Unbless object, get naked hash
=head2 increment
Increment fields
$customer->increment('age')->save;
=head2 decrement
Decrement fields
$customer->decrement('age')->save;
=head1 AUTHOR
shootnix, C<< <shootnix at cpan.org> >>
=head1 BUGS
Please report any bugs or feature requests to C<shootnix@cpan.org>, or through
the github: https://github.com/shootnix/activerecord-simple/issues
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc ActiveRecord::Simple
You can also look for information at:
=over 1
=item * Github wiki:
L<https://github.com/shootnix/activerecord-simple/wiki>
=back
=head1 ACKNOWLEDGEMENTS
=head1 LICENSE AND COPYRIGHT
Copyright 2013-2018 shootnix.
This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
=cut
lib/ActiveRecord/Simple/Connect.pm view on Meta::CPAN
package ActiveRecord::Simple::Connect;
use strict;
use warnings;
use 5.010;
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;
}
return $self;
}
sub db_connect {
my ($self) = @_;
$self->{dbh} = DBI->connect(
$self->{dsn},
$self->{username},
$self->{password},
) or die DBI->errstr;
return $self;
}
sub username {
my ($self, $username) = @_;
$self->{username} = $username if $username;
return $self->{username};
}
sub password {
my ($self, $password) = @_;
$self->{password} = $password if $password;
return $self->{password};
}
sub dsn {
my ($self, $dsn) = @_;
$self->{dsn} = $dsn;
return $self->{dsn};
}
sub connection_parameters {
my ($self, $connection_parameters) = @_;
$self->{connection_parameters} = $connection_parameters;
return $self->{connection_parameters};
}
sub dbh {
my ($self, $dbh) = @_;
$self->{dbh} = $dbh if $dbh;
$self->db_connect unless $self->{dbh} && $self->{dbh}->ping;
return $self->{dbh};
}
1;
lib/ActiveRecord/Simple/Find.pm view on Meta::CPAN
package ActiveRecord::Simple::Find;
use 5.010;
use strict;
use warnings;
use vars qw/$AUTOLOAD/;
use Carp;
use parent 'ActiveRecord::Simple';
use ActiveRecord::Simple::Utils qw/load_module/;
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);
if (!ref $param[0] && scalar @param == 1) {
$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;
$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;
}
push @{ $self->{prep_select_fields} }, $fields if $fields;
push @{ $self->{prep_select_from} }, $from if $from;
push @{ $self->{prep_select_where} }, $where if $where;
return $self;
}
sub count {
my $inv = shift;
my $self = ref $inv ? $inv : $inv->new(@_);
$self->{prep_select_fields} = [ 'COUNT(*)' ];
if (@{ $self->{prep_group_by} || [] }) {
my $table_name = $self->{class}->_get_table_name;
push @{ $self->{prep_select_fields} }, map qq/"$table_name".$_/, @{ $self->{prep_group_by} };
my @group_by = @{ $self->{prep_group_by} };
s/"//g foreach @group_by;
my @results;
foreach my $item ($self->fetch) {
push my @line, (count => $item->{'COUNT(*)'}), map { $_ => $item->{$_} } @group_by;
push @results, { @line };
}
return @results;
}
else {
return $self->fetch->{'COUNT(*)'};
}
}
sub first {
my ($self, $limit) = @_;
$limit //= 1;
$self->{class}->can('_get_primary_key') or croak 'Can\'t use "first" without primary key';
my $primary_key = $self->{class}->_get_primary_key;
return $self->order_by($primary_key)->limit($limit)->fetch;
}
sub last {
my ($self, $limit) = @_;
$self->{class}->can('_get_primary_key') or croak 'Can\'t use "first" without primary key';
my $primary_key = $self->{class}->_get_primary_key;
$limit //= 1;
return $self->order_by($primary_key)->desc->limit($limit)->fetch;
}
sub only {
my ($self, @fields) = @_;
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 {
push @filtered_prep_select_fields, qq/"$table_name"."$fld"/;
}
}
$self->{prep_select_fields} = \@filtered_prep_select_fields;
return $self;
}
# alias to only:
sub fields { shift->only(@_) }
sub order_by {
my ($self, @param) = @_;
#return if not defined $self->{SQL}; ### TODO: die
$self->{prep_order_by} ||= [];
push @{$self->{prep_order_by}}, map qq/"$_"/, @param;
delete $self->{prep_asc_desc};
return $self;
}
sub desc {
return shift->_order_by_direction('DESC');
}
sub asc {
return shift->_order_by_direction('ASC');
}
sub group_by {
my ($self, @param) = @_;
$self->{prep_group_by} ||= [];
push @{$self->{prep_group_by}}, map qq/"$_"/, @param;
return $self;
}
sub limit {
my ($self, $limit) = @_;
#return if not defined $self->{SQL};
return $self if exists $self->{prep_limit};
$self->{prep_limit} = $limit; ### TODO: move $limit to $self->{BIND}
return $self;
}
sub offset {
my ($self, $offset) = @_;
#return if not defined $self->{SQL};
return $self if exists $self->{prep_offset};
$self->{prep_offset} = $offset; ### TODO: move $offset to $self->{BIND}
return $self;
}
sub fetch {
my ($self, $param) = @_;
my ($read_only, $limit);
if (ref $param eq 'HASH') {
$limit = $param->{limit};
$read_only = $param->{read_only};
}
else {
$limit = $param;
}
return $self->_get_slice($limit) if $self->{_objects};
$self->_finish_sql_stmt();
$self->_quote_sql_stmt();
my $class = $self->{class};
my $sth = $self->dbh->prepare($self->{SQL}) or croak $self->dbh->errstr;
$sth->execute(@{ $self->{BIND} }) or croak $self->dbh->errstr;
if (wantarray) {
my @objects;
my $i = 0;
while (my $object_data = $sth->fetchrow_hashref()) {
$i++;
my $obj = $class->new($object_data);
$self->_finish_object_representation($obj, $object_data, $read_only);
push @objects, $obj;
last if $limit && $i == $limit;
}
delete $self->{has_joined_table};
return @objects;
}
else {
my $object_data = $sth->fetchrow_hashref() or return;
my $obj = $class->new($object_data);
$self->_finish_object_representation($obj, $object_data, $read_only);
delete $self->{has_joined_table};
return $obj;
}
}
sub upload {
my ($self, $param) = @_;
my $o = $self->fetch($param);
$_[0] = $o;
return $_[0];
}
sub next {
my ($self, $n) = @_;
$n ||= 1;
$self->{prep_limit} = $n;
$self->{prep_offset} = 0 unless defined $self->{prep_offset};
my @result = $self->fetch;
$self->{prep_offset} += $n;
return wantarray ? @result : $result[0];
}
sub with {
my ($self, @rels) = @_;
return $self if exists $self->{prep_left_joins};
return $self unless @rels;
$self->{class}->can('_get_relations')
or die "Class doesn't have any relations";
my $table_name = $self->{class}->_get_table_name;
$self->{prep_left_joins} = [];
$self->{with} = \@rels;
RELATION:
for my $rel_name (@rels) {
my $relation = $self->{class}->_get_relations->{$rel_name}
or next RELATION;
next RELATION unless grep { $_ eq $relation->{type} } qw/one only/;
my $rel_table_name = $relation->{class}->_get_table_name;
my $rel_columns = $relation->{class}->_get_columns;
REL_COLUMN:
for (@$rel_columns) {
next REL_COLUMN if ref $_;
push @{ $self->{prep_select_fields} }, qq/"$rel_table_name"."$_" AS "JOINED_$rel_name\_$_"/;
}
if ($relation->{type} eq 'one') {
my $join_sql = qq/LEFT JOIN "$rel_table_name" ON /;
$join_sql .= qq/"$rel_table_name"."$relation->{params}{pk}"/;
$join_sql .= qq/ = "$table_name"."$relation->{params}{fk}"/;
push @{ $self->{prep_left_joins} }, $join_sql;
}
}
return $self;
}
sub left_join { shift->with(@_) }
sub to_sql {
my ($self) = @_;
$self->_finish_sql_stmt();
$self->_quote_sql_stmt();
return wantarray ? ($self->{SQL}, $self->{BIND}) : $self->{SQL};
}
sub exists {
my ($self) = @_;
$self->{prep_select_fields} = ['1'];
$self->_finish_sql_stmt;
$self->_quote_sql_stmt;
my $sth = $self->dbh->prepare($self->{SQL});
$sth->execute(@{ $self->{BIND} });
return $sth->fetchrow_arrayref();
}
### Private
sub _find_many_to_many {
my ($self_class, $class, $param) = @_;
return unless $self_class->dbh && $class && $param;
my $mc_fkey;
my $class_opts = {};
my $root_class_opts = {};
if ($param->{m_class}) {
#eval { load $param->{m_class} };
#if (!is_loaded $param->{m_class}) {
# load $param->{m_class};
# mark_as_loaded
#}
load_module $param->{m_class};
for my $opts ( values %{ $param->{m_class}->_get_relations } ) {
if ($opts->{class} eq $param->{root_class}) {
$root_class_opts = $opts;
}
elsif ($opts->{class} eq $class) {
$class_opts = $opts;
}
}
my $self = $self_class->new($class, @{ $param->{where_statement} });
my $connected_table_name = $class->_get_table_name;
$self->{prep_select_from} = [ $param->{m_class}->_get_table_name ];
push @{ $self->{prep_left_joins} },
'JOIN ' . $connected_table_name . ' ON ' . $connected_table_name . '.' . $class->_get_primary_key . ' = '
. $param->{m_class}->_get_table_name . '.' . $class_opts->{params}{fk};
push @{ $self->{prep_select_where} },
$root_class_opts->{params}{fk} . ' = ' . $param->{self}->{ $param->{root_class}->_get_primary_key };
return $self;
}
else {
my $self = $self_class->new($class, @{ $param->{where_statement} });
my $connected_table_name = $class->_get_table_name;
$self->{prep_select_from} = [ $param->{via_table} ];
my $fk = ActiveRecord::Simple::Utils::class_to_table_name($class);
$fk .= '_id';
push @{ $self->{prep_left_joins} },
'JOIN ' . $connected_table_name . ' ON ' . $connected_table_name . '.' . $class->_get_primary_key . ' = '
. $param->{via_table} . '.' . $fk;
my $fk2 = ActiveRecord::Simple::Utils::class_to_table_name($param->{root_class}) . '_id';
push @{ $self->{prep_select_where} },
$fk2 . ' = ' . $param->{self}->{ $param->{root_class}->_get_primary_key };
return $self;
}
}
sub _get_slice {
my ($self, $time) = @_;
return unless $self->{_objects}
&& ref $self->{_objects} eq 'ARRAY'
&& scalar @{ $self->{_objects} } > 0;
if (wantarray) {
$time ||= scalar @{ $self->{_objects} };
return splice @{ $self->{_objects} }, 0, $time;
}
else {
return shift @{ $self->{_objects} };
}
}
sub _quote_sql_stmt {
my ($self) = @_;
return unless $self->{SQL} && $self->dbh;
my $driver_name = $self->dbh->{Driver}{Name};
$driver_name //= 'Pg';
my $quotes_map = {
Pg => q/"/,
mysql => q/`/,
SQLite => q/`/,
};
my $quote = $quotes_map->{$driver_name};
$self->{SQL} =~ s/"/$quote/g;
return $self;
}
sub _finish_object_representation {
my ($self, $obj, $object_data, $read_only) = @_;
if ($self->{has_joined_table}) {
RELATION:
for my $rel_name (@{ $self->{with} }) {
my $relation = $self->{class}->_get_relations->{$rel_name} or next RELATION;
my %pairs = map { $_, $object_data->{$_} } grep { $_ =~ /^JOINED\_$rel_name\_/ } keys %$object_data;
next RELATION unless %pairs;
for my $key (keys %pairs) {
my $val = delete $pairs{$key};
$key =~ s/^JOINED\_$rel_name\_//;
$pairs{$key} = $val;
}
$obj->{"relation_instance_$rel_name"} = $relation->{class}->new(\%pairs);
$obj->_delete_keys(qr/^JOINED\_$rel_name/);
}
}
$obj->{read_only} = 1 if defined $read_only;
$obj->{isin_database} = 1;
return $obj;
}
sub _finish_sql_stmt {
my ($self) = @_;
ref $self->{prep_select_fields} or croak 'Invalid prepare SQL statement';
ref $self->{prep_select_from} or croak 'Invalid prepare SQL statement';
my $table_name = $self->{class}->_get_table_name;
my @add = grep { $_ !~~ $self->{prep_select_fields} } map qq/"$table_name".$_/, @{ $self->{prep_group_by}||[] };
push @{ $self->{prep_select_fields} }, @add;
$self->{SQL} = "SELECT " . (join q/, /, @{ $self->{prep_select_fields} }) . "\n";
$self->{SQL} .= "FROM " . (join q/, /, @{ $self->{prep_select_from} }) . "\n";
if (defined $self->{prep_left_joins}) {
$self->{SQL} .= "$_\n" for @{ $self->{prep_left_joins} };
$self->{has_joined_table} = 1;
}
if (@{ $self->{prep_select_where}||[] }) {
$self->{SQL} .= "WHERE\n";
$self->{SQL} .= join " AND ", @{ $self->{prep_select_where} };
}
if (@{ $self->{prep_group_by}||[] }) {
$self->{SQL} .= ' GROUP BY ';
$self->{SQL} .= join q/, /, @{ $self->{prep_group_by} };
}
if (@{ $self->{prep_order_by}||[] }) {
$self->{SQL} .= ' ORDER BY ';
$self->{SQL} .= join q/, /, @{ $self->{prep_order_by} };
}
$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;
next if $relation->{type} ne 'one';
my $fk = $relation->{params}{fk};
my $pk = $relation->{params}{pk};
if (ref $param_hash->{$param_name} eq __PACKAGE__) {
my $object = $param_hash->{$param_name};
my $tmp_table = qq/tmp_table_/ . sprintf("%x", $object);
my $request_table = $object->{class}->_get_table_name;
$object->{prep_select_fields} = [qq/"$request_table"."$pk"/];
$object->_finish_sql_stmt;
push @$condition_pairs, qq/"$table_name"."$fk" IN (SELECT "$tmp_table"."$pk" from ($object->{SQL}) as $tmp_table)/;
push @$bind, @{ $object->{BIND} } if ref $object->{BIND} eq 'ARRAY';
}
else {
my $object = $param_hash->{$param_name};
if (ref $object eq 'ARRAY') {
push @$bind, map $_->$pk, @$object;
push @$condition_pairs, qq/"$table_name"."$fk" IN (@{[ join ', ', map "?", @$object ]})/;
}
else {
push @$condition_pairs, qq/"$table_name"."$fk" = ?/;
push @$bind, $object->$pk;
}
}
}
else {
if (defined $param_hash->{$param_name}) {
push @$condition_pairs, qq/"$table_name"."$param_name" = ?/;
push @$bind, $param_hash->{$param_name};
}
else {
# is NULL
push @$condition_pairs, qq/"$table_name"."$param_name" IS NULL/;
}
}
}
return ($bind, $condition_pairs);
}
sub _order_by_direction {
my ($self, $direction) = @_;
# There are no fields for order yet
return unless ref $self->{prep_order_by} eq 'ARRAY' and scalar @{ $self->{prep_order_by} } > 0;
# asc/desc is called before: ->asc->desc
return if defined $self->{prep_asc_desc};
# $direction should be ASC/DESC
return unless $direction =~ /^(ASC|DESC)$/i;
# Add $direction to the latest field
@{$self->{prep_order_by}}[-1] .= " $direction";
$self->{prep_asc_desc} = 1;
return $self;
}
sub DESTROY { }
sub AUTOLOAD {
my $call = $AUTOLOAD;
my $self = shift;
my $class = ref $self;
$call =~ s/.*:://;
my $error = "Can't call method `$call` on class $class.\nPerhaps you have forgotten to fetch your object?";
croak $error;
}
1;
__END__;
=head1 NAME
ActiveRecord::Simple::Find
=head1 DESCRIPTION
ActiveRecord::Simple is a simple lightweight implementation of ActiveRecord
pattern. It's fast, very simple and very light.
ActiveRecord::Simple::Find is a class to search, ordering, organize and fetch data from database.
It generates SQL-code and iteracts with DBI to execute it.
=head1 SYNOPSIS
my @customers = Customer->find({ name => 'Bill' })->fetch;
my @customers = Customer->find({ zip => [1001, 1002, 1003] })->fetch;
my @customers = Customer->find('age > ?', 21)->fetch;
my @customers = Customer->find([1, 2, 3, 4, 5])->order_by('id')->desc->fetch;
=head1 METHODS
L<ActiveRecord::Simple::Find> implements the following methods.
=head2 new
Object constructor, creates basic search pattern. Available from method "find"
of the base class:
# SELECT * FROM log WHERE site_id = 1 AND level = 'error';
my $f = Log->find({ id => 1, level => 'error' });
# SELECT * FROM log WHERE site_id = 1 AND level IN ('error', 'warning');
my $f = Log->find({ id => 1, level => ['error', 'warning'] });
# SELECT * FROM customer WHERE age > 21;
Customer->find('age > ?', 21);
# SELECT * FROM customer WHERE id = 100;
Customer->find(100);
# SELECT * FROM customer WHERE id IN (100, 101, 191);
Customer->find([100, 101, 191]);
=head2 last
Fetch last row from database:
# get very last log:
my $last_log = Log->find->last;
# get last error log of site number 1:
my $last_log = Log->find({ level => 'error', site_id => 1 })->last;
=head2 first
Fetch first row:
# get very first log:
my $first_log = Log->find->first;
# get first error log of site number 1:
my $first_log = Log->find({ level => 'error', site_id => 1 })->first;
=head2 count
Fetch number of records in the database:
my $cnt = Log->find->count();
my $cnt_warnings = Log->find({ level => 'warnings' })->count;
=head2 exists
Check the record is exist:
if (Log->find({ level => 'fatal' })->exists) {
die "got fatal error log!";
}
=head2 fetch
Fetch data from the database as objects:
my @errors = Log->find({ level => 'error' })->fetch;
my $errors = Log->find({ level => 'error' })->fetch; # the same, but returns ARRAY ref
my $error = Log->find(1)->fetch; # only one record
my @only_five_errors = Log->find({ level => 'error' })->fetch(5);
=head2 next
Fetch next n rows from the database:
my $finder = Log->find({ level => 'info' });
# get logs by lists of 10 elements:
while (my @logs = $finder->next(10)) {
print $_->id, "\n" for @logs;
}
=head2 only
Specify field names to get from database:
# SELECT id, message FROM log;
my @logs = Log->find->only('id', 'message');
=head2 fields
The same as "only":
# SELECT id, message FROM log;
my @logs = Log->find->fields('id', 'message');
=head2 order_by
Set "ORDER BY" command to the query:
# SELECT * FROM log ORDER BY inserted_time;
my @logs = Log->find->order_by('inserted_time');
# SELECT * FROM log ORDER BY level, id;
my @logs = Log->find->order_by('level', 'id');
=head2 asc
Set "ASC" to the query:
# SELECT * FROM log ORDER BY id ASC;
my @logs = Log->find->order_by('id')->asc;
=head2 desc
Set "DESC" to the query:
# SELECT * FROM log ORDER BY id DESC;
my @logs = Log->find->order_by('id')->desc;
=head2 limit
SET "LIMIT" to the query:
# SELECT * FROM log LIMIT 100;
my @logs = Log->find->limit(100);
=head2 offset
SET "OFFSET" to the query:
# SELECT * FROM log LIMIT 100 OFFSET 99;
my @logs = Log->find->limit(100)->offset(99);
=head2 group_by
Set "GROUP BY":
my @logs = Log->find->group_by('level');
=head2 with
Set "LEFT JOIN" command to the query:
# SELECT l.*, s.* FROM logs l LEFT JOIN sites s ON s.id = l.site_id
my @logs_and_sites = Log->find->with('sites');
print $_->site->name, ": ", $_->mesage for @logs_and_sites;
=head2 left_join
The same as "with" method
=head2 uplod
Fetch object from database and load into ActiveRecord::Simple::Find object:
my $logs = Log->find({ level => ['error', 'fatal'] });
$logs->order_by('level')->desc;
$logs->limit(100);
$logs->upload;
print $_->message for @$logs;
=head2 to_sql
Show SQL-query that genereted by ActiveRecord::Simple::Find class:
my $finder = Log->frind->only('message')->order_by('level')->desc->limit(100);
print $finder->to_sql; # prints: SELECT message FROM log ORDER BY level DESC LIMIT 100;
=head1 EXAMPLES
# SELECT * FROM pizza WHERE name = 'pepperoni';
Pizza->find({ name => 'pepperoni' });
# SELECT first_name, last_name FORM customer WHERE age > 21 ORDER BY id DESC LIMIT 100;
Customer->find('age > ?', 21)->only('first_name', 'last_name')->order_by('id')->desc->limit(100);
# SELECT p.filename, p.id, pp.* FROM photo p LEFT JOIN person pp ON p.person_id = pp.id WHERE p.size = '1020x768';
Photo->find({ size => '1020x768' })->with('person')->only('filename', 'id');
# SELECT t.* FROM topping_pizza tp LEFT JOIN topping t ON t.id = tp.topping_id WHERE tp.pizza_id = <$val>;
Pizza->get(<$val>)->toppings();
=head1 AUTHOR
shootnix, C<< <shootnix at cpan.org> >>
=head1 BUGS
Please report any bugs or feature requests to C<shootnix@cpan.org>, or through
the github: https://github.com/shootnix/activerecord-simple/issues
=head1 SUPPORT
You can find documentation for this module with the perldoc command.
perldoc ActiveRecord::Simple
You can also look for information at:
=over 1
=item * Github wiki:
L<https://github.com/shootnix/activerecord-simple/wiki>
=back
=head1 ACKNOWLEDGEMENTS
=head1 LICENSE AND COPYRIGHT
Copyright 2013-2018 shootnix.
This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.
=cut
lib/ActiveRecord/Simple/QueryManager.pm view on Meta::CPAN
package ActiveRecord::Simple::QueryManager;
use ActiveRecord::Simple::Find;
sub new { bless {}, shift }
sub all { ActiveRecord::Simple::Find->new(shift->{caller})->fetch }
sub get { ActiveRecord::Simple::Find->new(shift->{caller}, @_)->fetch }
sub find { ActiveRecord::Simple::Find->new(shift->{caller}, @_) }
sub sql_fetch_all {
my ($self, $sql, @bind) = @_;
my $data = $self->{caller}->dbh->selectall_arrayref($sql, { Slice => {} }, @bind);
my @list;
for my $row (@$data) {
$self->{caller}->_mk_ro_accessors([keys %$row]);
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__;
=head1 NAME
ActiveRecord::Simple::QueryManager - query manager for ActiveRecord classes
=head1 DESCRIPTION
Query manager for ActiveRecord classes.
=head1 SYNOPSIS
my $qm = ActiveRecord::Simple::QueryManager->new;
$qm->{caller} = 'User';
my @users = $qm->all(); # SELECT * FROM user;
my @johns = $qm->find({ name => 'John' })->fetch;
=head2 sql_fetch_all
Execute any SQL code and fetch data. Returns list of objects. Accessors for all not specified fields
will be created as read-only.
my @values = Purchase->sql_fetch_all('SELECT id, amount FROM purchase WHERE amount > ?', 100);
print $_->id, " ", $_->amount for, "\n" @values;
=head2 sql_fetch_row
Execute any SQL and fetch data. Returns an object.
my $customer = Customer->sql_fetch_row('SELECT id, name FORM customer WHERE id = ?', 1);
print $customer->name, "\n";
=head2 find
Returns L<ActiveRecord::Simple::Find> object.
my $finder = Customer->find(); # it's like ActiveRecord::Simple::Find->new();
$finder->order_by('id');
my @customers = $finder->fetch;
=head2 all
Same as __PACKAGE__->find->fetch;
=head2 get
Get object by primary_key
my $customer = Customer->get(1);
# same as Customer->find({ id => 1 })->fetch;
lib/ActiveRecord/Simple/Utils.pm view on Meta::CPAN
package ActiveRecord::Simple::Utils;
use strict;
use warnings;
require Exporter;
use Module::Load;
use Module::Loaded;
use Scalar::Util qw/blessed/;
our @ISA = qw/Exporter/;
our @EXPORT = qw/class_to_table_name all_blessed load_module/;
sub quote_sql_stmt {
my ($sql, $driver_name) = @_;
return unless $sql && $driver_name;
$driver_name //= 'Pg';
my $quotes_map = {
Pg => q/"/,
mysql => q/`/,
SQLite => q/`/,
};
my $quote = $quotes_map->{$driver_name};
$sql =~ s/"/$quote/g;
return $sql;
}
sub class_to_table_name {
my ($class) = @_;
#load $class;
return $class->_get_table_name if $class->can('_get_table_name');
$class =~ s/.*:://;
#$class_name = lc $class_name;
my $table_name = join('_', map {lc} grep {length} split /([A-Z]{1}[^A-Z]*)/, $class);
return $table_name;
}
sub is_integer {
my ($data_type) = @_;
return unless $data_type;
return grep { $data_type eq $_ } qw/integer bigint tinyint int smallint/;
}
sub is_numeric {
my ($data_type) = @_;
return unless $data_type;
return 1 if is_integer($data_type);
return grep { $data_type eq $_ } qw/numeric decimal/;
}
sub all_blessed {
my ($list) = @_;
for my $item (@$list) {
return unless defined $item;
return unless blessed $item;
}
return 1;
}
sub load_module {
my ($module_name) = @_;
if (!is_loaded $module_name) {
eval { load $module_name; };
mark_as_loaded $module_name;
}
}
1;
t/00-load.t view on Meta::CPAN
#!perl -T
use Test::More tests => 1;
BEGIN {
use_ok( 'ActiveRecord::Simple' ) || print "Bail out!\n";
}
diag( "Testing ActiveRecord::Simple $ActiveRecord::Simple::VERSION, Perl $], $^X" );
t/01-boilerplate.t view on Meta::CPAN
#!/usr/bin/env perl
use 5.006;
use strict;
use warnings;
use Test::More tests => 4;
sub not_in_file_ok {
my ($filename, %regex) = @_;
open( my $fh, '<', $filename )
or die "couldn't open $filename for reading: $!";
my %violated;
while (my $line = <$fh>) {
while (my ($desc, $regex) = each %regex) {
if ($line =~ $regex) {
push @{$violated{$desc}||=[]}, $.;
}
}
}
if (%violated) {
fail("$filename contains boilerplate text");
diag "$_ appears on lines @{$violated{$_}}" for keys %violated;
} else {
pass("$filename contains no boilerplate text");
}
}
sub module_boilerplate_ok {
my ($module) = @_;
not_in_file_ok($module =>
'the great new $MODULENAME' => qr/ - The great new /,
'boilerplate description' => qr/Quick summary of what the module/,
'stub function definition' => qr/function[12]/,
);
}
TODO: {
local $TODO = "Need to replace the boilerplate text";
not_in_file_ok(README =>
"The README is used..." => qr/The README is used/,
"'version information here'" => qr/to provide version information/,
);
not_in_file_ok(Changes =>
"placeholder date/time" => qr(Date/time)
);
module_boilerplate_ok('lib/ActiveRecord/Simple.pm');
module_boilerplate_ok('lib/ActiveRecord/Simple/Find.pm');
}
#!perl -T
use strict;
use warnings;
use Test::More;
# Ensure a recent version of Test::Pod
my $min_tp = 1.22;
eval "use Test::Pod $min_tp";
plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
pod_file_ok('lib/ActiveRecord/Simple.pm', 'ActiveRecord::Simple POD is ok');
done_testing();
t/03-pod-coverage.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use FindBin qw/$Bin/;
use lib "$Bin/../lib";
# Ensure a recent version of Test::Pod::Coverage
my $min_tpc = 1.08;
eval "use Test::Pod::Coverage $min_tpc";
plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
if $@;
# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
# but older versions don't recognize some common documentation styles
my $min_pc = 0.18;
eval "use Pod::Coverage $min_pc";
plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
if $@;
pod_coverage_ok('ActiveRecord::Simple', 'ActiveRecord::Simple POD is ok');
done_testing();
t/04-manifest.t view on Meta::CPAN
#!perl -T
use strict;
use warnings;
use Test::More;
unless ( $ENV{RELEASE_TESTING} ) {
plan( skip_all => "Author tests not required for installation" );
}
eval "use Test::CheckManifest 0.9";
plan skip_all => "Test::CheckManifest 0.9 required" if $@;
ok_manifest();
t/05-accessors.t view on Meta::CPAN
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
package Customer;
use FindBin qw/$Bin/;
use lib "$Bin/../lib";
use parent 'ActiveRecord::Simple';
__PACKAGE__->table_name('customer');
__PACKAGE__->columns(qw/id first_name last_name email/);
__PACKAGE__->primary_key('id');
package main;
use Test::More;
my $customer = Customer->new;
eval { $customer->id };
ok ! $@;
ok $customer->id(1);
is $customer->id, 1;
ok $customer->first_name('Bill');
is $customer->first_name, 'Bill';
ok $customer->last_name('Cleantone')->email('bill@cleantone.com');
is $customer->last_name, 'Cleantone';
is $customer->email, 'bill@cleantone.com';
is $customer->_get_table_name, 'customer';
my $c2 = Customer->new(
id => 2,
first_name => 'Bob',
last_name => 'Rock!',
email => 'bob@rock.com',
);
is $c2->id, 2;
is $c2->first_name, 'Bob';
Customer->_mk_ro_accessors(['say_hello']);
$customer->{say_hello} = 'Hello!';
is $customer->say_hello, 'Hello!';
done_testing();
t/06-no-accessors.t view on Meta::CPAN
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
package Customer;
use FindBin qw/$Bin/;
use lib "$Bin/../lib";
use parent 'ActiveRecord::Simple';
__PACKAGE__->make_columns_accessors(0);
__PACKAGE__->table_name('customer');
__PACKAGE__->columns(qw/id first_name last_name email/);
__PACKAGE__->primary_key('id');
package main;
use Test::More;
my $customer = Customer->new(
id => 2,
first_name => 'Bob',
last_name => 'Rock!',
email => 'bob@rock.com',
);
eval { $customer->id(1) };
ok $@;
like $@, qr/Can't locate object method "id"/;
is $customer->{id}, 2;
is $customer->{first_name}, 'Bob';
done_testing();
t/07-auto_load.t view on Meta::CPAN
#!/usr/bin/perl
BEGIN {
package Schema;
use FindBin qw/$Bin/;
use lib "$Bin/../lib";
use parent 'ActiveRecord::Simple';
eval { require DBD::SQLite } or exit 0;
__PACKAGE__->connect("dbi:SQLite:dbname=:memory:","","");
my $_INIT_SQL_CUSTOMER = q{
CREATE TABLE `customer` (
`id` int AUTO_INCREMENT,
`first_name` varchar(200) NULL,
`second_name` varchar(200) NOT NULL,
`age` tinyint(2) NULL,
`email` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
);
};
__PACKAGE__->dbh->do($_INIT_SQL_CUSTOMER);
}
package Customer;
#
our @ISA = qw/Schema/;
#
__PACKAGE__->auto_load();
#
#
package main;
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/08-basic.t view on Meta::CPAN
#!/usr/bin/perl
use strict;
use warnings;
no warnings 'redefine';
use 5.010;
use Data::Dumper;
use FindBin '$Bin';
use lib "$Bin/../lib";
package t::class;
use base 'ActiveRecord::Simple';
__PACKAGE__->table_name('t');
__PACKAGE__->columns('foo', 'bar');
__PACKAGE__->primary_key('foo');
#__PACKAGE__->belongs_to(class2 => 't::class2');
1;
package t::class2;
use base 'ActiveRecord::Simple';
__PACKAGE__->table_name('t');
__PACKAGE__->columns('foo', 'bar');
__PACKAGE__->primary_key('foo');
#__PACKAGE__->belongs_to(class => 't::class');
1;
package t::ClaSs3;
use base 'ActiveRecord::Simple';
package MockDBI;
sub selectrow_array { 1 }
sub do { 1 }
sub selectrow_hashref { { DUMMY => 'hash' } }
sub fetchrow_hashref { { DUMMY => 'hash' } }
sub prepare { bless {}, 'MockDBI' }
sub execute { 1 }
sub last_insert_id { 1 }
sub selectall_arrayref { [{ foo => 1 }, { bar => 2 }] }
1;
*ActiveRecord::Simple::dbh = sub {
return bless { Driver => { Name => 'mysql' } }, 'MockDBI';
};
package main;
use Test::More;
ok my $c = t::class->new({
foo => 1,
bar => 2,
});
ok $c->save(), 'save';
ok $c->foo(100);
is $c->foo, 100, 'update in memory ok';
ok $c->save(), 'update in database ok';
ok my $c2 = t::class->objects->find(1), 'find, primary key';
isa_ok $c2, 'ActiveRecord::Simple::Find';
ok my $c21 = t::class->objects->get(1), 'get';
isa_ok $c21, 't::class';
ok my $c3 = t::class->objects->find({ foo => 'bar' }), 'find, params';
isa_ok $c3, 'ActiveRecord::Simple::Find';
ok my $c4 = t::class->objects->find([1, 2, 3]), 'find, primary keys';
isa_ok $c4, 'ActiveRecord::Simple::Find';
ok my $c5 = t::class->objects->find('foo = ?', 'bar'), 'find, binded params';
isa_ok $c5, 'ActiveRecord::Simple::Find';
is ref $c->to_hash, 'HASH', 'to_hash';
my $order_find = t::class->objects->find->order_by('foo');
$order_find->fetch;
ok $order_find->{SQL} =~ m/order by/i, 'order by';
$order_find = t::class->objects->find->order_by('foo')->desc;
$order_find->fetch;
ok $order_find->{SQL} =~ m/order by/i, 'order by';
ok $order_find->{SQL} =~ m/desc/i, 'order by, desc';
my $limit_find = t::class->objects->find->limit(1);
$limit_find->fetch;
ok $limit_find->{SQL} =~ m/limit\s+1/i, 'limit 1';
my $offset_find = t::class->objects->find->offset(2);
$offset_find->fetch();
ok $offset_find->{SQL} =~ m/offset\s+2/i, 'offset 2';
my $total_sql = t::class->objects->find->limit(1)->offset(2)->order_by('foo')->desc;
$total_sql->fetch;
ok $total_sql->{SQL} =~ /limit\s+1/i, 'use all predicats, find "limit 1"';
ok $total_sql->{SQL} =~ /offset\s+2/i, 'use all predicats, find "offset 2"';
ok $total_sql->{SQL} =~ /order\s+by/i, 'use all predicats, find "order by"';
ok $total_sql->{SQL} =~ /desc/i, 'use all predicats, find "desc"';
ok $c->delete(), 'delete';
ok my $c6 = t::class->objects->find->only('foo', 'bar'), 'find only "foo"';
my $r;
ok $r = t::class->objects->find->first, 'first';
ok $r = t::class->objects->find->first(10), 'first 10';
ok $r = t::class->objects->find->last, 'last';
#is(t::ClaSs3->_table_name, 'cla_ss3');
#is(t::class->_table_name, 't');
my $cs1 = t::class->new();
my $cs2 = t::ClaSs3->new();
done_testing();
t/09-find.t view on Meta::CPAN
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use FindBin '$Bin';
use lib "$Bin/../lib";
use Data::Dumper;
use Test::More;
use DBI;
eval { require DBD::SQLite } or plan skip_all => 'Need DBD::SQLite for testing';
package Customer;
use parent 'ActiveRecord::Simple';
__PACKAGE__->table_name('customers');
__PACKAGE__->primary_key('id');
__PACKAGE__->columns(qw/id first_name second_name age email/);
#__PACKAGE__->has_one(info => 'CustomersInfo');
__PACKAGE__->mixins(
mixin => sub {
'SUM("id")'
},
);
package main;
my $dbh = DBI->connect("dbi:SQLite:dbname=:memory:","","")
or die DBI->errstr;
my $_INIT_SQL = q{
CREATE TABLE `customers` (
`id` int AUTO_INCREMENT,
`first_name` varchar(200) NULL,
`second_name` varchar(200) NOT NULL,
`age` tinyint(2) NULL,
`email` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL = q{
INSERT INTO `customers` (`id`, `first_name`, `second_name`, `age`, `email`)
VALUES
(1,'Bob','Dylan',NULL,'bob.dylan@aol.com'),
(2,'John','Doe',77,'john@doe.com'),
(3,'Bill','Clinton',50,'mynameisbill@gmail.com'),
(4,'Bob','Marley',NULL,'bob.marley@forever.com'),
(5,'','',NULL,'foo.bar@bazz.com'),
(6, 'Lady', 'Gaga', 666, 'gaga-o-la-la@bad.romance');
};
$dbh->do($_INIT_SQL);
$dbh->do($_DATA_SQL);
Customer->dbh($dbh);
my $finder = Customer->objects->find({ first_name => 'Bob' })->order_by('id');
isa_ok $finder, 'ActiveRecord::Simple::Find';
my @bobs = (1, 4); my $i = 0;
while (my $bob = $finder->next) {
ok $bob->id == $bobs[$i], 'next, bob.id == ' . $bobs[$i];
$i++;
}
$finder = Customer->objects->find;
while (my @customers_pair = $finder->next(2)) {
is scalar @customers_pair, 2, 'next(n) works good';
}
my $f = Customer->objects->find({ first_name => 'Bob' });
ok my $Bob = Customer->objects->find({ first_name => 'Bob' })->fetch, 'find Bob';
isa_ok $Bob, 'Customer';
is $Bob->first_name, 'Bob', 'Bob has a right name';
is $Bob->second_name, 'Dylan';
ok !$Bob->age;
ok my $John = Customer->objects->get(2), 'get John';
is $John->first_name, 'John';
ok my $Bill = Customer->objects->find('second_name = ?', 'Clinton')->fetch, 'find Bill';
is $Bill->first_name, 'Bill';
ok my @customers = Customer->objects->get([1, 2, 3]), 'get customers with #1,2,3';
is scalar @customers, 3;
is $customers[0]->first_name, 'Bob';
is $customers[1]->first_name, 'John';
is $customers[2]->first_name, 'Bill';
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;
ok my $customized2 = Customer->objects->find({ first_name => 'Bob' })->fields('id')->fetch, 'fields (alias to "only")';
is $customized2->id, 1;
ok !$customized2->first_name;
my $c = Customer->objects->find->only('first_name')->first;
$c = Customer->objects->find->only('id')->first;
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();
t/10-relations.t view on Meta::CPAN
BEGIN {
package Schema;
use FindBin '$Bin';
use lib "$Bin/../lib";
use parent 'ActiveRecord::Simple';
__PACKAGE__->connect("dbi:SQLite:dbname=:memory:","","");
my $_DROP_SQL_CUSTOMER = q{
DROP TABLE IF EXISTS customer;
};
my $_INIT_SQL_CUSTOMER = q{
CREATE TABLE `customer` (
`id` int AUTO_INCREMENT,
`first_name` varchar(200) NULL,
`last_name` varchar(200) NOT NULL,
`age` tinyint(2) NULL,
`email` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_CUSTOMER = q{
INSERT INTO `customer` (`id`, `first_name`, `last_name`, `age`, `email`)
VALUES
(1,'Bob','Dylan',NULL,'bob.dylan@aol.com'),
(2,'John','Doe',77,'john@doe.com'),
(3,'Bill','Clinton',50,'mynameisbill@gmail.com'),
(4,'Bob','Marley',NULL,'bob.marley@forever.com'),
(5,'','',NULL,'foo.bar@bazz.com');
};
Schema->dbh->do($_DROP_SQL_CUSTOMER);
Schema->dbh->do($_INIT_SQL_CUSTOMER);
Schema->dbh->do($_DATA_SQL_CUSTOMER);
my $_DROP_SQL_PURCHASE = q{
DROP TABLE IF EXISTS purchase;
};
my $_INIT_SQL_PURCHASE = q{
CREATE TABLE `purchase` (
`id` int AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`amount` decimal(10,2) NOT NULL DEFAULT 0.0,
`customer_id` int NOT NULL references `customer` (`id`),
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_PURCHASE = q{
INSERT INTO `purchase` (`id`, `title`, `amount`, `customer_id`)
VALUES
(1, 'The Order #1', 10, 1),
(2, 'The Order #2', 5.66, 2),
(3, 'The Order #3', 6.43, 3),
(4, 'The Order #4', 2.20, 1),
(5, 'The Order #5', 3.39, 4);
};
Schema->dbh->do($_DROP_SQL_PURCHASE);
Schema->dbh->do($_INIT_SQL_PURCHASE);
Schema->dbh->do($_DATA_SQL_PURCHASE);
my $_DROP_SQL_ACHIEVEMENT = q{
DROP TABLE IF EXISTS achievement;
};
my $_INIT_SQL_ACHIEVEMENT = q{
CREATE TABLE `achievement` (
`id` int AUTO_INCREMENT,
`title` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_ACHEIVEMENT = q{
INSERT INTO `achievement` (`id`, `title`)
VALUES
(1, 'Bronze'),
(2, 'Silver'),
(3, 'Gold');
};
Schema->dbh->do($_DROP_SQL_ACHIEVEMENT);
Schema->dbh->do($_INIT_SQL_ACHIEVEMENT);
Schema->dbh->do($_DATA_SQL_ACHEIVEMENT);
my $_DROP_SQL_CA = q{
DROP TABLE IF EXISTS customer_achievement;
};
my $_INIT_SQL_CA = q{
CREATE TABLE `customer_achievement` (
`customer_id` int NOT NULL references customer (id),
`achievement_id` int NOT NULL references achievement (id)
);
};
my $_DATA_SQL_CA = q{
INSERT INTO `customer_achievement` (`customer_id`, `achievement_id`)
VALUES
(1, 1),
(1, 2),
(2, 1),
(2, 3),
(3, 1),
(3, 2),
(3, 3);
};
Schema->dbh->do($_DROP_SQL_CA);
Schema->dbh->do($_INIT_SQL_CA);
Schema->dbh->do($_DATA_SQL_CA);
}
package Purchase;
our @ISA = qw/Schema/;
__PACKAGE__->table_name('purchase');
__PACKAGE__->columns(qw/id title amount customer_id/);
__PACKAGE__->primary_key('id');
__PACKAGE__->belongs_to(customer => 'Customer');
package Customer;
our @ISA = qw/Schema/;
__PACKAGE__->table_name('customer');
__PACKAGE__->columns(qw/id first_name last_name age email/);
__PACKAGE__->primary_key('id');
__PACKAGE__->has_many(purchases => 'Purchase');
__PACKAGE__->has_many(achievements => 'Achievement', { via => 'customer_achievement' });
package Achievement;
our @ISA = qw/Schema/;
__PACKAGE__->table_name('achievement');
__PACKAGE__->columns(qw/id title/);
__PACKAGE__->primary_key('id');
__PACKAGE__->has_many(customers => 'Customer', { via => 'customer_achievement' });
package main;
use Test::More;
use Data::Dumper;
use 5.010;
ok my $customer = Customer->objects->get(1);
is $customer->first_name, 'Bob';
my @purchases = $customer->purchases->fetch;
is scalar @purchases, 2;
my $purchase = Purchase->objects->get(2);
is $purchase->id, 2;
is $purchase->title, 'The Order #2';
is $purchase->customer->first_name, 'John';
my $achievement = Achievement->objects->get(1);
is $achievement->title, 'Bronze';
my @customers = $achievement->customers->fetch;
is scalar @customers, 3;
my @achievements = $customer->achievements->fetch;
is scalar @achievements, 2;
ok my $Bill = Customer->objects->get(3), 'got Bill';
ok $achievement = Achievement->new({ title => 'Bill Achievement', id => 4 })->save, 'create achievement';
is $Bill->id, 3;
is $achievement->id, 4;
ok $Bill->achievements($achievement)->save, 'trying to bind achievement to the customer';
ok my $cnt = $Bill->achievements({ title => 'Bill Achievement' })->count(), 'trying to count customers achievements';
is $cnt, 1, 'looks good';
ok $Bill->achievements({ title => 'Bill Achievement' })->exists;
ok !$Bill->achievements({ title => 'Not Existing Achievement' })->exists;
ok my @bills_orders = $Bill->purchases->fetch, 'got Bill\'s orders';
is scalar @bills_orders, 1;
ok my $order = Purchase->objects->get(3), 'order';
ok $order->customer, 'the order has a customer';
is $order->customer->id, $bills_orders[0]->id;
ok @achievements = $Bill->achievements->fetch;#
is @achievements, 4;
isa_ok $achievements[0], 'Achievement';
ok my $a = Achievement->objects->get(1);
ok @customers = $a->customers->order_by('id')->fetch;
is @customers, 3;
done_testing();
t/11-crud-methods.t view on Meta::CPAN
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use FindBin '$Bin';
use lib "$Bin/../lib";
use Data::Dumper;
use DBI;
package Customer;
use parent 'ActiveRecord::Simple';
__PACKAGE__->table_name('customer');
__PACKAGE__->primary_key('id');
__PACKAGE__->columns(qw/id first_name second_name age email/);
__PACKAGE__->has_many('orders' => 'Order');
__PACKAGE__->has_many(achievements => 'Achievement', { via => 'customer_achievement' });
package Order;
use parent 'ActiveRecord::Simple';
__PACKAGE__->table_name('order');
__PACKAGE__->primary_key('id');
__PACKAGE__->columns(qw/id title amount customer_id/);
__PACKAGE__->belongs_to(customer => 'Customer');
package Achievement;
use parent 'ActiveRecord::Simple';
__PACKAGE__->table_name('achievement');
__PACKAGE__->primary_key('id');
__PACKAGE__->columns(qw/id title/);
__PACKAGE__->has_many(customers => 'Customer', { via => 'customer_achievement' });
package main;
use Test::More;
eval { require DBD::SQLite } or plan skip_all => 'Need DBD::SQLite for testing';
my $dbh = DBI->connect("dbi:SQLite:dbname=:memory:","","")
or die DBI->errstr;
my $_INIT_SQL_CUSTOMERS = q{
CREATE TABLE `customer` (
`id` int AUTO_INCREMENT,
`first_name` varchar(200) NULL,
`second_name` varchar(200) NOT NULL,
`age` tinyint(2) NULL,
`email` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_CUSTOMERS = q{
INSERT INTO `customer` (`id`, `first_name`, `second_name`, `age`, `email`)
VALUES
(1,'Bob','Dylan',NULL,'bob.dylan@aol.com'),
(2,'John','Doe',77,'john@doe.com'),
(3,'Bill','Clinton',50,'mynameisbill@gmail.com'),
(4,'Bob','Marley',NULL,'bob.marley@forever.com'),
(5,'','',NULL,'foo.bar@bazz.com');
};
$dbh->do($_INIT_SQL_CUSTOMERS);
$dbh->do($_DATA_SQL_CUSTOMERS);
my $_INIT_SQL_ORDERS = q{
CREATE TABLE `order` (
`id` int AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`amount` decimal(10,2) NOT NULL DEFAULT 0.0,
`customer_id` int NOT NULL references `customer` (`id`),
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_ORDERS = q{
INSERT INTO `order` (`id`, `title`, `amount`, `customer_id`)
VALUES
(1, 'The Order #1', 10, 1),
(2, 'The Order #2', 5.66, 2),
(3, 'The Order #3', 6.43, 3),
(4, 'The Order #4', 2.20, 1),
(5, 'The Order #5', 3.39, 4);
};
$dbh->do($_INIT_SQL_ORDERS);
$dbh->do($_DATA_SQL_ORDERS);
my $_INIT_SQL_ACHIEVEMENTS = q{
CREATE TABLE `achievement` (
`id` int AUTO_INCREMENT,
`title` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_ACHEIVEMENTS = q{
INSERT INTO `achievement` (`id`, `title`)
VALUES
(1, 'Bronze'),
(2, 'Silver'),
(3, 'Gold');
};
$dbh->do($_INIT_SQL_ACHIEVEMENTS);
$dbh->do($_DATA_SQL_ACHEIVEMENTS);
my $_INIT_SQL_CA = q{
CREATE TABLE `customer_achievement` (
`customer_id` int NOT NULL references customer (id),
`achievement_id` int NOT NULL references achievement (id)
);
};
my $_DATA_SQL_CA = q{
INSERT INTO `customer_achievement` (`customer_id`, `achievement_id`)
VALUES
(1, 1),
(1, 2),
(2, 1),
(2, 3),
(3, 1),
(3, 2),
(3, 3);
};
$dbh->do($_INIT_SQL_CA);
$dbh->do($_DATA_SQL_CA);
Customer->dbh($dbh);
ok my $Bill = Customer->objects->get(3), 'got Bill';
ok my $achievement = Achievement->new({ title => 'Bill Achievement', id => 4 })->save, 'create achievement';
is $Bill->id, 3;
is $achievement->id, 4;
ok $Bill->achievements($achievement)->save, 'trying to bind achievement to the customer';
#ok my $ca = CustomersAchievement->find({ customer_id => $Bill->id, achievement_id => $achievement->id })->fetch, 'fetching binding';
#is $ca->customer_id, $Bill->id;
#is $ca->achievement_id, $achievement->id;
#my @ca = CustomersAchievement->find({ customer_id => $Bill->id, achievement_id => $achievement->id })->fetch;
ok my $cnt = $Bill->achievements({ title => 'Bill Achievement' })->count(), 'trying to count customers achievements';
is $cnt, 1, 'looks good';
ok $Bill->achievements({ title => 'Bill Achievement' })->exists;
ok !$Bill->achievements({ title => 'Not Existing Achievement' })->exists;
ok my @bills_orders = $Bill->orders->fetch, 'got Bill\'s orders';
is scalar @bills_orders, 1;
ok my $order = Order->objects->get(3), 'order';
ok $order->customer, 'the order has a customer';
is $order->customer->id, $bills_orders[0]->id;
ok my @achievements = $Bill->achievements->fetch;#
is @achievements, 4;
isa_ok $achievements[0], 'Achievement';
ok my $a = Achievement->objects->get(1);
ok my @customers = $a->customers->order_by('id')->fetch;
is @customers, 3;
done_testing();
t/12-connect.t view on Meta::CPAN
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use FindBin '$Bin';
use lib "$Bin/../lib";
use Data::Dumper;
use DBI;
#use Scalar::Util qw/blessed/;
package Customer;
use parent 'ActiveRecord::Simple';
__PACKAGE__->table_name('customers');
__PACKAGE__->primary_key('id');
__PACKAGE__->columns(qw/id first_name second_name age email/);
__PACKAGE__->has_many('orders' => 'Order');
__PACKAGE__->has_many('achievements' => { CustomersAchievement => 'Achievement' });
package main;
use Test::More;
eval { require DBD::SQLite } or plan skip_all => 'Need DBD::SQLite for testing';
ok(Customer->connect("dbi:SQLite:dbname=:memory:","",""), 'connect');
eval { require DBIx::Connector };
if ($@) {
# There is no DBIx::Connector, use DBI/ARS::Connect
}
else {
isa_ok $ActiveRecord::Simple::connector, 'DBIx::Connector';
}
my $hello = Customer->dbh->selectrow_array('SELECT "hello"');
is $hello, 'hello';
done_testing();
t/13-init.t view on Meta::CPAN
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Test::More;
use FindBin '$Bin';
use lib "$Bin/../lib";
BEGIN {
package Schema;
use parent 'ActiveRecord::Simple';
eval { require DBD::SQLite } or exit 0;
__PACKAGE__->connect("dbi:SQLite:dbname=:memory:","","");
my $_INIT_SQL_CUSTOMERS = q{
CREATE TABLE `customer` (
`id` int AUTO_INCREMENT,
`first_name` varchar(200) NULL,
`second_name` varchar(200) NOT NULL,
`age` tinyint(2) NULL,
`email` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_CUSTOMERS = q{
INSERT INTO `customer` (`id`, `first_name`, `second_name`, `age`, `email`)
VALUES
(1,'Bob','Dylan',NULL,'bob.dylan@aol.com'),
(2,'John','Doe',77,'john@doe.com'),
(3,'Bill','Clinton',50,'mynameisbill@gmail.com'),
(4,'Bob','Marley',NULL,'bob.marley@forever.com'),
(5,'','',NULL,'foo.bar@bazz.com');
};
Schema->dbh->do($_INIT_SQL_CUSTOMERS);
Schema->dbh->do($_DATA_SQL_CUSTOMERS);
my $_INIT_SQL_ORDERS = q{
CREATE TABLE `order` (
`id` int AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`amount` decimal(10,2) NOT NULL DEFAULT 0.0,
`customer_id` int NOT NULL references `customers` (`id`),
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_ORDERS = q{
INSERT INTO `order` (`id`, `title`, `amount`, `customer_id`)
VALUES
(1, 'The Order #1', 10, 1),
(2, 'The Order #2', 5.66, 2),
(3, 'The Order #3', 6.43, 3),
(4, 'The Order #4', 2.20, 1),
(5, 'The Order #5', 3.39, 4);
};
Schema->dbh->do($_INIT_SQL_ORDERS);
Schema->dbh->do($_DATA_SQL_ORDERS);
my $_INIT_SQL_ACHIEVEMENTS = q{
CREATE TABLE `achievement` (
`id` int AUTO_INCREMENT,
`title` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_ACHEIVEMENTS = q{
INSERT INTO `achievement` (`id`, `title`)
VALUES
(1, 'Bronze'),
(2, 'Silver'),
(3, 'Gold');
};
Schema->dbh->do($_INIT_SQL_ACHIEVEMENTS);
Schema->dbh->do($_DATA_SQL_ACHEIVEMENTS);
my $_INIT_SQL_CA = q{
CREATE TABLE `customer_achievement` (
`customer_id` int NOT NULL references customers (id),
`achievement_id` int NOT NULL references achievements (id)
);
};
my $_DATA_SQL_CA = q{
INSERT INTO `customer_achievement` (`customer_id`, `achievement_id`)
VALUES
(1, 1),
(1, 2),
(2, 1),
(2, 3),
(3, 1),
(3, 2),
(3, 3);
};
Schema->dbh->do($_INIT_SQL_CA);
Schema->dbh->do($_DATA_SQL_CA);
}
package Customer;
#use parent 'Schema';
our @ISA = qw/Schema/;
__PACKAGE__->auto_load();
__PACKAGE__->has_many(orders => 'Order');
__PACKAGE__->has_many(achievements => 'Achievement', { via => 'customer_achievement' });
package Order;
our @ISA = qw/Schema/;
__PACKAGE__->auto_load();
__PACKAGE__->belongs_to(customer => 'Customer');
package Achievement;
our @ISA = qw/Schema/;
__PACKAGE__->auto_load();
__PACKAGE__->has_many(customers => 'Customer', { via => 'customer_achievement' });
package main;
ok my $Bill = Customer->objects->get(3), 'got Bill';
ok my @bills_orders = $Bill->orders->fetch, 'got Bill\'s orders';
is scalar @bills_orders, 1;
ok my $order = Order->objects->get(3), 'order';
ok $order->customer, 'the order has a customer';
is $order->customer->id, $bills_orders[0]->id, 'id == id';
ok my @achievements = $Bill->achievements->fetch;
is @achievements, 3;
isa_ok $achievements[0], 'Achievement';
ok my $a = Achievement->objects->get(1);
ok my @customers = $a->customers->fetch;
is @customers, 3;
done_testing();
t/14-smart-accessors.t view on Meta::CPAN
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use FindBin '$Bin';
use lib "$Bin/../lib";
use Test::More;
BEGIN {
package Schema;
use parent 'ActiveRecord::Simple';
eval { require DBD::SQLite } or exit 0;
__PACKAGE__->connect("dbi:SQLite:dbname=:memory:","","");
my $_INIT_SQL_CUSTOMERS = q{
CREATE TABLE `customer` (
`id` int AUTO_INCREMENT,
`first_name` varchar(200) NULL,
`second_name` varchar(200) NOT NULL,
`age` tinyint(2) NULL,
`email` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_CUSTOMERS = q{
INSERT INTO `customer` (`id`, `first_name`, `second_name`, `age`, `email`)
VALUES
(1,'Bob','Dylan',NULL,'bob.dylan@aol.com'),
(2,'John','Doe',77,'john@doe.com'),
(3,'Bill','Clinton',50,'mynameisbill@gmail.com'),
(4,'Bob','Marley',NULL,'bob.marley@forever.com'),
(5,'','',NULL,'foo.bar@bazz.com');
};
Schema->dbh->do($_INIT_SQL_CUSTOMERS);
Schema->dbh->do($_DATA_SQL_CUSTOMERS);
my $_INIT_SQL_ORDERS = q{
CREATE TABLE `order` (
`id` int AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`amount` decimal(10,2) NOT NULL DEFAULT 0.0,
`customer_id` int NOT NULL references `customers` (`id`),
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_ORDERS = q{
INSERT INTO `order` (`id`, `title`, `amount`, `customer_id`)
VALUES
(1, 'The Order #1', 10, 1),
(2, 'The Order #2', 5.66, 2),
(3, 'The Order #3', 6.43, 3),
(4, 'The Order #4', 2.20, 1),
(5, 'The Order #5', 3.39, 4);
};
Schema->dbh->do($_INIT_SQL_ORDERS);
Schema->dbh->do($_DATA_SQL_ORDERS);
my $_INIT_SQL_ACHIEVEMENTS = q{
CREATE TABLE `achievement` (
`id` int AUTO_INCREMENT,
`title` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_ACHEIVEMENTS = q{
INSERT INTO `achievement` (`id`, `title`)
VALUES
(1, 'Bronze'),
(2, 'Silver'),
(3, 'Gold');
};
Schema->dbh->do($_INIT_SQL_ACHIEVEMENTS);
Schema->dbh->do($_DATA_SQL_ACHEIVEMENTS);
my $_INIT_SQL_CA = q{
CREATE TABLE `customer_achievement` (
`customer_id` int NOT NULL references customers (id),
`achievement_id` int NOT NULL references achievements (id)
);
};
my $_DATA_SQL_CA = q{
INSERT INTO `customer_achievement` (`customer_id`, `achievement_id`)
VALUES
(1, 1),
(1, 2),
(2, 1),
(2, 3),
(3, 1),
(3, 2),
(3, 3);
};
Schema->dbh->do($_INIT_SQL_CA);
Schema->dbh->do($_DATA_SQL_CA);
}
package Customer;
#use parent 'Schema';
our @ISA = qw/Schema/;
__PACKAGE__->auto_load();
__PACKAGE__->has_many(orders => 'Order');
__PACKAGE__->has_many(achievements => 'Achievement', { via => 'customer_achievement' });
package Order;
our @ISA = qw/Schema/;
__PACKAGE__->auto_load();
__PACKAGE__->belongs_to(customer => 'Customer');
package Achievement;
our @ISA = qw/Schema/;
__PACKAGE__->auto_load();
__PACKAGE__->has_many(customers => 'Customer', { via => 'customer_achievement' });
package main;
ok my $Bill = Customer->objects->find({ first_name => 'Bill' })->fetch, 'get Bill';
ok $Bill->orders(Order->new({ title => 'Test smart accessor', amount => '100' }))->save;
ok my $order = Order->objects->find({ title => 'Test smart accessor' })->fetch, 'one->many, yep!';
my $new_order = Order->new({ title => 'New Order vol. 1', amount => '7.77', customer_id => 1 })->save;
ok $new_order->customer($Bill)->save;
is $new_order->customer_id, $Bill->id, 'many->one, good!';
my $new_order2 = Order->new({ title => 'New Order vol. 2', amount => '7.78', customer => $Bill });
$new_order2->save;
ok my $no2 = Order->objects->find({ title => 'New Order vol. 2' })->fetch;
is $no2->customer_id, $Bill->id, 'saving with relational accessors works fine';
ok my @orders = Order->objects->find({ customer => $Bill })->fetch;
is scalar @orders, 3, 'accessors in find';
is(Order->objects->find({ customer => $Bill })->count, scalar @orders, 'accessors in count');
done_testing();
t/15-sql-row.t view on Meta::CPAN
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Test::More;
use FindBin '$Bin';
use lib "$Bin/../lib";
BEGIN {
package Schema;
use parent 'ActiveRecord::Simple';
eval { require DBD::SQLite } or exit 0;
__PACKAGE__->connect("dbi:SQLite:dbname=:memory:","","");
my $_INIT_SQL_CUSTOMERS = q{
CREATE TABLE `customer` (
`id` int AUTO_INCREMENT,
`first_name` varchar(200) NULL,
`second_name` varchar(200) NOT NULL,
`age` tinyint(2) NULL,
`email` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_CUSTOMERS = q{
INSERT INTO `customer` (`id`, `first_name`, `second_name`, `age`, `email`)
VALUES
(1,'Bob','Dylan',NULL,'bob.dylan@aol.com'),
(2,'John','Doe',77,'john@doe.com'),
(3,'Bill','Clinton',50,'mynameisbill@gmail.com'),
(4,'Bob','Marley',NULL,'bob.marley@forever.com'),
(5,'','',NULL,'foo.bar@bazz.com');
};
Schema->dbh->do($_INIT_SQL_CUSTOMERS);
Schema->dbh->do($_DATA_SQL_CUSTOMERS);
my $_INIT_SQL_ORDERS = q{
CREATE TABLE `order` (
`id` int AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`amount` decimal(10,2) NOT NULL DEFAULT 0.0,
`customer_id` int NOT NULL references `customers` (`id`),
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_ORDERS = q{
INSERT INTO `order` (`id`, `title`, `amount`, `customer_id`)
VALUES
(1, 'The Order #1', 10, 1),
(2, 'The Order #2', 5.66, 2),
(3, 'The Order #3', 6.43, 3),
(4, 'The Order #4', 2.20, 1),
(5, 'The Order #5', 3.39, 4);
};
Schema->dbh->do($_INIT_SQL_ORDERS);
Schema->dbh->do($_DATA_SQL_ORDERS);
my $_INIT_SQL_ACHIEVEMENTS = q{
CREATE TABLE `achievement` (
`id` int AUTO_INCREMENT,
`title` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
);
};
my $_DATA_SQL_ACHEIVEMENTS = q{
INSERT INTO `achievement` (`id`, `title`)
VALUES
(1, 'Bronze'),
(2, 'Silver'),
(3, 'Gold');
};
Schema->dbh->do($_INIT_SQL_ACHIEVEMENTS);
Schema->dbh->do($_DATA_SQL_ACHEIVEMENTS);
my $_INIT_SQL_CA = q{
CREATE TABLE `customer_achievement` (
`customer_id` int NOT NULL references customers (id),
`achievement_id` int NOT NULL references achievements (id)
);
};
my $_DATA_SQL_CA = q{
INSERT INTO `customer_achievement` (`customer_id`, `achievement_id`)
VALUES
(1, 1),
(1, 2),
(2, 1),
(2, 3),
(3, 1),
(3, 2),
(3, 3);
};
Schema->dbh->do($_INIT_SQL_CA);
Schema->dbh->do($_DATA_SQL_CA);
}
package Customer;
#use parent 'Schema';
our @ISA = qw/Schema/;
__PACKAGE__->auto_load();
__PACKAGE__->has_many('orders' => 'Order');
__PACKAGE__->has_many('achievements' => 'Achievement', { via => 'customer_achievement' });
package Order;
our @ISA = qw/Schema/;
__PACKAGE__->auto_load();
__PACKAGE__->belongs_to(customer => 'Customer');
package Achievement;
our @ISA = qw/Schema/;
__PACKAGE__->auto_load();
__PACKAGE__->has_many(customers => 'Customer', { via => 'customer_achievement' });
package main;
ok my $one = Customer->objects->sql_fetch_all('select 1 as one, 2 as two');
is ref $one, 'ARRAY';
is scalar @$one, 1;
my $one1 = shift @$one;
isa_ok $one1, 'Customer';
ok $one1->one;
is $one1->one, 1;
ok $one1->two;
is $one1->two, 2;
eval { $one1->foo };
ok $@;
ok my $two = Customer->objects->sql_fetch_row('select 3 as three, 4 as four');
isa_ok $two, 'Customer';
ok $two->three;
is $two->three, 3;
eval { $two->five };
ok $@;
eval { $two->three(4) };
ok $@;
like $@, qr/read-only/;
#is $two->three, 3, 'still 3';
done_testing();