1 package SL::Controller::Helper::GetModels;
 
   5 use parent 'Rose::Object';
 
   6 use SL::Controller::Helper::GetModels::Filtered;
 
   7 use SL::Controller::Helper::GetModels::Sorted;
 
   8 use SL::Controller::Helper::GetModels::Paginated;
 
  10 use Scalar::Util qw(weaken);
 
  12 use Rose::Object::MakeMethods::Generic (
 
  13   scalar => [ qw(controller model query with_objects filtered sorted paginated finalized final_params) ],
 
  14   'scalar --get_set_init' => [ qw(handlers source list_action additional_url_params) ],
 
  15   array => [ qw(plugins) ],
 
  18 use constant PRIV => '__getmodelshelperpriv';
 
  25   my %params = $self->finalize;
 
  27   return $self->manager->get_all(%params);
 
  32   my %params = $self->finalize;
 
  34   return $self->manager->get_all_count(%params);
 
  38   my ($self, $plugin) = @_;
 
  39   die 'cannot change internal state after finalize was called' if $self->finalized;
 
  40   die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base');
 
  42   $self->$plugin->disabled(1);
 
  46   my ($self, $plugin) = @_;
 
  47   die 'cannot change internal state after finalize was called' if $self->finalized;
 
  48   die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base');
 
  49   $self->$plugin->disabled(0);
 
  52 sub is_enabled_plugin {
 
  53   my ($self, $plugin) = @_;
 
  54   die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base');
 
  55   $self->$plugin->is_enabled;
 
  58 # TODO: get better delegation
 
  59 sub set_report_generator_sort_options {
 
  60   my ($self, %params) = @_;
 
  63   $self->sorted->set_report_generator_sort_options(%params);
 
  66 sub get_paginate_args {
 
  68   my %params = $self->finalize;
 
  70   $self->paginated->get_current_paginate_params(%params);
 
  79 sub get_current_sort_params {
 
  82   $self->sorted->read_params;
 
  86   my ($self, %params) = @_;
 
  88   my $model = delete $params{model};
 
  89   if (!$model && $params{controller} && ref $params{controller}) {
 
  90     $model = ref $params{controller};
 
  92     die 'Need a valid model' unless $model;
 
  97   for my $plugin (qw(filtered sorted paginated)) {
 
  98     next if exists($params{$plugin}) && !$params{$plugin};
 
 100     my $spec         = delete $params{$plugin} // {};
 
 101     my $plugin_class = "SL::Controller::Helper::GetModels::" . ucfirst $plugin;
 
 102     push @plugins, $self->$plugin($plugin_class->new(%$spec, get_models => $self));
 
 104   $self->plugins(@plugins);
 
 106   $self->SUPER::init(%params);
 
 108   $_->read_params for $self->plugins;
 
 110   weaken $self->controller if $self->controller;
 
 114   my ($self, %params) = @_;
 
 116   return %{ $self->final_params } if $self->finalized;
 
 118   $self->register_handlers(callback => sub { shift; (@_, %{ $self->additional_url_params }) }) if %{ $self->additional_url_params };
 
 120   push @{ $params{query}        ||= [] }, @{ $self->query || [] };
 
 121   push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] };
 
 123   %params = $_->finalize(%params) for $self->plugins;
 
 126   $self->final_params(\%params);
 
 131 sub register_handlers {
 
 132   my ($self, %additional_handlers) = @_;
 
 134   my $handlers    = $self->handlers;
 
 135   map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers;
 
 138 sub add_additional_url_params {
 
 139   my ($self, %params) = @_;
 
 141   $self->additional_url_params({ %{ $self->additional_url_params }, %params });
 
 146 sub get_models_url_params {
 
 147   my ($self, $sub_name_or_code) = @_;
 
 149   my $code     = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->controller->$sub_name_or_code(@_) };
 
 151     my ($self, %params)   = @_;
 
 152     my @additional_params = $code->($self);
 
 155       (scalar(@additional_params) == 1) && (ref($additional_params[0]) eq 'HASH') ? %{ $additional_params[0] } : @additional_params,
 
 159   $self->register_handlers('callback' => $callback);
 
 162 sub get_callback_params {
 
 163   my ($self, %override_params) = @_;
 
 165   my %default_params = $self->_run_handlers('callback', action => $self->list_action);
 
 169   my ($self, %override_params) = @_;
 
 171   my %default_params = $self->get_callback_params(%override_params);
 
 173   return $self->controller->url_for(%default_params, %override_params);
 
 177   die "No 'model' to work on" unless $_[0]->model;
 
 178   "SL::DB::Manager::" . $_[0]->model;
 
 182 # private/internal functions
 
 186   my ($self, $handler_type, %params) = @_;
 
 188   foreach my $sub (@{ $self->handlers->{$handler_type} }) {
 
 189     if (ref $sub eq 'CODE') {
 
 190       %params = $sub->($self, %params);
 
 191     } elsif ($self->can($sub)) {
 
 192       %params = $self->$sub(%params);
 
 194       die "SL::Controller::Helper::GetModels::get_callback: Cannot call $sub on " . ref($self) . ")";
 
 211 sub init_list_action {
 
 212   $_[0]->controller->action_name
 
 215 sub init_additional_url_params { +{} }
 
 226 SL::Controller::Helper::GetModels - Base class for the GetModels system.
 
 232   use SL::Controller::Helper::GetModels;
 
 234   my $get_models = SL::Controller::Helper::GetModels->new(
 
 238   my $models = $self->get_models->get;
 
 242 Building a CRUD controller would be easy, were it not for those stupid
 
 243 list actions. People unreasonably expect stuff like filtering, sorting,
 
 244 paginating, exporting etc simply to work. Well, lets try to make it simply work
 
 247 This class is a proxy between a controller and specialized
 
 248 helper modules that handle these things (sorting, paginating etc) and gives you
 
 249 the means to retrieve the information when needed to display sort headers or
 
 252 Information about the requested data query can be stored in the object up to
 
 253 a certain point, from which on the object becomes locked and can only be
 
 254 accessed for information. (See C<STATES>).
 
 256 =head1 INTERFACE METHODS
 
 262 Create a new GetModels object. Params must have at least an entry
 
 263 C<controller>, other than that, see C<CONFIGURATION> for options.
 
 267 Retrieve all models for the current configuration. Will finalize the object.
 
 269 =item get_models_url_params SUB
 
 271 Register a sub to be called whenever an URL has to be generated (e.g. for sort
 
 272 and pagination links). This is a way for the controller to add additional
 
 273 parameters to the URL (e.g. for filter parameters).
 
 275 The parameter can be either a code reference or the name of
 
 276 one of the controller's functions.
 
 278 The value returned by C<SUB> must be either a single hash
 
 279 reference or a hash of key/value pairs to add to the URL.
 
 281 =item add_additional_url_params C<%params>
 
 283 Sets additional parameters that will be added to each URL generated by
 
 284 this model (e.g. for pagination/sorting). This is just sugar for a
 
 285 proper call to L<get_models_url_params> with an anonymous sub adding
 
 290 Returns a URL suitable for use as a callback parameter. It maps to the
 
 291 current controller and action. All registered handlers of type
 
 292 'callback' (e.g. the ones by C<Sorted> and C<Paginated>) can inject
 
 293 the parameters they need so that the same list view as is currently
 
 294 visible can be re-rendered.
 
 296 Optional C<%params> passed to this function may override any parameter
 
 297 set by the registered handlers.
 
 299 =item enable_plugin PLUGIN
 
 301 =item disable_plugin PLUGIN
 
 303 =item is_enabled_plugin PLUGIN
 
 305 Enable or disable the specified plugin. Useful to disable paginating for
 
 306 exports for example. C<is_enabled_plugin> can be used to check the current
 
 309 Must not be finalized to use this.
 
 313 Forces finalized state. Can be used on finalized objects without error.
 
 315 Note that most higher functions will call this themselves to force a finalized
 
 316 state. If you do use it it must come before any other finalizing methods, and
 
 317 will most likely function as a reminder for maintainers where your code
 
 318 switches from configuration to finalized state.
 
 322 The source for user supplied information. Defaults to $::form. Changing it
 
 323 after C<Base> phase has no effect.
 
 325 =item controller CONTROLLER
 
 327 A weakened link to the controller that created the GetModels object. Needed for
 
 328 certain plugin methods.
 
 332 =head1 DELEGATION METHODS
 
 334 All of these finalize.
 
 336 Methods delegating to C<Sorted>:
 
 342 set_report_generator_sort_options
 
 350 get_current_sort_params
 
 354 Methods delegating to C<Paginated>:
 
 366 A GetModels object is in one of 3 states at any given time. Their purpose is to
 
 367 make a class of bugs impossible that orginated from changing the configuration
 
 368 of a GetModels object halfway during the request. This was a huge problem in
 
 369 the old implementation.
 
 375 This is the state after creating a new object.
 
 379 In this state all the information needed from the source ($::form) has been read
 
 380 and subsequent changes to the source have no effect. In the current
 
 381 implementation this will happen during creation, so that the return value of
 
 382 C<new> is already in state C<Init>.
 
 386 In this state no new configuration will be accepted so that information gotten
 
 387 through the various methods is consistent. Every information retrieval method
 
 388 will trigger finalize.
 
 395 Most of the configuration will be handed to GetModels on creation via C<new>.
 
 396 This is a list of accepted params.
 
 400 =item controller SELF
 
 402 The creating controller. Currently this is mandatory.
 
 406 The name of the model for this GetModels instance. If none is given, the model
 
 407 is inferred from the name of the controller class.
 
 409 =item list_action ACTION
 
 411 If callbacks are generated, use this action instead of the current action.
 
 412 Usually you can omit this. In case the reporting is done without redirecting
 
 413 from a mutating action, this is necessary to have callbacks for paginating and
 
 414 sorting point to the correct action.
 
 418 =item paginated PARAMS
 
 420 =item filtered PARAMS
 
 422 Configuration for plugins. If the option for any plugin is omitted, it defaults
 
 423 to enabled and is configured by default. Giving a falsish value as first argument
 
 424 will disable the plugin.
 
 426 If the value is a hashref, it will be passed to the plugin's C<init> method.
 
 432 Additional static parts for Rose to include into the final query.
 
 436 Source for plugins to pull their data from. Defaults to $::form.
 
 440 =head1 BUGS AND CAVEATS
 
 446 Delegation is not as clean as it should be. Most of the methods rely on action
 
 447 at a distance and should be moved out.
 
 453 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
 
 455 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>