Commit | Line | Data |
7adfd53f |
1 | package Reaction::InterfaceModel::Reflector::DBIC; |
2 | |
3 | use aliased 'Reaction::InterfaceModel::Action::DBIC::ResultSet::Create'; |
b8faba69 |
4 | use aliased 'Reaction::InterfaceModel::Action::DBIC::ResultSet::DeleteAll'; |
7adfd53f |
5 | use aliased 'Reaction::InterfaceModel::Action::DBIC::Result::Update'; |
6 | use aliased 'Reaction::InterfaceModel::Action::DBIC::Result::Delete'; |
7 | |
8 | use aliased 'Reaction::InterfaceModel::Collection::Virtual::ResultSet'; |
9 | use aliased 'Reaction::InterfaceModel::Object'; |
10 | use aliased 'Reaction::InterfaceModel::Action'; |
11 | use Reaction::Class; |
12 | use Class::MOP; |
13 | |
f670cfd0 |
14 | use Catalyst::Utils; |
15 | |
81393881 |
16 | use namespace::clean -except => [ qw(meta) ]; |
17 | |
18 | |
19 | has make_classes_immutable => (isa => "Bool", is => "rw", required => 1, default => sub{ 1 }); |
20 | |
21 | #user defined actions and prototypes |
22 | has object_actions => (isa => "HashRef", is => "rw", lazy_build => 1); |
23 | has collection_actions => (isa => "HashRef", is => "rw", lazy_build => 1); |
24 | |
25 | #which actions to create by default |
26 | has default_object_actions => (isa => "ArrayRef", is => "rw", lazy_build => 1); |
27 | has default_collection_actions => (isa => "ArrayRef", is => "rw", lazy_build => 1); |
28 | |
29 | #builtin actions and prototypes |
30 | has builtin_object_actions => (isa => "HashRef", is => "rw", lazy_build => 1); |
31 | has builtin_collection_actions => (isa => "HashRef", is => "rw", lazy_build => 1); |
32 | sub _build_object_actions { {} }; |
33 | sub _build_collection_actions { {} }; |
34 | sub _build_default_object_actions { [ qw/Update Delete/ ] }; |
35 | sub _build_default_collection_actions { [ qw/Create DeleteAll/ ] }; |
36 | sub _build_builtin_object_actions { |
37 | { |
38 | Update => { name => 'Update', base => Update }, |
39 | Delete => { name => 'Delete', base => Delete, attributes => [] }, |
84135177 |
40 | }; |
81393881 |
41 | }; |
42 | sub _build_builtin_collection_actions { |
43 | { |
44 | Create => {name => 'Create', base => Create }, |
45 | DeleteAll => {name => 'DeleteAll', base => DeleteAll, attributes => [] } |
84135177 |
46 | }; |
81393881 |
47 | }; |
48 | sub _all_object_actions { |
49 | my $self = shift; |
50 | return $self->merge_hashes |
51 | ($self->builtin_object_actions, $self->object_actions); |
52 | }; |
53 | sub _all_collection_actions { |
54 | my $self = shift; |
55 | return $self->merge_hashes |
56 | ($self->builtin_collection_actions, $self->collection_actions); |
57 | }; |
58 | sub dm_name_from_class_name { |
59 | my($self, $class) = @_; |
60 | confess("wrong arguments") unless $class; |
61 | $class =~ s/::/_/g; |
62 | $class = "_" . $self->_class_to_attribute_name($class) . "_store"; |
63 | return $class; |
64 | }; |
65 | sub dm_name_from_source_name { |
66 | my($self, $source) = @_; |
67 | confess("wrong arguments") unless $source; |
68 | $source =~ s/([a-z0-9])([A-Z])/${1}_${2}/g ; |
69 | $source = "_" . $self->_class_to_attribute_name($source) . "_store"; |
70 | return $source; |
71 | }; |
72 | sub class_name_from_source_name { |
73 | my ($self, $model_class, $source_name) = @_; |
74 | confess("wrong arguments") unless $model_class && $source_name; |
75 | return join "::", $model_class, $source_name; |
76 | }; |
77 | sub class_name_for_collection_of { |
78 | my ($self, $object_class) = @_; |
79 | confess("wrong arguments") unless $object_class; |
80 | return "${object_class}::Collection"; |
81 | }; |
82 | sub merge_hashes { |
83 | my($self, $left, $right) = @_; |
84 | return Catalyst::Utils::merge_hashes($left, $right); |
85 | }; |
86 | sub parse_reflect_rules { |
87 | my ($self, $rules, $haystack) = @_; |
88 | confess('$rules must be an array reference') unless ref $rules eq 'ARRAY'; |
89 | confess('$haystack must be an array reference') unless ref $haystack eq 'ARRAY'; |
90 | |
91 | my $needles = {}; |
92 | my (@exclude, @include, $global_opts); |
93 | if(@$rules == 2 && $rules->[0] eq '-exclude'){ |
94 | push(@exclude, (ref $rules->[1] eq 'ARRAY' ? @{$rules->[1]} : $rules->[1])); |
95 | } else { |
84135177 |
96 | for my $rule ( @$rules ){ |
81393881 |
97 | if (ref $rule eq 'ARRAY' && $rule->[0] eq '-exclude'){ |
98 | push(@exclude, (ref $rule->[1] eq 'ARRAY' ? @{$rule->[1]} : $rule->[1])); |
99 | } elsif( ref $rule eq 'HASH' ){ |
100 | $global_opts = ref $global_opts eq 'HASH' ? |
101 | $self->merge_hashes($global_opts, $rule) : $rule; |
102 | } else { |
103 | push(@include, $rule); |
84135177 |
104 | } |
105 | } |
81393881 |
106 | } |
107 | my $check_exclude = sub{ |
108 | for my $rule (@exclude){ |
109 | return 1 if(ref $rule eq 'Regexp' ? $_[0] =~ /$rule/ : $_[0] eq $rule); |
110 | } |
111 | return; |
f670cfd0 |
112 | }; |
113 | |
81393881 |
114 | @$haystack = grep { !$check_exclude->($_) } @$haystack; |
115 | $self->merge_reflect_rules(\@include, $needles, $haystack, $global_opts); |
116 | return $needles; |
117 | }; |
118 | sub merge_reflect_rules { |
119 | my ($self, $rules, $needles, $haystack, $local_opts) = @_; |
120 | for my $rule ( @$rules ){ |
121 | if(!ref $rule && ( grep {$rule eq $_} @$haystack ) ){ |
122 | $needles->{$rule} = defined $needles->{$rule} ? |
123 | $self->merge_hashes($needles->{$rule}, $local_opts) : $local_opts; |
124 | } elsif( ref $rule eq 'Regexp' ){ |
125 | for my $match ( grep { /$rule/ } @$haystack ){ |
126 | $needles->{$match} = defined $needles->{$match} ? |
127 | $self->merge_hashes($needles->{$match}, $local_opts) : $local_opts; |
128 | } |
129 | } elsif( ref $rule eq 'ARRAY' ){ |
130 | my $opts; |
131 | $opts = pop(@$rule) if @$rule > 1 and ref $rule->[$#$rule] eq 'HASH'; |
132 | $opts = $self->merge_hashes($local_opts, $opts) if defined $local_opts; |
133 | $self->merge_reflect_rules($rule, $needles, $haystack, $opts); |
f670cfd0 |
134 | } |
81393881 |
135 | } |
136 | }; |
137 | sub reflect_schema { |
138 | my ($self, %opts) = @_; |
139 | my $base = delete $opts{base} || Object; |
27f4dfaa |
140 | my $roles = delete $opts{roles} || []; |
81393881 |
141 | my $model = delete $opts{model_class}; |
142 | my $schema = delete $opts{schema_class}; |
143 | my $dm_name = delete $opts{domain_model_name}; |
144 | my $dm_args = delete $opts{domain_model_args} || {}; |
145 | $dm_name ||= $self->dm_name_from_class_name($schema); |
146 | |
147 | #load all necessary classes |
148 | confess("model_class and schema_class are required parameters") |
149 | unless($model && $schema); |
150 | Class::MOP::load_class( $base ); |
151 | Class::MOP::load_class( $schema ); |
27f4dfaa |
152 | my $meta = $self->_load_or_create( |
153 | $model, |
154 | superclasses => [$base], |
155 | ( @$roles ? (roles => $roles) : ()), |
156 | ); |
157 | |
81393881 |
158 | # sources => undef, #default to qr/./ |
159 | # sources => [], #default to nothing |
160 | # sources => qr//, #DWIM, treated as [qr//] |
161 | # sources => [{...}] #DWIM, treat as [qr/./, {...} ] |
162 | # sources => [[-exclude => ...]] #DWIM, treat as [qr/./, [-exclude => ...]] |
163 | my $haystack = [ $schema->sources ]; |
164 | |
27f4dfaa |
165 | my $rules = delete $opts{sources}; |
81393881 |
166 | if(!defined $rules){ |
167 | $rules = [qr/./]; |
168 | } elsif( ref $rules eq 'Regexp'){ |
169 | $rules = [ $rules ]; |
170 | } elsif( ref $rules eq 'ARRAY' && @$rules){ |
171 | #don't add a qr/./ rule if we have at least one match rule |
172 | push(@$rules, qr/./) unless grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude') |
173 | || !ref $_ || ref $_ eq 'Regexp'} @$rules; |
174 | } |
175 | |
176 | my $sources = $self->parse_reflect_rules($rules, $haystack); |
177 | |
178 | my $make_immutable = $meta->is_immutable || $self->make_classes_immutable; |
179 | $meta->make_mutable if $meta->is_immutable; |
180 | |
181 | $meta->add_domain_model |
182 | ($dm_name, is => 'rw', isa => $schema, required => 1, %$dm_args); |
183 | |
184 | for my $source_name (keys %$sources){ |
185 | my $source_opts = $sources->{$source_name} || {}; |
186 | $self->reflect_source( |
187 | source_name => $source_name, |
188 | parent_class => $model, |
189 | schema_class => $schema, |
190 | source_class => $schema->class($source_name), |
191 | parent_domain_model_name => $dm_name, |
192 | %$source_opts |
193 | ); |
194 | } |
195 | |
196 | $meta->make_immutable if $make_immutable; |
197 | return $meta; |
198 | }; |
199 | sub _compute_source_options { |
200 | my ($self, %opts) = @_; |
201 | my $schema = delete $opts{schema_class}; |
202 | my $source_name = delete $opts{source_name}; |
203 | my $source_class = delete $opts{source_class}; |
204 | my $parent = delete $opts{parent_class}; |
205 | my $parent_dm = delete $opts{parent_domain_model_name}; |
206 | |
207 | #this is the part where I hate my life for promissing all sorts of DWIMery |
208 | confess("parent_class and source_name or source_class are required parameters") |
209 | unless($parent && ($source_name || $source_class)); |
210 | |
211 | OUTER: until( $schema && $source_name && $source_class && $parent_dm ){ |
212 | if( $schema && !$source_name){ |
213 | next OUTER if $source_name = $source_class->result_source_instance->source_name; |
214 | } elsif( $schema && !$source_class){ |
215 | next OUTER if $source_class = eval { $schema->class($source_name) }; |
7adfd53f |
216 | } |
217 | |
81393881 |
218 | if($source_class && (!$schema || !$source_name)){ |
219 | if(!$schema){ |
220 | $schema = $source_class->result_source_instance->schema; |
221 | next OUTER if $schema && Class::MOP::load_class($schema); |
f670cfd0 |
222 | } |
81393881 |
223 | if(!$source_name){ |
224 | $source_name = $source_class->result_source_instance->source_name; |
225 | next OUTER if $source_name; |
f670cfd0 |
226 | } |
81393881 |
227 | } |
228 | my @haystack = $parent_dm ? |
229 | $parent->meta->find_attribute_by_name($parent_dm) : $parent->domain_models; |
230 | |
231 | #there's a lot of guessing going on, but it should work fine on most cases |
232 | INNER: for my $needle (@haystack){ |
233 | my $isa = $needle->_isa_metadata; |
234 | next INNER unless Class::MOP::load_class( $isa->_isa_metadata ); |
235 | next INNER unless $isa->isa('DBIx::Class::Schema'); |
236 | if(!$parent_dm && $schema && $isa eq $schema){ |
237 | $parent_dm = $needle->name; |
238 | next OUTER; |
f670cfd0 |
239 | } |
240 | |
81393881 |
241 | if( $source_name ){ |
242 | my $src_class = eval{ $isa->class($source_name) }; |
243 | next INNER unless $src_class; |
244 | next INNER if($source_class && $source_class ne $src_class); |
245 | $schema = $isa; |
246 | $parent_dm = $needle->name; |
247 | $source_class = $src_class; |
248 | next OUTER; |
f670cfd0 |
249 | } |
f670cfd0 |
250 | } |
251 | |
81393881 |
252 | #do we even need to go this far? |
253 | if( !$parent_dm && $schema ){ |
254 | my $tentative = $self->dm_name_from_class_name($schema); |
255 | $parent_dm = $tentative if grep{$_->name eq $tentative} @haystack; |
f670cfd0 |
256 | } |
81393881 |
257 | |
258 | confess("Could not determine options automatically from: schema " . |
259 | "'${schema}', source_name '${source_name}', source_class " . |
260 | "'${source_class}', parent_domain_model_name '${parent_dm}'"); |
261 | } |
262 | |
263 | return { |
264 | source_name => $source_name, |
265 | schema_class => $schema, |
266 | source_class => $source_class, |
267 | parent_class => $parent, |
268 | parent_domain_model_name => $parent_dm, |
269 | }; |
270 | }; |
271 | sub _class_to_attribute_name { |
272 | my ( $self, $str ) = @_; |
273 | confess("wrong arguments passed for _class_to_attribute_name") unless $str; |
274 | return join('_', map lc, split(/::|(?<=[a-z0-9])(?=[A-Z])/, $str)) |
275 | }; |
276 | sub add_source { |
277 | my ($self, %opts) = @_; |
278 | |
279 | my $model = delete $opts{model_class}; |
280 | my $reader = delete $opts{reader}; |
281 | my $source = delete $opts{source_name}; |
282 | my $dm_name = delete $opts{domain_model_name}; |
283 | my $collection = delete $opts{collection_class}; |
284 | my $name = delete $opts{attribute_name} || $source; |
285 | |
286 | confess("model_class and source_name are required parameters") |
287 | unless $model && $source; |
288 | my $meta = $model->meta; |
289 | |
290 | unless( $collection ){ |
291 | my $object = $self->class_name_from_source_name($model, $source); |
292 | $collection = $self->class_name_for_collection_of($object); |
293 | } |
294 | unless( $reader ){ |
295 | $reader = $source; |
296 | $reader =~ s/([a-z0-9])([A-Z])/${1}_${2}/g ; |
297 | $reader = $self->_class_to_attribute_name($reader) . "_collection"; |
298 | } |
299 | unless( $dm_name ){ |
300 | my @haystack = $meta->domain_models; |
301 | if( @haystack > 1 ){ |
302 | @haystack = grep { $_->_isa_metadata->isa('DBIx::Class::Schema') } @haystack; |
7adfd53f |
303 | } |
81393881 |
304 | if(@haystack == 1){ |
305 | $dm_name = $haystack[0]->name; |
306 | } elsif(@haystack > 1){ |
307 | confess("Failed to automatically determine domain_model_name. More than one " . |
308 | "possible match (".(join ", ", map{"'".$_->name."'"} @haystack).")"); |
309 | } else { |
310 | confess("Failed to automatically determine domain_model_name. No matches."); |
f670cfd0 |
311 | } |
81393881 |
312 | } |
313 | |
314 | my %attr_opts = |
315 | ( |
316 | lazy => 1, |
317 | required => 1, |
318 | isa => $collection, |
319 | reader => $reader, |
320 | predicate => "has_" . $self->_class_to_attribute_name($name) , |
321 | domain_model => $dm_name, |
322 | orig_attr_name => $source, |
323 | default => sub { |
324 | $collection->new |
325 | ( |
326 | _source_resultset => $_[0]->$dm_name->resultset($source), |
327 | _parent => $_[0], |
328 | ); |
329 | }, |
330 | ); |
331 | |
332 | my $make_immutable = $meta->is_immutable; |
333 | $meta->make_mutable if $make_immutable; |
334 | my $attr = $meta->add_attribute($name, %attr_opts); |
335 | $meta->make_immutable if $make_immutable; |
336 | |
337 | return $attr; |
338 | }; |
339 | sub reflect_source { |
340 | my ($self, %opts) = @_; |
341 | my $collection = delete $opts{collection} || {}; |
342 | %opts = %{ $self->merge_hashes(\%opts, $self->_compute_source_options(%opts)) }; |
343 | |
344 | my $obj_meta = $self->reflect_source_object(%opts); |
345 | my $col_meta = $self->reflect_source_collection |
346 | ( |
347 | object_class => $obj_meta->name, |
348 | source_class => $opts{source_class}, |
349 | %$collection |
350 | ); |
351 | |
352 | $self->add_source( |
353 | %opts, |
354 | model_class => delete $opts{parent_class}, |
355 | domain_model_name => delete $opts{parent_domain_model_name}, |
356 | collection_class => $col_meta->name, |
357 | ); |
358 | }; |
359 | sub reflect_source_collection { |
360 | my ($self, %opts) = @_; |
361 | my $base = delete $opts{base} || ResultSet; |
27f4dfaa |
362 | my $roles = delete $opts{roles} || []; |
81393881 |
363 | my $class = delete $opts{class}; |
364 | my $object = delete $opts{object_class}; |
365 | my $source = delete $opts{source_class}; |
366 | my $action_rules = delete $opts{actions}; |
367 | |
368 | confess('object_class and source_class are required parameters') |
369 | unless $object && $source; |
370 | $class ||= $self->class_name_for_collection_of($object); |
371 | |
372 | Class::MOP::load_class( $base ); |
373 | Class::MOP::load_class( $object ); |
27f4dfaa |
374 | |
375 | my $meta = $self->_load_or_create( |
376 | $class, |
377 | superclasses => [$base], |
378 | ( @$roles ? (roles => $roles) : ()), |
379 | ); |
81393881 |
380 | |
381 | my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;; |
382 | $meta->make_mutable if $meta->is_immutable; |
383 | $meta->add_method(_build_member_type => sub{ $object } ); |
384 | #XXX as a default pass the domain model as a target_model until i come up with something |
385 | #better through the coercion method |
386 | my $def_act_args = sub { |
387 | my $super = shift; |
388 | return { (target_model => $_[0]->_source_resultset), %{ $super->(@_) } }; |
f670cfd0 |
389 | }; |
81393881 |
390 | $meta->add_around_method_modifier('_default_action_args_for', $def_act_args); |
f670cfd0 |
391 | |
7adfd53f |
392 | |
81393881 |
393 | { |
394 | my $all_actions = $self->_all_collection_actions; |
395 | my $action_haystack = [keys %$all_actions]; |
396 | if(!defined $action_rules){ |
397 | $action_rules = $self->default_collection_actions; |
398 | } elsif( (!ref $action_rules && $action_rules) || (ref $action_rules eq 'Regexp') ){ |
399 | $action_rules = [ $action_rules ]; |
400 | } elsif( ref $action_rules eq 'ARRAY' && @$action_rules){ |
401 | #don't add a qr/./ rule if we have at least one match rule |
402 | push(@$action_rules, qr/./) |
403 | unless grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude') |
404 | || !ref $_ || ref $_ eq 'Regexp'} @$action_rules; |
f670cfd0 |
405 | } |
7adfd53f |
406 | |
81393881 |
407 | # XXX this is kind of a dirty hack to support custom actions that are not |
408 | # previously defined and still be able to use the parse_reflect_rules mechanism |
409 | my @custom_actions = grep {!exists $all_actions->{$_}} |
410 | map{ $_->[0] } grep {ref $_ eq 'ARRAY' && $_->[0] ne '-exclude'} @$action_rules; |
411 | push(@$action_haystack, @custom_actions); |
412 | my $actions = $self->parse_reflect_rules($action_rules, $action_haystack); |
413 | for my $action (keys %$actions){ |
414 | my $action_opts = $self->merge_hashes |
415 | ($all_actions->{$action} || {}, $actions->{$action} || {}); |
416 | |
417 | #NOTE: If the name of the action is not specified in the prototype then use it's |
418 | #hash key as the name. I think this is sane beahvior, but I've actually been thinking |
419 | #of making Action prototypes their own separate objects |
420 | $self->reflect_source_action( |
421 | name => $action, |
422 | object_class => $object, |
423 | source_class => $source, |
424 | %$action_opts, |
425 | ); |
426 | |
427 | # XXX i will move this to use the coercion method soon. this will be |
428 | # GoodEnough until then. I still need to think a little about the type coercion |
429 | # thing so i don't make a mess of it |
430 | my $act_args = sub { #override target model for this action |
431 | my $super = shift; |
432 | return { %{ $super->(@_) }, |
433 | ($_[1] eq $action ? (target_model => $_[0]->_source_resultset) : () ) }; |
7adfd53f |
434 | }; |
81393881 |
435 | $meta->add_around_method_modifier('_default_action_args_for', $act_args); |
7adfd53f |
436 | } |
81393881 |
437 | } |
438 | $meta->make_immutable if $make_immutable; |
439 | return $meta; |
440 | }; |
441 | sub reflect_source_object { |
442 | my($self, %opts) = @_; |
443 | %opts = %{ $self->merge_hashes(\%opts, $self->_compute_source_options(%opts)) }; |
7adfd53f |
444 | |
27f4dfaa |
445 | my $base = delete $opts{base} || Object; |
446 | my $roles = delete $opts{roles} || []; |
447 | my $class = delete $opts{class}; |
448 | my $dm_name = delete $opts{domain_model_name}; |
449 | my $dm_opts = delete $opts{domain_model_args} || {}; |
7adfd53f |
450 | |
81393881 |
451 | my $source_name = delete $opts{source_name}; |
452 | my $schema = delete $opts{schema_class}; |
453 | my $source_class = delete $opts{source_class}; |
454 | my $parent = delete $opts{parent_class}; |
455 | my $parent_dm = delete $opts{parent_domain_model_name}; |
f670cfd0 |
456 | |
81393881 |
457 | my $action_rules = delete $opts{actions}; |
458 | my $attr_rules = delete $opts{attributes}; |
7adfd53f |
459 | |
81393881 |
460 | $class ||= $self->class_name_from_source_name($parent, $source_name); |
f670cfd0 |
461 | |
81393881 |
462 | Class::MOP::load_class($parent); |
463 | Class::MOP::load_class($schema) if $schema; |
464 | Class::MOP::load_class($source_class); |
7adfd53f |
465 | |
27f4dfaa |
466 | my $meta = $self->_load_or_create( |
467 | $class, |
468 | superclasses => [$base], |
469 | ( @$roles ? (roles => $roles) : ()), |
470 | ); |
7adfd53f |
471 | |
81393881 |
472 | #create the domain model |
473 | $dm_name ||= $self->dm_name_from_source_name($source_name); |
f670cfd0 |
474 | |
81393881 |
475 | $dm_opts->{isa} = $source_class; |
476 | $dm_opts->{is} ||= 'rw'; |
477 | $dm_opts->{required} ||= 1; |
df9a9c12 |
478 | |
81393881 |
479 | my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;; |
480 | $meta->make_mutable if $meta->is_immutable; |
7adfd53f |
481 | |
81393881 |
482 | my $dm_attr = $meta->add_domain_model($dm_name, %$dm_opts); |
483 | my $dm_reader = $dm_attr->get_read_method; |
df9a9c12 |
484 | |
81393881 |
485 | unless( $class->can('inflate_result') ){ |
486 | my $inflate_method = sub { |
487 | my $class = shift; my ($src) = @_; |
488 | $src = $src->resolve if $src->isa('DBIx::Class::ResultSourceHandle'); |
489 | $class->new($dm_name, $src->result_class->inflate_result(@_)); |
490 | }; |
491 | $meta->add_method('inflate_result', $inflate_method); |
492 | } |
493 | |
494 | #XXX this is here to allow action prototypes to work with ListView |
495 | # maybe Collections hsould have this kind of thing too to allow you to reconstruct them? |
496 | #i like the possibility to be honest... as aset of key/value pairs they could be URId |
497 | #XXX move to using 'handles' for this? |
498 | $meta->add_method('__id', sub {shift->$dm_reader->id} ) |
499 | unless $class->can('__id'); |
500 | #XXX this one is for Action, ChooseOne and ChooseMany need this shit |
501 | $meta->add_method('__ident_condition', sub {shift->$dm_reader->ident_condition} ) |
502 | unless $class->can('__ident_condition'); |
503 | |
504 | #XXX this is just a disaster |
505 | $meta->add_method('display_name', sub {shift->$dm_reader->display_name} ) |
506 | if( $source_class->can('display_name') && !$class->can('display_name')); |
507 | |
508 | #XXX as a default pass the domain model as a target_model until i come up with something |
509 | #better through the coercion method |
510 | my $def_act_args = sub { |
511 | my $super = shift; |
512 | confess "no dm reader: $dm_reader on $_[0]" unless $_[0]->can($dm_reader); |
513 | return { (target_model => $_[0]->$dm_reader), %{ $super->(@_) } }; |
7adfd53f |
514 | }; |
81393881 |
515 | $meta->add_around_method_modifier('_default_action_args_for', $def_act_args); |
7adfd53f |
516 | |
81393881 |
517 | { |
f670cfd0 |
518 | # attributes => undef, #default to qr/./ |
519 | # attributes => [], #default to nothing |
520 | # attributes => qr//, #DWIM, treated as [qr//] |
521 | # attributes => [{...}] #DWIM, treat as [qr/./, {...} ] |
522 | # attributes => [[-exclude => ...]] #DWIM, treat as [qr/./, [-exclude => ...]] |
81393881 |
523 | my $attr_haystack = |
524 | [ map { $_->name } $source_class->meta->compute_all_applicable_attributes ]; |
525 | |
f670cfd0 |
526 | if(!defined $attr_rules){ |
527 | $attr_rules = [qr/./]; |
528 | } elsif( (!ref $attr_rules && $attr_rules) || (ref $attr_rules eq 'Regexp') ){ |
529 | $attr_rules = [ $attr_rules ]; |
530 | } elsif( ref $attr_rules eq 'ARRAY' && @$attr_rules){ |
531 | #don't add a qr/./ rule if we have at least one match rule |
532 | push(@$attr_rules, qr/./) unless |
533 | grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude') |
534 | || !ref $_ || ref $_ eq 'Regexp'} @$attr_rules; |
535 | } |
7adfd53f |
536 | |
81393881 |
537 | my $attributes = $self->parse_reflect_rules($attr_rules, $attr_haystack); |
538 | for my $attr_name (keys %$attributes){ |
539 | $self->reflect_source_object_attribute( |
540 | class => $class, |
541 | source_class => $source_class, |
542 | parent_class => $parent, |
543 | attribute_name => $attr_name, |
544 | domain_model_name => $dm_name, |
545 | %{ $attributes->{$attr_name} || {}}, |
546 | ); |
547 | } |
548 | } |
549 | |
550 | { |
551 | my $all_actions = $self->_all_object_actions; |
552 | my $action_haystack = [keys %$all_actions]; |
553 | if(!defined $action_rules){ |
554 | $action_rules = $self->default_object_actions; |
555 | } elsif( (!ref $action_rules && $action_rules) || (ref $action_rules eq 'Regexp') ){ |
556 | $action_rules = [ $action_rules ]; |
557 | } elsif( ref $action_rules eq 'ARRAY' && @$action_rules){ |
558 | #don't add a qr/./ rule if we have at least one match rule |
559 | push(@$action_rules, qr/./) |
560 | unless grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude') |
561 | || !ref $_ || ref $_ eq 'Regexp'} @$action_rules; |
562 | } |
7adfd53f |
563 | |
81393881 |
564 | # XXX this is kind of a dirty hack to support custom actions that are not |
565 | # previously defined and still be able to use the parse_reflect_rules mechanism |
566 | my @custom_actions = grep {!exists $all_actions->{$_}} map{ $_->[0] } |
567 | grep {ref $_ eq 'ARRAY' && $_->[0] ne '-exclude'} @$action_rules; |
568 | push(@$action_haystack, @custom_actions); |
569 | my $actions = $self->parse_reflect_rules($action_rules, $action_haystack); |
570 | for my $action (keys %$actions){ |
571 | my $action_opts = $self->merge_hashes |
572 | ($all_actions->{$action} || {}, $actions->{$action} || {}); |
573 | |
574 | #NOTE: If the name of the action is not specified in the prototype then use it's |
575 | #hash key as the name. I think this is sane beahvior, but I've actually been thinking |
576 | #of making Action prototypes their own separate objects |
577 | $self->reflect_source_action( |
578 | name => $action, |
579 | object_class => $class, |
580 | source_class => $source_class, |
581 | %$action_opts, |
582 | ); |
583 | |
584 | # XXX i will move this to use the coercion method soon. this will be |
585 | # GoodEnough until then. I still need to think a little about the type coercion |
586 | # thing so i don't make a mess of it |
587 | my $act_args = sub { #override target model for this action |
588 | my $super = shift; |
589 | confess "no dm reader: $dm_reader on $_[0]" unless $_[0]->can($dm_reader); |
590 | return { %{ $super->(@_) }, |
591 | ($_[1] eq $action ? (target_model => $_[0]->$dm_reader) : () ) }; |
592 | }; |
593 | $meta->add_around_method_modifier('_default_action_args_for', $act_args); |
594 | } |
595 | } |
7adfd53f |
596 | |
81393881 |
597 | $meta->make_immutable if $make_immutable; |
598 | return $meta; |
599 | }; |
7adfd53f |
600 | |
81393881 |
601 | # needs class, attribute_name domain_model_name |
602 | sub reflect_source_object_attribute { |
603 | my ($self, %opts) = @_; |
604 | unless( $opts{attribute_name} && $opts{class} && $opts{parent_class} |
605 | && ( $opts{source_class} || $opts{domain_model_name} ) ){ |
606 | confess( "Error: class, parent_class, attribute_name, and either " . |
607 | "domain_model_name or source_class are required parameters" ); |
608 | } |
7adfd53f |
609 | |
81393881 |
610 | my $meta = $opts{class}->meta; |
611 | my $attr_opts = $self->parameters_for_source_object_attribute(%opts); |
7adfd53f |
612 | |
81393881 |
613 | my $make_immutable = $meta->is_immutable; |
614 | $meta->make_mutable if $meta->is_immutable; |
615 | |
616 | my $attr = $meta->add_attribute($opts{attribute_name}, %$attr_opts); |
7adfd53f |
617 | |
81393881 |
618 | $meta->make_immutable if $make_immutable; |
619 | return $attr; |
620 | }; |
df9a9c12 |
621 | |
81393881 |
622 | # needs class, attribute_name domain_model_name |
623 | sub parameters_for_source_object_attribute { |
624 | my ($self, %opts) = @_; |
625 | |
626 | my $class = delete $opts{class}; |
627 | my $attr_name = delete $opts{attribute_name}; |
628 | my $dm_name = delete $opts{domain_model_name}; |
629 | my $source_class = delete $opts{source_class}; |
630 | my $parent_class = delete $opts{parent_class}; |
631 | confess("parent_class is a required argument") unless $parent_class; |
632 | confess("You must supply at least one of domain_model_name and source_class") |
633 | unless $dm_name || $source_class; |
634 | |
635 | my $source; |
636 | $source = $source_class->result_source_instance if $source_class; |
637 | #puke! dwimery |
638 | if( !$source_class ){ |
639 | my $dm = $class->meta->find_attribute_by_name($dm_name); |
640 | $source_class = $dm->_isa_metadata; |
641 | $source = $source_class->result_source_instance; |
642 | } elsif( !$dm_name ){ |
643 | ($dm_name) = map{$_->name} grep{$_->_isa_metadata eq $source_class} |
644 | $class->meta->domain_models; |
645 | if( !$dm_name ){ #last resort guess |
646 | my $tentative = $self->dm_name_from_source_name($source->source_name); |
647 | ($dm_name) = $tentative if grep{$_->name eq $tentative} $class->domain_models; |
648 | } |
649 | } |
650 | |
651 | my $from_attr = $source_class->meta->find_attribute_by_name($attr_name); |
652 | my $reader = $from_attr->get_read_method; |
09d2d18d |
653 | die("Could not find reader for attribute '$attr_name' on $source_class") |
654 | unless $reader; |
81393881 |
655 | |
656 | #default options. lazy build but no outsider method |
657 | my %attr_opts = ( is => 'ro', lazy => 1, required => 1, |
658 | clearer => "_clear_${attr_name}", |
659 | predicate => { |
660 | "has_${attr_name}" => |
661 | sub { defined(shift->$dm_name->$reader) } |
662 | }, |
663 | domain_model => $dm_name, |
664 | orig_attr_name => $attr_name, |
665 | ); |
7b6797a0 |
666 | $attr_opts{coerce} = 1 if $from_attr->should_coerce; |
81393881 |
667 | |
668 | #m2m / has_many |
669 | my $m2m_meta; |
670 | if(my $coderef = $source->result_class->can('_m2m_metadata')){ |
671 | $m2m_meta = $source->result_class->$coderef; |
672 | } |
673 | |
674 | my $constraint_is_ArrayRef = |
675 | $from_attr->type_constraint->name eq 'ArrayRef' || |
676 | $from_attr->type_constraint->is_subtype_of('ArrayRef'); |
677 | |
678 | if( my $rel_info = $source->relationship_info($attr_name) ){ |
679 | my $rel_accessor = $rel_info->{attrs}->{accessor}; |
680 | my $rel_moniker = $rel_info->{class}->result_source_instance->source_name; |
681 | |
682 | if($rel_accessor eq 'multi' && $constraint_is_ArrayRef) { |
683 | #has_many |
684 | my $sm = $self->class_name_from_source_name($parent_class, $rel_moniker); |
685 | #type constraint is a collection, and default builds it |
686 | my $isa = $attr_opts{isa} = $self->class_name_for_collection_of($sm); |
687 | $attr_opts{default} = eval "sub { |
688 | my \$rs = shift->${dm_name}->related_resultset('${attr_name}'); |
689 | return ${isa}->new(_source_resultset => \$rs); |
690 | }"; |
691 | } elsif( $rel_accessor eq 'single' || $rel_accessor eq 'filter' ) { |
692 | #belongs_to |
693 | #type constraint is the foreign IM object, default inflates it |
694 | my $isa = $attr_opts{isa} = $self->class_name_from_source_name($parent_class, $rel_moniker); |
695 | $attr_opts{default} = eval "sub { |
696 | if (defined(my \$o = shift->${dm_name}->${reader})) { |
697 | return ${isa}->inflate_result(\$o->result_source, { \$o->get_columns }); |
698 | } |
699 | return undef; |
700 | }"; |
df9a9c12 |
701 | } |
81393881 |
702 | } elsif( $constraint_is_ArrayRef && $attr_name =~ m/^(.*)_list$/ ) { |
703 | #m2m magic |
704 | my $mm_name = $1; |
705 | my $link_table = "links_to_${mm_name}_list"; |
706 | my ($hm_source, $far_side); |
707 | eval { $hm_source = $source->related_source($link_table); } |
708 | || confess "Can't find ${link_table} has_many for ${mm_name}_list"; |
709 | eval { $far_side = $hm_source->related_source($mm_name); } |
710 | || confess "Can't find ${mm_name} belongs_to on ".$hm_source->result_class |
711 | ." traversing many-many for ${mm_name}_list"; |
712 | |
713 | my $sm = $self->class_name_from_source_name($parent_class,$far_side->source_name); |
714 | my $isa = $attr_opts{isa} = $self->class_name_for_collection_of($sm); |
715 | |
716 | #proper collections will remove the result_class uglyness. |
717 | $attr_opts{default} = eval "sub { |
718 | my \$rs = shift->${dm_name}->related_resultset('${link_table}')->related_resultset('${mm_name}'); |
719 | return ${isa}->new(_source_resultset => \$rs); |
720 | }"; |
721 | } elsif( $constraint_is_ArrayRef && defined $m2m_meta && exists $m2m_meta->{$attr_name} ){ |
722 | #m2m if using introspectable m2m component |
723 | my $rel = $m2m_meta->{$attr_name}->{relation}; |
724 | my $far_rel = $m2m_meta->{$attr_name}->{foreign_relation}; |
725 | my $far_source = $source->related_source($rel)->related_source($far_rel); |
726 | my $sm = $self->class_name_from_source_name($parent_class, $far_source->source_name); |
727 | my $isa = $attr_opts{isa} = $self->class_name_for_collection_of($sm); |
728 | |
729 | my $rs_meth = $m2m_meta->{$attr_name}->{rs_method}; |
730 | $attr_opts{default} = eval "sub { |
731 | return ${isa}->new(_source_resultset => shift->${dm_name}->${rs_meth}); |
732 | }"; |
733 | } else { |
734 | #no rel |
735 | $attr_opts{isa} = $from_attr->_isa_metadata; |
09d2d18d |
736 | my $default_code = "sub{ shift->${dm_name}->${reader} }"; |
737 | $attr_opts{default} = eval $default_code; |
738 | die "Could not generate default for attribute, code '$default_code' did not compile with: $@" if $@; |
81393881 |
739 | } |
740 | return \%attr_opts; |
741 | }; |
742 | sub reflect_source_action { |
743 | my($self, %opts) = @_; |
27f4dfaa |
744 | my $name = delete $opts{name}; |
745 | my $base = delete $opts{base} || Action; |
746 | my $roles = delete $opts{roles} || []; |
747 | my $class = delete $opts{class}; |
81393881 |
748 | my $object = delete $opts{object_class}; |
749 | my $source = delete $opts{source_class}; |
750 | |
751 | confess("name, object_class and source_class are required arguments") |
752 | unless $source && $name && $object; |
753 | |
754 | my $attr_rules = delete $opts{attributes}; |
755 | $class ||= $object->_default_action_class_for($name); |
756 | |
757 | Class::MOP::load_class( $base ); |
758 | Class::MOP::load_class( $object ); |
759 | Class::MOP::load_class( $source ); |
760 | |
761 | #print STDERR "\n\t", ref $attr_rules eq 'ARRAY' ? @$attr_rules : $attr_rules,"\n"; |
762 | # attributes => undef, #default to qr/./ |
763 | # attributes => [], #default to nothing |
764 | # attributes => qr//, #DWIM, treated as [qr//] |
765 | # attributes => [{...}] #DWIM, treat as [qr/./, {...} ] |
766 | # attributes => [[-exclude => ...]] #DWIM, treat as [qr/./, [-exclude => ...]] |
767 | my $attr_haystack = [ map { $_->name } $object->parameter_attributes ]; |
768 | if(!defined $attr_rules){ |
769 | $attr_rules = [qr/./]; |
770 | } elsif( (!ref $attr_rules && $attr_rules) || (ref $attr_rules eq 'Regexp') ){ |
771 | $attr_rules = [ $attr_rules ]; |
772 | } elsif( ref $attr_rules eq 'ARRAY' && @$attr_rules){ |
773 | #don't add a qr/./ rule if we have at least one match rule |
774 | push(@$attr_rules, qr/./) unless |
775 | grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude') |
776 | || !ref $_ || ref $_ eq 'Regexp'} @$attr_rules; |
777 | } |
778 | |
779 | #print STDERR "${name}\t${class}\t${base}\n"; |
780 | #print STDERR "\t${object}\t${source}\n"; |
781 | #print STDERR "\t",@$attr_rules,"\n"; |
782 | |
783 | my $o_meta = $object->meta; |
784 | my $s_meta = $source->meta; |
785 | my $attributes = $self->parse_reflect_rules($attr_rules, $attr_haystack); |
786 | |
787 | #create the class |
27f4dfaa |
788 | my $meta = $self->_load_or_create( |
789 | $class, |
790 | superclasses => [$base], |
791 | ( @$roles ? (roles => $roles) : ()), |
792 | ); |
81393881 |
793 | my $make_immutable = $meta->is_immutable || $self->make_classes_immutable; |
794 | $meta->make_mutable if $meta->is_immutable; |
795 | |
796 | for my $attr_name (keys %$attributes){ |
797 | my $attr_opts = $attributes->{$attr_name} || {}; |
798 | my $o_attr = $o_meta->find_attribute_by_name($attr_name); |
799 | my $s_attr_name = $o_attr->orig_attr_name || $attr_name; |
800 | my $s_attr = $s_meta->find_attribute_by_name($s_attr_name); |
801 | confess("Unable to find attribute for '${s_attr_name}' via '${source}'") |
802 | unless defined $s_attr; |
803 | next unless $s_attr->get_write_method |
804 | && $s_attr->get_write_method !~ /^_/; #only rw attributes! |
805 | |
806 | my $attr_params = $self->parameters_for_source_object_action_attribute |
807 | ( |
808 | object_class => $object, |
809 | source_class => $source, |
810 | attribute_name => $attr_name |
811 | ); |
812 | $meta->add_attribute( $attr_name => %$attr_params); |
813 | } |
814 | |
815 | $meta->make_immutable if $make_immutable; |
816 | return $meta; |
817 | }; |
818 | sub parameters_for_source_object_action_attribute { |
819 | my ($self, %opts) = @_; |
820 | |
821 | my $object = delete $opts{object_class}; |
822 | my $attr_name = delete $opts{attribute_name}; |
823 | my $source_class = delete $opts{source_class}; |
824 | confess("object_class and attribute_name are required parameters") |
825 | unless $attr_name && $object; |
826 | |
827 | my $o_meta = $object->meta; |
828 | my $dm_name = $o_meta->find_attribute_by_name($attr_name)->domain_model; |
829 | $source_class ||= $o_meta->find_attribute_by_name($dm_name)->_isa_metadata; |
830 | my $from_attr = $source_class->meta->find_attribute_by_name($attr_name); |
831 | |
832 | #print STDERR "$attr_name is type: " . $from_attr->meta->name . "\n"; |
833 | |
834 | confess("${attr_name} is not writeable and can not be reflected") |
835 | unless $from_attr->get_write_method; |
836 | |
837 | my %attr_opts = ( |
838 | is => 'rw', |
839 | isa => $from_attr->_isa_metadata, |
840 | required => $from_attr->is_required, |
841 | ($from_attr->is_required |
842 | ? () : (clearer => "clear_${attr_name}")), |
843 | predicate => "has_${attr_name}", |
844 | ); |
845 | |
846 | if ($attr_opts{required}) { |
847 | if($from_attr->has_default) { |
848 | $attr_opts{lazy} = 1; |
849 | $attr_opts{default} = $from_attr->default; |
850 | } else { |
851 | $attr_opts{lazy_fail} = 1; |
7adfd53f |
852 | } |
81393881 |
853 | } |
854 | |
855 | |
856 | my $m2m_meta; |
857 | if(my $coderef = $source_class->result_class->can('_m2m_metadata')){ |
858 | $m2m_meta = $source_class->result_class->$coderef; |
859 | } |
860 | #test for relationships |
861 | my $constraint_is_ArrayRef = |
862 | $from_attr->type_constraint->name eq 'ArrayRef' || |
863 | $from_attr->type_constraint->is_subtype_of('ArrayRef'); |
864 | |
865 | my $source = $source_class->result_source_instance; |
866 | if (my $rel_info = $source->relationship_info($attr_name)) { |
867 | my $rel_accessor = $rel_info->{attrs}->{accessor}; |
868 | |
869 | if($rel_accessor eq 'multi' && $constraint_is_ArrayRef) { |
870 | confess "${attr_name} is a rw has_many, this won't work."; |
871 | } elsif( $rel_accessor eq 'single' || $rel_accessor eq 'filter') { |
df9a9c12 |
872 | $attr_opts{valid_values} = sub { |
81393881 |
873 | shift->target_model->result_source->related_source($attr_name)->resultset; |
df9a9c12 |
874 | }; |
7adfd53f |
875 | } |
81393881 |
876 | } elsif ( $constraint_is_ArrayRef && $attr_name =~ m/^(.*)_list$/) { |
877 | my $mm_name = $1; |
878 | my $link_table = "links_to_${mm_name}_list"; |
879 | $attr_opts{default} = sub { [] }; |
880 | $attr_opts{valid_values} = sub { |
881 | shift->target_model->result_source->related_source($link_table) |
882 | ->related_source($mm_name)->resultset; |
883 | }; |
884 | } elsif( $constraint_is_ArrayRef && defined $m2m_meta && exists $m2m_meta->{$attr_name} ){ |
885 | #m2m if using introspectable m2m component |
886 | my $rel = $m2m_meta->{$attr_name}->{relation}; |
887 | my $far_rel = $m2m_meta->{$attr_name}->{foreign_relation}; |
888 | $attr_opts{default} = sub { [] }; |
889 | $attr_opts{valid_values} = sub { |
890 | shift->target_model->result_source->related_source($rel) |
891 | ->related_source($far_rel)->resultset; |
892 | }; |
893 | } |
894 | #use Data::Dumper; |
895 | #print STDERR "\n" .$attr_name ." - ". $object . "\n"; |
896 | #print STDERR Dumper(\%attr_opts); |
897 | return \%attr_opts; |
898 | }; |
27f4dfaa |
899 | |
81393881 |
900 | sub _load_or_create { |
27f4dfaa |
901 | my ($self, $class, %options) = @_; |
902 | |
903 | if( $self->_maybe_load_class($class) ){ |
904 | return $class->meta; |
905 | } |
906 | my $base; |
907 | if( exists $options{superclasses} ){ |
908 | ($base) = @{ $options{superclasses} }; |
909 | } else { |
910 | $base = 'Reaction::InterfaceModel::Action'; |
911 | } |
912 | return $base->meta->create($class, %options); |
913 | } |
914 | |
81393881 |
915 | sub _maybe_load_class { |
916 | my ($self, $class) = @_; |
917 | my $file = $class . '.pm'; |
918 | $file =~ s{::}{/}g; |
919 | my $ret = eval { Class::MOP::load_class($class) }; |
920 | if ($INC{$file} && $@) { |
921 | confess "Error loading ${class}: $@"; |
922 | } |
923 | return $ret; |
27f4dfaa |
924 | } |
c11c77ee |
925 | |
81393881 |
926 | __PACKAGE__->meta->make_immutable; |
c11c77ee |
927 | |
7adfd53f |
928 | |
929 | 1; |
0402136b |
930 | |
931 | #--------#---------#---------#---------#---------#---------#---------#---------# |
932 | __END__; |
933 | |
934 | =head1 NAME |
935 | |
936 | Reaction::InterfaceModel::Reflector::DBIC - |
937 | Automatically Generate InterfaceModels from DBIx::Class models |
938 | |
939 | =head1 DESCRIPTION |
940 | |
941 | The InterfaceModel reflectors are classes that are meant to aid you in easily |
942 | generating Reaction::InterfaceModel classes that represent their underlying |
943 | DBIx::Class domain models by introspecting your L<DBIx::Class::ResultSource>s |
944 | and creating a collection of L<Reaction::InterfaceModel::Object> and |
945 | L<Reaction::InterfaceModel::Collection> classes for you to use. |
946 | |
947 | The default base class of all Object classes will be |
948 | L<Reaction::InterfaceModel::Object> and the default Collection type will be |
949 | L<Reaction::InterfaceModel::Collection::Virtual::ResultSet>. |
950 | |
951 | Additionally, the reflector can create InterfaceModel actions that interact |
952 | with the supplied L<Reaction::UI::Controller::Collection::CRUD>, allowing you |
953 | to easily set up a highly customizable CRUD interface in minimal time. |
954 | |
955 | At this time, supported collection actions consist of: |
956 | |
957 | =over 4 |
958 | |
959 | =item B<> L<Reaction::INterfaceModel::Action::DBIC::ResultSet::Create> |
960 | |
961 | Creates a new item in the collection and underlying ResultSet. |
962 | |
963 | =item B<> L<Reaction::INterfaceModel::Action::DBIC::ResultSet::DeleteAll> |
964 | |
965 | Deletes all the items in a collection and it's underlying resultset using |
966 | C<delete_all> |
967 | |
968 | =back |
969 | |
970 | And supported object actions are : |
971 | |
972 | =over 4 |
973 | |
974 | =item B<Update> - via L<Reaction::INterfaceModel::Action::DBIC::Result::Update> |
975 | |
976 | Updates an existing object. |
977 | |
978 | =item B<Delete> - via L<Reaction::INterfaceModel::Action::DBIC::Result::Delete> |
979 | |
980 | Deletes an existing object. |
981 | |
982 | =back |
983 | |
984 | =head1 SYNOPSIS |
985 | |
986 | package MyApp::IM::TestModel; |
987 | use base 'Reaction::InterfaceModel::Object'; |
988 | use Reaction::Class; |
989 | use Reaction::InterfaceModel::Reflector::DBIC; |
990 | my $reflector = Reaction::InterfaceModel::Reflector::DBIC->new; |
991 | |
992 | #Reflect everything |
993 | $reflector->reflect_schema |
994 | ( |
995 | model_class => __PACKAGE__, |
996 | schema_class => 'MyApp::Schema', |
997 | ); |
998 | |
999 | =head2 Selectively including and excluding sources |
1000 | |
1001 | #reflect everything except for the FooBar and FooBaz classes |
1002 | $reflector->reflect_schema |
1003 | ( |
1004 | model_class => __PACKAGE__, |
1005 | schema_class => 'MyApp::Schema', |
1006 | sources => [-exclude => [qw/FooBar FooBaz/] ], |
1007 | # you could also do: |
1008 | sources => [-exclude => qr/(?:FooBar|FooBaz)/, |
1009 | # or even |
1010 | sources => [-exclude => [qr/FooBar/, qr/FooBaz/], |
1011 | ); |
1012 | |
1013 | #reflect only the Foo family of sources |
1014 | $reflector->reflect_schema |
1015 | ( |
1016 | model_class => __PACKAGE__, |
1017 | schema_class => 'MyApp::Schema', |
1018 | sources => qr/^Foo/, |
1019 | ); |
1020 | |
1021 | =head2 Selectively including and excluding fields in sources |
1022 | |
1023 | #Reflect Foo and Baz in their entirety and exclude the field 'avatar' in the Bar ResultSource |
1024 | $reflector->reflect_schema |
1025 | ( |
1026 | model_class => __PACKAGE__, |
1027 | schema_class => 'MyApp::Schema', |
1028 | sources => [qw/Foo Baz/, |
1029 | [ Bar => {attributes => [[-exclude => 'avatar']] } ], |
1030 | # or exclude by regex |
1031 | [ Bar => {attributes => [-exclude => qr/avatar/] } ], |
1032 | # or simply do not include it... |
1033 | [ Bar => {attributes => [qw/id name description/] } ], |
1034 | ], |
1035 | ); |
1036 | |
1037 | =head1 ATTRIBUTES |
1038 | |
1039 | =head2 make_classes_immutable |
1040 | |
1041 | =head2 object_actions |
1042 | |
1043 | =head2 collection_actions |
1044 | |
1045 | =head2 default_object_actions |
1046 | |
1047 | =head2 default_collection_actions |
1048 | |
1049 | =head2 builtin_object_actions |
1050 | |
1051 | =head2 builtin_collection_actions |
1052 | |
1053 | =head1 METHODS |
1054 | |
1055 | =head2 new |
1056 | |
1057 | =head2 _all_object_actions |
1058 | |
1059 | =head2 _all_collection_actions |
1060 | |
1061 | =head2 dm_name_from_class_name |
1062 | |
1063 | =head2 dm_name_from_source_name |
1064 | |
1065 | =head2 class_name_from_source_name |
1066 | |
1067 | =head2 class_name_for_collection_of |
1068 | |
1069 | =head2 merge_hashes |
1070 | |
1071 | =head2 parse_reflect_rules |
1072 | |
1073 | =head2 merge_reflect_rules |
1074 | |
1075 | =head2 reflect_schema |
1076 | |
1077 | =head2 _compute_source_options |
1078 | |
1079 | =head2 add_source |
1080 | |
1081 | =head2 reflect_source |
1082 | |
1083 | =head2 reflect_source_collection |
1084 | |
1085 | =head2 reflect_source_object |
1086 | |
1087 | =head2 reflect_source_object_attribute |
1088 | |
1089 | =head2 parameters_for_source_object_attribute |
1090 | |
1091 | =head2 reflect_source_action |
1092 | |
1093 | =head2 parameters_for_source_object_action_attribute |
1094 | |
1095 | =head1 TODO |
1096 | |
1097 | Allow the reflector to dump the generated code out as files, eliminating the need to |
1098 | reflect on startup every time. This will likely take quite a bit of work though. The |
1099 | main work is already in place, but the grunt work is still left. At the moment there |
1100 | is no closures that can't be dumped out as code with a little bit of work. |
1101 | |
1102 | =head1 AUTHORS |
1103 | |
1104 | See L<Reaction::Class> for authors. |
1105 | |
1106 | =head1 LICENSE |
1107 | |
1108 | See L<Reaction::Class> for the license. |
1109 | |
1110 | =cut |