better error reporting on reflection failure for Object viewport
[catagits/Reaction.git] / lib / Reaction / UI / ViewPort / Object.pm
CommitLineData
ddccc6a2 1package Reaction::UI::ViewPort::Object;
2
3use Reaction::Class;
4
5use aliased 'Reaction::UI::ViewPort::Field::Text';
6use aliased 'Reaction::UI::ViewPort::Field::Number';
7use aliased 'Reaction::UI::ViewPort::Field::Integer';
8use aliased 'Reaction::UI::ViewPort::Field::Boolean';
9use aliased 'Reaction::UI::ViewPort::Field::String';
10use aliased 'Reaction::UI::ViewPort::Field::DateTime';
11use aliased 'Reaction::UI::ViewPort::Field::RelatedObject';
c8fbb8ad 12use aliased 'Reaction::UI::ViewPort::Field::Array';
ddccc6a2 13use aliased 'Reaction::UI::ViewPort::Field::Collection';
14
15use aliased 'Reaction::InterfaceModel::Object' => 'IM_Object';
16
17class Object is 'Reaction::UI::ViewPort', which {
18
19 #everything is read only right now. Later I can make somethings read-write
20 #but first I need to figure out what depends on what so we can have decent triggers
21 has model => (is => 'ro', isa => IM_Object, required => 1);
22 has fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
23
24 has field_args => (is => 'ro');
25 has field_order => (is => 'ro', isa => 'ArrayRef');
26
27 has builder_cache => (is => 'ro', isa => 'HashRef', lazy_build => 1);
ddccc6a2 28 has excluded_fields => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
cc44a337 29 has computed_field_order => (is => 'ro', isa => 'ArrayRef', lazy_build => 1);
ddccc6a2 30
31 implements BUILD => as {
32 my ($self, $args) = @_;
33 my $field_args = delete $args->{Field};
34 $self->field_args( $field_args ) if ref $field_args;
35 };
36
37 implements _build_excluded_fields => as { [] };
38 implements _build_builder_cache => as { {} };
39
40 implements _build_fields => as {
41 my ($self) = @_;
42 my $obj = $self->model;
43 my $args = $self->has_field_args ? $self->field_args : {};
44 my @fields;
cc44a337 45 for my $field_name (@{ $self->computed_field_order }) {
ddccc6a2 46 my $attr = $obj->meta->find_attribute_by_name($field_name);
47 my $meth = $self->builder_cache->{$field_name} ||= $self->get_builder_for($attr);
c03f75a7 48 my $field = $self->$meth($attr, ($args->{$field_name} || {}));
ddccc6a2 49 push(@fields, $field) if $field;
50 }
c8fbb8ad 51 return \@fields;
ddccc6a2 52 };
53
cc44a337 54 implements _build_computed_field_order => as {
ddccc6a2 55 my ($self) = @_;
56 my %excluded = map { $_ => undef } @{ $self->excluded_fields };
57 #treat _$field_name as private and exclude fields with no reader
c8fbb8ad 58 my @names = grep { $_ !~ /^_/ && !exists($excluded{$_})} map { $_->name }
ddccc6a2 59 grep { defined $_->get_read_method } $self->model->meta->parameter_attributes;
c03f75a7 60 return $self->sort_by_spec($self->field_order || [], \@names);
ddccc6a2 61 };
62
63 override child_event_sinks => sub {
36d54b14 64 return ( @{shift->fields}, super());
ddccc6a2 65 };
66
67 #candidate for shared role!
68 implements get_builder_for => as {
69 my ($self, $attr) = @_;
70 my $attr_name = $attr->name;
71 my $builder = "_build_fields_for_name_${attr_name}";
72 return $builder if $self->can($builder);
73 if ($attr->has_type_constraint) {
74 my $constraint = $attr->type_constraint;
75 my $base_name = $constraint->name;
76 my $tried_isa = 0;
2c36a66b 77 my @tried;
ddccc6a2 78 CONSTRAINT: while (defined($constraint)) {
79 my $name = $constraint->name;
80 $name = $attr->_isa_metadata if($name eq '__ANON__');
81 if (eval { $name->can('meta') } && !$tried_isa++) {
82 foreach my $class ($name->meta->class_precedence_list) {
2c36a66b 83 push(@tried, $class);
ddccc6a2 84 my $mangled_name = $class;
85 $mangled_name =~ s/:+/_/g;
86 my $builder = "_build_fields_for_type_${mangled_name}";
87 return $builder if $self->can($builder);
88 }
89 }
90 if (defined($name)) {
2c36a66b 91 push(@tried, $name);
ddccc6a2 92 unless (defined($base_name)) {
93 $base_name = "(anon subtype of ${name})";
94 }
95 my $mangled_name = $name;
96 $mangled_name =~ s/:+/_/g;
97 my $builder = "_build_fields_for_type_${mangled_name}";
98 return $builder if $self->can($builder);
99 }
100 $constraint = $constraint->parent;
101 }
102 if (!defined($constraint)) {
2c36a66b 103 confess "Can't build field ${attr_name} of type ${base_name} without "
104 ."$builder method or _build_fields_for_type_<type> method "
105 ."for type or any supertype (tried ".join(', ', @tried).")";
ddccc6a2 106 }
107 } else {
108 confess "Can't build field ${attr} without $builder method or type constraint";
109 }
110 };
111
112 implements _build_simple_field => as {
113 my ($self, %args) = @_;
114 my $class = delete $args{class};
115 confess("Can not build simple field without a viewport class")
116 unless $class;
117 confess("Can not build simple field without attribute")
118 unless defined $args{attribute};
119
120 my $field_name = $args{attribute}->name;
121 return $class->new(
122 ctx => $self->ctx,
123 model => $self->model,
124 location => join('-', $self->location, 'field', $field_name),
125 %args
126 );
127 };
128
129 implements _build_fields_for_type_Num => as {
130 my ($self, $attr, $args) = @_;
131 $self->_build_simple_field(attribute => $attr, class => Number, %$args);
132 };
133
134 implements _build_fields_for_type_Int => as {
135 my ($self, $attr, $args) = @_;
136 #XXX
137 $self->_build_simple_field(attribute => $attr, class => Integer, %$args);
138 };
139
140 implements _build_fields_for_type_Bool => as {
141 my ($self, $attr, $args) = @_;
142 $self->_build_simple_field(attribute => $attr, class => Boolean, %$args);
143 };
144
145 #XXX
146 implements _build_fields_for_type_Password => as { return };
147
148 implements _build_fields_for_type_Str => as {
149 my ($self, $attr, $args) = @_;
150 #XXX
151 $self->_build_simple_field(attribute => $attr, class => String, %$args);
152 };
153
154 implements _build_fields_for_type_SimpleStr => as {
155 my ($self, $attr, $args) = @_;
156 $self->_build_simple_field(attribute => $attr, class => String, %$args);
157 };
158
159 implements _build_fields_for_type_DateTime => as {
160 my ($self, $attr, $args) = @_;
161 $self->_build_simple_field(attribute => $attr, class => DateTime, %$args);
162 };
163
164 implements _build_fields_for_type_Enum => as {
165 my ($self, $attr, $args) = @_;
166 #XXX
167 $self->_build_simple_field(attribute => $attr, class => String, %$args);
168 };
169
170 implements _build_fields_for_type_ArrayRef => as {
171 my ($self, $attr, $args) = @_;
c8fbb8ad 172 $self->_build_simple_field(attribute => $attr, class => Array, %$args);
ddccc6a2 173 };
174
175 implements _build_fields_for_type_Reaction_InterfaceModel_Object => as {
176 my ($self, $attr, $args) = @_;
177 #XXX
178 $self->_build_simple_field(attribute => $attr, class => RelatedObject, %$args);
179 };
180
181 implements _build_fields_for_type_Reaction_InterfaceModel_Collection => as {
182 my ($self, $attr, $args) = @_;
183 $self->_build_simple_field(attribute => $attr, class => Collection, %$args);
184 };
185
186};
187
1881;
2dba7201 189
190__END__;
191
192=head1 NAME
193
194Reaction::UI::ViewPort::Object
195
196=head1 DESCRIPTION
197
198=head1 ATTRIBUTES
199
200=head2 model
201
202=head2 fields
203
204=head2 field_args
205
206=head2 field_order
207
208=head2 builder_cache
209
210=head2 excluded_fields
211
212=head2 computed_field_order
213
214=head1 INTERNAL METHODS
215
216These methods, although stable, are subject to change without notice. These are meant
217to be used only by developers. End users should refrain from using these methods to
218avoid potential breakages.
219
220=head2 BUILD
221
222=head2 get_builder_for
223
224=head2 _build_simple_field
225
226=head2 _build_fields_for_type_Num
227
228=head2 _build_fields_for_type_Int
229
230=head2 _build_fields_for_type_Bool
231
232=head2 _build_fields_for_type_Password
233
234=head2 _build_fields_for_type_Str
235
236=head2 _build_fields_for_type_SimpleStr
237
238=head2 _build_fields_for_type_DateTime
239
240=head2 _build_fields_for_type_Enum
241
242=head2 _build_fields_for_type_ArrayRef
243
244=head2 _build_fields_for_type_Reaction_InterfaceModel_Object
245
246=head2 _build_fields_for_type_Reaction_InterfaceModel_Collection
247
248=head1 AUTHORS
249
250See L<Reaction::Class> for authors.
251
252=head1 LICENSE
253
254See L<Reaction::Class> for the license.
255
256=cut