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 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 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 $self->register_handlers(callback => sub { shift; (@_, %{ $self->additional_url_params }) }) if %{ $self->additional_url_params };
118 push @{ $params{query} ||= [] }, @{ $self->query || [] };
119 push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] };
121 %params = $_->finalize(%params) for $self->plugins;
124 $self->final_params(\%params);
129 sub register_handlers {
130 my ($self, %additional_handlers) = @_;
132 my $handlers = $self->handlers;
133 map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers;
136 sub add_additional_url_params {
137 my ($self, %params) = @_;
139 $self->additional_url_params({ %{ $self->additional_url_params }, %params });
144 sub get_models_url_params {
145 my ($self, $sub_name_or_code) = @_;
147 my $code = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->controller->$sub_name_or_code(@_) };
149 my ($self, %params) = @_;
150 my @additional_params = $code->($self);
153 (scalar(@additional_params) == 1) && (ref($additional_params[0]) eq 'HASH') ? %{ $additional_params[0] } : @additional_params,
157 $self->register_handlers('callback' => $callback);
161 my ($self, %override_params) = @_;
163 my %default_params = $self->_run_handlers('callback', action => $self->controller->action_name);
165 return $self->controller->url_for(%default_params, %override_params);
169 die "No 'model' to work on" unless $_[0]->model;
170 "SL::DB::Manager::" . $_[0]->model;
174 # private/internal functions
178 my ($self, $handler_type, %params) = @_;
180 foreach my $sub (@{ $self->handlers->{$handler_type} }) {
181 if (ref $sub eq 'CODE') {
182 %params = $sub->($self, %params);
183 } elsif ($self->can($sub)) {
184 %params = $self->$sub(%params);
186 die "SL::Controller::Helper::GetModels::get_callback: Cannot call $sub on " . ref($self) . ")";
203 sub init_additional_url_params { +{} }
214 SL::Controller::Helper::GetModels - Base class for the GetModels system.
220 use SL::Controller::Helper::GetModels;
222 my $get_models = SL::Controller::Helper::GetModels->new(
226 my $models = $self->get_models->get;
230 Building a CRUD controller would be easy, were it not for those stupid
231 list actions. People unreasonably expect stuff like filtering, sorting,
232 paginating, exporting etc simply to work. Well, lets try to make it simply work
235 This class is a proxy between a controller and specialized
236 helper modules that handle these things (sorting, paginating etc) and gives you
237 the means to retrieve the information when needed to display sort headers or
240 Information about the requested data query can be stored in the object up to
241 a certain point, from which on the object becomes locked and can only be
242 accessed for information. (See C<STATES>).
244 =head1 INTERFACE METHODS
250 Create a new GetModels object. Params must have at least an entry
251 C<controller>, other than that, see C<CONFIGURATION> for options.
255 Retrieve all models for the current configuration. Will finalize the object.
257 =item get_models_url_params SUB
259 Register a sub to be called whenever an URL has to be generated (e.g. for sort
260 and pagination links). This is a way for the controller to add additional
261 parameters to the URL (e.g. for filter parameters).
263 The parameter can be either a code reference or the name of
264 one of the controller's functions.
266 The value returned by C<SUB> must be either a single hash
267 reference or a hash of key/value pairs to add to the URL.
269 =item add_additional_url_params C<%params>
271 Sets additional parameters that will be added to each URL generated by
272 this model (e.g. for pagination/sorting). This is just sugar for a
273 proper call to L<get_models_url_params> with an anonymous sub adding
278 Returns a URL suitable for use as a callback parameter. It maps to the
279 current controller and action. All registered handlers of type
280 'callback' (e.g. the ones by C<Sorted> and C<Paginated>) can inject
281 the parameters they need so that the same list view as is currently
282 visible can be re-rendered.
284 Optional C<%params> passed to this function may override any parameter
285 set by the registered handlers.
287 =item enable_plugin PLUGIN
289 =item disable_plugin PLUGIN
291 =item is_enabled_plugin PLUGIN
293 Enable or disable the specified plugin. Useful to disable paginating for
294 exports for example. C<is_enabled_plugin> can be used to check the current
297 Must not be finalized to use this.
301 Forces finalized state. Can be used on finalized objects without error.
303 Note that most higher functions will call this themselves to force a finalized
304 state. If you do use it it must come before any other finalizing methods, and
305 will most likely function as a reminder for maintainers where your code
306 switches from configuration to finalized state.
310 The source for user supplied information. Defaults to $::form. Changing it
311 after C<Base> phase has no effect.
313 =item controller CONTROLLER
315 A weakened link to the controller that created the GetModels object. Needed for
316 certain plugin methods.
320 =head1 DELEGATION METHODS
322 All of these finalize.
324 Methods delegating to C<Sorted>:
330 set_report_generator_sort_options
338 get_current_sort_params
342 Methods delegating to C<Paginated>:
354 A GetModels object is in one of 3 states at any given time. Their purpose is to
355 make a class of bugs impossible that orginated from changing the configuration
356 of a GetModels object halfway during the request. This was a huge problem in
357 the old implementation.
363 This is the state after creating a new object.
367 In this state all the information needed from the source ($::form) has been read
368 and subsequent changes to the source have no effect. In the current
369 implementation this will happen during creation, so that the return value of
370 C<new> is already in state C<Init>.
374 In this state no new configuration will be accepted so that information gotten
375 through the various methods is consistent. Every information retrieval method
376 will trigger finalize.
383 Most of the configuration will be handed to GetModels on creation via C<new>.
384 This is a list of accepted params.
388 =item controller SELF
390 The creating controller. Currently this is mandatory.
394 The name of the model for this GetModels instance. If none is given, the model
395 is inferred from the name of the controller class.
399 =item paginated PARAMS
401 =item filtered PARAMS
403 Configuration for plugins. If the option for any plugin is omitted, it defaults
404 to enabled and is configured by default. Giving a falsish value as first argument
405 will disable the plugin.
407 If the value is a hashref, it will be passed to the plugin's C<init> method.
413 Additional static parts for Rose to include into the final query.
417 Source for plugins to pull their data from. Defaults to $::form.
421 =head1 BUGS AND CAVEATS
427 Delegation is not as clean as it should be. Most of the methods rely on action
428 at a distance and should be moved out.
434 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
436 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>