Jifty::DBI::HasFilters
/
;
our
$VERSION
=
'0.01'
;
Jifty::DBI::Record->mk_classdata(
'COLUMNS'
);
Jifty::DBI::Record->mk_classdata(
'TABLE_NAME'
);
Jifty::DBI::Record->mk_classdata(
'_READABLE_COLS_CACHE'
);
Jifty::DBI::Record->mk_classdata(
'_WRITABLE_COLS_CACHE'
);
Jifty::DBI::Record->mk_classdata(
'_COLUMNS_CACHE'
);
Jifty::DBI::Record->mk_classdata(
'RECORD_MIXINS'
=> []);
sub
new {
my
$proto
=
shift
;
my
$class
=
ref
(
$proto
) ||
$proto
;
my
$self
= {};
bless
(
$self
,
$class
);
$self
->_init_columns()
unless
$self
->COLUMNS;
$self
->input_filters(
'Jifty::DBI::Filter::Truncate'
);
if
(
scalar
(
@_
) == 1 ) {
Carp::cluck(
"new(\$handle) is deprecated, use new( handle => \$handle )"
);
$self
->_init(
handle
=>
shift
);
}
else
{
$self
->_init(
@_
);
}
return
$self
;
}
sub
_init {
my
$self
=
shift
;
my
%args
= (
@_
);
if
(
$args
{
'handle'
} ) {
$self
->_handle(
$args
{
'handle'
} );
}
}
sub
import
{
my
$class
=
shift
;
my
(
$flag
) =
@_
;
if
(
$class
->isa(__PACKAGE__) and
defined
$flag
and
$flag
eq
'-base'
) {
my
$descendant
= (
caller
)[0];
no
strict
'refs'
;
push
@{
$descendant
.
'::ISA'
},
$class
;
shift
;
my
$callback
=
shift
;
$callback
->()
if
$callback
;
}
$class
->SUPER::
import
(
@_
);
@_
= (
warnings
=>
'redefine'
);
goto
&warnings::unimport
;
}
sub
id {
my
$pkey
=
$_
[0]->_primary_key();
my
$ret
=
$_
[0]->{
'values'
}->{
$pkey
};
return
$ret
;
}
sub
primary_keys {
my
$self
=
shift
;
my
%hash
=
map
{
$_
=>
$self
->{
'values'
}->{
$_
} } @{
$self
->_primary_keys };
return
(
%hash
);
}
sub
_accessible {
my
$self
=
shift
;
my
$column_name
=
shift
;
my
$attribute
=
lc
(
shift
||
''
);
my
$col
=
$self
->column(
$column_name
);
return
undef
unless
(
$col
and
$col
->can(
$attribute
) );
return
$col
->
$attribute
();
}
sub
_primary_keys {
my
$self
=
shift
;
return
[
'id'
];
}
sub
_primary_key {
my
$self
=
shift
;
my
$pkeys
=
$self
->_primary_keys();
die
"No primary key"
unless
(
ref
(
$pkeys
) eq
'ARRAY'
and
$pkeys
->[0] );
die
"Too many primary keys"
unless
(
scalar
(
@$pkeys
) == 1 );
return
$pkeys
->[0];
}
sub
_init_columns {
my
$self
=
shift
;
return
if
defined
$self
->COLUMNS;
$self
->COLUMNS( {} );
foreach
my
$column_name
( @{
$self
->_primary_keys } ) {
my
$column
=
$self
->add_column(
$column_name
);
$column
->writable(0);
$column
->readable(1);
$column
->type(
'serial'
);
$column
->mandatory(1);
$self
->_init_methods_for_column(
$column
);
}
}
sub
_init_methods_for_columns {
my
$self
=
shift
;
for
my
$column
(
sort
keys
%{
$self
->COLUMNS || {} } ) {
$self
->_init_methods_for_column(
$self
->COLUMNS->{
$column
} );
}
}
sub
_init_methods_for_column {
my
$self
=
$_
[0];
my
$column
=
$_
[1];
my
$column_name
= (
$column
->aliased_as ?
$column
->aliased_as :
$column
->name );
my
$package
=
ref
(
$self
) ||
$self
;
$column
->record_class(
$package
)
if
not
$column
->record_class;
if
(
grep
{
$_
eq
'Jifty::DBI::Filter::Storable'
}
(
$column
->input_filters,
$column
->output_filters )
and not
grep
{
$_
eq
'Jifty::DBI::Filter::base64'
}
(
$column
->input_filters,
$column
->output_filters )
and
$column
->type !~ /^(blob|bytea)$/i )
{
die
"Column '$column_name' in @{[$column->record_class]} "
.
"uses the Storable filter but is not of type 'blob'.\n"
;
}
no
strict
'refs'
;
if
( not
$self
->can(
$column_name
) ) {
my
$subref
;
if
(
$column
->active ) {
if
(
$column
->readable ) {
if
(UNIVERSAL::isa(
$column
->refers_to,
"Jifty::DBI::Record"
)
)
{
$subref
=
sub
{
if
(
@_
> 1 ) {
Carp::carp
"Value passed to column accessor. You probably want to use the mutator."
;
}
$_
[0]->_to_record(
$column_name
,
$_
[0]->__value(
$column_name
) );
};
}
elsif
(
UNIVERSAL::isa(
$column
->refers_to,
"Jifty::DBI::Collection"
)
)
{
$subref
=
sub
{
$_
[0]->_collection_value(
$column_name
) };
}
else
{
$subref
=
sub
{
if
(
@_
> 1 ) {
Carp::carp
"Value passed to column accessor. You probably want to use the mutator."
;
}
return
(
$_
[0]->_value(
$column_name
) );
};
}
}
else
{
$subref
=
sub
{
return
''
}
}
}
else
{
$subref
=
sub
{
Carp::croak(
"column $column_name is not available for $package for schema version "
.
$self
->schema_version );
};
}
*{
$package
.
"::"
.
$column_name
} =
$subref
;
}
if
( not
$self
->can(
"set_"
.
$column_name
) ) {
my
$subref
;
if
(
$column
->active ) {
if
(
$column
->writable ) {
if
(UNIVERSAL::isa(
$column
->refers_to,
"Jifty::DBI::Record"
)
)
{
$subref
=
sub
{
my
$self
=
shift
;
my
$val
=
shift
;
$val
=
$val
->id
if
UNIVERSAL::isa(
$val
,
'Jifty::DBI::Record'
);
return
(
$self
->_set(
column
=>
$column_name
,
value
=>
$val
)
);
};
}
elsif
(
UNIVERSAL::isa(
$column
->refers_to,
"Jifty::DBI::Collection"
)
)
{
my
$ret
= Class::ReturnValue->new();
my
$message
=
"Collection column '$column_name' not writable"
;
$ret
->as_array( 0,
$message
);
$ret
->as_error(
errno
=> 3,
do_backtrace
=> 0,
message
=>
$message
);
$subref
=
sub
{
return
(
$ret
->return_value ); };
}
else
{
$subref
=
sub
{
return
(
$_
[0]->_set(
column
=>
$column_name
,
value
=>
$_
[1]
)
);
};
}
}
else
{
my
$ret
= Class::ReturnValue->new();
my
$message
=
'Immutable column'
;
$ret
->as_array( 0,
$message
);
$ret
->as_error(
errno
=> 3,
do_backtrace
=> 0,
message
=>
$message
);
$subref
=
sub
{
return
(
$ret
->return_value ); };
}
}
else
{
$subref
=
sub
{
Carp::croak(
"column $column_name is not available for $package for schema version "
.
$self
->schema_version );
};
}
*{
$package
.
"::"
.
"set_"
.
$column_name
} =
$subref
;
}
}
sub
null_reference {
return
1;
}
sub
_to_record {
my
$self
=
shift
;
my
$column_name
=
shift
;
my
$value
=
shift
;
my
$column
=
$self
->column(
$column_name
);
my
$classname
=
$column
->refers_to();
my
$remote_column
=
$column
->by() ||
'id'
;
return
undef
if
not
defined
$value
and
$self
->null_reference;
return
undef
unless
$classname
;
return
unless
UNIVERSAL::isa(
$classname
,
'Jifty::DBI::Record'
);
if
(
my
$prefetched
=
$self
->prefetched(
$column_name
) ) {
return
$prefetched
;
}
my
$object
=
$classname
->new(
$self
->_new_record_args );
$object
->load_by_cols(
$remote_column
=>
$value
)
if
defined
$value
;
return
$object
;
}
sub
_new_record_args {
my
$self
=
shift
;
return
(
handle
=>
$self
->_handle );
}
sub
_collection_value {
my
$self
=
shift
;
my
$column_name
=
shift
;
my
$column
=
$self
->column(
$column_name
);
my
$classname
=
$column
->refers_to();
return
undef
unless
$classname
;
return
unless
UNIVERSAL::isa(
$classname
,
'Jifty::DBI::Collection'
);
if
(
my
$prefetched
=
$self
->prefetched(
$column_name
) ) {
return
$prefetched
;
}
my
$coll
=
$classname
->new(
$self
->_new_collection_args );
$coll
->limit(
column
=>
$column
->by,
value
=>
$self
->id )
if
$column
->by and
$self
->id;
return
$coll
;
}
sub
_new_collection_args {
my
$self
=
shift
;
return
(
handle
=>
$self
->_handle );
}
sub
prefetched {
my
$self
=
shift
;
my
$column_name
=
shift
;
if
(
@_
) {
my
$column
=
$self
->column(
$column_name
);
if
(
$column
and not
$column
->refers_to ) {
warn
"$column_name isn't supposed to be an object reference!"
;
return
;
}
elsif
(
$column
and not UNIVERSAL::isa(
$_
[0],
$column
->refers_to ) )
{
warn
"$column_name is supposed to be a @{[$column->refers_to]}!"
;
}
else
{
$self
->{
'_prefetched'
}->{
$column_name
} =
shift
;
}
}
else
{
return
$self
->{
'_prefetched'
}->{
$column_name
};
}
}
sub
add_column {
my
$self
=
shift
;
my
$name
=
shift
;
$self
->COLUMNS->{
$name
} = Jifty::DBI::Column->new()
unless
exists
$self
->COLUMNS->{
$name
};
$self
->_READABLE_COLS_CACHE(
undef
);
$self
->_WRITABLE_COLS_CACHE(
undef
);
$self
->_COLUMNS_CACHE(
undef
);
$self
->COLUMNS->{
$name
}->name(
$name
);
my
$class
=
ref
(
$self
) ||
$self
;
$self
->COLUMNS->{
$name
}->record_class(
$class
);
return
$self
->COLUMNS->{
$name
};
}
sub
column {
my
$self
=
shift
;
my
$name
= (
shift
||
''
);
my
$col
=
$self
->_columns_hashref;
return
undef
unless
$col
&&
exists
$col
->{
$name
};
return
$col
->{
$name
};
}
sub
columns {
my
$self
=
shift
;
return
@{
$self
->_COLUMNS_CACHE() ||
$self
->_COLUMNS_CACHE(
[
sort
{
( ( (
$b
->type ||
''
) eq
'serial'
)
<=> ( (
$a
->type ||
''
) eq
'serial'
) )
or (
(
$a
->sort_order || 0 ) <=> (
$b
->sort_order || 0 ) )
or (
$a
->name cmp
$b
->name )
}
grep
{
$_
->active
}
values
%{
$self
->_columns_hashref }
]
)
};
}
sub
all_columns {
my
$self
=
shift
;
return
sort
{
( ( (
$b
->type ||
''
) eq
'serial'
)
<=> ( (
$a
->type ||
''
) eq
'serial'
) )
or ( (
$a
->sort_order || 0 ) <=> (
$b
->sort_order || 0 ) )
or (
$a
->name cmp
$b
->name )
}
values
%{
$self
->_columns_hashref || {} };
}
sub
_columns_hashref {
my
$self
=
shift
;
return
(
$self
->COLUMNS || {} );
}
sub
readable_attributes {
my
$self
=
shift
;
return
@{
$self
->_READABLE_COLS_CACHE() ||
$self
->_READABLE_COLS_CACHE(
[
sort
map
{
$_
->name }
grep
{
$_
->readable }
$self
->columns ]
)
};
}
sub
serialize_metadata {
my
$self
=
shift
;
return
{
class
=> (
ref
(
$self
) ||
$self
),
table
=>
$self
->table,
columns
=> {
$self
->_serialize_columns },
};
}
sub
_serialize_columns {
my
$self
=
shift
;
my
%serialized_columns
;
foreach
my
$column
(
$self
->columns ) {
$serialized_columns
{
$column
->name } =
$column
->serialize_metadata();
}
return
%serialized_columns
;
}
sub
writable_attributes {
my
$self
=
shift
;
return
@{
$self
->_WRITABLE_COLS_CACHE() ||
$self
->_WRITABLE_COLS_CACHE(
[
sort
map
{
$_
->name }
grep
{
$_
->writable }
$self
->columns ]
)
};
}
sub
_value {
my
$self
=
shift
;
my
$column
=
shift
;
my
$value
=
$self
->__value(
$column
=>
@_
);
$self
->_run_callback(
name
=>
"after_"
.
$column
,
args
=> \
$value
);
return
$value
;
}
sub
__raw_value {
my
$self
=
shift
;
my
$column_name
=
shift
;
return
$self
->{
'raw_values'
}{
$column_name
}
if
$self
->{
'fetched'
}{
$column_name
};
if
( !
$self
->{
'fetched'
}{
$column_name
} and
my
$id
=
$self
->id() ) {
my
$pkey
=
$self
->_primary_key();
my
$query_string
=
"SELECT "
.
$column_name
.
" FROM "
.
$self
->table
.
" WHERE $pkey = ?"
;
my
$sth
=
$self
->_handle->simple_query(
$query_string
,
$id
);
my
(
$value
) =
eval
{
$sth
->fetchrow_array() };
$self
->{
'raw_values'
}{
$column_name
} =
$value
;
$self
->{
'fetched'
}{
$column_name
} = 1;
}
return
$self
->{
'raw_values'
}{
$column_name
};
}
sub
resolve_column {
my
$self
=
shift
;
my
$column_name
=
shift
;
return
unless
$column_name
;
return
$self
->COLUMNS->{
$column_name
};
}
sub
__value {
my
$self
=
shift
;
my
$column
=
$self
->COLUMNS->{ +
shift
};
return
unless
$column
;
my
$column_name
=
$column
->{name};
return
$self
->{
'values'
}{
$column_name
}
if
(
$self
->{
'fetched'
}{
$column_name
}
&&
$self
->{
'decoded'
}{
$column_name
} );
unless
(
$self
->{
'fetched'
}{
$column_name
}) {
$self
->{
'values'
}{
$column_name
} =
$self
->__raw_value(
$column_name
);
$self
->{
'decoded'
}{
$column_name
} = 0;
}
unless
(
$self
->{
'decoded'
}{
$column_name
} ) {
$self
->_apply_output_filters(
column
=>
$column
,
value_ref
=> \
$self
->{
'values'
}{
$column_name
},
)
if
exists
$self
->{
'values'
}{
$column_name
};
$self
->{
'decoded'
}{
$column_name
} = 1;
}
return
$self
->{
'values'
}{
$column_name
};
}
sub
as_hash {
my
$self
=
shift
;
my
%values
;
$values
{
$_
} =
$self
->
$_
()
for
$self
->readable_attributes;
return
%values
;
}
sub
_set {
my
$self
=
shift
;
my
%args
= (
'column'
=>
undef
,
'value'
=>
undef
,
'is_sql_function'
=>
undef
,
@_
);
my
$ok
=
$self
->_run_callback(
name
=>
"before_set"
,
args
=> \
%args
,
);
return
$ok
if
( not
defined
$ok
);
$ok
=
$self
->_run_callback(
name
=>
"before_set_"
.
$args
{column},
args
=> \
%args
,
);
return
$ok
if
( not
defined
$ok
);
$ok
=
$self
->__set(
%args
);
return
$ok
if
not
$ok
;
my
$value
=
$self
->_value(
$args
{column} );
$self
->_run_callback(
name
=>
"after_set"
,
args
=> {
column
=>
$args
{column},
value
=>
$value
},
);
$self
->_run_callback(
name
=>
"after_set_"
.
$args
{column},
args
=> {
column
=>
$args
{column},
value
=>
$value
},
);
return
$ok
;
}
sub
__set {
my
$self
=
shift
;
my
%args
= (
'column'
=>
undef
,
'value'
=>
undef
,
'is_sql_function'
=>
undef
,
@_
);
my
$ret
= Class::ReturnValue->new();
my
$column
=
$self
->column(
$args
{
'column'
} );
unless
(
$column
) {
$ret
->as_array( 0,
'No column specified'
);
$ret
->as_error(
errno
=> 5,
do_backtrace
=> 0,
message
=>
"No column specified"
);
return
(
$ret
->return_value );
}
$self
->_apply_input_filters(
column
=>
$column
,
value_ref
=> \
$args
{
'value'
}
);
if
(
$self
->{
'fetched'
}{
$column
->name }
|| !
$self
->{
'decoded'
}{
$column
->name } )
{
if
(( !
defined
$args
{
'value'
}
&& !
defined
$self
->{
'values'
}{
$column
->name }
)
|| (
defined
$args
{
'value'
}
&&
defined
$self
->{
'values'
}{
$column
->name }
&&
$args
{value}
.
""
eq
""
.
$self
->{
'values'
}{
$column
->name }
)
)
{
$ret
->as_array( 1,
"That is already the current value"
);
return
(
$ret
->return_value );
}
}
if
(
my
$sub
=
$column
->validator ) {
my
(
$ok
,
$msg
) =
$sub
->(
$self
,
$args
{
'value'
} );
unless
(
$ok
) {
$ret
->as_array( 0,
'Illegal value for '
.
$column
->name );
$ret
->as_error(
errno
=> 3,
do_backtrace
=> 0,
message
=>
"Illegal value for "
.
$column
->name
);
return
(
$ret
->return_value );
}
}
if
(
$column
->distinct ) {
my
$ret
=
$self
->is_distinct(
$column
->name,
$args
{
'value'
} );
return
(
$ret
)
if
not(
$ret
);
}
my
$unmunged_value
=
$args
{
'value'
};
if
(
$column
->type =~ /^(text|longtext|clob|blob|lob|bytea)$/i ) {
my
$bhash
=
$self
->_handle->blob_params(
$column
->name,
$column
->type );
$bhash
->{
'value'
} =
$args
{
'value'
};
$args
{
'value'
} =
$bhash
;
}
my
$val
=
$self
->_handle->update_record_value(
%args
,
table
=>
$self
->table(),
primary_keys
=> {
$self
->primary_keys() }
);
unless
(
$val
) {
my
$message
=
$column
->name .
" could not be set to "
.
$args
{
'value'
} .
"."
;
$ret
->as_array( 0,
$message
);
$ret
->as_error(
errno
=> 4,
do_backtrace
=> 0,
message
=>
$message
);
return
(
$ret
->return_value );
}
if
(
$args
{
'is_sql_function'
} ) {
$self
->load_by_cols(
id
=>
$self
->id );
}
else
{
$self
->{
'raw_values'
}{
$column
->name } =
$unmunged_value
;
$self
->{
'values'
}{
$column
->name } =
$unmunged_value
;
$self
->{
'decoded'
}{
$column
->name } = 0;
}
$ret
->as_array( 1,
"The new value has been set."
);
return
(
$ret
->return_value );
}
sub
load {
my
$self
=
shift
;
return
unless
@_
and
defined
$_
[0];
return
$self
->load_by_cols(
id
=>
shift
);
}
sub
load_by_cols {
my
$class
=
shift
;
my
%hash
= (
@_
);
my
(
$self
);
if
(
ref
(
$class
) ) {
(
$self
,
$class
) = (
$class
,
undef
);
}
else
{
$self
=
$class
->new(
handle
=> (
delete
$hash
{
'_handle'
} ||
undef
) );
}
my
(
@bind
,
@phrases
);
foreach
my
$key
(
keys
%hash
) {
if
(
defined
$hash
{
$key
} &&
$hash
{
$key
} ne
''
) {
my
$op
;
my
$value
;
my
$function
=
"?"
;
my
$column_obj
=
$self
->column(
$key
);
Carp::confess(
"Unknown column '$key' in class '"
.
ref
(
$self
) .
"'"
)
if
!
defined
$column_obj
;
my
$case_sensitive
=
$column_obj
->case_sensitive;
if
(
ref
$hash
{
$key
} eq
'HASH'
) {
$op
=
$hash
{
$key
}->{operator};
$value
=
$hash
{
$key
}->{value};
$function
=
$hash
{
$key
}->{function} ||
"?"
;
$case_sensitive
=
$hash
{
$key
}->{case_sensitive}
if
exists
$hash
{
$key
}->{case_sensitive};
}
else
{
$op
=
'='
;
$value
=
$hash
{
$key
};
}
if
( blessed
$value
&&
$value
->isa(
'Jifty::DBI::Record'
) ) {
$value
=
$value
->id;
}
$self
->_apply_input_filters(
column
=>
$column_obj
,
value_ref
=> \
$value
,
)
if
$column_obj
->encode_on_select;
if
(
$self
->_handle->case_sensitive &&
$value
) {
if
(
$column_obj
->is_string && !
$case_sensitive
) {
(
$key
,
$op
,
$function
)
=
$self
->_handle->_make_clause_case_insensitive(
$key
,
$op
,
$function
);
}
}
push
@phrases
,
"$key $op $function"
;
push
@bind
,
$value
;
}
elsif
( !
defined
$hash
{
$key
} ) {
push
@phrases
,
"$key IS NULL"
;
}
else
{
push
@phrases
,
"($key IS NULL OR $key = ?)"
;
my
$column
=
$self
->column(
$key
);
if
(
$column
->is_numeric ) {
push
@bind
, 0;
}
else
{
push
@bind
,
''
;
}
}
}
my
$query_string
=
"SELECT * FROM "
.
$self
->table
.
" WHERE "
.
join
(
' AND '
,
@phrases
);
if
(
$class
) {
$self
->_load_from_sql(
$query_string
,
@bind
);
return
$self
;
}
else
{
return
$self
->_load_from_sql(
$query_string
,
@bind
);
}
}
sub
load_by_primary_keys {
my
$self
=
shift
;
my
$data
= (
ref
$_
[0] eq
'HASH'
) ?
$_
[0] : {
@_
};
my
%cols
= ();
foreach
( @{
$self
->_primary_keys } ) {
return
( 0,
"Missing PK column: '$_'"
)
unless
defined
$data
->{
$_
};
$cols
{
$_
} =
$data
->{
$_
};
}
return
(
$self
->load_by_cols(
%cols
) );
}
sub
load_from_hash {
my
$self
=
shift
;
my
$hashref
=
shift
;
my
%args
=
@_
;
if
(
$args
{fast}) {
$self
->{
values
} =
$hashref
;
$self
->{fetched}{
$_
} = 1
for
keys
%{
$hashref
};
$self
->{raw_values} = {};
$self
->{decoded} = {};
return
$self
->{
values
}{id};
}
unless
(
ref
$self
) {
$self
=
$self
->new(
handle
=>
delete
$hashref
->{
'_handle'
} );
}
$self
->{
'values'
} = {};
$self
->{
'raw_values'
} = {};
$self
->{
'fetched'
} = {};
foreach
my
$col
(
grep
exists
$hashref
->{
lc
$_
},
map
$_
->name,
$self
->columns ) {
$self
->{
'fetched'
}{
$col
} = 1;
$self
->{
'values'
}{
$col
} =
$hashref
->{
lc
$col
};
}
$self
->{
'decoded'
} = {};
return
$self
->id();
}
sub
_load_from_sql {
my
$self
=
shift
;
my
$query_string
=
shift
;
my
@bind_values
= (
@_
);
my
$sth
=
$self
->_handle->simple_query(
$query_string
,
@bind_values
);
return
( 0,
"Couldn't execute query"
)
unless
$sth
;
my
$hashref
=
$sth
->fetchrow_hashref;
delete
$self
->{
'values'
};
delete
$self
->{
'raw_values'
};
$self
->{
'fetched'
} = {};
$self
->{
'decoded'
} = {};
foreach
my
$col
(
map
{
$_
->name }
$self
->columns ) {
next
unless
exists
$hashref
->{
lc
(
$col
) };
$self
->{
'fetched'
}{
$col
} = 1;
$self
->{
'values'
}->{
$col
} =
$hashref
->{
lc
(
$col
) };
$self
->{
'raw_values'
}->{
$col
} =
$hashref
->{
lc
(
$col
) };
}
if
( !
$self
->{
'values'
} &&
$sth
->err ) {
return
( 0,
"Couldn't fetch row: "
.
$sth
->err );
}
unless
(
$self
->{
'values'
} ) {
return
( 0,
"Couldn't find row"
);
}
if
(
grep
{ not
defined
}
$self
->primary_keys ) {
return
( 0,
"Missing a primary key?"
);
}
return
( 1,
"Found object"
);
}
sub
create {
my
$class
=
shift
;
my
%attribs
=
@_
;
my
(
$self
);
if
(
ref
(
$class
) ) {
(
$self
,
$class
) = (
$class
,
undef
);
}
else
{
$self
=
$class
->new(
handle
=> (
delete
$attribs
{
'_handle'
} ||
undef
) );
}
my
$ok
=
$self
->_run_callback(
name
=>
"before_create"
,
args
=> \
%attribs
);
return
$ok
if
( not
defined
$ok
);
my
$ret
=
$self
->__create(
%attribs
);
$ok
=
$self
->_run_callback(
name
=>
"after_create"
,
args
=> \
$ret
);
return
$ok
if
( not
defined
$ok
);
if
(
$class
) {
$self
->load_by_cols(
id
=>
$ret
);
return
(
$self
);
}
else
{
return
(
$ret
);
}
}
sub
__create {
my
(
$self
,
%attribs
) =
@_
;
foreach
my
$column_name
(
keys
%attribs
) {
my
$column
=
$self
->column(
$column_name
);
unless
(
$column
) {
next
if
$column_name
=~ /^__/;
Carp::confess
"$column_name isn't a column we know about"
;
}
if
(
$column
->readable
and
$column
->refers_to
and UNIVERSAL::isa(
$column
->refers_to,
"Jifty::DBI::Record"
) )
{
$attribs
{
$column_name
} =
$attribs
{
$column_name
}->id
if
UNIVERSAL::isa(
$attribs
{
$column_name
},
'Jifty::DBI::Record'
);
}
$self
->_apply_input_filters(
column
=>
$column
,
value_ref
=> \
$attribs
{
$column_name
},
);
if
(
$column
->distinct ) {
my
$ret
=
$self
->is_distinct(
$column_name
,
$attribs
{
$column_name
} );
if
( not
$ret
) {
Carp::cluck(
"$self failed a 'is_distinct' check for $column_name on "
.
$attribs
{
$column_name
} );
return
(
$ret
);
}
}
if
(
$column
->type =~ /^(text|longtext|clob|blob|lob|bytea)$/i ) {
my
$bhash
=
$self
->_handle->blob_params(
$column_name
,
$column
->type );
$bhash
->{
'value'
} =
$attribs
{
$column_name
};
$attribs
{
$column_name
} =
$bhash
;
}
}
for
my
$column
(
$self
->columns ) {
if
( not
defined
$attribs
{
$column
->name }
and
defined
$column
->
default
and not
ref
$column
->
default
)
{
$attribs
{
$column
->name } =
$column
->
default
;
$self
->_apply_input_filters(
column
=>
$column
,
value_ref
=> \
$attribs
{
$column
->name },
);
}
if
( not
defined
$attribs
{
$column
->name }
and
$column
->mandatory
and
$column
->type ne
"serial"
)
{
Carp::carp
"Did not supply value for mandatory column "
.
$column
->name;
unless
(
$column
->active ) {
Carp::carp
"The mandatory column "
.
$column
->name
.
" is no longer active. This is likely to cause problems!"
;
}
return
(0);
}
}
return
$self
->_handle->insert(
$self
->table,
%attribs
);
}
sub
delete
{
my
$self
=
shift
;
my
$before_ret
=
$self
->_run_callback(
name
=>
'before_delete'
);
return
$before_ret
unless
(
defined
$before_ret
);
my
$ret
=
$self
->__delete;
my
$after_ret
=
$self
->_run_callback(
name
=>
'after_delete'
,
args
=> \
$ret
);
return
$after_ret
unless
(
defined
$after_ret
);
return
(
$ret
);
}
sub
__delete {
my
$self
=
shift
;
my
%pkeys
=
$self
->primary_keys();
my
$return
=
$self
->_handle->
delete
(
$self
->table,
$self
->primary_keys );
if
( UNIVERSAL::isa(
'Class::ReturnValue'
,
$return
) ) {
return
(
$return
);
}
else
{
return
(1);
}
}
sub
table {
my
$self
=
shift
;
$self
->TABLE_NAME(
$self
->_guess_table_name )
unless
(
$self
->TABLE_NAME() );
return
$self
->TABLE_NAME();
}
sub
collection_class {
my
$self
=
shift
;
my
$class
=
ref
(
$self
) ||
$self
;
$class
.
'Collection'
;
}
sub
_guess_table_name {
my
$self
=
shift
;
my
$class
=
ref
(
$self
) ?
ref
(
$self
) :
$self
;
die
"Couldn't turn "
.
$class
.
" into a table name"
unless
(
$class
=~ /(?:\:\:)?(\w+)$/ );
my
$table
= $1;
$table
=~ s/(?<=[a-z])([A-Z]+)/
"_"
.
lc
($1)/eg;
$table
=~
tr
/A-Z/a-z/;
$table
= Lingua::EN::Inflect::PL_N(
$table
);
return
(
$table
);
}
sub
_handle {
my
$self
=
shift
;
if
(
@_
) {
$self
->{
'DBIxHandle'
} =
shift
;
}
return
(
$self
->{
'DBIxHandle'
} );
}
sub
_filters {
my
$self
=
shift
;
my
%args
= (
direction
=>
'input'
,
column
=>
undef
,
@_
);
if
(
$args
{
'direction'
} eq
'input'
) {
return
grep
$_
,
map
$_
->input_filters,
(
$self
,
$args
{
'column'
},
$self
->_handle );
}
else
{
return
grep
$_
,
map
$_
->output_filters,
(
$self
->_handle,
$args
{
'column'
},
$self
);
}
}
sub
_apply_input_filters {
return
(
shift
)->_apply_filters(
direction
=>
'input'
,
@_
);
}
sub
_apply_output_filters {
return
(
shift
)->_apply_filters(
direction
=>
'output'
,
@_
);
}
{
my
%cache
= ();
sub
_apply_filters {
my
$self
=
shift
;
my
%args
= (
direction
=>
'input'
,
column
=>
undef
,
value_ref
=>
undef
,
@_
);
my
@filters
=
$self
->_filters(
%args
);
my
$action
=
$args
{
'direction'
} eq
'output'
?
'decode'
:
'encode'
;
foreach
my
$filter_class
(
@filters
) {
unless
(
exists
$cache
{
$filter_class
} ) {
local
$UNIVERSAL::require::ERROR
;
$filter_class
->
require
;
if
(
$UNIVERSAL::require::ERROR
) {
warn
$UNIVERSAL::require::ERROR
;
$cache
{
$filter_class
} = 0;
next
;
}
$cache
{
$filter_class
} = 1;
}
elsif
( !
$cache
{
$filter_class
} ) {
next
;
}
my
$filter
=
$filter_class
->new(
record
=>
$self
,
column
=>
$args
{
'column'
},
value_ref
=>
$args
{
'value_ref'
},
handle
=>
$self
->_handle,
);
$filter
->
$action
();
}
} }
sub
is_distinct {
my
$self
=
shift
;
my
$column
=
shift
;
my
$value
=
shift
;
my
$record
=
$self
->new(
$self
->_new_record_args );
$record
->load_by_cols(
$column
=>
$value
);
my
$ret
= Class::ReturnValue->new();
if
(
$record
->id ) {
$ret
->as_array( 0,
"Value already exists for unique column $column"
);
$ret
->as_error(
errno
=> 3,
do_backtrace
=> 0,
message
=>
"Value already exists for unique column $column"
,
);
return
(
$ret
->return_value );
}
else
{
return
(1);
}
}
sub
run_canonicalization_for_column {
my
$self
=
shift
;
my
%args
= (
column
=>
undef
,
value
=>
undef
,
@_
);
my
(
$ret
,
$value_ref
) =
$self
->_run_callback(
name
=>
"canonicalize_"
.
$args
{
'column'
},
args
=>
$args
{
'value'
},
short_circuit
=> 0,
);
return
unless
defined
$ret
;
return
(
exists
$value_ref
->[-1]->[0]
?
$value_ref
->[-1]->[0]
:
$args
{
'value'
}
);
}
sub
has_canonicalizer_for_column {
my
$self
=
shift
;
my
$key
=
shift
;
my
$method
=
"canonicalize_$key"
;
if
(
$self
->can(
$method
) ) {
return
1;
}
elsif
( Class::Trigger::__fetch_all_triggers(
$self
,
$method
) ) {
return
1;
}
else
{
return
undef
;
}
}
sub
run_validation_for_column {
my
$self
=
shift
;
my
%args
= (
column
=>
undef
,
value
=>
undef
,
extra
=> [],
@_
);
my
$key
=
$args
{
'column'
};
my
$attr
=
$args
{
'value'
};
my
(
$ret
,
$results
)
=
$self
->_run_callback(
name
=>
"validate_"
.
$key
,
args
=>
$attr
,
extra
=>
$args
{
'extra'
} );
if
(
defined
$ret
) {
return
( 1,
'Validation ok'
);
}
else
{
return
( @{
$results
->[-1] } );
}
}
sub
has_validator_for_column {
my
$self
=
shift
;
my
$key
=
shift
;
my
$method
=
"validate_$key"
;
if
(
$self
->can(
$method
) ) {
return
1;
}
elsif
( Class::Trigger::__fetch_all_triggers(
$self
,
$method
) ) {
return
1;
}
else
{
return
undef
;
}
}
sub
_run_callback {
my
$self
=
shift
;
my
%args
= (
name
=>
undef
,
args
=>
undef
,
short_circuit
=> 1,
extra
=> [],
@_
);
my
$ret
;
my
$method
=
$args
{
'name'
};
my
@results
;
if
(
my
$func
=
$self
->can(
$method
) ) {
@results
=
$func
->(
$self
,
$args
{args}, @{
$args
{
'extra'
}} );
return
(
wantarray
? (
undef
, [ [
@results
] ] ) :
undef
)
if
$args
{short_circuit} and not
$results
[0];
}
$ret
=
$self
->call_trigger(
$args
{
'name'
} =>
$args
{args}, @{
$args
{
'extra'
}} );
return
(
wantarray
? (
$ret
, [ [
@results
], @{
$self
->last_trigger_results } ] )
:
$ret
);
}
sub
unload_value {
my
$self
=
shift
;
my
$column
=
shift
;
delete
$self
->{
$_
}{
$column
}
for
qw/values raw_values fetched decoded _prefetched/
;
}
1;