| Jifty documentation | Contained in the Jifty distribution. |
Jifty::Record - Represents a Jifty object that lives in the database.
Jifty::Record is a kind of Jifty::Object that has a database
representation; that is, it is also a Jifty::DBI::Record as well.
create can be called as either a class method or an object method.
Takes an array of key-value pairs and inserts a new row into the database representing this object.
Overrides Jifty::DBI::Record in these ways:
id values unless they are truly numericReturns the record id value. This routine short-circuits a much heavier call up through Jifty::DBI
load_or_create can be called as either a class method or an object method.
It attempts to load a record with the named parameters passed in. If it
can't do so, it creates a new record.
Returns the Jifty::Action::Record::Create action for this model class.
The PARAMHASH allows you to add additional parameters to pass to new_action in Jifty::Web.
Returns the Jifty::Action::Record::Update action for this model class. The current record is passed to the constructor.
The PARAMHASH allows you to add additional parameters to pass to new_action in Jifty::Web.
Returns the Jifty::Action::Record::Delete action for this model class. The current record is passed to the constructor.
The PARAMHASH allows you to add additional parameters to pass to new_action in Jifty::Web.
Returns the Jifty::Action::Record::Search action for this model class.
The PARAMHASH allows you to add additional parameters to pass to new_action in Jifty::Web.
Guesses a table name based on the class's last part. In addition to the work performed in Jifty::DBI::Record, this method also prefixes the table name with the plugin table prefix, if the model belongs to a plugin.
Should return true if the current user ($self->current_user) is
allowed to do RIGHT. Possible values for RIGHT are:
Called just before an object's create method is called, as well as
before parameter validation. ATTRIBUTES is the attributes that
the object is trying to be created with, as the attributes aren't on
the object yet to be inspected.
Called before any attribute is accessed on the object.
ATTRIBUTES is a hash with a single key column and a single
value, the name of the column being queried.
Called before any attribute is changed on the object. ATTRIBUTES is a hash of the arguments passed to _set.
Called before the object is deleted.
Models wishing to customize authorization checks should override this method. You can do so like this:
sub current_user_can {
my ($self, $right, %args) = @_;
# Make any custom checks that return 1 to allow or return 0 to deny...
# Fallback upon the default implementation to handle the
# SkipAccessControl configuration setting, superuser, bootstrap,
# delegation, and the before_access hook
return $self->SUPER::current_user_can($right, %args);
}
If you are sure you don't want your model to fallback using the default implementation, you can replace the last line with whatever fallback policy required.
The default implementation proceeds as follows:
If the SkipAccessControl setting is set to a true value in the
framework configuration section of etc/config.yml,
current_user_can always returns true.
The method first attempts to call the before_access hooks to check
for any allow or denial. See The before_access hook.
Next, the default implementation returns true if the current user is a superuser or a bootstrap user.
Then, if the model can perform delegation, usually by using
Jifty::RightsFrom, the access control decision is deferred to
another object (via the delegate_current_user_can subroutine).
Otherwise, it returns false.
This implementation may make use of a trigger called before_access
to make the decision. A new handler can be added to the trigger point
by calling add_handler:
$record->add_trigger(
name => 'before_access',
code => \&before_access,
abortable => 1,
);
The before_access handler will be passed the same arguments that
were used to call current_user_can, including the current record
object, the operation being checked, and any arguments being passed to
the operation.
The before_access handler should return one of three strings:
'deny', 'allow', or 'ignore'. The current_user_can
implementation reacts as follows to these results:
If a handler is abortable and aborts by returning a false value (such
as undef), current_user_can returns false.
If any handler returns 'deny', current_user_can returns false.
If any handler returns 'allow' and no handler returns 'deny',
current_user_can returns true.
In all other cases, the results of the handlers are ignored and
current_user_can proceeds to check using superuser, bootstrap, and
delegation.
Internal helper to call current_user_can with create.
Internal helper to call current_user_can with read.
Passes column as a named parameter for the column the user is checking rights on.
Internal helper to call current_user_can with update.
Internal helper to call current_user_can with delete.
Returns a copy of this object with the current_user set to the given current_user. This is a way to act on behalf of a particular user (perhaps the owner of the object)
Returns a copy of this object with the current_user set to the superuser. This is a convenient way to duck around ACLs if you have code that needs to for some reason or another.
Overrides Jifty::DBI::Record to check the delete ACL.
Display the friendly name of the record according to _brief_description.
To change what this returns, override _brief_description instead.
When displaying a list of records, Jifty can display a friendly value rather than the column's unique id. Out of the box, Jifty always tries to display the 'name' field from the record. If there is no 'name' field, Jifty falls back to the record id.
You can override this method to return the name of a method on your record class which will return a nice short human readable description for this record.
By default, Jifty::DBI::Record returns undef on non-existent
related fields; Jifty prefers to get back an object with an undef id.
Overrides the default arguments which this collection passes to new
collections, to pass the current_user.
Overrides the default arguments which this collection passes to new
records, to pass the current_user.
Returns a unique key for this application for the Memcached cache. This should be global within a given Jifty application instance.
By default, all models exist since undef, the ur-time when the
application was created. Please override it for your model class.
When called, this method will generate the SQL schema for the current version of this class and return it as a scalar, suitable for printing or execution in your database's command line.
When called, this method will generate the SQL schema statements for the current version of this class and return it as array.
When called, this method will generate the SQL schema for the current version of this class and insert it into the application's currently open database.
When called, this method will generate the SQL to remove this model's table in the database and execute it in the application's currently open database. This method can destroy a lot of data. Be sure you know what you're doing.
Returns the SQL statement necessary to add column_name to this
class's representation in the database
Executes the SQL code generated by add_column_sql. Dies on failure.
Returns the SQL statement necessary to remove column_name from this
class's representation in the database
Executes the SQL code generated by drop_column_sql. Dies on failure.
This method is used by Jifty::DBI::Record to determine which schema version is in use. It returns the current database version stored in the configuration.
Jifty's notion of the schema version is currently broken into two:
The Jifty version is the first. In the case of models defined by Jifty
itself, these use the version found in $Jifty::VERSION.
Any model defined by your application use the database version declared in the configuration. In etc/config.yml, this is located at:
framework:
Database:
Version: 0.0.1
A model is considered to be defined by Jifty if it the package name starts with "Jifty::". Otherwise, it is assumed to be an application model.
This is used to create a hash reference of the object's values. Unlike Jifty::DBI::Record->as_hash, this won't transform refers_to columns into JDBI objects. Override this if you want to include calculated values (for use in, say, your REST interface)
Controls which of the Jifty::Action::Record subclasses are
automatically set up for this model; this subroutine is passed one of
the strings Create, Update, Delete, Search or Execute, and should
return a true value if that action should be autogenerated.
The default method returns 0 for all action classes if the model is
marked as is_private. It returns 0 for all actions that are not
Search if the model is marked as is_protected; otherwise, it
returns true.
Override this method to return true to not generate any actions for this model, and to hide it from REST introspection.
Override this method to return true to only generate Search actions for this model.
Controls whether autogenerated actions with columns that refer to this class should attempt to provide a drop-down of possible values or not. This method will be called as a class method, and defaults to true.
| Jifty documentation | Contained in the Jifty distribution. |
use warnings; use strict; package Jifty::Record; use Jifty::Config; use List::MoreUtils qw();
use base qw( Jifty::Object Jifty::DBI::Record); __PACKAGE__->mk_accessors('_is_readable'); sub _init { my $self = shift; my %args = (@_); $self->_get_current_user(%args); $self->SUPER::_init(@_); }
sub create { my $class = shift; my $self; if ( ref($class) ) { ( $self, $class ) = ( $class, undef ); } else { $self = $class->new(); } my %attribs = @_; unless ( $self->check_create_rights(@_) ) { $self->log->error( $self->current_user->id . " tried to create a ", ref $self, " without permission" ); wantarray ? return ( 0, _('Permission denied') ) : return (0); } foreach my $key ( keys %attribs ) { $attribs{$key} = $self->run_canonicalization_for_column( column => $key, value => $attribs{$key}, extra => [\%attribs, { for => 'create' }], ); } foreach my $key ( keys %attribs ) { my $attr = $attribs{$key}; my ( $val, $msg ) = $self->run_validation_for_column( column => $key, value => $attribs{$key}, extra => [\%attribs, { for => 'create' }], ); if ( not $val ) { $self->log->error("There was a validation error for $key"); if ($class) { return ($self); } else { return ( $val, $msg ); } } # remove blank values. We'd rather have nulls if ( exists $attribs{$key} and ( !defined $attr || ( not ref($attr) and $attr eq '' ) ) ) { delete $attribs{$key}; } } my $msg = $self->SUPER::create(%attribs); if ( ref($msg) ) { # It's a Class::ReturnValue return $msg; } my ( $id, $status ) = $msg; $self->load_by_cols( id => $id ) if ($id); if ($class) { return $self; } else { return wantarray ? ( $id, $status ) : $id; } }
sub _primary_key {'id'} sub id { $_[0]->{'values'}->{'id'} }
sub load_or_create { my $class = shift; my $self; if ( ref($class) ) { ( $self, $class ) = ( $class, undef ); } else { $self = $class->new(); } my %args = (@_); my ( $id, $msg ) = $self->load_by_cols(%args); unless ( $self->id ) { return $self->create(%args); } return ( $id, $msg ); }
sub _action_from_record { my $self = shift; my $verb = shift; my $class = ref $self || $self; $class =~ s/::Model::/::Action::$verb/; return $class; } sub as_create_action { my $self = shift; my $action_class = $self->_action_from_record('Create'); return Jifty->web->new_action( class => $action_class, @_ ); }
sub as_update_action { my $self = shift; my $action_class = $self->_action_from_record('Update'); return Jifty->web->new_action( class => $action_class, record => $self, @_, ); }
sub as_delete_action { my $self = shift; my $action_class = $self->_action_from_record('Delete'); return Jifty->web->new_action( class => $action_class, record => $self, @_, ); }
sub as_search_action { my $self = shift; my $action_class = $self->_action_from_record('Search'); return Jifty->web->new_action( class => $action_class, @_, ); }
sub _guess_table_name { my $self = shift; my $table = $self->SUPER::_guess_table_name; # Add plugin table prefix if a plugin model my $class = ref($self) ? ref($self) : $self; my $app_plugin_root = Jifty->app_class({require => 0}, 'Plugin'); if ( $class =~ /^(?:Jifty::Plugin::|$app_plugin_root)/ ) { # Guess the plugin class name my $plugin_class = $class; $plugin_class =~ s/::Model::(.*)$//; # Try to load that plugin's configuration my ($plugin) = grep { ref $_ eq $plugin_class } Jifty->plugins; # Add the prefix if found if ( defined $plugin ) { $table = $plugin->table_prefix . $table; } # Uh oh. Warn, but try to keep going. else { warn "Model $class looks like a plugin model, but $plugin_class could not be found."; } } return $table; }
sub current_user_can { my $self = shift; my $right = shift; # Turn off access control for the whole application if ( Jifty->config->framework('SkipAccessControl') ) { return 1; } my $hook_status = $self->call_trigger( before_access => $right, @_ ); # If not aborted... if ( defined $hook_status ) { # Compile the handler results my %results; $results{ $_->[0] }++ for ( @{ $self->last_trigger_results } ); # Deny always takes precedent if ( $results{deny} ) { return 0; } # Then allow... elsif ( $results{allow} ) { return 1; } # Otherwise, no instruction from the handlers, move along... } # Abort! Return false for safety if the hook exploded else { return 0; } Carp::confess "No current user" unless ( $self->current_user ); if ( $self->current_user->is_bootstrap_user or $self->current_user->is_superuser ) { return (1); } if ( $self->can('delegate_current_user_can') ) { return $self->delegate_current_user_can( $right, @_ ); } unless ( $self->current_user->isa('Jifty::CurrentUser') ) { $self->log->error( "Hm. called to authenticate without a currentuser - " . $self->current_user ); return (0); } return (0); }
sub check_create_rights { return shift->current_user_can( 'create', @_ ) }
sub check_read_rights { my $self = shift; return (1) if $self->_is_readable; return $self->current_user_can( 'read', column => shift ); }
sub check_update_rights { return shift->current_user_can( 'update', @_ ) }
sub check_delete_rights { return shift->current_user_can( 'delete', @_ ) } sub _set { my $self = shift; unless ( $self->check_update_rights(@_) ) { return ( 0, _('Permission denied') ); } $self->SUPER::_set(@_); } sub _value { my $self = shift; my $column = shift; unless ( $self->check_read_rights( $column => @_ ) ) { return (undef); } my $value = $self->SUPER::_value( $column => @_ ); return $value if ref $value or $self->column($column)->type eq 'blob'; Encode::_utf8_on($value) if defined $value; $value; }
sub as_user { my $self = shift; my $user = shift; my $clone = $self->new( current_user => $user ); $clone->load( $self->id ); return $clone; }
sub as_superuser { my $self = shift; return $self->as_user( $self->current_user->superuser ); }
sub delete { my $self = shift; unless ( $self->check_delete_rights(@_) ) { $self->log->logcluck("Permission denied"); return ( 0, _('Permission denied') ); } $self->SUPER::delete(@_); }
sub brief_description { my $self = shift; my $method = $self->_brief_description; return $self->$method; }
sub _brief_description { my $self = shift; return 'name' if $self->can('name'); return 'id'; }
sub null_reference { 0 }
sub _new_collection_args { my $self = shift; return ( current_user => $self->current_user ); }
sub _new_record_args { my $self = shift; return ( current_user => $self->current_user ); }
sub cache_key_prefix { Jifty->config->framework('Database')->{'Database'}; } sub _cache_config { { 'cache_p' => 1, 'cache_for_sec' => 60, }; }
sub printable_table_schema { my $class = shift; my $schema_gen = $class->_make_schema(); return $schema_gen->create_table_sql_text; }
sub table_schema_statements { my $class = shift; my $schema_gen = $class->_make_schema(); return $schema_gen->create_table_sql_statements; }
sub create_table_in_db { my $class = shift; # Run all CREATE commands for my $statement ( $class->table_schema_statements ) { my $ret = Jifty->handle->simple_query($statement); $ret or die "error creating table $class: " . $ret->error_message; } }
sub drop_table_in_db { my $self = shift; my $ret = Jifty->handle->simple_query( 'DROP TABLE ' . $self->table ); $ret or die "error removing table $self: " . $ret->error_message; } sub _make_schema { my $class = shift; require Jifty::DBI::SchemaGenerator; my $schema_gen = Jifty::DBI::SchemaGenerator->new( Jifty->handle ) or die "Can't make Jifty::DBI::SchemaGenerator"; my $ret = $schema_gen->add_model( $class->new ); $ret or die "couldn't add model $class: " . $ret->error_message; return $schema_gen; }
sub add_column_sql { my $self = shift; my $column_name = shift; my $col = $self->column($column_name); my $definition = $self->_make_schema() ->column_definition_sql( $self->table => $col->name ); return "ALTER TABLE " . $self->table . " ADD COLUMN " . $definition; }
sub add_column_in_db { my $self = shift; my $ret = Jifty->handle->simple_query( $self->add_column_sql(@_) ); $ret or die "error adding column " . $_[0] . " to $self: " . $ret->error_message; }
sub drop_column_sql { my $self = shift; my $column_name = shift; my $col = $self->column($column_name); return "ALTER TABLE " . $self->table . " DROP COLUMN " . $col->name; }
sub drop_column_in_db { my $self = shift; my $ret = Jifty->handle->simple_query( $self->drop_column_sql(@_) ); $ret or die "error dropping column " . $_[0] . " to $self: " . $ret->error_message; }
sub schema_version { my $class = shift; # Return the Jifty schema version if ( $class =~ /^Jifty::Model::/ ) { return $Jifty::VERSION; } # TODO need to consider Jifty plugin versions? # Return the application schema version else { my $config = Jifty->config(); return $config->framework('Database')->{'Version'}; } }
sub column_serialized_as { my ($class, $column) = @_; my $meta = $column->attributes->{serialized} or return; $meta->{columns} ||= [$column->refers_to->default_serialized_as_columns] if $column->refers_to; return $meta; }
sub default_serialized_as_columns { my $class = shift; return (List::MoreUtils::uniq 'id', $class->_brief_description); }
sub jifty_serialize_format { my $record = shift; my %data; # XXX: maybe just test ->virtual? for my $column (grep { $_->readable } $record->columns ) { next if UNIVERSAL::isa($column->refers_to, 'Jifty::DBI::Collection'); next if $column->container; my $name = $column->aliased_as || $column->name; if ((my $refers_to = $column->refers_to) && (my $serialize_meta = $record->column_serialized_as($column))) { my $column_data = $record->$name(); if ( $column_data && $column_data->id ) { $name = $serialize_meta->{name} if $serialize_meta->{name}; $data{$name} = { map { $_ => scalar $record->$name->$_ } @{$serialize_meta->{columns} } }; } else { $data{$name} = undef; } } else { $data{$name} = Jifty::Util->stringify($record->_value($name)); } } return \%data; }
sub autogenerate_action { my $class = shift; my($action) = @_; return 0 if $class->is_private; return 0 if $class->is_protected and $action ne "Search"; return 1; }
sub is_private { 0 }
sub is_protected { return shift->is_private }
sub enumerable { 1 } 1;