1 package Reaction::InterfaceModel::Reflector::DBIC;
3 use aliased 'Reaction::InterfaceModel::Action::DBIC::ResultSet::Create';
4 use aliased 'Reaction::InterfaceModel::Action::DBIC::ResultSet::DeleteAll';
5 use aliased 'Reaction::InterfaceModel::Action::DBIC::Result::Update';
6 use aliased 'Reaction::InterfaceModel::Action::DBIC::Result::Delete';
8 use aliased 'Reaction::InterfaceModel::Collection::Virtual::ResultSet';
9 use aliased 'Reaction::InterfaceModel::Object';
10 use aliased 'Reaction::InterfaceModel::Action';
18 has make_classes_immutable => (isa => "Bool", is => "rw", required => 1, default => sub{ 1 });
20 implements create_classes => as {
22 my $packages = $self->packages;
24 while(my($name, $properties) = each %$packages){
25 my $base = $properties->{base} || 'Reaction::Object';
26 my $meta = $self->_load_or_create($name, $base);
31 implements reflect_schema => as {
32 my ($self, %opts) = @_;
33 my $base = delete $opts{base} || Object;
34 my $model = delete $opts{model_class};
35 my $schema = delete $opts{schema_class};
36 my $dm_name = delete $opts{domain_model_name};
37 my $dm_args = delete $opts{domain_model_args} || {};
38 $dm_name ||= $self->dm_name_from_class_name($schema);
40 #load all necessary classes
41 confess("model_class and schema_class are required parameters")
42 unless($model && $schema);
43 Class::MOP::load_class( $base );
44 Class::MOP::load_class( $schema );
47 # sources => undef, #default to qr/./
48 # sources => [], #default to nothing
49 # sources => qr//, #DWIM, treated as [qr//]
50 # sources => [{...}] #DWIM, treat as [qr/./, {...} ]
51 # sources => [[-exclude => ...]] #DWIM, treat as [qr/./, [-exclude => ...]]
52 my $haystack = [ $schema->sources ];
54 my $rules = delete $opts{sources};
57 } elsif( ref $rules eq 'Regexp'){
59 } elsif( ref $rules eq 'ARRAY' && @$rules){
60 #don't add a qr/./ rule if we have at least one match rule
61 push(@$rules, qr/./) unless grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude')
62 || !ref $_ || ref $_ eq 'Regexp'} @$rules;
65 my $sources = $self->parse_reflect_rules($rules, $haystack);
67 my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;
68 $meta->make_mutable if $meta->is_immutable;
70 $meta->add_domain_model
71 ($dm_name, is => 'rw', isa => $schema, required => 1, %$dm_args);
73 for my $source_name (keys %$sources){
74 my $source_opts = $sources->{$source_name} || {};
75 $self->reflect_source(
76 source_name => $source_name,
77 parent_class => $model,
78 schema_class => $schema,
79 source_class => $schema->class($source_name),
80 parent_domain_model_name => $dm_name,
85 $meta->make_immutable if $make_immutable;
89 implements _compute_source_options => as {
90 my ($self, %opts) = @_;
91 my $schema = delete $opts{schema_class};
92 my $source_name = delete $opts{source_name};
93 my $source_class = delete $opts{source_class};
94 my $parent = delete $opts{parent_class};
95 my $parent_dm = delete $opts{parent_domain_model_name};
97 #this is the part where I hate my life for promissing all sorts of DWIMery
98 confess("parent_class and source_name or source_class are required parameters")
99 unless($parent && ($source_name || $source_class));
101 OUTER: until( $schema && $source_name && $source_class && $parent_dm ){
102 if( $schema && !$source_name){
103 next OUTER if $source_name = $source_class->result_source_instance->source_name;
104 } elsif( $schema && !$source_class){
105 next OUTER if $source_class = eval { $schema->class($source_name) };
108 if($source_class && (!$schema || !$source_name)){
110 $schema = $source_class->result_source_instance->schema;
111 next OUTER if $schema && Class::MOP::load_class($schema);
114 $source_name = $source_class->result_source_instance->source_name;
115 next OUTER if $source_name;
118 my @haystack = $parent_dm ?
119 $parent->meta->find_attribute_by_name($parent_dm) : $parent->domain_models;
121 #there's a lot of guessing going on, but it should work fine on most cases
122 INNER: for my $needle (@haystack){
123 my $isa = $needle->_isa_metadata;
124 next INNER unless Class::MOP::load_class( $isa->_isa_metadata );
125 next INNER unless $isa->isa('DBIx::Class::Schema');
126 if(!$parent_dm && $schema && $isa eq $schema){
127 $parent_dm = $needle->name;
132 my $src_class = eval{ $isa->class($source_name) };
133 next INNER unless $src_class;
134 next INNER if($source_class && $source_class ne $src_class);
136 $parent_dm = $needle->name;
137 $source_class = $src_class;
142 #do we even need to go this far?
143 if( !$parent_dm && $schema ){
144 my $tentative = $self->dm_name_from_class_name($schema);
145 $parent_dm = $tentative if grep{$_->name eq $tentative} @haystack;
148 confess("Could not determine options automatically from: schema " .
149 "'${schema}', source_name '${source_name}', source_class " .
150 "'${source_class}', parent_domain_model_name '${parent_dm}'");
154 source_name => $source_name,
155 schema_class => $schema,
156 source_class => $source_class,
157 parent_class => $parent,
158 parent_domain_model_name => $parent_dm,
162 implements _class_to_attribute_name => as {
163 my ( $self, $str ) = @_;
164 confess("wrong arguments passed for _class_to_attribute_name") unless $str;
165 return join('_', map lc, split(/::|(?<=[a-z0-9])(?=[A-Z])/, $str))
168 implements add_source => as {
169 my ($self, %opts) = @_;
171 my $model = delete $opts{model_class};
172 my $reader = delete $opts{reader};
173 my $source = delete $opts{source_name};
174 my $dm_name = delete $opts{domain_model_name};
175 my $collection = delete $opts{collection_class};
176 my $name = delete $opts{attribute_name} || $source;
178 confess("model_class and source_name are required parameters")
179 unless $model && $source;
180 my $meta = $model->meta;
182 unless( $collection ){
183 my $object = $self->class_name_from_source_name($model, $source);
184 $collection = $self->class_name_for_collection_of($object);
188 $reader =~ s/([a-z0-9])([A-Z])/${1}_${2}/g ;
189 $reader = $self->_class_to_attribute_name($reader) . "_collection";
192 my @haystack = $meta->domain_models;
194 @haystack = grep { $_->_isa_metadata->isa('DBIx::Class::Schema') } @haystack;
197 $dm_name = $haystack[0]->name;
198 } elsif(@haystack > 1){
199 confess("Failed to automatically determine domain_model_name. More than one " .
200 "possible match (".(join ", ", map{"'".$_->name."'"} @haystack).")");
202 confess("Failed to automatically determine domain_model_name. No matches.");
212 predicate => "has_" . $self->_class_to_attribute_name($name) ,
213 domain_model => $dm_name,
214 orig_attr_name => $source,
218 _source_resultset => $_[0]->$dm_name->resultset($source),
224 # my %debug_attr_opts =
228 # isa => $collection,
230 # predicate => "has_" . $self->_class_to_attribute_name($name) ,
231 # domain_model => $dm_name,
232 # orig_attr_name => $source,
233 # default => qq^sub {
234 # my \$self = \$_[0];
235 # return $collection->new(
236 # _source_resultset => \$self->$dm_name->resultset("$source"),
244 my $make_immutable = $meta->is_immutable;
245 $meta->make_mutable if $make_immutable;
246 my $attr = $meta->add_attribute($name, %attr_opts);
247 $meta->make_immutable if $make_immutable;
252 implements reflect_source => as {
253 my ($self, %opts) = @_;
254 my $collection = delete $opts{collection} || {};
255 %opts = %{ $self->merge_hashes(\%opts, $self->_compute_source_options(%opts)) };
257 my $obj_meta = $self->reflect_source_object(%opts);
258 my $col_meta = $self->reflect_source_collection
260 object_class => $obj_meta->name,
261 source_class => $opts{source_class},
267 model_class => delete $opts{parent_class},
268 domain_model_name => delete $opts{parent_domain_model_name},
269 collection_class => $col_meta->name,
273 implements reflect_source_collection => as {
274 my ($self, %opts) = @_;
275 my $base = delete $opts{base} || ResultSet;
276 my $class = delete $opts{class};
277 my $object = delete $opts{object_class};
278 my $source = delete $opts{source_class};
279 my $action_rules = delete $opts{actions};
281 confess('object_class and source_class are required parameters')
282 unless $object && $source;
283 $class ||= $self->class_name_for_collection_of($object);
285 Class::MOP::load_class( $base );
286 Class::MOP::load_class( $object );
287 my $meta = $self->_load_or_create($class, $base);
289 my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;;
290 $meta->make_mutable if $meta->is_immutable;
291 $meta->add_method(_build_member_type => sub{ $object } );
292 #XXX as a default pass the domain model as a target_model until i come up with something
293 #better through the coercion method
294 my $def_act_args = sub {
296 return { (target_model => $_[0]->_source_resultset), %{ $super->(@_) } };
298 $meta->add_around_method_modifier('_default_action_args_for', $def_act_args);
302 my $all_actions = $self->_all_collection_actions;
303 my $action_haystack = [keys %$all_actions];
304 if(!defined $action_rules){
305 $action_rules = $self->default_collection_actions;
306 } elsif( (!ref $action_rules && $action_rules) || (ref $action_rules eq 'Regexp') ){
307 $action_rules = [ $action_rules ];
308 } elsif( ref $action_rules eq 'ARRAY' && @$action_rules){
309 #don't add a qr/./ rule if we have at least one match rule
310 push(@$action_rules, qr/./)
311 unless grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude')
312 || !ref $_ || ref $_ eq 'Regexp'} @$action_rules;
315 # XXX this is kind of a dirty hack to support custom actions that are not
316 # previously defined and still be able to use the parse_reflect_rules mechanism
317 my @custom_actions = grep {!exists $all_actions->{$_}}
318 map{ $_->[0] } grep {ref $_ eq 'ARRAY' && $_->[0] ne '-exclude'} @$action_rules;
319 push(@$action_haystack, @custom_actions);
320 my $actions = $self->parse_reflect_rules($action_rules, $action_haystack);
321 for my $action (keys %$actions){
322 my $action_opts = $self->merge_hashes
323 ($all_actions->{$action} || {}, $actions->{$action} || {});
325 #NOTE: If the name of the action is not specified in the prototype then use it's
326 #hash key as the name. I think this is sane beahvior, but I've actually been thinking
327 #of making Action prototypes their own separate objects
328 $self->reflect_source_action(
330 object_class => $object,
331 source_class => $source,
335 # XXX i will move this to use the coercion method soon. this will be
336 # GoodEnough until then. I still need to think a little about the type coercion
337 # thing so i don't make a mess of it
338 my $act_args = sub { #override target model for this action
340 return { %{ $super->(@_) },
341 ($_[1] eq $action ? (target_model => $_[0]->_source_resultset) : () ) };
343 $meta->add_around_method_modifier('_default_action_args_for', $act_args);
346 $meta->make_immutable if $make_immutable;
350 implements reflect_source_object => as {
351 my($self, %opts) = @_;
352 %opts = %{ $self->merge_hashes(\%opts, $self->_compute_source_options(%opts)) };
354 my $base = delete $opts{base} || Object;
355 my $class = delete $opts{class};
356 my $dm_name = delete $opts{domain_model_name};
357 my $dm_opts = delete $opts{domain_model_args} || {};
359 my $source_name = delete $opts{source_name};
360 my $schema = delete $opts{schema_class};
361 my $source_class = delete $opts{source_class};
362 my $parent = delete $opts{parent_class};
363 my $parent_dm = delete $opts{parent_domain_model_name};
365 my $action_rules = delete $opts{actions};
366 my $attr_rules = delete $opts{attributes};
368 $class ||= $self->class_name_from_source_name($parent, $source_name);
370 Class::MOP::load_class($parent);
371 Class::MOP::load_class($schema) if $schema;
372 Class::MOP::load_class($source_class);
374 my $meta = $self->_load_or_create($class, $base);
376 #create the domain model
377 $dm_name ||= $self->dm_name_from_source_name($source_name);
379 $dm_opts->{isa} = $source_class;
380 $dm_opts->{is} ||= 'rw';
381 $dm_opts->{required} ||= 1;
383 my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;;
384 $meta->make_mutable if $meta->is_immutable;
386 my $dm_attr = $meta->add_domain_model($dm_name, %$dm_opts);
387 my $dm_reader = $dm_attr->get_read_method;
389 unless( $class->can('inflate_result') ){
390 my $inflate_method = sub {
391 my $class = shift; my ($src) = @_;
392 $src = $src->resolve if $src->isa('DBIx::Class::ResultSourceHandle');
393 $class->new($dm_name, $src->result_class->inflate_result(@_));
395 $meta->add_method('inflate_result', $inflate_method);
398 #XXX this is here to allow action prototypes to work with ListView
399 # maybe Collections hsould have this kind of thing too to allow you to reconstruct them?
400 #i like the possibility to be honest... as aset of key/value pairs they could be URId
401 #XXX move to using 'handles' for this?
402 $meta->add_method('__id', sub {shift->$dm_reader->id} )
403 unless $class->can('__id');
404 #XXX this one is for Action, ChooseOne and ChooseMany need this shit
405 $meta->add_method('__ident_condition', sub {shift->$dm_reader->ident_condition} )
406 unless $class->can('__ident_condition');
408 #XXX this is just a disaster
409 $meta->add_method('display_name', sub {shift->$dm_reader->display_name} )
410 if( $source_class->can('display_name') && !$class->can('display_name'));
412 #XXX as a default pass the domain model as a target_model until i come up with something
413 #better through the coercion method
414 my $def_act_args = sub {
416 confess "no dm reader: $dm_reader on $_[0]" unless $_[0]->can($dm_reader);
417 return { (target_model => $_[0]->$dm_reader), %{ $super->(@_) } };
419 $meta->add_around_method_modifier('_default_action_args_for', $def_act_args);
422 # attributes => undef, #default to qr/./
423 # attributes => [], #default to nothing
424 # attributes => qr//, #DWIM, treated as [qr//]
425 # attributes => [{...}] #DWIM, treat as [qr/./, {...} ]
426 # attributes => [[-exclude => ...]] #DWIM, treat as [qr/./, [-exclude => ...]]
428 [ map { $_->name } $source_class->meta->compute_all_applicable_attributes ];
430 if(!defined $attr_rules){
431 $attr_rules = [qr/./];
432 } elsif( (!ref $attr_rules && $attr_rules) || (ref $attr_rules eq 'Regexp') ){
433 $attr_rules = [ $attr_rules ];
434 } elsif( ref $attr_rules eq 'ARRAY' && @$attr_rules){
435 #don't add a qr/./ rule if we have at least one match rule
436 push(@$attr_rules, qr/./) unless
437 grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude')
438 || !ref $_ || ref $_ eq 'Regexp'} @$attr_rules;
441 my $attributes = $self->parse_reflect_rules($attr_rules, $attr_haystack);
442 for my $attr_name (keys %$attributes){
443 $self->reflect_source_object_attribute(
445 source_class => $source_class,
446 parent_class => $parent,
447 attribute_name => $attr_name,
448 domain_model_name => $dm_name,
449 %{ $attributes->{$attr_name} || {}},
455 my $all_actions = $self->_all_object_actions;
456 my $action_haystack = [keys %$all_actions];
457 if(!defined $action_rules){
458 $action_rules = $self->default_object_actions;
459 } elsif( (!ref $action_rules && $action_rules) || (ref $action_rules eq 'Regexp') ){
460 $action_rules = [ $action_rules ];
461 } elsif( ref $action_rules eq 'ARRAY' && @$action_rules){
462 #don't add a qr/./ rule if we have at least one match rule
463 push(@$action_rules, qr/./)
464 unless grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude')
465 || !ref $_ || ref $_ eq 'Regexp'} @$action_rules;
468 # XXX this is kind of a dirty hack to support custom actions that are not
469 # previously defined and still be able to use the parse_reflect_rules mechanism
470 my @custom_actions = grep {!exists $all_actions->{$_}} map{ $_->[0] }
471 grep {ref $_ eq 'ARRAY' && $_->[0] ne '-exclude'} @$action_rules;
472 push(@$action_haystack, @custom_actions);
473 my $actions = $self->parse_reflect_rules($action_rules, $action_haystack);
474 for my $action (keys %$actions){
475 my $action_opts = $self->merge_hashes
476 ($all_actions->{$action} || {}, $actions->{$action} || {});
478 #NOTE: If the name of the action is not specified in the prototype then use it's
479 #hash key as the name. I think this is sane beahvior, but I've actually been thinking
480 #of making Action prototypes their own separate objects
481 $self->reflect_source_action(
483 object_class => $class,
484 source_class => $source_class,
488 # XXX i will move this to use the coercion method soon. this will be
489 # GoodEnough until then. I still need to think a little about the type coercion
490 # thing so i don't make a mess of it
491 my $act_args = sub { #override target model for this action
493 confess "no dm reader: $dm_reader on $_[0]" unless $_[0]->can($dm_reader);
494 return { %{ $super->(@_) },
495 ($_[1] eq $action ? (target_model => $_[0]->$dm_reader) : () ) };
497 $meta->add_around_method_modifier('_default_action_args_for', $act_args);
501 $meta->make_immutable if $make_immutable;
505 # needs class, attribute_name domain_model_name
506 implements reflect_source_object_attribute => as {
507 my ($self, %opts) = @_;
508 unless( $opts{attribute_name} && $opts{class} && $opts{parent_class}
509 && ( $opts{source_class} || $opts{domain_model_name} ) ){
510 confess( "Error: class, parent_class, attribute_name, and either " .
511 "domain_model_name or source_class are required parameters" );
514 my $meta = $opts{class}->meta;
515 my $attr_opts = $self->parameters_for_source_object_attribute(%opts);
517 my $make_immutable = $meta->is_immutable;
518 $meta->make_mutable if $meta->is_immutable;
520 my $attr = $meta->add_attribute($opts{attribute_name}, %$attr_opts);
522 $meta->make_immutable if $make_immutable;
526 # needs class, attribute_name domain_model_name
527 implements parameters_for_source_object_attribute => as {
528 my ($self, %opts) = @_;
530 my $class = delete $opts{class};
531 my $attr_name = delete $opts{attribute_name};
532 my $dm_name = delete $opts{domain_model_name};
533 my $source_class = delete $opts{source_class};
534 my $parent_class = delete $opts{parent_class};
535 confess("parent_class is a required argument") unless $parent_class;
536 confess("You must supply at least one of domain_model_name and source_class")
537 unless $dm_name || $source_class;
540 $source = $source_class->result_source_instance if $source_class;
542 if( !$source_class ){
543 my $dm = $class->meta->find_attribute_by_name($dm_name);
544 $source_class = $dm->_isa_metadata;
545 $source = $source_class->result_source_instance;
546 } elsif( !$dm_name ){
547 ($dm_name) = map{$_->name} grep{$_->_isa_metadata eq $source_class}
548 $class->meta->domain_models;
549 if( !$dm_name ){ #last resort guess
550 my $tentative = $self->dm_name_from_source_name($source->source_name);
551 ($dm_name) = $tentative if grep{$_->name eq $tentative} $class->domain_models;
555 my $from_attr = $source_class->meta->find_attribute_by_name($attr_name);
557 #default options. lazy build but no outsider method
558 my %attr_opts = ( is => 'ro', lazy => 1, required => 1,
559 clearer => "_clear_${attr_name}",
561 "has_${attr_name}" =>
562 sub { defined(shift->$dm_name->$attr_name) }
564 domain_model => $dm_name,
565 orig_attr_name => $attr_name,
569 my $constraint_is_ArrayRef =
570 $from_attr->type_constraint->name eq 'ArrayRef' ||
571 $from_attr->type_constraint->is_subtype_of('ArrayRef');
575 if( my $rel_info = $source->relationship_info($attr_name) ){
576 my $rel_accessor = $rel_info->{attrs}->{accessor};
577 my $rel_moniker = $rel_info->{class}->result_source_instance->source_name;
579 if($rel_accessor eq 'multi' && $constraint_is_ArrayRef) {
581 my $sm = $self->class_name_from_source_name($parent_class, $rel_moniker);
582 #type constraint is a collection, and default builds it
583 $attr_opts{isa} = $self->class_name_for_collection_of($sm);
584 $attr_opts{default} = sub {
585 my $rs = shift->$dm_name->related_resultset($attr_name);
586 return $attr_opts{isa}->new(_source_resultset => $rs);
588 } elsif( $rel_accessor eq 'single') {
590 #type constraint is the foreign IM object, default inflates it
591 $attr_opts{isa} = $self->class_name_from_source_name($parent_class, $rel_moniker);
592 $attr_opts{default} = sub {
593 if (defined(my $o = shift->$dm_name->$attr_name)) {
594 return $attr_opts{isa}->inflate_result($o->result_source, { $o->get_columns });
597 #->find_related($attr_name, {},{result_class => $attr_opts{isa}});
600 } elsif( $constraint_is_ArrayRef && $attr_name =~ m/^(.*)_list$/ ) {
603 my $link_table = "links_to_${mm_name}_list";
604 my ($hm_source, $far_side);
605 eval { $hm_source = $source->related_source($link_table); }
606 || confess "Can't find ${link_table} has_many for ${mm_name}_list";
607 eval { $far_side = $hm_source->related_source($mm_name); }
608 || confess "Can't find ${mm_name} belongs_to on ".$hm_source->result_class
609 ." traversing many-many for ${mm_name}_list";
611 my $sm = $self->class_name_from_source_name($parent_class,$far_side->source_name);
612 $attr_opts{isa} = $self->class_name_for_collection_of($sm);
614 #proper collections will remove the result_class uglyness.
615 $attr_opts{default} = sub {
616 my $rs = shift->$dm_name->related_resultset($link_table)->related_resultset($mm_name);
617 return $attr_opts{isa}->new(_source_resultset => $rs);
619 #} elsif( $constraint_is_ArrayRef ){
620 #test these to see if rel is m2m
621 #my $meth = $attr_name;
622 #if( $source->can("set_${meth}") && $source->can("add_to_${meth}") &&
623 # $source->can("${meth}_rs") && $source->can("remove_from_${meth}") ){
629 my $reader = $from_attr->get_read_method;
630 $attr_opts{isa} = $from_attr->_isa_metadata;
631 $attr_opts{default} = sub{ shift->$dm_name->$reader };
637 implements reflect_source_action => as{
638 my($self, %opts) = @_;
639 my $name = delete $opts{name};
640 my $class = delete $opts{class};
641 my $base = delete $opts{base} || Action;
642 my $object = delete $opts{object_class};
643 my $source = delete $opts{source_class};
645 confess("name, object_class and source_class are required arguments")
646 unless $source && $name && $object;
648 my $attr_rules = delete $opts{attributes};
649 $class ||= $object->_default_action_class_for($name);
651 Class::MOP::load_class( $base );
652 Class::MOP::load_class( $object );
653 Class::MOP::load_class( $source );
655 #print STDERR "\n\t", ref $attr_rules eq 'ARRAY' ? @$attr_rules : $attr_rules,"\n";
656 # attributes => undef, #default to qr/./
657 # attributes => [], #default to nothing
658 # attributes => qr//, #DWIM, treated as [qr//]
659 # attributes => [{...}] #DWIM, treat as [qr/./, {...} ]
660 # attributes => [[-exclude => ...]] #DWIM, treat as [qr/./, [-exclude => ...]]
661 my $attr_haystack = [ map { $_->name } $object->meta->parameter_attributes ];
662 if(!defined $attr_rules){
663 $attr_rules = [qr/./];
664 } elsif( (!ref $attr_rules && $attr_rules) || (ref $attr_rules eq 'Regexp') ){
665 $attr_rules = [ $attr_rules ];
666 } elsif( ref $attr_rules eq 'ARRAY' && @$attr_rules){
667 #don't add a qr/./ rule if we have at least one match rule
668 push(@$attr_rules, qr/./) unless
669 grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude')
670 || !ref $_ || ref $_ eq 'Regexp'} @$attr_rules;
673 #print STDERR "${name}\t${class}\t${base}\n";
674 #print STDERR "\t${object}\t${source}\n";
675 #print STDERR "\t",@$attr_rules,"\n";
677 my $o_meta = $object->meta;
678 my $s_meta = $source->meta;
679 my $attributes = $self->parse_reflect_rules($attr_rules, $attr_haystack);
682 my $meta = $self->_load_or_create($class, $base);
683 my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;
684 $meta->make_mutable if $meta->is_immutable;
686 for my $attr_name (keys %$attributes){
687 my $attr_opts = $attributes->{$attr_name} || {};
688 my $o_attr = $o_meta->find_attribute_by_name($attr_name);
689 my $s_attr_name = $o_attr->orig_attr_name || $attr_name;
690 my $s_attr = $s_meta->find_attribute_by_name($s_attr_name);
691 confess("Unable to find attribute for '${s_attr_name}' via '${source}'")
692 unless defined $s_attr;
693 next unless $s_attr->get_write_method
694 && $s_attr->get_write_method !~ /^_/; #only rw attributes!
696 my $attr_params = $self->parameters_for_source_object_action_attribute
698 object_class => $object,
699 source_class => $source,
700 attribute_name => $attr_name
702 $meta->add_attribute( $attr_name => %$attr_params);
705 $meta->make_immutable if $make_immutable;
709 implements parameters_for_source_object_action_attribute => as {
710 my ($self, %opts) = @_;
712 my $object = delete $opts{object_class};
713 my $attr_name = delete $opts{attribute_name};
714 my $source_class = delete $opts{source_class};
715 confess("object_class and attribute_name are required parameters")
716 unless $attr_name && $object;
718 my $o_meta = $object->meta;
719 my $dm_name = $o_meta->find_attribute_by_name($attr_name)->domain_model;
720 $source_class ||= $o_meta->find_attribute_by_name($dm_name)->_isa_metadata;
721 my $from_attr = $source_class->meta->find_attribute_by_name($attr_name);
723 #print STDERR "$attr_name is type: " . $from_attr->meta->name . "\n";
725 confess("${attr_name} is not writeable and can not be reflected")
726 unless $from_attr->get_write_method;
730 isa => $from_attr->_isa_metadata,
731 required => $from_attr->is_required,
732 ($from_attr->is_required
733 ? () : (clearer => "clear_${attr_name}")),
734 predicate => "has_${attr_name}",
737 if ($attr_opts{required}) {
738 if($from_attr->has_default) {
739 $attr_opts{lazy} = 1;
740 $attr_opts{default} = $from_attr->default;
742 $attr_opts{lazy_fail} = 1;
746 #test for relationships
747 my $constraint_is_ArrayRef =
748 $from_attr->type_constraint->name eq 'ArrayRef' ||
749 $from_attr->type_constraint->is_subtype_of('ArrayRef');
751 my $source = $source_class->result_source_instance;
752 if (my $rel_info = $source->relationship_info($attr_name)) {
753 my $rel_accessor = $rel_info->{attrs}->{accessor};
755 if($rel_accessor eq 'multi' && $constraint_is_ArrayRef) {
756 confess "${attr_name} is a rw has_many, this won't work.";
757 } elsif( $rel_accessor eq 'single') {
758 $attr_opts{valid_values} = sub {
759 shift->target_model->result_source->related_source($attr_name)->resultset;
762 } elsif ( $constraint_is_ArrayRef && $attr_name =~ m/^(.*)_list$/) {
764 my $link_table = "links_to_${mm_name}_list";
765 my ($hm_source, $far_side);
766 eval { $hm_source = $source->related_source($link_table); }
767 || confess "Can't find ${link_table} has_many for ${mm_name}_list";
768 eval { $far_side = $hm_source->related_source($mm_name); }
769 || confess "Can't find ${mm_name} belongs_to on ".$hm_source->result_class
770 ." traversing many-many for ${mm_name}_list";
772 $attr_opts{default} = sub { [] };
773 $attr_opts{valid_values} = sub {
774 shift->target_model->result_source->related_source($link_table)
775 ->related_source($mm_name)->resultset;
779 #print STDERR "\n" .$attr_name ." - ". $object . "\n";
780 #print STDERR Dumper(\%attr_opts);
784 implements _load_or_create => as {
785 my ($self, $class, $base) = @_;
786 my $meta = $self->_maybe_load_class($class) ?
787 $class->meta : $base->meta->create($class, superclasses => [ $base ]);
791 implements _maybe_load_class => as {
792 my ($self, $class) = @_;
793 my $file = $class . '.pm';
795 my $ret = eval { Class::MOP::load_class($class) };
796 if ($INC{$file} && $@) {
797 confess "Error loading ${class}: $@";
806 #--------#---------#---------#---------#---------#---------#---------#---------#
811 Reaction::InterfaceModel::Reflector::DBIC -
812 Automatically Generate InterfaceModels from DBIx::Class models
816 The InterfaceModel reflectors are classes that are meant to aid you in easily
817 generating Reaction::InterfaceModel classes that represent their underlying
818 DBIx::Class domain models by introspecting your L<DBIx::Class::ResultSource>s
819 and creating a collection of L<Reaction::InterfaceModel::Object> and
820 L<Reaction::InterfaceModel::Collection> classes for you to use.
822 The default base class of all Object classes will be
823 L<Reaction::InterfaceModel::Object> and the default Collection type will be
824 L<Reaction::InterfaceModel::Collection::Virtual::ResultSet>.
826 Additionally, the reflector can create InterfaceModel actions that interact
827 with the supplied L<Reaction::UI::Controller::Collection::CRUD>, allowing you
828 to easily set up a highly customizable CRUD interface in minimal time.
830 At this time, supported collection actions consist of:
834 =item B<> L<Reaction::INterfaceModel::Action::DBIC::ResultSet::Create>
836 Creates a new item in the collection and underlying ResultSet.
838 =item B<> L<Reaction::INterfaceModel::Action::DBIC::ResultSet::DeleteAll>
840 Deletes all the items in a collection and it's underlying resultset using
845 And supported object actions are :
849 =item B<Update> - via L<Reaction::INterfaceModel::Action::DBIC::Result::Update>
851 Updates an existing object.
853 =item B<Delete> - via L<Reaction::INterfaceModel::Action::DBIC::Result::Delete>
855 Deletes an existing object.
861 package MyApp::IM::TestModel;
862 use base 'Reaction::InterfaceModel::Object';
864 use Reaction::InterfaceModel::Reflector::DBIC;
865 my $reflector = Reaction::InterfaceModel::Reflector::DBIC->new;
868 $reflector->reflect_schema
870 model_class => __PACKAGE__,
871 schema_class => 'MyApp::Schema',
874 =head2 Selectively including and excluding sources
876 #reflect everything except for the FooBar and FooBaz classes
877 $reflector->reflect_schema
879 model_class => __PACKAGE__,
880 schema_class => 'MyApp::Schema',
881 sources => [-exclude => [qw/FooBar FooBaz/] ],
883 sources => [-exclude => qr/(?:FooBar|FooBaz)/,
885 sources => [-exclude => [qr/FooBar/, qr/FooBaz/],
888 #reflect only the Foo family of sources
889 $reflector->reflect_schema
891 model_class => __PACKAGE__,
892 schema_class => 'MyApp::Schema',
896 =head2 Selectively including and excluding fields in sources
898 #Reflect Foo and Baz in their entirety and exclude the field 'avatar' in the Bar ResultSource
899 $reflector->reflect_schema
901 model_class => __PACKAGE__,
902 schema_class => 'MyApp::Schema',
903 sources => [qw/Foo Baz/,
904 [ Bar => {attributes => [[-exclude => 'avatar']] } ],
905 # or exclude by regex
906 [ Bar => {attributes => [-exclude => qr/avatar/] } ],
907 # or simply do not include it...
908 [ Bar => {attributes => [qw/id name description/] } ],
914 =head2 make_classes_immutable
916 =head2 object_actions
918 =head2 collection_actions
920 =head2 default_object_actions
922 =head2 default_collection_actions
924 =head2 builtin_object_actions
926 =head2 builtin_collection_actions
932 =head2 _all_object_actions
934 =head2 _all_collection_actions
936 =head2 dm_name_from_class_name
938 =head2 dm_name_from_source_name
940 =head2 class_name_from_source_name
942 =head2 class_name_for_collection_of
946 =head2 parse_reflect_rules
948 =head2 merge_reflect_rules
950 =head2 reflect_schema
952 =head2 _compute_source_options
956 =head2 reflect_source
958 =head2 reflect_source_collection
960 =head2 reflect_source_object
962 =head2 reflect_source_object_attribute
964 =head2 parameters_for_source_object_attribute
966 =head2 reflect_source_action
968 =head2 parameters_for_source_object_action_attribute
972 Allow the reflector to dump the generated code out as files, eliminating the need to
973 reflect on startup every time. This will likely take quite a bit of work though. The
974 main work is already in place, but the grunt work is still left. At the moment there
975 is no closures that can't be dumped out as code with a little bit of work.
979 See L<Reaction::Class> for authors.
983 See L<Reaction::Class> for the license.