Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision c805dfd9

Von Moritz Bunkus vor mehr als 10 Jahren hinzugefügt

  • ID c805dfd964cc6b4d24868b1835548024363e1f0f
  • Vorgänger c26b5ac5
  • Nachfolger 3cae447e

Clipboard: eine erste allgemeine Implementation eines Clipboard-Mechanismus-Backends

Enthält bereits Spezialisierungen für Pflichtenhefttextblöcke und
-items (Abschnitte, Funktionsblöcke, Unterfunktionsblöcke).

Unterschiede anzeigen:

SL/Clipboard.pm
1
package SL::Clipboard;
2

  
3
use strict;
4

  
5
use parent qw(Rose::Object);
6

  
7
use Rose::Object::MakeMethods::Generic (
8
  'scalar --get_set_init' => [ qw(content) ],
9
);
10

  
11
use Carp;
12
use List::MoreUtils qw(apply);
13
use List::Util qw(first);
14
use Scalar::Util qw(blessed);
15

  
16
use SL::Clipboard::RequirementSpecItem;
17
use SL::Clipboard::RequirementSpecTextBlock;
18

  
19
sub init_content {
20
  my $value = $::auth->get_session_value('clipboard-content');
21
  return ref($value) eq 'HASH' ? $value : { entries => [] };
22
}
23

  
24
sub copy {
25
  my ($self, $object) = @_;
26

  
27
  my $copied = $self->_create_copy_of($object);
28
  push @{ $self->content->{entries} }, $copied;
29

  
30
  $self->_save_content;
31

  
32
  return $copied;
33
}
34

  
35
sub get_entry {
36
  my ($self, $type) = @_;
37

  
38
  $type ||= qr/./;
39

  
40
  return first   { $_->type =~ $type          }
41
         reverse @{ $self->content->{entries} };
42
}
43

  
44
sub get_entries {
45
  my ($self, $type) = @_;
46

  
47
  $type ||= qr/./;
48

  
49
  return grep    { $_->{type} =~ $type        }
50
         reverse @{ $self->content->{entries} };
51
}
52

  
53
sub clear {
54
  my ($self) = @_;
55

  
56
  $self->content->{entries} = [];
57
  $self->_save_content;
58

  
59
  return $self;
60
}
61

  
62
sub _log_entries {
63
  my ($self) = @_;
64

  
65
  $::lxdebug->message(0, "Clipboard entries: " . scalar(@{ $self->content->{entries} }));
66
  foreach (@{ $self->content->{entries} }) {
67
    $::lxdebug->message(0, "  " . $_->type . ' ' . $_->timestamp . ' ' . $_->describe);
68
  }
69
}
70

  
71
sub _create_copy_of {
72
  my ($self, $object) = @_;
73

  
74
  croak "\$object is not a blessed reference." unless blessed($object);
75

  
76
  my $type   = (split(m/::/, ref($object)))[-1];
77
  my $copied = eval { "SL::Clipboard::${type}"->new(timestamp => DateTime->now_local) };
78

  
79
  croak "Class '" . ref($object) . "' not supported for copy/paste operations" if !$copied;
80

  
81
  $copied->content($copied->dump($object));
82

  
83
  return $copied;
84
}
85

  
86
sub _save_content {
87
  my ($self) = @_;
88

  
89
  $::auth->set_session_value('clipboard-content', $self->content);
90

  
91
  return $self;
92
}
93

  
94
1;
95

  
96
__END__
97

  
98
=pod
99

  
100
=encoding utf8
101

  
102
=head1 NAME
103

  
104
SL::Clipboard - A session-based clipboard mechanism for
105
Rose::DB::Object instances
106

  
107
=head1 SYNOPSIS
108

  
109
  # In a controller, e.g. for customers, you can react to a "copy" operation:
110
  my $customer = SL::DB::Customer->new(id => $::form->{id});
111
  SL::Clipboard->new->copy($customer);
112

  
113
  # Later in a paste action:
114
  my $copied = SL::Clipboard->new->get_entry(qr/^Customer$/);
115
  if ($copied) {
116
    my $customer = $copied->to_object;
117
    $customer->save;
118
  }
