File Coverage

lib/DBIx/ActiveRecord/Model.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package DBIx::ActiveRecord::Model;
2 1     1   4490 use strict;
  1         1  
  1         32  
3 1     1   6 use warnings;
  1         2  
  1         23  
4 1     1   5 use Carp;
  1         1  
  1         67  
5              
6 1     1   4 use POSIX;
  1         2  
  1         6  
7              
8 1     1   3303 use DBIx::ActiveRecord::Arel;
  1         2  
  1         43  
9 1     1   57 use DBIx::ActiveRecord;
  0            
  0            
10             use DBIx::ActiveRecord::Relation;
11             use DBIx::ActiveRecord::Scope;
12              
13             use constant INSERT_RECORD_TIMESTAMPS => [qw/created_at updated_at/];
14             use constant UPDATE_RECORD_TIMESTAMPS => [qw/updated_at/];
15             use constant MAIN_TABLE_ALIAS => 'me';
16              
17             sub dbh {DBIx::ActiveRecord->dbh}
18              
19             sub _global {
20             my $self = shift;
21             my $p = ref $self || $self;
22             $DBIx::ActiveRecord::GLOBAL{$p} ||= {};
23             }
24              
25             sub table {
26             my ($self, $table_name) = @_;
27             return $self->_global->{table} if !$table_name;
28             $self->_global->{table} = $table_name;
29             $self->_global->{arel} = DBIx::ActiveRecord::Arel->create($table_name);
30             }
31              
32             sub columns {
33             my $self = shift;
34             push @{$self->_global->{columns}}, @_;
35             }
36              
37             sub primary_keys {
38             my $self = shift;
39             push @{$self->_global->{primary_keys}}, @_;
40             }
41              
42             sub belongs_to {
43             my ($self, $name, $package, $opt) = @_;
44              
45             $self->_global->{belongs_to} ||= [];
46             push @{$self->_global->{belongs_to}}, [$name, $package, $opt];
47             }
48              
49             sub has_one {
50             my ($self, $name, $package, $opt) = @_;
51             $self->_add_has_relation($name, $package, $opt, 1);
52             }
53              
54             sub has_many {
55             my ($self, $name, $package, $opt) = @_;
56             $self->_add_has_relation($name, $package, $opt, 0);
57             }
58              
59             sub _add_has_relation {
60             my ($self, $name, $package, $opt, $has_one) = @_;
61              
62             $self->_global->{has_relation} ||= [];
63             push @{$self->_global->{has_relation}}, [$name, $package, $opt, $has_one];
64             }
65              
66             sub default_scope {
67             my ($self, $coderef) = @_;
68             $self->_global->{default_scope} = $coderef;
69             }
70              
71             sub scope {
72             my ($self, $name, $coderef) = @_;
73             $self->_global->{scopes}->{$name} = $coderef;
74             }
75              
76             our $AUTOLOAD;
77             sub AUTOLOAD {
78             my $self = shift;
79             $AUTOLOAD =~ /([^:]+)$/;
80             my $m = $1;
81             my $s = $self->_global->{scopes}->{$m};
82             croak "method missing $AUTOLOAD" if !$s;
83             $s->($self->scoped, @_);
84             }
85             sub DESTROY{}
86              
87             sub arel {shift->_global->{arel}->clone->as(MAIN_TABLE_ALIAS)}
88              
89             sub transaction {
90             my $self = shift;
91             DBIx::ActiveRecord->transaction(@_);
92             }
93              
94             sub all {DBIx::ActiveRecord::Scope::all(@_)}
95             sub first {DBIx::ActiveRecord::Scope::first(@_)}
96             sub last {DBIx::ActiveRecord::Scope::last(@_)}
97              
98             sub scoped {
99             my ($self) = @_;
100             my $r = DBIx::ActiveRecord::Relation->new($self);
101             my $ds = $self->_global->{default_scope};
102             $r = $ds->($r) if $ds;
103             $r;
104             }
105              
106             sub unscoped {
107             my ($self) = @_;
108             DBIx::ActiveRecord::Relation->new($self);
109             }
110              
111             sub new {
112             my ($self, $hash) = @_;
113             bless {-org => {}, -set => $hash || {}, in_storage => 0}, $self;
114             }
115              
116             sub _new_from_storage {
117             my ($self, $hash) = @_;
118             bless {-org => $hash, -set => {}, in_storage => 1}, $self;
119             }
120              
121             sub get_column {
122             my ($self, $name) = @_;
123             exists $self->{-set}->{$name} ? $self->{-set}->{$name} : $self->{-org}->{$name};
124             }
125              
126             sub set_column {
127             my ($self, $name, $value) = @_;
128             $self->{-set}->{$name} = $value;
129             }
130              
131             sub to_hash {
132             my $self = shift;
133             my %h;
134             foreach (keys %{$self->{-org}}, keys %{$self->{-set}}) {
135             $h{$_} = $self->get_column($_);
136             }
137             \%h;
138             }
139              
140             sub in_storage { shift->{in_storage} }
141              
142             sub create {
143             my ($self, $hash) = @_;
144             my $o = $self->new($hash);
145             $o->save;
146             $o;
147             }
148              
149             sub save {
150             my $self = shift;
151             my $res = $self->in_storage ? $self->update(@_) : $self->insert(@_);
152             $self->{in_storage} = 1;
153             %{$self->{-org}} = (%{$self->{-org}}, %{$self->{-set}});
154             $self->{-set} = {};
155             $res;
156             }
157              
158             sub insert {
159             my ($self) = @_;
160             return if $self->in_storage;
161              
162             my $s = $self->scoped;
163             $self->_record_timestamp(INSERT_RECORD_TIMESTAMPS);
164             my $arel = $s->{arel}->insert($self->to_hash, $self->_global->{columns});
165             my $sth = $self->dbh->prepare($arel->to_sql);
166             my $res = $sth->execute($arel->binds) || croak $sth->errstr;
167              
168             my $insert_id = $sth->{'insertid'} || $self->dbh->{'mysql_insertid'};
169             $self->{-set}->{$self->_global->{primary_keys}->[0]} = $insert_id if $insert_id;
170             $res;
171             }
172              
173             sub update {
174             my ($self) = @_;
175             return if !%{$self->{-set}};
176             return if !$self->in_storage;
177              
178             my $s = $self->_pkey_scope;
179             $self->_record_timestamp(UPDATE_RECORD_TIMESTAMPS);
180             my $arel = $s->{arel}->update($self->{-set}, $self->_global->{columns});
181             my $sth = $self->dbh->prepare($arel->to_sql);
182             $sth->execute($arel->binds) || croak $sth->errstr;
183             }
184              
185             sub delete {
186             my ($self) = @_;
187             return if !$self->in_storage;
188              
189             my $s = $self->_pkey_scope;
190             my $arel = $s->{arel}->delete;
191             my $sth = $self->dbh->prepare($arel->to_sql);
192             $sth->execute($arel->binds) || croak $sth->errstr;
193             }
194              
195             sub count { shift->scoped->count }
196              
197             sub _record_timestamp {
198             my ($self, $columns) = @_;
199             my %cs = map {$_ => 1} @{$self->_global->{columns}};
200             my $now = POSIX::strftime("%Y-%m-%d %H:%M:%S", localtime);
201             foreach (@$columns) {
202             $self->{-set}->{$_} = $now if $cs{$_};
203             }
204             }
205              
206             sub _pkey_scope {
207             my $self = shift;
208             my $s = $self->unscoped;
209             $s = $s->eq($_ => $self->{-org}->{$_} || croak 'no primary key') for @{$self->_global->{primary_keys}};
210             $s;
211             }
212              
213             sub instantiates_by_relation {
214             my ($self, $relation) = @_;
215             my $sth = $self->dbh->prepare($relation->to_sql);
216             $sth->execute($relation->_binds) || croak $sth->errstr;
217             my @all;
218             while (my $row = $sth->fetchrow_hashref) {
219             push @all, $self->_new_from_storage($row);
220             }
221             \@all;
222             }
223              
224             1;