[Jifty-commit] r3858 - in jifty/trunk: . lib lib/Jifty
lib/Jifty/Action/Record
jifty-commit at lists.jifty.org
jifty-commit at lists.jifty.org
Sun Aug 12 16:23:47 EDT 2007
Author: sterling
Date: Sun Aug 12 16:23:46 2007
New Revision: 3858
Modified:
jifty/trunk/ (props changed)
jifty/trunk/lib/Jifty.pm
jifty/trunk/lib/Jifty/API.pm
jifty/trunk/lib/Jifty/Action/Autocomplete.pm
jifty/trunk/lib/Jifty/Action/Record.pm
jifty/trunk/lib/Jifty/Action/Record/Create.pm
jifty/trunk/lib/Jifty/Action/Record/Delete.pm
jifty/trunk/lib/Jifty/Action/Record/Search.pm
jifty/trunk/lib/Jifty/Action/Record/Update.pm
jifty/trunk/lib/Jifty/Action/Redirect.pm
Log:
r8534 at riddle: andrew | 2007-08-12 15:13:21 -0500
Improving some of the Pod, code comments, and minor perl tidying.
Modified: jifty/trunk/lib/Jifty.pm
==============================================================================
--- jifty/trunk/lib/Jifty.pm (original)
+++ jifty/trunk/lib/Jifty.pm Sun Aug 12 16:23:46 2007
@@ -143,13 +143,13 @@
sub new {
my $ignored_class = shift;
+ # Setup the defaults
my %args = (
no_handle => 0,
logger_component => undef,
@_
);
-
# Add the appliation's library path
push @INC, File::Spec->catdir(Jifty::Util->app_root, "lib");
@@ -158,10 +158,12 @@
# which is likely Record::Cachable or Record::Memcached
@Jifty::Record::ISA = grep { $_ ne 'Jifty::DBI::Record' } @Jifty::Record::ISA;
+ # Configure the base class used by Jifty models
my $record_base_class = Jifty->config->framework('Database')->{'RecordBaseClass'};
Jifty::Util->require( $record_base_class );
push @Jifty::Record::ISA, $record_base_class unless $record_base_class eq 'Jifty::Record';
+ # Logger turn on
Jifty->logger( Jifty::Logger->new( $args{'logger_component'} ) );
# Set up plugins
@@ -169,19 +171,30 @@
my @plugins_to_load = @{Jifty->config->framework('Plugins')};
my $app_plugin = Jifty->app_class('Plugin');
for (my $i = 0; my $plugin = $plugins_to_load[$i]; $i++) {
+
+ # Prepare to learn the plugin class name
my $plugin_name = (keys %{$plugin})[0];
my $class;
+
+ # Is the plugin name a fully-qualified class name?
if ($plugin_name =~ /^(?:Jifty::Plugin|$app_plugin)::/) {
# app-specific plugins use fully qualified names, Jifty plugins may
$class = $plugin_name;
}
+
# otherwise, assume it's a short name, qualify it
else {
$class = "Jifty::Plugin::".$plugin_name;
}
+
+ # Load the plugin options
my %options = %{ $plugin->{ $plugin_name } };
+
+ # Load the plugin code
Jifty::Util->require($class);
Jifty::ClassLoader->new(base => $class)->require;
+
+ # Initialize the plugin and mark the prerequisites for loading too
my $plugin_obj = $class->new(%options);
push @plugins, $plugin_obj;
foreach my $name ($plugin_obj->prereq_plugins) {
@@ -190,6 +203,7 @@
}
}
+ # All plugins loaded, save them for later reference
Jifty->plugins(@plugins);
# Now that we have the config set up and loaded plugins,
@@ -201,9 +215,11 @@
base => Jifty->app_class,
);
+ # Save the class loader for later reference
Jifty->class_loader($class_loader);
$class_loader->require;
+ # Configure the request handler and action API handler
Jifty->handler(Jifty::Handler->new());
Jifty->api(Jifty::API->new());
@@ -217,9 +233,9 @@
# application specific for startup
my $app = Jifty->app_class;
+ # Run the App::start() method if it exists for app-specific initialization
$app->start()
if $app->can('start');
-
}
# Explicitly destroy the classloader; if this happens during global
@@ -431,15 +447,20 @@
my $self = shift;
my %args = (no_handle =>0,
@_);
+
+ # Don't setup the database connection if we're told not to
unless ( $args{'no_handle'}
or Jifty->config->framework('SkipDatabase')
or not Jifty->config->framework('Database') )
{
+ # Load the app's database handle and connect
my $handle_class = Jifty->app_class("Handle");
Jifty::Util->require( $handle_class );
Jifty->handle( $handle_class->new );
Jifty->handle->connect();
+
+ # Make sure the app version matches the database version
Jifty->handle->check_schema_version()
unless $args{'no_version_check'};
}
Modified: jifty/trunk/lib/Jifty/API.pm
==============================================================================
--- jifty/trunk/lib/Jifty/API.pm (original)
+++ jifty/trunk/lib/Jifty/API.pm Sun Aug 12 16:23:46 2007
@@ -57,8 +57,10 @@
my $class = shift;
my $self = bless {}, $class;
+ # Setup the basic allow/deny rules
$self->reset;
+ # Find all the actions for the API reference (available at _actions)
Jifty::Module::Pluggable->import(
search_path => [
Jifty->app_class("Action"),
@@ -85,12 +87,15 @@
my $self = shift;
my $action = shift;
+ # Get the application class name
my $base_path = Jifty->app_class;
+ # Return the class now if it's already fully qualified
return $action
if ($action =~ /^Jifty::/
or $action =~ /^\Q$base_path\E::/);
+ # Otherwise qualify it
return $base_path . "::Action::" . $action;
}
@@ -109,6 +114,7 @@
# Set up defaults
my $app_actions = Jifty->app_class("Action");
+ # These are the default action limits
$self->action_limits(
[ { deny => 1, restriction => qr/.*/ },
{ allow => 1,
@@ -180,6 +186,7 @@
my $polarity = shift;
my @restrictions = @_;
+ # Check the sanity of the polarity
die "Polarity must be 'allow' or 'deny'"
unless $polarity eq "allow"
or $polarity eq "deny";
Modified: jifty/trunk/lib/Jifty/Action/Autocomplete.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Autocomplete.pm (original)
+++ jifty/trunk/lib/Jifty/Action/Autocomplete.pm Sun Aug 12 16:23:46 2007
@@ -59,12 +59,15 @@
sub take_action {
my $self = shift;
+ # Load the arguments
my $moniker = $self->argument_value('moniker');
my $argument = $self->argument_value('argument');
+ # Load the action associated with the moniker
my $request_action = Jifty->web->request->action($moniker);
my $action = Jifty->web->new_action_from_request($request_action);
+ # Call the autocompleter for that action and argument and set the result
my @completions = $action->_autocomplete_argument($argument);
$self->result->content->{completions} = \@completions;
Modified: jifty/trunk/lib/Jifty/Action/Record.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Record.pm (original)
+++ jifty/trunk/lib/Jifty/Action/Record.pm Sun Aug 12 16:23:46 2007
@@ -69,26 +69,31 @@
);
my $self = $class->SUPER::new(%args);
-
+ # Load the associated record class just in case it hasn't been already
my $record_class = $self->record_class;
Jifty::Util->require($record_class);
+ # Die if we were given a record that wasn't a record
if (ref $args{'record'} && !$args{'record'}->isa($record_class)) {
Carp::confess($args{'record'}." isn't a $record_class");
}
-
-
- # Set up record
+ # If the record class is a record, use that
if ( ref $record_class ) {
$self->record($record_class);
$self->argument_value( $_, $self->record->$_ )
for @{ $self->record->_primary_keys };
- } elsif ( ref $args{record} and $args{record}->isa($record_class) ) {
+ }
+
+ # Otherwise, try to use the record passed to the constructor
+ elsif ( ref $args{record} and $args{record}->isa($record_class) ) {
$self->record( $args{record} );
$self->argument_value( $_, $self->record->$_ )
for @{ $self->record->_primary_keys };
- } else {
+ }
+
+ # Otherwise, try to use the arguments to load the record
+ else {
# We could leave out the explicit current user, but it'd have
# a slight negative performance implications
@@ -101,6 +106,7 @@
}
$self->record->load_by_primary_keys(%given_pks) if %given_pks;
}
+
return $self;
}
@@ -132,195 +138,270 @@
sub arguments {
my $self = shift;
+ # Don't do this twice, it's too expensive
return $self->_cached_arguments if $self->_cached_arguments;
- my $field_info = {};
+ # Get ready to rumble
+ my $field_info = {};
+ my @fields = $self->possible_fields;
+
+ # we use a while here because we may be modifying the fields on the fly.
+ while ( my $field = shift @fields ) {
+ my $info = {};
+ my $column;
+
+ # The field is a column object, adjust to that
+ if ( ref $field ) {
+ $column = $field;
+ $field = $column->name;
+ }
+
+ # Otherwise, we need to load the column info
+ else {
+ # Load teh column object and the record's current value
+ $column = $self->record->column($field);
+ my $current_value = $self->record->$field;
+
+ # If the current value is actually a pointer to
+ # another object, turn it into an ID
+ $current_value = $current_value->id
+ if blessed($current_value)
+ and $current_value->isa('Jifty::Record');
- my @fields = $self->possible_fields;
-
- # we use a while here because we may be modifying the fields
- # on the fly.
- while ( my $field = shift @fields ) {
- my $info = {};
- my $column;
- if ( ref $field ) {
- $column = $field;
- $field = $column->name;
- } else {
- $column = $self->record->column($field);
- my $current_value = $self->record->$field;
-
- # If the current value is actually a pointer to
- # another object, dereference it
- $current_value = $current_value->id
- if blessed($current_value)
- and $current_value->isa('Jifty::Record');
- $info->{default_value} = $current_value if $self->record->id;
- }
+ # The record's current value becomes the widget's default value
+ $info->{default_value} = $current_value if $self->record->id;
+ }
- #
- # if($field =~ /^(.*)_id$/ && $self->record->column($1)) {
- # $column = $self->record->column($1);
- #}
-
- ##################
- my $render_as = $column->render_as;
- $render_as = defined $render_as ? lc($render_as) : '';
-
- if ( defined (my $valid_values = $column->valid_values)) {
- $info->{valid_values} = $valid_values;
- $info->{render_as} = 'Select';
- } elsif ( defined $column->type && $column->type =~ /^bool/i ) {
- $info->{render_as} = 'Checkbox';
- } elsif ( $render_as eq 'password' )
- {
- my $same = sub {
- my ( $self, $value ) = @_;
- if ( $value ne $self->argument_value($field) ) {
- return $self->validation_error(
- ($field.'_confirm') => _("The passwords you typed didn't match each other")
- );
- } else {
- return $self->validation_ok( $field . '_confirm' );
- }
- };
+ #
+ # if($field =~ /^(.*)_id$/ && $self->record->column($1)) {
+ # $column = $self->record->column($1);
+ #}
+
+ #########
+
+ # Canonicalize the render_as setting for the column
+ my $render_as = $column->render_as;
+ $render_as = defined $render_as ? lc($render_as) : '';
+
+ # Use a select box if we have a list of valid values
+ if ( defined (my $valid_values = $column->valid_values)) {
+ $info->{valid_values} = $valid_values;
+ $info->{render_as} = 'Select';
+ }
+
+ # Use a checkbox for boolean fields
+ elsif ( defined $column->type && $column->type =~ /^bool/i ) {
+ $info->{render_as} = 'Checkbox';
+ }
+
+ # Add an additional _confirm field for passwords
+ elsif ( $render_as eq 'password' ) {
+
+ # Add a validator to make sure both fields match
+ my $same = sub {
+ my ( $self, $value ) = @_;
+ if ( $value ne $self->argument_value($field) ) {
+ return $self->validation_error(
+ ($field.'_confirm') => _("The passwords you typed didn't match each other")
+ );
+ } else {
+ return $self->validation_ok( $field . '_confirm' );
+ }
+ };
- $field_info->{ $field . "_confirm" } = {
- render_as => 'Password',
- virtual => '1',
- validator => $same,
- sort_order => ($column->sort_order +.01),
- mandatory => 0
- };
- }
+ # Add the extra confirmation field
+ $field_info->{ $field . "_confirm" } = {
+ render_as => 'Password',
+ virtual => '1',
+ validator => $same,
+ sort_order => ($column->sort_order +.01),
+ mandatory => 0
+ };
+ }
- elsif ( defined (my $refers_to = $column->refers_to) ) {
- if ( UNIVERSAL::isa( $refers_to, 'Jifty::Record' ) ) {
- $info->{render_as} = $render_as || 'Select';
- }
+ # Handle the X-to-one references
+ elsif ( defined (my $refers_to = $column->refers_to) ) {
- if ( UNIVERSAL::isa( $refers_to, 'Jifty::Record' ) && $info->{render_as} eq 'Select' ) {
- my $collection = Jifty::Collection->new(
- record_class => $refers_to,
- current_user => $self->record->current_user
- );
- $collection->unlimit;
+ # Render as a select box unless they override
+ if ( UNIVERSAL::isa( $refers_to, 'Jifty::Record' ) ) {
+ $info->{render_as} = $render_as || 'Select';
+ }
- my $method = $refers_to->_brief_description();
+ # If it's a select box, setup the available values
+ if ( $info->{render_as} eq 'Select' ) {
- # FIXME: we should get value_from with the actualy refered by key
- $info->{valid_values} = [
- { display_from => $refers_to->can($method) ? $method : "id",
- value_from => 'id',
- collection => $collection
- }
- ];
- } else {
- # No need to generate arguments for
- # JDBI::Collections, as we can't do anything
- # useful with them yet, anyways.
-
- # However, if the column comes with a
- # "render_as", we can assume that the app
- # developer know what he/she is doing.
- # So we just render it as whatever specified.
+ # Get an unlimited collection
+ my $collection = Jifty::Collection->new(
+ record_class => $refers_to,
+ current_user => $self->record->current_user
+ );
+ $collection->unlimit;
+
+ # Fetch the _brief_description() method
+ my $method = $refers_to->_brief_description();
+
+ # FIXME: we should get value_from with the actualy refered by key
+
+ # Setup the list of valid values
+ $info->{valid_values} = [
+ { display_from => $refers_to->can($method) ? $method : "id",
+ value_from => 'id',
+ collection => $collection
+ }
+ ];
+ }
+
+ # If the reference is X-to-many instead, skip it
+ else {
+ # No need to generate arguments for
+ # JDBI::Collections, as we can't do anything
+ # useful with them yet, anyways.
+
+ # However, if the column comes with a
+ # "render_as", we can assume that the app
+ # developer know what he/she is doing.
+ # So we just render it as whatever specified.
- next unless $render_as;
- }
+ next unless $render_as;
}
+ }
- #########
+ #########
+ # Figure out what the action's validation method would for this field
+ my $validate_method = "validate_" . $field;
- # build up a validator sub if the column implements validation
- # and we're not overriding it at the action level
- my $validate_method = "validate_" . $field;
-
- if ( $column->validator and not $self->can($validate_method) ) {
- $info->{ajax_validates} = 1;
- $info->{validator} = sub {
- my $self = shift;
- my $value = shift;
- my ( $is_valid, $message )
- = &{ $column->validator }( $self->record, $value );
-
- if ($is_valid) {
- return $self->validation_ok($field);
- } else {
- unless ($message) {
- $self->log->error(
- qq{Schema validator for $field didn't explain why the value '$value' is invalid}
- );
- }
- return (
- $self->validation_error(
- $field => ($message || _("That doesn't look right, but I don't know why"))
- )
+ # Build up a validator sub if the column implements validation
+ # and we're not overriding it at the action level
+ if ( $column->validator and not $self->can($validate_method) ) {
+ $info->{ajax_validates} = 1;
+ $info->{validator} = sub {
+ my $self = shift;
+ my $value = shift;
+
+ # Check the column's validator
+ my ( $is_valid, $message )
+ = &{ $column->validator }( $self->record, $value );
+
+ # The validator reported valid, return OK
+ if ($is_valid) {
+ return $self->validation_ok($field);
+ }
+
+ # Bad stuff, report an error
+ else {
+ unless ($message) {
+ $self->log->error(
+ qq{Schema validator for $field didn't explain why the value '$value' is invalid}
);
}
- };
- }
- my $autocomplete_method = "autocomplete_" . $field;
-
- if ( $self->record->can($autocomplete_method) ) {
- $info->{'autocompleter'} ||= sub {
- my ( $self, $value ) = @_;
- my %columns;
- $columns{$_} = $self->argument_value($_)
- for grep { $_ ne $field } $self->possible_fields;
- return $self->record->$autocomplete_method( $value,
- %columns );
- };
- }
- elsif ($column->autocompleted) {
- # Auto-generated autocompleter
- $info->{'autocompleter'} ||= sub {
- my ( $self, $value ) = @_;
-
- my $collection = Jifty::Collection->new(
- record_class => $self->record_class,
- current_user => $self->record->current_user
+ return (
+ $self->validation_error(
+ $field => ($message || _("That doesn't look right, but I don't know why"))
+ )
);
+ }
+ };
+ }
- $collection->unlimit;
- $collection->rows_per_page(20);
- $collection->limit(column => $field, value => $value, operator => 'STARTSWITH', entry_aggregator => 'AND') if length($value);
- $collection->limit(column => $field, value => 'NULL', operator => 'IS NOT', entry_aggregator => 'AND');
- $collection->limit(column => $field, value => '', operator => '!=', entry_aggregator => 'AND');
- $collection->columns('id', $field);
- $collection->order_by(column => $field);
- $collection->group_by(column => $field);
-
- my @choices;
- while (my $record = $collection->next) {
- push @choices, $record->$field;
- }
- return @choices;
- };
- }
+ # What would the autocomplete method be for this column in the record
+ my $autocomplete_method = "autocomplete_" . $field;
- if ( $self->record->has_canonicalizer_for_column($field) ) {
- $info->{'ajax_canonicalizes'} = 1;
- $info->{'canonicalizer'} ||= sub {
- my ( $self, $value ) = @_;
- return $self->record->run_canonicalization_for_column(column => $field, value => $value);
- };
- } elsif ( $render_as eq 'date')
- {
- $info->{'ajax_canonicalizes'} = 1;
- }
+ # Set the autocompleter if the record has one
+ if ( $self->record->can($autocomplete_method) ) {
+ $info->{'autocompleter'} ||= sub {
+ my ( $self, $value ) = @_;
+ my %columns;
+ $columns{$_} = $self->argument_value($_)
+ for grep { $_ ne $field } $self->possible_fields;
+ return $self->record->$autocomplete_method( $value,
+ %columns );
+ };
+ }
- # If we're hand-coding a render_as, hints or label, let's use it.
- for (qw(render_as label hints max_length mandatory sort_order container)) {
+ # The column requests an automagically generated autocompleter, which
+ # is baed upon the values available in the field
+ elsif ($column->autocompleted) {
+ # Auto-generated autocompleter
+ $info->{'autocompleter'} ||= sub {
+ my ( $self, $value ) = @_;
+
+ my $collection = Jifty::Collection->new(
+ record_class => $self->record_class,
+ current_user => $self->record->current_user
+ );
+
+ # Return the first 20 matches...
+ $collection->unlimit;
+ $collection->rows_per_page(20);
+
+ # ...that start with the value typed...
+ if (length $value) {
+ $collection->limit(
+ column => $field,
+ value => $value,
+ operator => 'STARTSWITH',
+ entry_aggregator => 'AND'
+ );
+ }
- if ( defined (my $val = $column->$_) ) {
- $info->{$_} = $val;
+ # ...but are not NULL...
+ $collection->limit(
+ column => $field,
+ value => 'NULL',
+ operator => 'IS NOT',
+ entry_aggregator => 'AND'
+ );
+
+ # ...and are not empty.
+ $collection->limit(
+ column => $field,
+ value => '',
+ operator => '!=',
+ entry_aggregator => 'AND'
+ );
+
+ # Optimize the query a little bit
+ $collection->columns('id', $field);
+ $collection->order_by(column => $field);
+ $collection->group_by(column => $field);
+
+ # Set up the list of choices to return
+ my @choices;
+ while (my $record = $collection->next) {
+ push @choices, $record->$field;
}
+ return @choices;
+ };
+ }
+
+ # Add a canonicalizer for the column if the record provides one
+ if ( $self->record->has_canonicalizer_for_column($field) ) {
+ $info->{'ajax_canonicalizes'} = 1;
+ $info->{'canonicalizer'} ||= sub {
+ my ( $self, $value ) = @_;
+ return $self->record->run_canonicalization_for_column(column => $field, value => $value);
+ };
+ }
+
+ # Otherwise, if it's a date, we have a built-in canonicalizer for that
+ elsif ( $render_as eq 'date') {
+ $info->{'ajax_canonicalizes'} = 1;
+ }
+
+ # If we're hand-coding a render_as, hints or label, let's use it.
+ for (qw(render_as label hints max_length mandatory sort_order container)) {
+
+ if ( defined (my $val = $column->$_) ) {
+ $info->{$_} = $val;
}
- $field_info->{$field} = $info;
}
+ $field_info->{$field} = $info;
+ }
+ # After all that, use the schema { ... } params for the final bits
if ($self->can('PARAMS')) {
+
# User-defined declarative schema fields can override default ones here
my $params = $self->PARAMS;
@@ -335,9 +416,13 @@
}
}
+ # Cache the result of merging the Jifty::Action::Record and schema
+ # parameters
use Jifty::Param::Schema ();
$self->_cached_arguments(Jifty::Param::Schema::merge_params($field_info, $params));
}
+
+ # No schema { ... } block, so just use what we generated
else {
$self->_cached_arguments($field_info);
}
@@ -374,6 +459,7 @@
sub _setup_event_before_action {
my $self = shift;
+ # Setup the information regarding the event for later publishing
my $event_info = {};
$event_info->{as_hash_before} = $self->record->as_hash;
$event_info->{record_id} = $self->record->id;
@@ -387,10 +473,13 @@
sub _setup_event_after_action {
my $self = shift;
my $event_info = shift;
+
+ # Add a few more bits about the result
$event_info->{result} = $self->result;
$event_info->{timestamp} = time();
$event_info->{as_hash_after} = $self->record->as_hash;
+ # Publish the event
my $event_class = $event_info->{'record_class'};
$event_class =~ s/::Model::/::Event::Model::/g;
Jifty::Util->require($event_class);
Modified: jifty/trunk/lib/Jifty/Action/Record/Create.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Record/Create.pm (original)
+++ jifty/trunk/lib/Jifty/Action/Record/Create.pm Sun Aug 12 16:23:46 2007
@@ -30,6 +30,7 @@
sub arguments {
my $self = shift;
+ # Add default values to the arguments configured by Jifty::Action::Record
my $args = $self->SUPER::arguments;
for my $arg (keys %{$args}) {
my $column = $self->record->column($arg) or next;
@@ -56,13 +57,18 @@
my $self = shift;
my $record = $self->record;
+ # Build the event to be fired later
my $event_info = $self->_setup_event_before_action();
-
my %values;
- # Virtual arguments aren't really ever backed by data structures. they're added by jifty for things like confirmations
+
+ # Iterate through all that are set, except for the virtual ones
for (grep { defined $self->argument_value($_) && !$self->arguments->{$_}->{virtual} } $self->argument_names) {
+
+ # Prepare the hash to pass to create for each argument
$values{$_} = $self->argument_value($_);
+
+ # Handle file uploads
if (ref $values{$_} eq "Fh") { # CGI.pm's "lightweight filehandle class"
local $/;
my $fh = $values{$_};
@@ -70,24 +76,31 @@
$values{$_} = scalar <$fh>;
}
}
+
+ # Attempt creating the record
my $id;
my $msg = $record->create(%values);
- # Handle errors?
- if (ref($msg)) { # If it's a Class::ReturnValue
+
+ # Convert Class::ReturnValue to an id and message
+ if (ref($msg)) {
($id,$msg) = $msg->as_array;
}
+ # If ID is 0/undef, the record didn't create, so we fail
if (! $record->id ) {
$self->log->warn(_("Create of %1 failed: %2", ref($record), $msg));
$self->result->error($msg || _("An error occurred. Try again later"));
}
+ # No errors! Report success
else {
# Return the id that we created
$self->result->content(id => $self->record->id);
$self->report_success if not $self->result->failure;
}
- $self->_setup_event_after_action($event_info) ;
+
+ # Publish the event, noting success or failure
+ $self->_setup_event_after_action($event_info);
return ($self->record->id);
}
@@ -105,5 +118,15 @@
$self->result->message(_("Created"))
}
+=head1 SEE ALSO
+
+L<Jifty::Action::Record>, L<Jifty::Record>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
1;
Modified: jifty/trunk/lib/Jifty/Action/Record/Delete.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Record/Delete.pm (original)
+++ jifty/trunk/lib/Jifty/Action/Record/Delete.pm Sun Aug 12 16:23:46 2007
@@ -33,6 +33,7 @@
my $self = shift;
my $arguments = {};
+ # Mark the primary key for use in the constructor and not rendered
for my $pk (@{ $self->record->_primary_keys }) {
$arguments->{$pk}{'constructor'} = 1;
# XXX TODO IS THERE A BETTER WAY TO NOT RENDER AN ITEM IN arguments
@@ -52,12 +53,17 @@
sub take_action {
my $self = shift;
+ # Setup the event info for later publishing
my $event_info = $self->_setup_event_before_action();
+ # Delete the record and return an error if delete fails
my ( $val, $msg ) = $self->record->delete;
$self->result->error($msg) if not $val and $msg;
+ # Otherwise, we seem to have succeeded, report that
$self->report_success if not $self->result->failure;
+
+ # Publish the event
$self->_setup_event_after_action($event_info);
return 1;
@@ -76,4 +82,15 @@
$self->result->message(_("Deleted"))
}
+=head1 SEE ALSO
+
+L<Jifty::Action::Record>, L<Jifty::Record>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
1;
Modified: jifty/trunk/lib/Jifty/Action/Record/Search.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Record/Search.pm (original)
+++ jifty/trunk/lib/Jifty/Action/Record/Search.pm Sun Aug 12 16:23:46 2007
@@ -51,27 +51,31 @@
sub arguments {
my $self = shift;
+
+ # The args processing here is involved, so only calculate them once
return $self->_cached_arguments if $self->_cached_arguments;
+ # Iterate through all the arguments setup by Jifty::Action::Record
my $args = $self->SUPER::arguments;
for my $field (keys %$args) {
+ # Figure out what information we know about the field
my $info = $args->{$field};
-
my $column = $self->record->column($field);
- # First, modify the ``exact match'' search field (same name as
- # the original argument)
+ # We don't care about validation and mandatories on search
delete $info->{validator};
delete $info->{mandatory};
- if($info->{valid_values}) {
+ # If the column has a set of valid values, deal with those
+ if ($info->{valid_values}) {
my $valid_values = $info->{valid_values};
+ # Canonicalize the valid values
local $@;
$info->{valid_values} = $valid_values = (eval { [ @$valid_values ] } || [$valid_values]);
- # For radio display, display an "any" label as empty choices looks weird
+ # For radio display, display an "any" label (empty looks weird)
if (lc $info->{render_as} eq 'radio') {
if (@$valid_values > 1) {
unshift @$valid_values, { display => _("(any)"), value => '' };
@@ -82,53 +86,98 @@
$info->{default_value} ||= $valid_values->[0];
}
}
+
+ # If not radio, add a blank options
else {
unshift @$valid_values, "";
}
}
+ # You can't search passwords, so remove the fields
if(lc $info->{'render_as'} eq 'password') {
delete $args->{$field};
next;
}
+ # Warn if we have a search field without an actual column
warn "No column for: $field" unless($column);
+ # Drop out X-to-many columns from the search
if(defined(my $refers_to = $column->refers_to)) {
delete $args->{$field}
if UNIVERSAL::isa($refers_to, 'Jifty::Collection');
}
+
# XXX TODO: What about booleans? Checkbox doesn't quite work,
# since there are three choices: yes, no, either.
# Magic _id refers_to columns
next if($field =~ /^(.*)_id$/ && $self->record->column($1));
+ # Setup the field label for the comparison operator selection
my $label = $info->{label} || $field;
+
+ # Add the "X is not" operator
$args->{"${field}_not"} = { %$info, label => _("%1 is not", $label) };
+
+ # The operators available depend on the type
my $type = lc($column->type);
+
+ # Add operators available for text fields
if($type =~ /(?:text|char)/) {
+
+ # Show a text entry box (rather than a textarea)
$info->{render_as} = 'text';
+
+ # Add the "X contains" operator
$args->{"${field}_contains"} = { %$info, label => _("%1 contains", $label) };
+
+ # Add the "X lacks" operator (i.e., opposite of "X contains")
$args->{"${field}_lacks"} = { %$info, label => _("%1 lacks", $label) };
- } elsif($type =~ /(?:date|time)/) {
+ }
+
+ # Handle date, datetime, time, and timestamp fields
+ elsif($type =~ /(?:date|time)/) {
+
+ # Add the "X after" date/time operation
$args->{"${field}_after"} = { %$info, label => _("%1 after", $label) };
+
+ # Add the "X before" date/time operation
$args->{"${field}_before"} = { %$info, label => _("%1 before", $label) };
+
+ # Add the "X since" date/time operation
$args->{"${field}_since"} = { %$info, label => _("%1 since", $label) };
+
+ # Add the "X until" date/time operation
$args->{"${field}_until"} = { %$info, label => _("%1 until", $label) };
- } elsif( $type =~ /(?:int|float|double|decimal|numeric)/
+ }
+
+ # Handle number fields
+ elsif( $type =~ /(?:int|float|double|decimal|numeric)/
&& !$column->refers_to) {
+
+ # Add the "X greater than" operation
$args->{"${field}_gt"} = { %$info, label => _("%1 greater than", $label) };
+
+ # Add the "X less than" operation
$args->{"${field}_lt"} = { %$info, label => _("%1 less than", $label) };
+
+ # Add the "X greater than or equal to" operation
$args->{"${field}_ge"} = { %$info, label => _("%1 greater or equal to", $label) };
+
+ # Add the "X less than or equal to" operation
$args->{"${field}_le"} = { %$info, label => _("%1 less or equal to", $label) };
+
+ # Add the "X is whatever the heck I say it is" operation
$args->{"${field}_dwim"} = { %$info, hints => _('!=>< allowed') };
}
}
+ # Add generic contains/lacks search boxes for all fields
$args->{contains} = { type => 'text', label => _('Any field contains') };
$args->{lacks} = { type => 'text', label => _('No field contains') };
+ # Cache the results so we don't have to do THAT again
return $self->_cached_arguments($args);
}
@@ -145,30 +194,43 @@
sub take_action {
my $self = shift;
+ # Create a generic collection for our record class
my $collection = Jifty::Collection->new(
record_class => $self->record_class,
current_user => $self->record->current_user
);
+ # Start with an unlimited collection
$collection->unlimit;
+ # For each field, process the limits
for my $field (grep {$self->has_argument($_)} $self->argument_names) {
+
+ # We process contains last, skip it here
next if $field eq 'contains';
+
+ # Get the value set on the field
my $value = $self->argument_value($field);
+ # Load the column this field belongs to
my $column = $self->record->column($field);
my $op = undef;
+ # A comparison or substring search rather than an exact match?
if (!$column) {
+
# If we don't have a column, this is a comparison or
# substring search. Skip undef values for those, since
# NULL makes no sense.
next unless defined($value);
next if $value =~ /^\s*$/;
+ # Decode the field_op name
if ($field =~ m{^(.*)_([[:alpha:]]+)$}) {
$field = $1;
$op = $2;
+
+ # Convert each operator into limit operators
if($op eq 'not') {
$op = '!=';
} elsif($op eq 'contains') {
@@ -193,41 +255,60 @@
$op = '=' if $op eq '==';
}
}
- } else {
+ }
+
+ # Doesn't look like a field_op, skip it
+ else {
next;
}
}
- if(defined($value)) {
- next if $value =~ /^\s*$/;
+ # Now, add the limit if we have a value set
+ if (defined($value)) {
+ next if $value =~ /^\s*$/; # skip blank values!
+ # Allow != and NOT LIKE to match NULL columns
if ($op && $op =~ /^(?:!=|NOT LIKE)$/) {
- $collection->limit( column => $field, value => $value, operator => $op || "=", entry_aggregator => 'OR', $op ? (case_sensitive => 0) : (),);
- $collection->limit( column => $field, value => 'NULL', operator => 'IS');
- } else {
-
+ $collection->limit(
+ column => $field,
+ value => $value,
+ operator => $op,
+ entry_aggregator => 'OR',
+ case_sensitive => 0,
+ );
+ $collection->limit(
+ column => $field,
+ value => 'NULL',
+ operator => 'IS',
+ );
+ }
- $collection->limit(
- column => $field,
- value => $value,
- operator => $op || "=",
- entry_aggregator => 'AND',
- $op ? (case_sensitive => 0) : (),
- );
-
+ # For any others, just the facts please
+ else {
+ $collection->limit(
+ column => $field,
+ value => $value,
+ operator => $op || "=",
+ entry_aggregator => 'AND',
+ $op ? (case_sensitive => 0) : (),
+ );
}
+ }
-
- } else {
+ # The value is not defined at all, so expect a NULL
+ else {
$collection->limit(
column => $field,
value => 'NULL',
operator => 'IS'
- );
+ );
}
}
+ # Handle the general contains last
if($self->has_argument('contains')) {
+
+ # See if any column contains the text described
my $any = $self->argument_value('contains');
for my $col ($self->record->columns) {
if($col->type =~ /(?:text|varchar)/) {
@@ -240,6 +321,7 @@
}
}
+ # Add the limited collection to the results
$self->result->content(search => $collection);
$self->result->success;
}
@@ -248,6 +330,11 @@
L<Jifty::Action::Record>, L<Jifty::Collection>
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
=cut
1;
Modified: jifty/trunk/lib/Jifty/Action/Record/Update.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Record/Update.pm (original)
+++ jifty/trunk/lib/Jifty/Action/Record/Update.pm Sun Aug 12 16:23:46 2007
@@ -33,12 +33,14 @@
my $self = shift;
my $arguments = $self->SUPER::arguments(@_);
+ # Mark read-only columns for read-only display
for my $column ( $self->record->columns ) {
if ( not $column->writable and $column->readable ) {
$arguments->{$column->name}{'render_mode'} = 'read';
}
}
+ # Add the primary keys to constructors and make them mandatory
for my $pk (@{ $self->record->_primary_keys }) {
$arguments->{$pk}{'constructor'} = 1;
$arguments->{$pk}{'mandatory'} = 1;
@@ -64,6 +66,7 @@
sub _validate_arguments {
my $self = shift;
+ # Only validate the arguments given
$self->_validate_argument($_) for grep {
$self->has_argument($_)
or $self->arguments->{$_}->{constructor}
@@ -84,12 +87,16 @@
my $self = shift;
my $changed = 0;
+ # Prepare the event for later publishing
my $event_info = $self->_setup_event_before_action();
+ # Iterate through all the possible arguments
for my $field ( $self->argument_names ) {
+
# Skip values that weren't submitted
next unless $self->has_argument($field);
+ # Load the column object for the field
my $column = $self->record->column($field);
# Skip nonexistent fields
@@ -108,6 +115,7 @@
next if lc $self->arguments->{$field}{render_as} eq "upload"
and (not defined $value or not ref $value);
+ # Handle file uploads
if (ref $value eq "Fh") { # CGI.pm's "lightweight filehandle class"
local $/;
binmode $value;
@@ -127,17 +135,21 @@
next if ( (not defined $old or not length $old)
and ( not defined $value or not length $value ));
+ # Calculate the name of the setter and set; asplode on failure
my $setter = "set_$field";
my ( $val, $msg ) = $self->record->$setter( $value );
$self->result->field_error($field, $msg)
if not $val and $msg;
+ # Remember that we changed something (if we did)
$changed = 1 if $val;
}
+ # Report success if there's a change and no error, otherwise say nah-thing
$self->report_success
if $changed and not $self->result->failure;
+ # Publish the update event
$self->_setup_event_after_action($event_info);
return 1;
@@ -156,4 +168,15 @@
$self->result->message(_("Updated"))
}
+=head1 SEE ALSO
+
+L<Jifty::Action::Record>, L<Jifty::Record>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
1;
Modified: jifty/trunk/lib/Jifty/Action/Redirect.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Redirect.pm (original)
+++ jifty/trunk/lib/Jifty/Action/Redirect.pm Sun Aug 12 16:23:46 2007
@@ -5,11 +5,26 @@
Jifty::Action::Redirect - Redirect the browser
+=head1 SYNOPSIS
+
+ Jifty->web->new_action(
+ class => 'Redirect',
+ arguments => {
+ url => '/my/other/page',
+ },
+ )->run;
+
+=head1 DESCRIPTION
+
+Given a URL, this action forces Jifty to perform a redirect to thast URL after processing the rest of the request.
+
=cut
package Jifty::Action::Redirect;
use base qw/Jifty::Action/;
+=head1 METHODS
+
=head2 new
By default, redirect actions happen as late as possible in the run
@@ -35,10 +50,9 @@
=cut
sub arguments {
- {
- url => { constructor => 1 },
- }
-
+ {
+ url => { constructor => 1 },
+ }
}
=head2 take_action
@@ -52,15 +66,32 @@
sub take_action {
my $self = shift;
+
+ # Return now if the URL is not set
return 1 unless ($self->argument_value('url'));
+
+ # Return now if the response is already sent (i.e., too late to redirect)
return 0 unless Jifty->web->response->success;
+ # Find the URL to redirect to
my $page = $self->argument_value('url');
+ # Set the next page and force the redirect
Jifty->web->next_page($page);
Jifty->web->force_redirect(1);
return 1;
}
+=head1 SEE ALSO
+
+L<Jifty::Action>, L<Jifty::Web/next_page>, L<Jity::Web/force_redirect>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
1;
More information about the Jifty-commit
mailing list