119

  
120
=head1 OVERVIEW
121

  
122
The clipboard can store an unlimited number of copies of
123
Rose::DB::Object instance. The instances are dumped into trees using
124
L<Rose::DB::Object::Helpers/as_tree>. How much of such an object is
125
copied depends on its type. For example, a dump of a customer object
126
might also include the dumps of the shipping address and contact
127
objects belonging to the customer.
128

  
129
Each clipped object is stored in the user's session along with the
130
timestamp of the copy operation. A controller can then query the
131
clipboard for the latest clipped object of a certain type (or more
132
types if the situation allows insertion of different types). If such a
133
clipped object is available it can be turned into a shiny new
134
Rose::DB::Object instance that can be saved to the database.
135

  
136
Primary key columns will always be reset as will other columns
137
depending on the type. For example, a copied requirement spec item
138
will have its C<requirement_spec_id> column cleared. The controller is
139
responsible for setting the columns before saving the object.
140

  
141
Not every Rose::DB::Object instance can be copied. For each supported
142
type C<Type> there must be a specialized clipboard support class
143
C<SL::Clipboard::Type>. The type's name is derived from the Rose class
144
name: by stripping the leading C<SL::DB::>. So the clipboard support
145
class for a requirement spec item Rose class
146
C<SL::DB::RequirementSpecItem> would be
147
C<SL::Clipboard::RequirementSpecItem>. These support classes must
148
inherit from L<SL::Clipboard::Base> which offers almost a full set of
149
support functions so that the actual specialized class has to do very
150
litte.
151

  
152
As the clipboard is session-based its contents will be lost when the
153
session expires (either due to timeouts or to the user logging off).
154

  
155
=head1 FUNCTIONS
156

  
157
=over 4
158

  
159
=item C<clear>
160

  
161
Clears the clipboard (removes all entries).
162

  
163
=item C<copy $object>
164

  
165
Creates a dumped copy of C<$object> and stores that copy in the
166
session. An unlimited number of copies of differeing types can be
167
made.
168

  
169
Returns the instance of the copied object, a sub-class of
170
L<SL::Clipboard::Base>.
171

  
172
=item C<get_entries [$type]>
173

  
174
Returns an array of clipped objects whose type matches the regular
175
expression C<$type>. If C<$type> is not given then all elements are
176
returned.
177

  
178
The array is sorted by the copy timestamp: the first element in the
179
array is the one most recently copied.
180

  
181
=item C<get_entry [$type]>
182

  
183
Returns the most recently clipped object whose type matches the
184
regular expression C<$type>. If C<$type> is not given then then then
185
most recently copied object is returned.
186

  
187
If no such object exists C<undef> is returned instead.
188

  
189
=back
190

  
191
=head1 BUGS
192

  
193
Nothing here yet.
194

  
195
=head1 AUTHOR
196

  
197
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
198

  
199
=cut
SL/Clipboard/Base.pm
1
package SL::Clipboard::Base;
2

  
3
use strict;
4

  
5
use parent qw(Rose::Object);
6

  
7
use Rose::Object::MakeMethods::Generic (
8
  'scalar --get_set_init' => [ qw(content timestamp) ],
9
);
10

  
11
use Rose::DB::Object::Helpers ();
12

  
13
sub init_timestamp { die "'timestamp' property not set"; }
14
sub init_content   { die "'content' property not set";   }
15

  
16
sub type {
17
  my ($self_or_class) = @_;
18
  return (split m/::/, ref($self_or_class) ? ref($self_or_class) : $self_or_class)[-1];
19
}
20

  
21
sub reload_object {
22
  my ($self, $object) = @_;
23

  
24
  return ref($object)->new(map { $_ => $object->$_ } $object->meta->primary_key)->load;
25
}
26

  
27
sub as_tree {
28
  my ($self, $object, %params) = @_;
29

  
30
  my $tree = Rose::DB::Object::Helpers::as_tree($object, %params);
31
  $self->_fix_tree($tree, $object);
32
  return $tree;
33
}
34

  
35
sub to_object {
36
  my ($self) = @_;
37
  my $object = Rose::DB::Object::Helpers::new_from_tree("SL::DB::" . $self->type, $self->content);
38

  
39
  # Reset primary key columns and itime/mtime if the class supports it.
40
  foreach ($object->meta->primary_key, 'itime', 'mtime') {
41
    $object->$_(undef) if $object->can($_);
42
  }
43

  
44
  # Let sub classes fix the objects further.
45
  $self->_fix_object($object);
46
  return $object;
47
}
48

  
49
sub dump {
50
  my ($self, $object) = @_;
51
  return $self->as_tree($self->reload_object($object), max_depth => 1);
52
}
53

  
54
sub describe {
55
  die "'describe' method not overwritten by derived class";
56
}
57

  
58
sub _fix_object {
59
  my ($self, $object) = @_;
60
  # To be overwritten by child classes.
61
}
62

  
63
sub _fix_tree {
64
  my ($self, $tree, $object) = @_;
65

  
66
  # Delete primary key columns and itime/mtime if the class supports it.
67
  foreach ($object->meta->primary_key, 'itime', 'mtime') {
68
    delete $tree->{$_} if $object->can($_);
69
  }
70
}
71

  
72
1;
73
__END__
74

  
75
=pod
76

  
77
=encoding utf8
78

  
79
=head1 NAME
80

  
81
SL::Clipboard::Base - Base class for clipboard specialization classes
82

  
83
=head1 SYNOPSIS
84

  
85
See the synopsis of L<SL::Clipboard>.
86

  
87
=head1 OVERVIEW
88

  
89
This is a base class providing a lot of utility and
90
defaults. Sub-classes must overwrite at least the function
91
L</describe> but can overwrite others as well.
92

  
93
Writing a specialized sub-class for a database type involves
94
overwriting one or more functions. These are:
95

  
96
=over 4
97

  
98
=item * C<describe>
99

  
100
Must be overwritten. Returns a human-readable description of the
101
content. Should only be one line.
102

  
103
=item * C<dump>
104

  
105
Optional. Overwrite if sub-class needs to dump more/less than the
106
implementation in this class dumps.
107

  
108
=item * C<_fix_object>
109

  
110
Optional. Overwrite if re-created Rose::DB::Object instances must be
111
cleaned further before they're returned to the caller.
112

  
113
=item * C<_fix_tree>
114

  
115
Optional. Overwrite if the tree created during a copy operation of a
116
Rose::DB::Object instance must be cleaned further before it's stored.
117

  
118
=back
119

  
120
You don't have to or should not overwrite the other functions:
121

  
122
=over 4
123

  
124
=item * C<as_tree>
125

  
126
=item * C<reload_object>
127

  
128
=item * C<to_object>
129

  
130
=item * C<type>
131

  
132
=back
133

  
134
Don't forget to C<use> the specialized module here in Base!
135

  
136
=head1 FUNCTIONS
137

  
138
=over 4
139

  
140
=item C<as_tree $object, %params>
141

  
142
A convenience function calling L<Rose::DB::Object::Helpers/as_tree>
143
with C<$object> and C<%params> as parameters. Returns a hash/array
144
reference tree of the function.
145

  
146
Don't overwrite this function in sub-classes. Overwrite L</dump>
147
instead.
148

  
149
=item C<describe>
150

  
151
Returns a human-readable description of the content. This should only
152
be a single line without any markup.
153

  
154
Sub-classes must overwrite this function.
155

  
156
=item C<dump $object>
157

  
158
Dumps the object as a hash/array tree and returns it by calling
159
L<Rose::DB::Object::Helpers/as_tree>. The default implementation
160
reloads the object first by calling L</reload_object>. It also only
161
dumps the object itself, not any of the relationships, by calling
162
C<as_tree> with the parameter C<max_depth =E<gt> 1>.
163

  
164
Overwrite this in a sub-class if you need to dump more or differently
165
(see L<SL::Clipboard::RequirementSpecItem> for an example).
166

  
167
=item C<reload_object $object>
168

  
169
Reloads C<$object> from the database and returns a new instance. Can
170
be useful for sanitizing the object given to L</dump> before
171
converting into a tree. It is used by the default implementation of
172
L</dump>.
173

  
174
=item C<to_object>
175

  
176
Converts the dumped representation back to an Rose::DB::Object
177
instance. Several columns of the newly created object are cleared by
178
C<to_object> itself: the primary key columns (if any) and the columns
179
C<itime> and C<mtime> (if the object has such columns).
180

  
181
This function should not be overwritten by sub-classes. Instead,
182
functions can overwrite C<_fix_object> which can be used for sanitzing
183
the newly created object before handing it back to the caller.
184

  
185
=item C<type>
186

  
187
Returns the actual clipped type (e.g. C<RequirementSpecItem>). This is
188
derived from the actual class name of C<$self>.
189

  
190
=item C<_fix_object $object>
191

  
192
This function is called by L</to_object> before the object is passed
193
back to the caller. It does not do anything in the default
194
implementation, but sub-classes are free to overwrite it if they need
195
to sanitize the object. See L<SL::Clipboard::RequirementSpecItem> for
196
an example.
197

  
198
Its return value is ignored.
199

  
200
=item C<_fix_tree $tree, $object>
201

  
202
This function is called by L</as_tree> after dumping and before the
203
object is stored during a copy operation. In the default
204
implementation all primary key columns and the columns C<itime> and
205
C<mtime> (if the object has such columns) are removed from the tree.
206
Sub-classes are free to overwrite it if they need to sanitize the
207
tree. See L<SL::Clipboard::RequirementSpecItem> for an example.
208

  
209
C<$object> is just passed in for reference and should not be modified.
210

  
211
Its return value is ignored.
212

  
213
=back
214

  
215
=head1 BUGS
216

  
217
Nothing here yet.
218

  
219
=head1 AUTHOR
220

  
221
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
222

  
223
=cut
SL/Clipboard/RequirementSpecItem.pm
1
package SL::Clipboard::RequirementSpecItem;
2

  
3
use strict;
4

  
5
use parent qw(SL::Clipboard::Base);
6

  
7
use List::Util qw(sum);
8

  
9
use SL::Common;
10
use SL::Locale::String;
11

  
12
sub dump {
13
  my ($self, $object) = @_;
14

  
15
  return $self->as_tree(_load_children($self->reload_object($object)), exclude => sub { ref($_[0]) !~ m/::RequirementSpecItem$/ });
16
}
17

  
18
sub describe {
19
  my ($self) = @_;
20

  
21
  my $item              = $self->content;
22
  my $num_children      = @{ $item->{children} || [] };
23
  my $num_grandchildren = sum map { scalar(@{ $_->{children} || [] }) } @{ $item->{children} || [] };
24

  
25
  if ($item->{item_type} eq 'section') {
26
    return t8('Requirement spec section #1 "#2" with #3 function blocks and a total of #4 sub function blocks; preamble: "#5"',
27
              $item->{fb_number}, $item->{title}, $num_children, $num_grandchildren, Common::truncate($item->{description}, strip => 'full'));
28
  } elsif ($item->{item_type} eq 'function-block') {
29
    return t8('Requirement spec function block #1 with #2 sub function blocks; description: "#3"',
30
              $item->{fb_number}, $num_children, Common::truncate($item->{description}, strip => 'full'));
31
  } else {
32
    return t8('Requirement spec sub function block #1; description: "#2"',
33
              $item->{fb_number}, Common::truncate($item->{description}, strip => 'full'));
34
  }
35
}
36

  
37
sub _load_children {
38
  my ($object) = @_;
39

  
40
  _load_children($_) for @{ $object->children };
41

  
42
  return $object;
43
}
44

  
45
sub _fix_object {
46
  my ($self, $object) = @_;
47

  
48
  $object->$_(undef)     for qw(fb_number);
49
  $self->_fix_object($_) for @{ $object->children || [] };
50
}
51

  
52
sub _fix_tree {
53
  my ($self, $tree, $object) = @_;
54

  
55
  delete @{ $tree }{ qw(id itime mtime parent_id position requirement_spec_id) };
56
  $self->_fix_tree($_) for @{ $tree->{children} || [] };
57
}
58

  
59
1;
60
__END__
61

  
62
=pod
63

  
64
=encoding utf8
65

  
66
=head1 NAME
67

  
68
SL::Clipboard::RequirementSpecItem - Clipboard specialization for
69
SL::DB::RequirementSpecItem
70

  
71
=head1 FUNCTIONS
72

  
73
=over 4
74

  
75
=item C<describe>
76

  
77
Returns a human-readable description depending on the copied type
78
(section, function block or sub function block).
79

  
80
=item C<dump $object>
81

  
82
This specialization reloads C<$object> from the database, loads all of
83
its children (but only the other requirement spec items, no other
84
relationships) and dumps it.
85

  
86
=item C<_fix_object $object>
87

  
88
Fixes C<$object> and all of its children by clearing certain columns
89
like the position or function block numbers.
90

  
91
=back
92

  
93
=head1 BUGS
94

  
95
Nothing here yet.
96

  
97
=head1 AUTHOR
98

  
99
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
100

  
101
=cut
SL/Clipboard/RequirementSpecTextBlock.pm
1
package SL::Clipboard::RequirementSpecTextBlock;
2

  
3
use strict;
4

  
5
use parent qw(SL::Clipboard::Base);
6

  
7
use SL::Common;
8
use SL::Locale::String;
9

  
10
sub describe {
11
  my ($self) = @_;
12

  
13
  return t8('Requirement spec text block "#1"; content: "#2"', $self->content->{title}, Common::truncate($self->content->{text}, strip => 'full'));
14
}
15

  
16
sub _fix_object {
17
  my ($self, $object) = @_;
18

  
19
  $object->$_(undef) for qw(output_position position requirement_spec_id);
20

  
21
  return $object;
22
}
23

  
24
1;
25
__END__
26

  
27
=pod
28

  
29
=encoding utf8
30

  
31
=head1 NAME
32

  
33
SL::Clipboard::RequirementSpecTextBlock - Clipboard specialization for
34
SL::DB::RequirementSpecTextBlock
35

  
36
=head1 FUNCTIONS
37

  
38
=over 4
39

  
40
=item C<describe>
41

  
42
Returns a human-readable description including the title and an
43
excerpt of its content.
44

  
45
=item C<_fix_object $object>
46

  
47
Fixes C<$object> by clearing certain columns like the position.
48

  
49
=back
50

  
51
=head1 BUGS
52

  
53
Nothing here yet.
54

  
55
=head1 AUTHOR
56

  
57
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
58

  
59
=cut

Auch abrufbar als: Unified diff