Commit | Line | Data |
7adfd53f |
1 | package Reaction::UI::CRUDController; |
2 | |
3 | use strict; |
4 | use warnings; |
5 | use base 'Reaction::UI::Controller'; |
6 | use Reaction::Class; |
7 | |
8 | use aliased 'Reaction::UI::ViewPort::ListView'; |
9 | use aliased 'Reaction::UI::ViewPort::ActionForm'; |
10 | use aliased 'Reaction::UI::ViewPort::ObjectView'; |
11 | |
a4f82080 |
12 | has 'model_name' => (isa => 'Str', is => 'rw', required => 1); |
13 | has 'collection_name' => (isa => 'Str', is => 'rw', required => 1); |
7adfd53f |
14 | |
b8faba69 |
15 | has action_viewport_map => (isa => 'HashRef', is => 'rw', lazy_build => 1); |
16 | has action_viewport_args => (isa => 'HashRef', is => 'rw', lazy_build => 1); |
17 | |
18 | sub build_action_viewport_map { |
19 | return { |
20 | list => ListView, |
21 | view => ObjectView, |
22 | create => ActionForm, |
23 | update => ActionForm, |
24 | delete => ActionForm, |
25 | delete_all => ActionForm, |
26 | }; |
27 | } |
28 | |
29 | sub build_action_viewport_args { |
30 | my $self = shift; |
31 | return { list => |
32 | { action_prototypes => |
33 | [ { label => 'Create', action => sub { |
34 | [ '', 'create', $_[1]->req->captures ] } }, |
35 | { label => 'Delete all', action => sub { |
36 | [ '', 'delete_all', $_[1]->req->captures ] } }, |
37 | ], |
38 | Entity => |
39 | { action_prototypes => |
40 | [ { label => 'View', action => sub { |
41 | [ '', 'view', [ @{$_[1]->req->captures}, $_[0]->__id ] ] } }, |
42 | { label => 'Edit', action => sub { |
43 | [ '', 'update', [ @{$_[1]->req->captures}, $_[0]->__id ] ] } }, |
44 | { label => 'Delete', action => sub { |
45 | [ '', 'delete', [ @{$_[1]->req->captures}, $_[0]->__id ] ] } }, |
46 | ], |
47 | }, |
48 | }, |
49 | }; |
50 | } |
7adfd53f |
51 | |
52 | sub base :Action :CaptureArgs(0) { |
53 | my ($self, $c) = @_; |
54 | } |
55 | |
c689b58e |
56 | #XXX candidate for futre optimization |
7adfd53f |
57 | sub get_collection { |
58 | my ($self, $c) = @_; |
a4f82080 |
59 | my $model = $c->model( $self->model_name ); |
60 | my $attr = $model->meta->find_attribute_by_name( $self->collection_name ); |
61 | my $reader = $attr->get_read_method; |
62 | return $model->$reader; |
7adfd53f |
63 | } |
64 | |
65 | sub get_model_action { |
66 | my ($self, $c, $name, $target) = @_; |
67 | |
68 | if ($target->can('action_for')) { |
69 | return $target->action_for($name, ctx => $c); |
70 | } |
71 | |
f670cfd0 |
72 | #can we please kill this already? |
7adfd53f |
73 | my $model_name = "Action::${name}".$self->model_name; |
74 | my $model = $c->model($model_name); |
75 | confess "no such Model $model_name" unless $model; |
76 | return $model->new(target_model => $target, ctx => $c); |
77 | } |
78 | |
79 | sub list :Chained('base') :PathPart('') :Args(0) { |
80 | my ($self, $c) = @_; |
81 | |
82 | $self->push_viewport( |
b8faba69 |
83 | $self->action_viewport_map->{list}, |
84 | %{ $self->action_viewport_args->{list} || {} }, |
85 | collection => $self->get_collection($c) |
86 | ); |
7adfd53f |
87 | } |
88 | |
89 | sub create :Chained('base') :PathPart('create') :Args(0) { |
90 | my ($self, $c) = @_; |
91 | my $action = $self->get_model_action($c, 'Create', $self->get_collection($c)); |
b8faba69 |
92 | $self->push_viewport |
93 | ( |
94 | $self->action_viewport_map->{create}, |
95 | %{ $self->action_viewport_args->{create} || {} }, |
96 | action => $action, |
97 | next_action => 'list', |
98 | on_apply_callback => sub { $self->after_create_callback($c => @_); }, |
99 | ); |
100 | } |
101 | |
102 | sub delete_all :Chained('base') :PathPart('delete_all') :Args(0) { |
103 | my ($self, $c) = @_; |
104 | my $action = $self->get_model_action($c, 'DeleteAll', $self->get_collection($c)); |
105 | $self->push_viewport |
106 | ( |
107 | $self->action_viewport_map->{delete_all}, |
108 | %{ $self->action_viewport_args->{delete_all} || {} }, |
109 | action => $action, |
110 | next_action => 'list', |
111 | ); |
7adfd53f |
112 | } |
113 | |
114 | sub after_create_callback { |
115 | my ($self, $c, $vp, $result) = @_; |
b8faba69 |
116 | return $self->redirect_to |
117 | ( $c, 'update', [ @{$c->req->captures}, $result->id ] ); |
7adfd53f |
118 | } |
119 | |
120 | sub object :Chained('base') :PathPart('id') :CaptureArgs(1) { |
121 | my ($self, $c, $key) = @_; |
f670cfd0 |
122 | my $object :Stashed = $self->get_collection($c)->find($key); |
7adfd53f |
123 | confess "Object? what object?" unless $object; # should be a 404. |
124 | } |
125 | |
126 | sub update :Chained('object') :Args(0) { |
127 | my ($self, $c) = @_; |
128 | my $object :Stashed; |
129 | my $action = $self->get_model_action($c, 'Update', $object); |
130 | my @cap = @{$c->req->captures}; |
131 | pop(@cap); # object id |
b8faba69 |
132 | $self->push_viewport |
133 | ( |
134 | $self->action_viewport_map->{update}, |
135 | %{ $self->action_viewport_args->{update} || {} }, |
136 | action => $action, |
137 | next_action => [ $self, 'redirect_to', 'list', \@cap ] |
7adfd53f |
138 | ); |
139 | } |
140 | |
141 | sub delete :Chained('object') :Args(0) { |
142 | my ($self, $c) = @_; |
143 | my $object :Stashed; |
144 | my $action = $self->get_model_action($c, 'Delete', $object); |
145 | my @cap = @{$c->req->captures}; |
146 | pop(@cap); # object id |
b8faba69 |
147 | $self->push_viewport |
148 | ( |
149 | $self->action_viewport_map->{delete}, |
150 | %{ $self->action_viewport_args->{delete} || {} }, |
151 | action => $action, |
152 | next_action => [ $self, 'redirect_to', 'list', \@cap ] |
7adfd53f |
153 | ); |
154 | } |
155 | |
156 | sub view :Chained('object') :Args(0) { |
157 | my ($self, $c) = @_; |
158 | my $object :Stashed; |
159 | my @cap = @{$c->req->captures}; |
160 | pop(@cap); # object id |
b8faba69 |
161 | $self->push_viewport |
162 | ( |
163 | $self->action_viewport_map->{view}, |
164 | %{ $self->action_viewport_args->{view} || {} }, |
165 | object => $object, |
166 | ); |
7adfd53f |
167 | } |
168 | |
169 | 1; |