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);
31 my ($self, $plugin) = @_;
32 die 'cannot change internal state after finalize was called' if $self->finalized;
33 die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base');
35 $self->$plugin->disabled(1);
39 my ($self, $plugin) = @_;
40 die 'cannot change internal state after finalize was called' if $self->finalized;
41 die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base');
42 $self->$plugin->disabled(0);
45 sub is_enabled_plugin {
46 my ($self, $plugin) = @_;
47 die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base');
48 $self->$plugin->is_enabled;
51 # TODO: get better delegation
52 sub set_report_generator_sort_options {
53 my ($self, %params) = @_;
56 $self->sorted->set_report_generator_sort_options(%params);
59 sub get_paginate_args {
61 my %params = $self->finalize;
63 $self->paginated->get_current_paginate_params(%params);
72 sub get_current_sort_params {
75 $self->sorted->read_params;
79 my ($self, %params) = @_;
81 my $model = delete $params{model};
82 if (!$model && $params{controller} && ref $params{controller}) {
83 $model = ref $params{controller};
85 die 'Need a valid model' unless $model;
90 for my $plugin (qw(filtered sorted paginated)) {
91 next unless my $spec = delete $params{$plugin} // {};
92 my $plugin_class = "SL::Controller::Helper::GetModels::" . ucfirst $plugin;
93 push @plugins, $self->$plugin($plugin_class->new(%$spec, get_models => $self));
95 $self->plugins(@plugins);
97 $self->SUPER::init(%params);
99 $_->read_params for $self->plugins;
101 weaken $self->controller if $self->controller;
105 my ($self, %params) = @_;
107 return %{ $self->final_params } if $self->finalized;
109 push @{ $params{query} ||= [] }, @{ $self->query || [] };
110 push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] };
112 %params = $_->finalize(%params) for $self->plugins;
115 $self->final_params(\%params);
120 sub register_handlers {
121 my ($self, %additional_handlers) = @_;
123 my $handlers = $self->handlers;
124 map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers;
127 sub get_models_url_params {
128 my ($self, $sub_name_or_code) = @_;
130 my $code = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->controller->$sub_name_or_code(@_) };
132 my ($self, %params) = @_;
133 my @additional_params = $code->($self);
136 (scalar(@additional_params) == 1) && (ref($additional_params[0]) eq 'HASH') ? %{ $additional_params[0] } : @additional_params,
140 $self->registere_handlers('callback' => $callback);
144 my ($self, %override_params) = @_;
146 my %default_params = $self->_run_handlers('callback', action => $self->controller->action_name);
148 return $self->controller->url_for(%default_params, %override_params);
152 die "No 'model' to work on" unless $_[0]->model;
153 "SL::DB::Manager::" . $_[0]->model;
157 # private/internal functions
161 my ($self, $handler_type, %params) = @_;
163 foreach my $sub (@{ $self->handlers->{$handler_type} }) {
164 if (ref $sub eq 'CODE') {
165 %params = $sub->($self, %params);
166 } elsif ($self->can($sub)) {
167 %params = $self->$sub(%params);
169 die "SL::Controller::Helper::GetModels::get_callback: Cannot call $sub on " . ref($self) . ")";
195 SL::Controller::Helper::GetModels - Base class for a the GetModels system.
201 use SL::Controller::Helper::GetModels;
203 my $get_models = SL::Controller::Helper::GetModels->new(
207 my $models = $self->get_models->get;
211 Building a CRUD controller would be easy, were it not for those stupid
212 list actions. People unreasonable expect stuff like filtering, sorting,
213 paginating, exporting etc simply to work. Well, lets try to make it simply work
216 This class is a proxy between a controller and specialized
217 helper modules that handle these things (sorting, paginating etc) and gives you
218 the means to retrieve the information when needed to display sort headers or
221 Information about the requested data query can be stored into the object up to
222 a certain point, from which on the object becomes locked and can only be
223 accessed for information. (See C<STATES>).
225 =head1 INTERFACE METHODS
231 Create a new GetModels object. Params must have at least an entry
232 C<controller>, other than that, see C<CONFIGURATION> for options.
236 Retrieve all models for the current configuration. Will finalize the object.
238 =item get_models_url_params SUB
240 Register a sub to be called whenever an URL has to be generated (e.g. for sort
241 and pagination links). This is a way for the controller to add additional
242 parameters to the URL (e.g. for filter parameters).
244 The parameter can be either a code reference or the name of
245 one of the controller's functions.
247 The value returned by C<SUB> must be either a single hash
248 reference or a hash of key/value pairs to add to the URL.
252 Returns a URL suitable for use as a callback parameter. It maps to the
253 current controller and action. All registered handlers of type
254 'callback' (e.g. the ones by C<Sorted> and C<Paginated>) can inject
255 the parameters they need so that the same list view as is currently
256 visible can be re-rendered.
258 Optional C<%params> passed to this function may override any parameter
259 set by the registered handlers.
261 =item enable_plugin PLUGIN
263 =item disable_plugin PLUGIN
265 =item is_enabled_plugin PLUGIN
267 Enable or disable the specified plugin. Useful to disable paginating for
268 exports for example. C<is_enabled_plugin> can be used to check the current
271 Must not be finalized to use this.
275 Forces finalized state. Can be used on finalized objects without error.
277 Note that most higher functions will call this themselves to force a finalized
278 state. If you do use it it must come before any other finalizing methods, and
279 will most likely function as a reminder or maintainers where your codes
280 switches from configuration to finalized state.
284 The source for user supplied information. Defaults to $::form. Changing it
285 after C<Base> phase has no effect.
287 =item controller CONTROLLER
289 A weakened link to the controller that created the GetModels object. Needed for
290 certain plugin methods.
294 =head1 DELEGATION METHODS
296 All of these finalize.
298 Methods delegating to C<Sorted>:
304 set_report_generator_sort_options
312 get_current_sort_params
316 Methods delegating to C<Paginated>:
328 A GetModels object is in one of 3 states at any given time. Their purpose is to
329 make a class of bugs impossible that orginated from changing the configuration
330 of a GetModels object halfway during the request. This was a huge problem in
331 the old implementation.
337 This is the state after creating a new object.
341 In this state every information needed from the source ($::form) has been read
342 and subsequent changes to the source have no effect. In the current
343 implementation this will happen during creation, so that the return value of
344 C<new> is already in state C<Init>.
348 In this state no new configuration will be accepted so that information gotten
349 through the various methods is consistent. Every information retrieval method
350 will trigger finalize.
357 Most of the configuration will be handed to GetModels on creation via C<new>.
358 This is a list of accepted params.
362 =item controller SELF
364 The creating controller. Currently this is mandatory.
368 The name of the model for this GetModels instance. If none is given, the model
369 is inferred from the name of the controller class.
373 =item paginated PARAMS
375 =item filtered PARAMS
377 Configuration for plugins. If the option for any plugin is omitted, it defaults
378 to enabled and configured by default. Giving a falsish value as first argument
379 will disable the plugin.
381 If the value is a hashref, it will be passed to the plugin's C<init> method.
387 Additional static parts for Rose to include into the final query.
391 Source for plugins to pull their data from. Defaults to $::form.
395 =head1 BUGS AND CAVEATS
401 Delegation is not as clean as it should be. Most of the methods rely on action
402 at a distance and should be moved out.
408 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
410 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>