Project

General

Profile

Download (11.1 KB) Statistics
| Branch: | Tag: | Revision:

kivitendo / SL / Controller / Helper / GetModels.pm @ 1413b1a8

1
package SL::Controller::Helper::GetModels;
2

    
3
use strict;
4

    
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;
9

    
10
use Scalar::Util qw(weaken);
11

    
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) ],
16
);
17

    
18
use constant PRIV => '__getmodelshelperpriv';
19

    
20

    
21
# official interface
22

    
23
sub get {
24
  my ($self) = @_;
25
  my %params = $self->finalize;
26

    
27
  return $self->manager->get_all(%params);
28
}
29

    
30
sub count {
31
  my ($self) = @_;
32
  my %params = $self->finalize;
33

    
34
  return $self->manager->get_all_count(%params);
35
}
36

    
37
sub disable_plugin {
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');
41

    
42
  $self->$plugin->disabled(1);
43
}
44

    
45
sub enable_plugin {
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);
50
}
51

    
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;
56
}
57

    
58
# TODO: get better delegation
59
sub set_report_generator_sort_options {
60
  my ($self, %params) = @_;
61
  $self->finalize;
62

    
63
  $self->sorted->set_report_generator_sort_options(%params);
64
}
65

    
66
sub get_paginate_args {
67
  my ($self) = @_;
68
  my %params = $self->finalize;
69

    
70
  $self->paginated->get_current_paginate_params(%params);
71
}
72

    
73
sub get_sort_spec {
74
  my ($self) = @_;
75

    
76
  $self->sorted->specs;
77
}
78

    
79
sub get_current_sort_params {
80
  my ($self) = @_;
81

    
82
  $self->sorted->read_params;
83
}
84

    
85
sub init {
86
  my ($self, %params) = @_;
87

    
88
  my $model = delete $params{model};
89
  if (!$model && $params{controller} && ref $params{controller}) {
90
    $model = ref $params{controller};
91
    $model =~ s/.*:://;
92
    die 'Need a valid model' unless $model;
93
  }
94
  $self->model($model);
95

    
96
  my @plugins;
97
  for my $plugin (qw(filtered sorted paginated)) {
98
    next if exists($params{$plugin}) && !$params{$plugin};
99

    
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));
103
  }
104
  $self->plugins(@plugins);
105

    
106
  $self->SUPER::init(%params);
107

    
108
  $_->read_params for $self->plugins;
109

    
110
  weaken $self->controller if $self->controller;
111
}
112

    
113
sub finalize {
114
  my ($self, %params) = @_;
115

    
116
  return %{ $self->final_params } if $self->finalized;
117

    
118
  $self->register_handlers(callback => sub { shift; (@_, %{ $self->additional_url_params }) }) if %{ $self->additional_url_params };
119

    
120
  push @{ $params{query}        ||= [] }, @{ $self->query || [] };
121
  push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] };
122

    
123
  %params = $_->finalize(%params) for $self->plugins;
124

    
125
  $self->finalized(1);
126
  $self->final_params(\%params);
127

    
128
  return %params;
129
}
130

    
131
sub register_handlers {
132
  my ($self, %additional_handlers) = @_;
133

    
134
  my $handlers    = $self->handlers;
135
  map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers;
136
}
137

    
138
sub add_additional_url_params {
139
  my ($self, %params) = @_;
140

    
141
  $self->additional_url_params({ %{ $self->additional_url_params }, %params });
142

    
143
  return $self;
144
}
145

    
146
sub get_models_url_params {
147
  my ($self, $sub_name_or_code) = @_;
148

    
149
  my $code     = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->controller->$sub_name_or_code(@_) };
150
  my $callback = sub {
151
    my ($self, %params)   = @_;
152
    my @additional_params = $code->($self);
153
    return (
154
      %params,
155
      (scalar(@additional_params) == 1) && (ref($additional_params[0]) eq 'HASH') ? %{ $additional_params[0] } : @additional_params,
156
    );
157
  };
158

    
159
  $self->register_handlers('callback' => $callback);
160
}
161

    
162
sub get_callback_params {
163
  my ($self, %override_params) = @_;
164

    
165
  my %default_params = $self->_run_handlers('callback', action => $self->list_action);
166
}
167

    
168
sub get_callback {
169
  my ($self, %override_params) = @_;
170

    
171
  my %default_params = $self->get_callback_params(%override_params);
172

    
173
  return $self->controller->url_for(%default_params, %override_params);
174
}
175

    
176
sub manager {
177
  die "No 'model' to work on" unless $_[0]->model;
178
  "SL::DB::Manager::" . $_[0]->model;
179
}
180

    
181
#
182
# private/internal functions
183
#
184

    
185
sub _run_handlers {
186
  my ($self, $handler_type, %params) = @_;
187

    
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);
193
    } else {
194
      die "SL::Controller::Helper::GetModels::get_callback: Cannot call $sub on " . ref($self) . ")";
195
    }
