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) ],
 
  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 unless my $spec = delete $params{$plugin} // {};
 
  99     my $plugin_class = "SL::Controller::Helper::GetModels::" . ucfirst $plugin;
 
 100     push @plugins, $self->$plugin($plugin_class->new(%$spec, get_models => $self));
 
 102   $self->plugins(@plugins);
 
 104   $self->SUPER::init(%params);
 
 106   $_->read_params for $self->plugins;
 
 108   weaken $self->controller if $self->controller;
 
 112   my ($self, %params) = @_;
 
 114   return %{ $self->final_params } if $self->finalized;
 
 116   push @{ $params{query}        ||= [] }, @{ $self->query || [] };
 
 117   push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] };
 
 119   %params = $_->finalize(%params) for $self->plugins;
 
 122   $self->final_params(\%params);
 
 127 sub register_handlers {
 
 128   my ($self, %additional_handlers) = @_;
 
 130   my $handlers    = $self->handlers;
 
 131   map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers;
 
 134 sub get_models_url_params {
 
 135   my ($self, $sub_name_or_code) = @_;
 
 137   my $code     = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->controller->$sub_name_or_code(@_) };
 
 139     my ($self, %params)   = @_;
 
 140     my @additional_params = $code->($self);
 
 143       (scalar(@additional_params) == 1) && (ref($additional_params[0]) eq 'HASH') ? %{ $additional_params[0] } : @additional_params,
 
 147   $self->registere_handlers('callback' => $callback);
 
 151   my ($self, %override_params) = @_;
 
 153   my %default_params = $self->_run_handlers('callback', action => $self->controller->action_name);
 
 155   return $self->controller->url_for(%default_params, %override_params);
 
 159   die "No 'model' to work on" unless $_[0]->model;
 
 160   "SL::DB::Manager::" . $_[0]->model;
 
 164 # private/internal functions
 
 168   my ($self, $handler_type, %params) = @_;
 
 170   foreach my $sub (@{ $self->handlers->{$handler_type} }) {
 
 171     if (ref $sub eq 'CODE') {
 
 172       %params = $sub->($self, %params);
 
 173     } elsif ($self->can($sub)) {
 
 174       %params = $self->$sub(%params);
 
 176       die "SL::Controller::Helper::GetModels::get_callback: Cannot call $sub on " . ref($self) . ")";
 
 202 SL::Controller::Helper::GetModels - Base class for a the GetModels system.
 
 208   use SL::Controller::Helper::GetModels;
 
 210   my $get_models = SL::Controller::Helper::GetModels->new(
 
 214   my $models = $self->get_models->get;
 
 218 Building a CRUD controller would be easy, were it not for those stupid
 
 219 list actions. People unreasonable expect stuff like filtering, sorting,
 
 220 paginating, exporting etc simply to work. Well, lets try to make it simply work
 
 223 This class is a proxy between a controller and specialized
 
 224 helper modules that handle these things (sorting, paginating etc) and gives you
 
 225 the means to retrieve the information when needed to display sort headers or
 
 228 Information about the requested data query can be stored into the object up to
 
 229 a certain point, from which on the object becomes locked and can only be
 
 230 accessed for information. (See C<STATES>).
 
 232 =head1 INTERFACE METHODS
 
 238 Create a new GetModels object. Params must have at least an entry
 
 239 C<controller>, other than that, see C<CONFIGURATION> for options.
 
 243 Retrieve all models for the current configuration. Will finalize the object.
 
 245 =item get_models_url_params SUB
 
 247 Register a sub to be called whenever an URL has to be generated (e.g. for sort
 
 248 and pagination links). This is a way for the controller to add additional
 
 249 parameters to the URL (e.g. for filter parameters).
 
 251 The parameter can be either a code reference or the name of
 
 252 one of the controller's functions.
 
 254 The value returned by C<SUB> must be either a single hash
 
 255 reference or a hash of key/value pairs to add to the URL.
 
 259 Returns a URL suitable for use as a callback parameter. It maps to the
 
 260 current controller and action. All registered handlers of type
 
 261 'callback' (e.g. the ones by C<Sorted> and C<Paginated>) can inject
 
 262 the parameters they need so that the same list view as is currently
 
 263 visible can be re-rendered.
 
 265 Optional C<%params> passed to this function may override any parameter
 
 266 set by the registered handlers.
 
 268 =item enable_plugin PLUGIN
 
 270 =item disable_plugin PLUGIN
 
 272 =item is_enabled_plugin PLUGIN
 
 274 Enable or disable the specified plugin. Useful to disable paginating for
 
 275 exports for example. C<is_enabled_plugin> can be used to check the current
 
 278 Must not be finalized to use this.
 
 282 Forces finalized state. Can be used on finalized objects without error.
 
 284 Note that most higher functions will call this themselves to force a finalized
 
 285 state. If you do use it it must come before any other finalizing methods, and
 
 286 will most likely function as a reminder or maintainers where your codes
 
 287 switches from configuration to finalized state.
 
 291 The source for user supplied information. Defaults to $::form. Changing it
 
 292 after C<Base> phase has no effect.
 
 294 =item controller CONTROLLER
 
 296 A weakened link to the controller that created the GetModels object. Needed for
 
 297 certain plugin methods.
 
 301 =head1 DELEGATION METHODS
 
 303 All of these finalize.
 
 305 Methods delegating to C<Sorted>:
 
 311 set_report_generator_sort_options
 
 319 get_current_sort_params
 
 323 Methods delegating to C<Paginated>:
 
 335 A GetModels object is in one of 3 states at any given time. Their purpose is to
 
 336 make a class of bugs impossible that orginated from changing the configuration
 
 337 of a GetModels object halfway during the request. This was a huge problem in
 
 338 the old implementation.
 
 344 This is the state after creating a new object.
 
 348 In this state every information needed from the source ($::form) has been read
 
 349 and subsequent changes to the source have no effect. In the current
 
 350 implementation this will happen during creation, so that the return value of
 
 351 C<new> is already in state C<Init>.
 
 355 In this state no new configuration will be accepted so that information gotten
 
 356 through the various methods is consistent. Every information retrieval method
 
 357 will trigger finalize.
 
 364 Most of the configuration will be handed to GetModels on creation via C<new>.
 
 365 This is a list of accepted params.
 
 369 =item controller SELF
 
 371 The creating controller. Currently this is mandatory.
 
 375 The name of the model for this GetModels instance. If none is given, the model
 
 376 is inferred from the name of the controller class.
 
 380 =item paginated PARAMS
 
 382 =item filtered PARAMS
 
 384 Configuration for plugins. If the option for any plugin is omitted, it defaults
 
 385 to enabled and configured by default. Giving a falsish value as first argument
 
 386 will disable the plugin.
 
 388 If the value is a hashref, it will be passed to the plugin's C<init> method.
 
 394 Additional static parts for Rose to include into the final query.
 
 398 Source for plugins to pull their data from. Defaults to $::form.
 
 402 =head1 BUGS AND CAVEATS
 
 408 Delegation is not as clean as it should be. Most of the methods rely on action
 
 409 at a distance and should be moved out.
 
 415 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
 
 417 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>