Bugfixes, optimisations
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Row.pm
1 package DBIx::Class::Row;
2
3 use strict;
4 use warnings;
5
6 =head1 NAME 
7
8 DBIx::Class::Row - Basic row methods
9
10 =head1 SYNOPSIS
11
12 =head1 DESCRIPTION
13
14 This class is responsible for defining and doing basic operations on rows
15 derived from L<DBIx::Class::Table> objects.
16
17 =head1 METHODS
18
19 =over 4
20
21 =item new
22
23   my $obj = My::Class->new($attrs);
24
25 Creates a new row object from column => value mappings passed as a hash ref
26
27 =cut
28
29 sub new {
30   my ($class, $attrs) = @_;
31   $class = ref $class if ref $class;
32   my $new = bless({ _column_data => { } }, $class);
33   if ($attrs) {
34     $new->throw("attrs must be a hashref" ) unless ref($attrs) eq 'HASH';
35     while (my ($k, $v) = each %{$attrs}) {
36       die "No such column $k on $class" unless exists $class->_columns->{$k};
37       $new->store_column($k => $v);
38     }
39   }
40   return $new;
41 }
42
43 =item insert
44
45   $obj->insert;
46
47 Inserts an object into the database if it isn't already in there. Returns
48 the object itself.
49
50 =cut
51
52 sub insert {
53   my ($self) = @_;
54   return $self if $self->in_storage;
55   #use Data::Dumper; warn Dumper($self);
56   my %in;
57   $in{$_} = $self->get_column($_)
58     for grep { defined $self->get_column($_) } $self->columns;
59   my %out = %{ $self->storage->insert($self->_table_name, \%in) };
60   $self->store_column($_, $out{$_})
61     for grep { $self->get_column($_) ne $out{$_} } keys %out;
62   $self->in_storage(1);
63   $self->{_dirty_columns} = {};
64   return $self;
65 }
66
67 =item in_storage
68
69   $obj->in_storage; # Get value
70   $obj->in_storage(1); # Set value
71
72 Indicated whether the object exists as a row in the database or not
73
74 =cut
75
76 sub in_storage {
77   my ($self, $val) = @_;
78   $self->{_in_storage} = $val if @_ > 1;
79   return $self->{_in_storage};
80 }
81
82 =item create
83
84   my $new = My::Class->create($attrs);
85
86 A shortcut for My::Class->new($attrs)->insert;
87
88 =cut
89
90 sub create {
91   my ($class, $attrs) = @_;
92   $class->throw( "create needs a hashref" ) unless ref $attrs eq 'HASH';
93   return $class->new($attrs)->insert;
94 }
95
96 =item update
97
98   $obj->update;
99
100 Must be run on an object that is already in the database; issues an SQL
101 UPDATE query to commit any changes to the object to the db if required.
102
103 =cut
104
105 sub update {
106   my ($self, $upd) = @_;
107   $self->throw( "Not in database" ) unless $self->in_storage;
108   if (ref $upd eq 'HASH') {
109     $self->$_($upd->{$_}) for keys %$upd;
110   }
111   my %to_update;
112   $to_update{$_} = $self->get_column($_) for $self->is_changed;
113   return -1 unless keys %to_update;
114   my $rows = $self->storage->update($self->_table_name, \%to_update,
115                                       $self->ident_condition);
116   if ($rows == 0) {
117     $self->throw( "Can't update ${self}: row not found" );
118   } elsif ($rows > 1) {
119     $self->throw("Can't update ${self}: updated more than one row");
120   }
121   $self->{_dirty_columns} = {};
122   return $self;
123 }
124
125 sub ident_condition {
126   my ($self) = @_;
127   my %cond;
128   $cond{$_} = $self->get_column($_) for keys %{$self->_primaries};
129   return \%cond;
130 }
131
132 =item delete
133
134   $obj->delete
135
136 Deletes the object from the database. The object is still perfectly usable
137 accessor-wise etc. but ->in_storage will now return 0 and the object must
138 be re ->insert'ed before it can be ->update'ed
139
140 =cut
141
142 sub delete {
143   my $self = shift;
144   if (ref $self) {
145     $self->throw( "Not in database" ) unless $self->in_storage;
146     #warn $self->_ident_cond.' '.join(', ', $self->_ident_values);
147     $self->storage->delete($self->_table_name, $self->ident_condition);
148     $self->in_storage(undef);
149     #$self->store_column($_ => undef) for $self->primary_columns;
150       # Should probably also arrange to trash PK if auto
151       # but if we do, post-delete cascade triggers fail :/
152   } else {
153     my $attrs = { };
154     if (@_ > 1 && ref $_[$#_] eq 'HASH') {
155       $attrs = { %{ pop(@_) } };
156     }
157     my $query = (ref $_[0] eq 'HASH' ? $_[0] : {@_});
158     $self->storage->delete($self->_table_name, $query);
159   }
160   return $self;
161 }
162
163 =item get_column
164
165   my $val = $obj->get_column($col);
166
167 Fetches a column value
168
169 =cut
170
171 sub get_column {
172   my ($self, $column) = @_;
173   $self->throw( "Can't fetch data as class method" ) unless ref $self;
174   $self->throw( "No such column '${column}'" ) unless $self->_columns->{$column};
175   return $self->{_column_data}{$column}
176     if exists $self->{_column_data}{$column};
177   return undef;
178 }
179
180 =item get_columns
181
182   my %data = $obj->get_columns;
183
184 Fetch all column values at once.
185
186 =cut
187
188 sub get_columns {
189   my $self = shift;
190   return map { $_ => $self->get_column($_) } $self->columns;
191 }
192
193 =item set_column
194
195   $obj->set_column($col => $val);
196
197 Sets a column value; if the new value is different to the old the column
198 is marked as dirty for when you next call $obj->update
199
200 =cut
201
202 sub set_column {
203   my $self = shift;
204   my ($column) = @_;
205   my $old = $self->get_column($column);
206   my $ret = $self->store_column(@_);
207   $self->{_dirty_columns}{$column} = 1 unless defined $old && $old eq $ret;
208   return $ret;
209 }
210
211 =item set_columns
212
213   my $copy = $orig->set_columns({ $col => $val, ... });
214
215 Set more than one column value at once.
216
217 =cut
218
219 sub set_columns {
220   my ($self,$data) = @_;
221   while (my ($col,$val) = each %$data) {
222     $self->set_column($col,$val);
223   }
224 }
225
226 =item copy
227
228   my $copy = $orig->copy({ change => $to, ... });
229
230 Insert a new row with the specified changes.
231
232 =cut
233
234 =item store_column
235
236   $obj->store_column($col => $val);
237
238 Sets a column value without marking it as dirty
239
240 =cut
241
242 sub store_column {
243   my ($self, $column, $value) = @_;
244   $self->throw( "No such column '${column}'" ) 
245     unless $self->_columns->{$column};
246   $self->throw( "set_column called for ${column} without value" ) 
247     if @_ < 3;
248   return $self->{_column_data}{$column} = $value;
249 }
250
251 sub _row_to_object {
252   my ($class, $cols, $row) = @_;
253   my %vals;
254   $vals{$cols->[$_]} = $row->[$_] for 0 .. $#$cols;
255   my $new = bless({ _column_data => \%vals }, ref $class || $class);
256   $new->in_storage(1);
257   return $new;
258 }
259
260 sub copy {
261   my ($self, $changes) = @_;
262   my $new = bless({ _column_data => { %{$self->{_column_data}}} }, ref $self);
263   $new->set_column($_ => $changes->{$_}) for keys %$changes;
264   return $new->insert;
265 }
266
267 =item insert_or_update
268
269   $obj->insert_or_update
270
271 Updates the object if it's already in the db, else inserts it
272
273 =cut
274
275 sub insert_or_update {
276   my $self = shift;
277   return ($self->in_storage ? $self->update : $self->insert);
278 }
279
280 =item is_changed
281
282   my @changed_col_names = $obj->is_changed
283
284 =cut
285
286 sub is_changed {
287   return keys %{shift->{_dirty_columns} || {}};
288 }
289
290 1;
291
292 =back
293
294 =head1 AUTHORS
295
296 Matt S. Trout <mst@shadowcatsystems.co.uk>
297
298 =head1 LICENSE
299
300 You may distribute this code under the same terms as Perl itself.
301
302 =cut
303