X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FController%2FHelper%2FGetModels.pm;h=481f1ee2984feea7252a7f22396e9142eafe8f7a;hb=911756998b29079f7f566c6ef70b938f03a5dbb3;hp=8e2ede133af541cfd8dd433c38fcd6511c51667d;hpb=33d5d38a2873a25f1e9c2c37a907644fdd33f026;p=kivitendo-erp.git diff --git a/SL/Controller/Helper/GetModels.pm b/SL/Controller/Helper/GetModels.pm index 8e2ede133..481f1ee29 100644 --- a/SL/Controller/Helper/GetModels.pm +++ b/SL/Controller/Helper/GetModels.pm @@ -2,30 +2,132 @@ package SL::Controller::Helper::GetModels; use strict; -use Exporter qw(import); -our @EXPORT = qw(get_models_url_params get_callback get_models); +use parent 'Rose::Object'; +use SL::Controller::Helper::GetModels::Filtered; +use SL::Controller::Helper::GetModels::Sorted; +use SL::Controller::Helper::GetModels::Paginated; + +use Scalar::Util qw(weaken); + +use Rose::Object::MakeMethods::Generic ( + scalar => [ qw(controller model query with_objects filtered sorted paginated finalized final_params) ], + 'scalar --get_set_init' => [ qw(handlers source) ], + array => [ qw(plugins) ], +); use constant PRIV => '__getmodelshelperpriv'; -my $registered_handlers = {}; -sub register_get_models_handlers { - my ($class, %additional_handlers) = @_; +# official interface + +sub get { + my ($self) = @_; + my %params = $self->finalize; + + return $self->manager->get_all(%params); +} + +sub disable_plugin { + my ($self, $plugin) = @_; + die 'cannot change internal state after finalize was called' if $self->finalized; + die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base'); + + $self->$plugin->disabled(1); +} + +sub enable_plugin { + my ($self, $plugin) = @_; + die 'cannot change internal state after finalize was called' if $self->finalized; + die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base'); + $self->$plugin->disabled(0); +} + +sub is_enabled_plugin { + my ($self, $plugin) = @_; + die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base'); + $self->$plugin->is_enabled; +} + +# TODO: get better delegation +sub set_report_generator_sort_options { + my ($self, %params) = @_; + $self->finalize; + + $self->sorted->set_report_generator_sort_options(%params); +} + +sub get_paginate_args { + my ($self) = @_; + my %params = $self->finalize; + + $self->paginated->get_current_paginate_params(%params); +} + +sub get_sort_spec { + my ($self) = @_; + + $self->sorted->specs; +} + +sub get_current_sort_params { + my ($self) = @_; + + $self->sorted->read_params; +} + +sub init { + my ($self, %params) = @_; + + my $model = delete $params{model}; + if (!$model && $params{controller} && ref $params{controller}) { + $model = ref $params{controller}; + $model =~ s/.*:://; + die 'Need a valid model' unless $model; + } + $self->model($model); + + my @plugins; + for my $plugin (qw(filtered sorted paginated)) { + next unless my $spec = delete $params{$plugin} // {}; + my $plugin_class = "SL::Controller::Helper::GetModels::" . ucfirst $plugin; + push @plugins, $self->$plugin($plugin_class->new(%$spec, get_models => $self)); + } + $self->plugins(@plugins); + + $self->SUPER::init(%params); - my $only = delete($additional_handlers{ONLY}) || []; - $only = [ $only ] if !ref $only; - my %hook_params = @{ $only } ? ( only => $only ) : (); + $_->read_params for $self->plugins; + + weaken $self->controller if $self->controller; +} - $class->run_before(sub { $_[0]->{PRIV()} = { current_action => $_[1] }; }, %hook_params); +sub finalize { + my ($self, %params) = @_; - my $handlers = _registered_handlers($class); + return %{ $self->final_params } if $self->finalized; + + push @{ $params{query} ||= [] }, @{ $self->query || [] }; + push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] }; + + %params = $_->finalize(%params) for $self->plugins; + + $self->finalized(1); + $self->final_params(\%params); + + return %params; +} + +sub register_handlers { + my ($self, %additional_handlers) = @_; + + my $handlers = $self->handlers; map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers; } sub get_models_url_params { - my ($class, $sub_name_or_code) = @_; + my ($self, $sub_name_or_code) = @_; - my $code = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->$sub_name_or_code(@_) }; + my $code = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->controller->$sub_name_or_code(@_) }; my $callback = sub { my ($self, %params) = @_; my @additional_params = $code->($self); @@ -35,25 +137,20 @@ sub get_models_url_params { ); }; - push @{ _registered_handlers($class)->{callback} }, $callback; + $self->registere_handlers('callback' => $callback); } sub get_callback { my ($self, %override_params) = @_; - my %default_params = _run_handlers($self, 'callback', action => ($self->{PRIV()} || {})->{current_action}); + my %default_params = $self->_run_handlers('callback', action => $self->controller->action_name); - return $self->url_for(%default_params, %override_params); + return $self->controller->url_for(%default_params, %override_params); } -sub get_models { - my ($self, %override_params) = @_; - - my %params = _run_handlers($self, 'get_models', %override_params); - - my $model = delete($params{model}) || die "No 'model' to work on"; - - return "SL::DB::Manager::${model}"->get_all(%params); +sub manager { + die "No 'model' to work on" unless $_[0]->model; + "SL::DB::Manager::" . $_[0]->model; } # @@ -63,7 +160,7 @@ sub get_models { sub _run_handlers { my ($self, $handler_type, %params) = @_; - foreach my $sub (@{ _registered_handlers(ref $self)->{$handler_type} }) { + foreach my $sub (@{ $self->handlers->{$handler_type} }) { if (ref $sub eq 'CODE') { %params = $sub->($self, %params); } elsif ($self->can($sub)) { @@ -76,8 +173,14 @@ sub _run_handlers { return %params; } -sub _registered_handlers { - $registered_handlers->{$_[0]} //= { callback => [], get_models => [] } +sub init_handlers { + { + callback => [], + } +} + +sub init_source { + $::form } 1; @@ -89,112 +192,221 @@ __END__ =head1 NAME -SL::Controller::Helper::GetModels - Base mixin for controller helpers -dealing with semi-automatic handling of sorting and paginating lists +SL::Controller::Helper::GetModels - Base class for a the GetModels system. =head1 SYNOPSIS -For a proper synopsis see L. +In controller: -=head1 OVERVIEW + use SL::Controller::Helper::GetModels; -For a generic overview see L. + my $get_models = SL::Controller::Helper::GetModels->new( + controller => $self, + ); -This base module is the interface between a controller and specialized -helper modules that handle things like sorting and paginating. The -specialized helpers register themselves with this module via a call to -L during compilation time (e.g. in the -case of C this happens when the controller calls -L). + my $models = $self->get_models->get; -A controller will later usually call the L -function. Templates will call the L function. Both -functions run the registered handlers handing over control to the -specialized helpers so that they may inject their parameters into the -call chain. +=head1 OVERVIEW + +Building a CRUD controller would be easy, were it not for those stupid +list actions. People unreasonable expect stuff like filtering, sorting, +paginating, exporting etc simply to work. Well, lets try to make it simply work +a little. -The C helper hooks into the controller call to the action -via a C hook. This is done so that it can remember the -action called by the user. This is used for constructing the callback -in L. +This class is a proxy between a controller and specialized +helper modules that handle these things (sorting, paginating etc) and gives you +the means to retrieve the information when needed to display sort headers or +paginating footers. -=head1 PACKAGE FUNCTIONS +Information about the requested data query can be stored into the object up to +a certain point, from which on the object becomes locked and can only be +accessed for information. (See C). + +=head1 INTERFACE METHODS =over 4 -=item C +=item new PARAMS + +Create a new GetModels object. Params must have at least an entry +C, other than that, see C for options. -Register one of the controller's subs to be called whenever an URL has -to be generated (e.g. for sort and pagination links). This is a way -for the controller to add additional parameters to the URL (e.g. for -filter parameters). +=item get -The C<$sub> parameter can be either a code reference or the name of +Retrieve all models for the current configuration. Will finalize the object. + +=item get_models_url_params SUB + +Register a sub to be called whenever an URL has to be generated (e.g. for sort +and pagination links). This is a way for the controller to add additional +parameters to the URL (e.g. for filter parameters). + +The parameter can be either a code reference or the name of one of the controller's functions. -The value returned by this C<$sub> must be either a single hash +The value returned by C must be either a single hash reference or a hash of key/value pairs to add to the URL. -=item C +=item get_callback + +Returns a URL suitable for use as a callback parameter. It maps to the +current controller and action. All registered handlers of type +'callback' (e.g. the ones by C and C) can inject +the parameters they need so that the same list view as is currently +visible can be re-rendered. + +Optional C<%params> passed to this function may override any parameter +set by the registered handlers. + +=item enable_plugin PLUGIN + +=item disable_plugin PLUGIN + +=item is_enabled_plugin PLUGIN + +Enable or disable the specified plugin. Useful to disable paginating for +exports for example. C can be used to check the current +state fo a plugin. + +Must not be finalized to use this. + +=item finalize + +Forces finalized state. Can be used on finalized objects without error. + +Note that most higher functions will call this themselves to force a finalized +state. If you do use it it must come before any other finalizing methods, and +will most likely function as a reminder or maintainers where your codes +switches from configuration to finalized state. -This function should only be called from other controller helpers like -C or C. It is not exported and must therefore be -called its full name. The first parameter C<$class> must be the actual -controller's class name. +=item source HASHREF -If C<%handlers> contains a key C then it is passed to the hook -registration in L. +The source for user supplied information. Defaults to $::form. Changing it +after C phase has no effect. -The C<%handlers> register callback functions in the specialized -controller helpers that are called during invocation of -L or L. Possible keys are C and -C. +=item controller CONTROLLER -Each handler (the value in the hash) can be either a code reference -(in which case it is called directly) or the name of an instance -function callable on a controller instance. In both cases the handler -receives a hash of parameters built during this very call to -L or L respectively. The handler's return -value must be the new hash to be used in calls to further handlers and -to the actual database model functions later on. +A weakened link to the controller that created the GetModels object. Needed for +certain plugin methods. =back -=head1 INSTANCE FUNCTIONS +=head1 DELEGATION METHODS + +All of these finalize. + +Methods delegating to C: =over 4 -=item C +=item * -Return an URL suitable for use as a callback parameter. It maps to the -current controller and action. All registered handlers of type -'callback' (e.g. the ones by C and C) can inject -the parameters they need so that the same list view as is currently -visible can be re-rendered. +set_report_generator_sort_options -Optional C<%params> passed to this function may override any parameter -set by the registered handlers. +=item * -=item C +get_sort_spec -Query the model manager via C and return its result. The -parameters to C are constructed by calling all registered -handlers of type 'models' (e.g. the ones by C and -C). +=item * -Optional C<%params> passed to this function may override any parameter -set by the registered handlers. +get_current_sort_params + +=back + +Methods delegating to C: + +=over 4 + +=item * -The return value is the an array reference of C models. +get_paginate_args =back -=head1 BUGS +=head1 STATES -Nothing here yet. +A GetModels object is in one of 3 states at any given time. Their purpose is to +make a class of bugs impossible that orginated from changing the configuration +of a GetModels object halfway during the request. This was a huge problem in +the old implementation. -=head1 AUTHOR +=over 4 + +=item Base + +This is the state after creating a new object. + +=item Init + +In this state every information needed from the source ($::form) has been read +and subsequent changes to the source have no effect. In the current +implementation this will happen during creation, so that the return value of +C is already in state C. + +=item Finalized + +In this state no new configuration will be accepted so that information gotten +through the various methods is consistent. Every information retrieval method +will trigger finalize. + +=back + + +=head1 CONFIGURATION + +Most of the configuration will be handed to GetModels on creation via C. +This is a list of accepted params. + +=over 4 + +=item controller SELF + +The creating controller. Currently this is mandatory. + +=item model MODEL + +The name of the model for this GetModels instance. If none is given, the model +is inferred from the name of the controller class. + +=item sorted PARAMS + +=item paginated PARAMS + +=item filtered PARAMS + +Configuration for plugins. If the option for any plugin is omitted, it defaults +to enabled and configured by default. Giving a falsish value as first argument +will disable the plugin. + +If the value is a hashref, it will be passed to the plugin's C method. + +=item query + +=item with_objects + +Additional static parts for Rose to include into the final query. + +=item source + +Source for plugins to pull their data from. Defaults to $::form. + +=back + +=head1 BUGS AND CAVEATS + +=over 4 + +=item * + +Delegation is not as clean as it should be. Most of the methods rely on action +at a distance and should be moved out. + +=back + +=head1 AUTHORS Moritz Bunkus Em.bunkus@linet-services.deE +Sven Schöling Es.schoeling@linet-services.deE + =cut