196
  }
197

    
198
  return %params;
199
}
200

    
201
sub init_handlers {
202
  {
203
    callback => [],
204
  }
205
}
206

    
207
sub init_source {
208
  $::form
209
}
210

    
211
sub init_list_action {
212
  $_[0]->controller->action_name
213
}
214

    
215
sub init_additional_url_params { +{} }
216

    
217
1;
218
__END__
219

    
220
=pod
221

    
222
=encoding utf8
223

    
224
=head1 NAME
225

    
226
SL::Controller::Helper::GetModels - Base class for the GetModels system.
227

    
228
=head1 SYNOPSIS
229

    
230
In controller:
231

    
232
  use SL::Controller::Helper::GetModels;
233

    
234
  my $get_models = SL::Controller::Helper::GetModels->new(
235
    controller   => $self,
236
  );
237

    
238
  my $models = $self->get_models->get;
239

    
240
=head1 OVERVIEW
241

    
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
245
a little.
246

    
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
250
paginating footers.
251

    
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>).
255

    
256
=head1 INTERFACE METHODS
257

    
258
=over 4
259

    
260
=item new PARAMS
261

    
262
Create a new GetModels object. Params must have at least an entry
263
C<controller>, other than that, see C<CONFIGURATION> for options.
264

    
265
=item get
266

    
267
Retrieve all models for the current configuration. Will finalize the object.
268

    
269
=item get_models_url_params SUB
270

    
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).
274

    
275
The parameter can be either a code reference or the name of
276
one of the controller's functions.
277

    
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.
280

    
281
=item add_additional_url_params C<%params>
282

    
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
286
those parameters.
287

    
288
=item get_callback
289

    
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.
295

    
296
Optional C<%params> passed to this function may override any parameter
297
set by the registered handlers.
298

    
299
=item enable_plugin PLUGIN
300

    
301
=item disable_plugin PLUGIN
302

    
303
=item is_enabled_plugin PLUGIN
304

    
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
307
state of a plugin.
308

    
309
Must not be finalized to use this.
310

    
311
=item finalize
312

    
313
Forces finalized state. Can be used on finalized objects without error.
314

    
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.
319

    
320
=item source HASHREF
321

    
322
The source for user supplied information. Defaults to $::form. Changing it
323
after C<Base> phase has no effect.
324

    
325
=item controller CONTROLLER
326

    
327
A weakened link to the controller that created the GetModels object. Needed for
328
certain plugin methods.
329

    
330
=back
331

    
332
=head1 DELEGATION METHODS
333

    
334
All of these finalize.
335

    
336
Methods delegating to C<Sorted>:
337

    
338
=over 4
339

    
340
=item *
341

    
342
set_report_generator_sort_options
343

    
344
=item *
345

    
346
get_sort_spec
347

    
348
=item *
349

    
350
get_current_sort_params
351

    
352
=back
353

    
354
Methods delegating to C<Paginated>:
355

    
356
=over 4
357

    
358
=item *
359

    
360
get_paginate_args
361

    
362
=back
363

    
364
=head1 STATES
365

    
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.
370

    
371
=over 4
372

    
373
=item Base
374

    
375
This is the state after creating a new object.
376

    
377
=item Init
378

    
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>.
383

    
384
=item Finalized
385

    
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.
389

    
390
=back
391

    
392

    
393
=head1 CONFIGURATION
394

    
395
Most of the configuration will be handed to GetModels on creation via C<new>.
396
This is a list of accepted params.
397

    
398
=over 4
399

    
400
=item controller SELF
401

    
402
The creating controller. Currently this is mandatory.
403

    
404
=item model MODEL
405

    
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.
408

    
409
=item list_action ACTION
410

    
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.
415

    
416
=item sorted PARAMS
417

    
418
=item paginated PARAMS
419

    
420
=item filtered PARAMS
421

    
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.
425

    
426
If the value is a hashref, it will be passed to the plugin's C<init> method.
427

    
428
=item query
429

    
430
=item with_objects
431

    
432
Additional static parts for Rose to include into the final query.
433

    
434
=item source
435

    
436
Source for plugins to pull their data from. Defaults to $::form.
437

    
438
=back
439

    
440
=head1 BUGS AND CAVEATS
441

    
442
=over 4
443

    
444
=item *
445

    
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.
448

    
449
=back
450

    
451
=head1 AUTHORS
452

    
453
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
454

    
455
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
456

    
457
=cut