fix URI type
[catagits/Reaction.git] / lib / Reaction / InterfaceModel / Reflector / DBIC.pm
index ca4f8ad..2c34cdf 100644 (file)
@@ -1,6 +1,7 @@
 package Reaction::InterfaceModel::Reflector::DBIC;
 
 use aliased 'Reaction::InterfaceModel::Action::DBIC::ResultSet::Create';
+use aliased 'Reaction::InterfaceModel::Action::DBIC::ResultSet::DeleteAll';
 use aliased 'Reaction::InterfaceModel::Action::DBIC::Result::Update';
 use aliased 'Reaction::InterfaceModel::Action::DBIC::Result::Delete';
 
@@ -10,210 +11,501 @@ use aliased 'Reaction::InterfaceModel::Action';
 use Reaction::Class;
 use Class::MOP;
 
+use Catalyst::Utils;
+
 class DBIC, which {
 
-  has model_class => (isa => "Str",  is => 'ro', required => 1);
-  has debug_mode  =>
-    (isa => 'Bool', is => 'rw', required => 1, default => '0');
-  has make_classes_immutable =>
-    (isa => 'Bool', is => 'rw', required => 1, default => '0');
-
-  has default_object_actions =>
-    ( isa => "ArrayRef", is => "rw", required => 1,
-      default => sub{
-        [ { name => 'Update', base => Update },
-          { name => 'Delete', base => Delete,
-            attributes => [],
-          },
-        ];
-      } );
-
-  has default_collection_actions =>
-    ( isa => "ArrayRef", is => "rw", required => 1,
-      default => sub{
-        [{name => 'Create', base => Create}],
-      } );
-
-  implements BUILD => as{
+  has make_classes_immutable => (isa => "Bool", is => "rw", required => 1, default => sub{ 1 });
+
+  #user defined actions and prototypes
+  has object_actions     => (isa => "HashRef", is => "rw", lazy_build => 1);
+  has collection_actions => (isa => "HashRef", is => "rw", lazy_build => 1);
+
+  #which actions to create by default
+  has default_object_actions     => (isa => "ArrayRef", is => "rw", lazy_build => 1);
+  has default_collection_actions => (isa => "ArrayRef", is => "rw", lazy_build => 1);
+
+  #builtin actions and prototypes
+  has builtin_object_actions     => (isa => "HashRef", is => "rw", lazy_build => 1);
+  has builtin_collection_actions => (isa => "HashRef", is => "rw", lazy_build => 1);
+
+  implements _build_object_actions     => as { {} };
+  implements _build_collection_actions => as { {} };
+
+  implements _build_default_object_actions     => as { [ qw/Update Delete/ ] };
+  implements _build_default_collection_actions => as { [ qw/Create DeleteAll/ ] };
+
+  implements _build_builtin_object_actions => as {
+    {
+      Update => { name => 'Update', base => Update },
+      Delete => { name => 'Delete', base => Delete, attributes => [] },
+    };
+  };
+
+  implements _build_builtin_collection_actions => as {
+    {
+      Create    => {name => 'Create',    base => Create    },
+      DeleteAll => {name => 'DeleteAll', base => DeleteAll, attributes => [] }
+    };
+  };
+
+  implements _all_object_actions => as {
+   my $self = shift;
+    return $self->merge_hashes
+      ($self->builtin_object_actions, $self->object_actions);
+  };
+
+  implements _all_collection_actions => as {
     my $self = shift;
-    my $ok = eval {Class::MOP::load_class( $self->model_class ); };
+    return $self->merge_hashes
+      ($self->builtin_collection_actions, $self->collection_actions);
+  };
 
-    unless ($ok){
-      print STDERR "Creating target class ". $self->model_class . "\n"
-        if $self->debug_mode;
-      Object->meta->create($self->model_class, superclasses => [ Object ]);
-    }
+  implements dm_name_from_class_name => as {
+    my($self, $class) = @_;
+    confess("wrong arguments") unless $class;
+    $class =~ s/::/_/g;
+    $class = "_" . lc($class) . "_store";
+    return $class;
+  };
+
+  implements dm_name_from_source_name => as {
+    my($self, $source) = @_;
+    confess("wrong arguments") unless $source;
+    $source =~ s/([a-z0-9])([A-Z])/${1}_${2}/g ;
+    $source = "_" . lc($source) . "_store";
+    return $source;
   };
 
-  implements submodel_classname_from_source_name => as {
-    my ($self, $moniker) = @_;
-    return join "::", $self->model_class, $moniker;
+  implements class_name_from_source_name => as {
+    my ($self, $model_class, $source_name) = @_;
+    confess("wrong arguments") unless $model_class && $source_name;
+    return join "::", $model_class, $source_name;
   };
 
-  implements classname_for_collection_of => as {
+  implements class_name_for_collection_of => as {
     my ($self, $object_class) = @_;
+    confess("wrong arguments") unless $object_class;
     return "${object_class}::Collection";
   };
 
-  #requires domain_model everything else optional
-  implements reflect_model => as {
+  implements merge_hashes => as {
+    my($self, $left, $right) = @_;
+    return Catalyst::Utils::merge_hashes($left, $right);
+  };
+
+  implements parse_reflect_rules => as {
+    my ($self, $rules, $haystack) = @_;
+    confess('$rules must be an array reference')    unless ref $rules    eq 'ARRAY';
+    confess('$haystack must be an array reference') unless ref $haystack eq 'ARRAY';
+
+    my $needles = {};
+    my (@exclude, @include, $global_opts);
+    if(@$rules == 2 && $rules->[0] eq '-exclude'){
+      push(@exclude, (ref $rules->[1] eq 'ARRAY' ? @{$rules->[1]} : $rules->[1]));
+    } else {
+      for my $rule ( @$rules ){
+        if (ref $rule eq 'ARRAY' && $rule->[0] eq '-exclude'){
+          push(@exclude, (ref $rule->[1] eq 'ARRAY' ? @{$rule->[1]} : $rule->[1]));
+        } elsif( ref $rule eq 'HASH' ){
+          $global_opts = ref $global_opts eq 'HASH' ?
+            $self->merge_hashes($global_opts, $rule) : $rule;
+        } else {
+          push(@include, $rule);
+        }
+      }
+    }
+    my $check_exclude = sub{
+      for my $rule (@exclude){
+        return 1 if(ref $rule eq 'Regexp' ? $_[0] =~ /$rule/ : $_[0] eq $rule);
+      }
+      return;
+    };
+
+    @$haystack = grep { !$check_exclude->($_) } @$haystack;
+    $self->merge_reflect_rules(\@include, $needles, $haystack, $global_opts);
+    return $needles;
+  };
+
+  implements merge_reflect_rules => as {
+    my ($self, $rules, $needles, $haystack, $local_opts) = @_;
+    for my $rule ( @$rules ){
+      if(!ref $rule && ( grep {$rule eq $_} @$haystack ) ){
+        $needles->{$rule} = defined $needles->{$rule} ?
+          $self->merge_hashes($needles->{$rule}, $local_opts) : $local_opts;
+      } elsif( ref $rule eq 'Regexp' ){
+        for my $match ( grep { /$rule/ } @$haystack ){
+          $needles->{$match} = defined $needles->{$match} ?
+            $self->merge_hashes($needles->{$match}, $local_opts) : $local_opts;
+        }
+      } elsif( ref $rule eq 'ARRAY' ){
+        my $opts;
+        $opts = pop(@$rule) if @$rule > 1 and ref $rule->[$#$rule] eq 'HASH';
+        $opts = $self->merge_hashes($local_opts, $opts) if defined $local_opts;
+        $self->merge_reflect_rules($rule, $needles, $haystack, $opts);
+      }
+    }
+  };
+
+  implements reflect_schema => as {
     my ($self, %opts) = @_;
-    my $meta = $self->model_class->meta;
-    my $source  = delete $opts{domain_model_class};
+    my $base    = delete $opts{base} || Object;
+    my $model   = delete $opts{model_class};
+    my $schema  = delete $opts{schema_class};
     my $dm_name = delete $opts{domain_model_name};
     my $dm_args = delete $opts{domain_model_args} || {};
+    $dm_name ||= $self->dm_name_from_class_name($schema);
+
+    #load all necessary classes
+    confess("model_class and schema_class are required parameters")
+      unless($model && $schema);
+    Class::MOP::load_class( $base );
+    Class::MOP::load_class( $schema );
+    my $meta = $self->_load_or_create($model, $base);
+
+    # sources => undef,              #default to qr/./
+    # sources => [],                 #default to nothing
+    # sources => qr//,               #DWIM, treated as [qr//]
+    # sources => [{...}]             #DWIM, treat as [qr/./, {...} ]
+    # sources => [[-exclude => ...]] #DWIM, treat as [qr/./, [-exclude => ...]]
+    my $haystack = [ $schema->sources ];
+
+    my $rules    = delete $opts{sources};
+    if(!defined $rules){
+      $rules = [qr/./];
+    } elsif( ref $rules eq 'Regexp'){
+      $rules = [ $rules ];
+    } elsif( ref $rules eq 'ARRAY' && @$rules){
+      #don't add a qr/./ rule if we have at least one match rule
+      push(@$rules, qr/./) unless grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude')
+                                          || !ref $_  || ref $_ eq 'Regexp'} @$rules;
+    }
 
-    my $reflect_submodels = delete $opts{reflect_submodels};
-    my %exclude_submodels = map {$_ => 1}
-      ref $opts{exclude_submodels} ? @{$opts{exclude_submodels}} : ();
+    my $sources = $self->parse_reflect_rules($rules, $haystack);
 
-    Class::MOP::load_class($source);
-    my $make_immutable = $self->make_classes_immutable || $meta->is_immutable;
+    my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;
     $meta->make_mutable if $meta->is_immutable;
 
-    unless( $dm_name ){
-      $dm_name = "_".$source;
-      $dm_name =~ s/::/_/g;
-    }
-
-    print STDERR "Reflecting model '$source' with domain model '$dm_name'\n"
-      if $self->debug_mode;
-    $meta->add_domain_model($dm_name, is => 'rw', required => 1, %$dm_args);
-
-    #reflect all applicable submodels on undef
-    @$reflect_submodels = $source->sources unless ref $reflect_submodels;
-    @$reflect_submodels = grep { !$exclude_submodels{$_} } @$reflect_submodels;
-
-    for my $moniker (@$reflect_submodels){
-      my $source_class = $source->class($moniker);
-      print STDERR "... and submodel '$source_class'\n" if $self->debug_mode;
-      my $sub_meta = $self->reflect_submodel(domain_model_class => $source_class);
-      my $col_meta = $self->reflect_collection_for(object_class => $sub_meta->name);
-
-      $self->add_submodel_to_model(
-                                   source_name       => $moniker,
-                                   domain_model_name => $dm_name,
-                                   collection_class  => $col_meta->name,
-                                  );
+    $meta->add_domain_model
+      ($dm_name, is => 'rw', isa => $schema, required => 1, %$dm_args);
+
+    for my $source_name (keys %$sources){
+      my $source_opts = $sources->{$source_name} || {};
+      $self->reflect_source(
+                            source_name  => $source_name,
+                            parent_class => $model,
+                            schema_class => $schema,
+                            source_class => $schema->class($source_name),
+                            parent_domain_model_name => $dm_name,
+                            %$source_opts
+                           );
     }
 
     $meta->make_immutable if $make_immutable;
     return $meta;
   };
 
-  #XXX I could make domain_model_name by exploiting the metadata in the
-  #DomainModelAttribute, I'm just waiting to properly redesign DMAttr,
-  #it'll be good, I promise.
+  implements _compute_source_options => as {
+    my ($self, %opts) = @_;
+    my $schema       = delete $opts{schema_class};
+    my $source_name  = delete $opts{source_name};
+    my $source_class = delete $opts{source_class};
+    my $parent       = delete $opts{parent_class};
+    my $parent_dm    = delete $opts{parent_domain_model_name};
+
+    #this is the part where I hate my life for promissing all sorts of DWIMery
+    confess("parent_class and source_name or source_class are required parameters")
+      unless($parent && ($source_name || $source_class));
+
+  OUTER: until( $schema && $source_name && $source_class && $parent_dm ){
+      if( $schema && !$source_name){
+        next OUTER if $source_name = $source_class->result_source_instance->source_name;
+      } elsif( $schema && !$source_class){
+        next OUTER if $source_class = eval { $schema->class($source_name) };
+      }
 
-  implements add_submodel_to_model => as {
-    my($self, %opts) = @_;
-    my $reader  = $opts{reader};
-    my $moniker = $opts{source_name};
-    my $dm_name = $opts{domain_model_name};
-    my $c_class = $opts{collection_class};
-    my $name    = $opts{attribute_name} || $moniker;
-    my $meta    = $self->model_class->meta;
+      if($source_class && (!$schema || !$source_name)){
+        if(!$schema){
+          $schema = $source_class->result_source_instance->schema;
+          next OUTER if $schema && Class::MOP::load_class($schema);
+        }
+        if(!$source_name){
+          $source_name = $source_class->result_source_instance->source_name;
+          next OUTER if $source_name;
+        }
+      }
+      my @haystack = $parent_dm ?
+        $parent->meta->find_attribute_by_name($parent_dm) : $parent->domain_models;
+
+      #there's a lot of guessing going on, but it should work fine on most cases
+    INNER: for my $needle (@haystack){
+        my $isa = $needle->_isa_metadata;
+        next INNER unless Class::MOP::load_class( $isa->_isa_metadata );
+        next INNER unless $isa->isa('DBIx::Class::Schema');
+        if(!$parent_dm && $schema && $isa eq $schema){
+          $parent_dm = $needle->name;
+          next OUTER;
+        }
+
+        if( $source_name ){
+          my $src_class = eval{ $isa->class($source_name) };
+          next INNER unless $src_class;
+          next INNER if($source_class && $source_class ne $src_class);
+          $schema = $isa;
+          $parent_dm = $needle->name;
+          $source_class = $src_class;
+          next OUTER;
+        }
+      }
 
-    my $make_immutable = $meta->is_immutable;
-    $meta->make_mutable if $meta->is_immutable;
+      #do we even need to go this far?
+      if( !$parent_dm && $schema ){
+        my $tentative = $self->dm_name_from_class_name($schema);
+        $parent_dm = $tentative if grep{$_->name eq $tentative} @haystack;
+      }
+
+      confess("Could not determine options automatically from: schema " .
+              "'${schema}', source_name '${source_name}', source_class " .
+              "'${source_class}', parent_domain_model_name '${parent_dm}'");
+    }
+
+    return {
+            source_name  => $source_name,
+            schema_class => $schema,
+            source_class => $source_class,
+            parent_class => $parent,
+            parent_domain_model_name => $parent_dm,
+           };
+  };
+
+  implements _class_to_attribute_name => as {
+    my ( $self, $str ) = @_;
+    confess("wrong arguments passed for _class_to_attribute_name") unless $str;
+    return join('_', map lc, split(/::|(?<=[a-z0-9])(?=[A-Z])/, $str))
+  };
 
-    unless ($reader){
-      $reader = $moniker;
+  implements add_source => as {
+    my ($self, %opts) = @_;
+
+    my $model      = delete $opts{model_class};
+    my $reader     = delete $opts{reader};
+    my $source     = delete $opts{source_name};
+    my $dm_name    = delete $opts{domain_model_name};
+    my $collection = delete $opts{collection_class};
+    my $name       = delete $opts{attribute_name} || $source;
+
+    confess("model_class and source_name are required parameters")
+      unless $model && $source;
+    my $meta = $model->meta;
+
+    unless( $collection ){
+      my $object = $self->class_name_from_source_name($model, $source);
+      $collection = $self->class_name_for_collection_of($object);
+    }
+    unless( $reader ){
+      $reader = $source;
       $reader =~ s/([a-z0-9])([A-Z])/${1}_${2}/g ;
-      $reader = lc($reader) . "_collection";
+      $reader = $self->_class_to_attribute_name($reader) . "_collection";
+    }
+    unless( $dm_name ){
+      my @haystack = $meta->domain_models;
+      if( @haystack > 1 ){
+        @haystack = grep { $_->_isa_metadata->isa('DBIx::Class::Schema') } @haystack;
+      }
+      if(@haystack == 1){
+        $dm_name = $haystack[0]->name;
+      } elsif(@haystack > 1){
+        confess("Failed to automatically determine domain_model_name. More than one " .
+                "possible match (".(join ", ", map{"'".$_->name."'"} @haystack).")");
+      } else {
+        confess("Failed to automatically determine domain_model_name. No matches.");
+      }
     }
 
     my %attr_opts =
       (
        lazy           => 1,
-       isa            => $c_class,
        required       => 1,
+       isa            => $collection,
        reader         => $reader,
-       predicate      => "has_${moniker}",
+       predicate      => "has_" . $self->_class_to_attribute_name($name) ,
        domain_model   => $dm_name,
-       orig_attr_name => $moniker,
+       orig_attr_name => $source,
        default        => sub {
-         $c_class->new(_source_resultset => shift->$dm_name->resultset($moniker) );
+         $collection->new
+           (
+            _source_resultset => $_[0]->$dm_name->resultset($source),
+            _parent           => $_[0],
+           );
        },
       );
-    print STDERR "... linking submodel '$c_class' through method '$reader'\n"
-      if $self->debug_mode;
 
-    my $attr = $meta->add_attribute($moniker, %attr_opts);
+#     my %debug_attr_opts =
+#       (
+#        lazy           => 1,
+#        required       => 1,
+#        isa            => $collection,
+#        reader         => $reader,
+#        predicate      => "has_" . $self->_class_to_attribute_name($name) ,
+#        domain_model   => $dm_name,
+#        orig_attr_name => $source,
+#        default        => qq^sub {
+#          my \$self = \$_[0];
+#          return $collection->new(
+#            _source_resultset => \$self->$dm_name->resultset("$source"),
+#            _parent => \$self,
+#          );
+#        }, ^,
+#       );
+
+
+
+    my $make_immutable = $meta->is_immutable;
+    $meta->make_mutable   if $make_immutable;
+    my $attr = $meta->add_attribute($name, %attr_opts);
     $meta->make_immutable if $make_immutable;
+
     return $attr;
   };
 
-  # requires #object_class, everything else optional
-  implements reflect_collection_for => as {
+  implements reflect_source => as {
+    my ($self, %opts) = @_;
+    my $collection  = delete $opts{collection} || {};
+    %opts = %{ $self->merge_hashes(\%opts, $self->_compute_source_options(%opts)) };
+
+    my $obj_meta = $self->reflect_source_object(%opts);
+    my $col_meta = $self->reflect_source_collection
+      (
+       object_class => $obj_meta->name,
+       source_class => $opts{source_class},
+       %$collection
+      );
+
+    $self->add_source(
+                      %opts,
+                      model_class       => delete $opts{parent_class},
+                      domain_model_name => delete $opts{parent_domain_model_name},
+                      collection_class  => $col_meta->name,
+                     );
+  };
+
+  implements reflect_source_collection => as {
     my ($self, %opts) = @_;
-    my $object  = delete $opts{object_class};
     my $base    = delete $opts{base} || ResultSet;
-    my $actions = delete $opts{reflect_actions} || $self->default_collection_actions;
-    my $class   = $opts{class} || $self->classname_for_collection_of($object);
+    my $class   = delete $opts{class};
+    my $object  = delete $opts{object_class};
+    my $source  = delete $opts{source_class};
+    my $action_rules = delete $opts{actions};
 
-    Class::MOP::load_class($base);
-    my $meta = eval { Class::MOP::load_class($class) } ?
-      $class->meta : $base->meta->create($class, superclasses =>[ $base ]);
-    my $make_immutable = $self->make_classes_immutable || $meta->is_immutable;
-    $meta->make_mutable if $meta->is_immutable;
+    confess('object_class and source_class are required parameters')
+      unless $object && $source;
+    $class ||= $self->class_name_for_collection_of($object);
 
-    $meta->add_method(_build_im_class => sub{ $object } );
-    print STDERR "... Reflecting collection of $object as $class\n"
-      if $self->debug_mode;
+    Class::MOP::load_class( $base );
+    Class::MOP::load_class( $object );
+    my $meta = $self->_load_or_create($class, $base);
 
-    for my $action (@$actions){
-      unless (ref $action){
-        my $default = grep {$_->{name} eq $action} @{ $self->default_collection_actions };
-        confess("unable to reflect action $action") unless $default;
-        $action = $default;
+    my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;;
+    $meta->make_mutable if $meta->is_immutable;
+    $meta->add_method(_build_member_type => sub{ $object } );
+    #XXX as a default pass the domain model as a target_model until i come up with something
+    #better through the coercion method
+    my $def_act_args = sub {
+      my $super = shift;
+      return { (target_model => $_[0]->_source_resultset), %{ $super->(@_) } };
+    };
+    $meta->add_around_method_modifier('_default_action_args_for', $def_act_args);
+
+
+    {
+      my $all_actions = $self->_all_collection_actions;
+      my $action_haystack = [keys %$all_actions];
+      if(!defined $action_rules){
+        $action_rules = $self->default_collection_actions;
+      } elsif( (!ref $action_rules && $action_rules) || (ref $action_rules eq 'Regexp') ){
+        $action_rules = [ $action_rules ];
+      } elsif( ref $action_rules eq 'ARRAY' && @$action_rules){
+        #don't add a qr/./ rule if we have at least one match rule
+        push(@$action_rules, qr/./)
+          unless grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude')
+                         || !ref $_  || ref $_ eq 'Regexp'} @$action_rules;
       }
-      $self->reflect_submodel_action(submodel_class => $object, %$action);
-      my $act_args =  sub {   #override target model for this action
-        my $super = shift;
-        return { %{$super->(@_)},($_[1] eq $action->{name} ?
-                                  (target_model => $_[0]->_source_resultset) : () )};
-      };
-      $meta->add_around_method_modifier('_default_action_args_for', $act_args);
-    }
 
+      # XXX this is kind of a dirty hack to support custom actions that are not
+      # previously defined and still be able to use the parse_reflect_rules mechanism
+      my @custom_actions = grep {!exists $all_actions->{$_}}
+        map{ $_->[0] } grep {ref $_ eq 'ARRAY' && $_->[0] ne '-exclude'} @$action_rules;
+      push(@$action_haystack, @custom_actions);
+      my $actions = $self->parse_reflect_rules($action_rules, $action_haystack);
+      for my $action (keys %$actions){
+        my $action_opts = $self->merge_hashes
+          ($all_actions->{$action} || {}, $actions->{$action} || {});
+
+        #NOTE: If the name of the action is not specified in the prototype then use it's
+        #hash key as the name. I think this is sane beahvior, but I've actually been thinking
+        #of making Action prototypes their own separate objects
+        $self->reflect_source_action(
+                                     name         => $action,
+                                     object_class => $object,
+                                     source_class => $source,
+                                     %$action_opts,
+                                    );
+
+        # XXX i will move this to use the coercion method soon. this will be
+        #  GoodEnough until then. I still need to think a little about the type coercion
+        #  thing so i don't make a mess of it
+        my $act_args = sub {   #override target model for this action
+          my $super = shift;
+          return { %{ $super->(@_) },
+                   ($_[1] eq $action ? (target_model => $_[0]->_source_resultset) : () ) };
+        };
+        $meta->add_around_method_modifier('_default_action_args_for', $act_args);
+      }
+    }
     $meta->make_immutable if $make_immutable;
     return $meta;
   };
 
-  #requires domain_model_class everything else optional
-  implements reflect_submodel => as {
-    my ($self, %opts) = @_;
-    my $source  = delete $opts{domain_model_class};
-    my $base    = delete $opts{base} || Object;
-    my $dm_name = delete $opts{domain_model_name};
-    my $dm_opts = delete $opts{domain_model_args} || {};
-    my $inflate = exists $opts{inflate} ? delete $opts{inflate} : 1;
-    my $class   = delete $opts{class} ||
-      $self->submodel_classname_from_source_name($source->source_name);
-    my $actions = delete $opts{reflect_actions} || $self->default_object_actions;
-
-    #create the custom class
-    Class::MOP::load_class($base);
-    my $meta = eval { Class::MOP::load_class($class) } ?
-      $class->meta : $base->meta->create($class, superclasses =>[ $base ]);
-    my $make_immutable = $self->make_classes_immutable || $meta->is_immutable;
-    $meta->make_mutable if $meta->is_immutable;
+  implements reflect_source_object => as {
+    my($self, %opts) = @_;
+    %opts = %{ $self->merge_hashes(\%opts, $self->_compute_source_options(%opts)) };
+
+    my $base         = delete $opts{base}  || Object;
+    my $class        = delete $opts{class};
+    my $dm_name      = delete $opts{domain_model_name};
+    my $dm_opts      = delete $opts{domain_model_args} || {};
+
+    my $source_name  = delete $opts{source_name};
+    my $schema       = delete $opts{schema_class};
+    my $source_class = delete $opts{source_class};
+    my $parent       = delete $opts{parent_class};
+    my $parent_dm    = delete $opts{parent_domain_model_name};
+
+    my $action_rules = delete $opts{actions};
+    my $attr_rules   = delete $opts{attributes};
+
+    $class ||= $self->class_name_from_source_name($parent, $source_name);
+
+    Class::MOP::load_class($parent);
+    Class::MOP::load_class($schema) if $schema;
+    Class::MOP::load_class($source_class);
+
+    my $meta = $self->_load_or_create($class, $base);
 
     #create the domain model
-    unless( $dm_name ){
-      ($dm_name) = ($source =~ /::([\w_\-]+)$/); #XXX be smarter at some point
-      $dm_name =~ s/([a-z0-9])([A-Z])/${1}_${2}/g ;
-      $dm_name = "_" . lc($dm_name) . "_store";
-    }
+    $dm_name ||= $self->dm_name_from_source_name($source_name);
 
-    $dm_opts->{isa} = $source;
+    $dm_opts->{isa}        = $source_class;
     $dm_opts->{is}       ||= 'rw';
     $dm_opts->{required} ||= 1;
-    my $dm_attr = $meta->add_domain_model($dm_name, %$dm_opts);
 
-    #Inflate the row into an IM object directly from DBIC
-    if( $inflate ){
+    my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;;
+    $meta->make_mutable if $meta->is_immutable;
+
+    my $dm_attr   = $meta->add_domain_model($dm_name, %$dm_opts);
+    my $dm_reader = $dm_attr->get_read_method;
+
+    unless( $class->can('inflate_result') ){
       my $inflate_method = sub {
         my $class = shift; my ($src) = @_;
         $src = $src->resolve if $src->isa('DBIx::Class::ResultSourceHandle');
@@ -222,38 +514,107 @@ class DBIC, which {
       $meta->add_method('inflate_result', $inflate_method);
     }
 
-    #attribute reflection
-    my $reflect_attrs = delete $opts{reflect_attributes};
-    my %exclude_attrs =
-      map {$_ => 1} ref $opts{exclude_attributes} ? @{$opts{exclude_attributes}} : ();
-
-    #reflect all applicable attributes on undef
-    $reflect_attrs = [map {$_->name} $source->meta->compute_all_applicable_attributes]
-      unless ref $reflect_attrs;
-    @$reflect_attrs = grep { !$exclude_attrs{$_} } @$reflect_attrs;
-
-    for my $attr_name (@$reflect_attrs){
-      $self->reflect_submodel_attribute(
-                                        class => $class,
-                                        attribute_name => $attr_name,
-                                        domain_model_name => $dm_name
-                                       );
+    #XXX this is here to allow action prototypes to work with ListView
+    # maybe Collections hsould have this kind of thing too to allow you to reconstruct them?
+    #i like the possibility to be honest... as aset of key/value pairs they could be URId
+    #XXX move to using 'handles' for this?
+    $meta->add_method('__id', sub {shift->$dm_reader->id} )
+      unless $class->can('__id');
+    #XXX this one is for Action, ChooseOne and ChooseMany need this shit
+    $meta->add_method('__ident_condition', sub {shift->$dm_reader->ident_condition} )
+      unless $class->can('__ident_condition');
+
+    #XXX this is just a disaster
+    $meta->add_method('display_name', sub {shift->$dm_reader->display_name} )
+      if( $source_class->can('display_name') && !$class->can('display_name'));
+
+    #XXX as a default pass the domain model as a target_model until i come up with something
+    #better through the coercion method
+    my $def_act_args = sub {
+      my $super = shift;
+      confess "no dm reader: $dm_reader on $_[0]" unless $_[0]->can($dm_reader);
+      return { (target_model => $_[0]->$dm_reader), %{ $super->(@_) } };
+    };
+    $meta->add_around_method_modifier('_default_action_args_for', $def_act_args);
+
+    {
+      # attributes => undef,              #default to qr/./
+      # attributes => [],                 #default to nothing
+      # attributes => qr//,               #DWIM, treated as [qr//]
+      # attributes => [{...}]             #DWIM, treat as [qr/./, {...} ]
+      # attributes => [[-exclude => ...]] #DWIM, treat as [qr/./, [-exclude => ...]]
+      my $attr_haystack =
+        [ map { $_->name } $source_class->meta->compute_all_applicable_attributes ];
+
+      if(!defined $attr_rules){
+        $attr_rules = [qr/./];
+      } elsif( (!ref $attr_rules && $attr_rules) || (ref $attr_rules eq 'Regexp') ){
+        $attr_rules = [ $attr_rules ];
+      } elsif( ref $attr_rules eq 'ARRAY' && @$attr_rules){
+        #don't add a qr/./ rule if we have at least one match rule
+        push(@$attr_rules, qr/./) unless
+          grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude')
+                  || !ref $_  || ref $_ eq 'Regexp'} @$attr_rules;
+      }
+
+      my $attributes = $self->parse_reflect_rules($attr_rules, $attr_haystack);
+      for my $attr_name (keys %$attributes){
+        $self->reflect_source_object_attribute(
+                                               class             => $class,
+                                               source_class      => $source_class,
+                                               parent_class      => $parent,
+                                               attribute_name    => $attr_name,
+                                               domain_model_name => $dm_name,
+                                               %{ $attributes->{$attr_name} || {}},
+                                              );
+      }
     }
 
-    for my $action (@$actions){
-      unless (ref $action){
-        my $default = grep {$_->{name} eq $action} @{ $self->default_object_actions };
-        confess("unable to reflect action $action") unless $default;
-        $action = $default;
+    {
+      my $all_actions = $self->_all_object_actions;
+      my $action_haystack = [keys %$all_actions];
+      if(!defined $action_rules){
+        $action_rules = $self->default_object_actions;
+      } elsif( (!ref $action_rules && $action_rules) || (ref $action_rules eq 'Regexp') ){
+        $action_rules = [ $action_rules ];
+      } elsif( ref $action_rules eq 'ARRAY' && @$action_rules){
+        #don't add a qr/./ rule if we have at least one match rule
+        push(@$action_rules, qr/./)
+          unless grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude')
+                         || !ref $_  || ref $_ eq 'Regexp'} @$action_rules;
+      }
+
+      # XXX this is kind of a dirty hack to support custom actions that are not
+      # previously defined and still be able to use the parse_reflect_rules mechanism
+      my @custom_actions = grep {!exists $all_actions->{$_}} map{ $_->[0] }
+        grep {ref $_ eq 'ARRAY' && $_->[0] ne '-exclude'} @$action_rules;
+      push(@$action_haystack, @custom_actions);
+      my $actions = $self->parse_reflect_rules($action_rules, $action_haystack);
+      for my $action (keys %$actions){
+        my $action_opts = $self->merge_hashes
+          ($all_actions->{$action} || {}, $actions->{$action} || {});
+
+        #NOTE: If the name of the action is not specified in the prototype then use it's
+        #hash key as the name. I think this is sane beahvior, but I've actually been thinking
+        #of making Action prototypes their own separate objects
+        $self->reflect_source_action(
+                                     name         => $action,
+                                     object_class => $class,
+                                     source_class => $source_class,
+                                     %$action_opts,
+                                    );
+
+        # XXX i will move this to use the coercion method soon. this will be
+        #  GoodEnough until then. I still need to think a little about the type coercion
+        #  thing so i don't make a mess of it
+        my $act_args = sub {   #override target model for this action
+          my $super = shift;
+          confess "no dm reader: $dm_reader on $_[0]" unless $_[0]->can($dm_reader);
+          return { %{ $super->(@_) },
+                   ($_[1] eq $action ? (target_model => $_[0]->$dm_reader) : () ) };
+        };
+        $meta->add_around_method_modifier('_default_action_args_for', $act_args);
       }
-      $self->reflect_submodel_action(submodel_class => $class, %$action);
-      my $dm = $dm_attr->get_read_method;
-      my $act_args = sub {   #override target model for this action
-        my $super = shift;
-        return { %{ $super->(@_) },
-            ($_[1] eq $action->{name} ? (target_model => $_[0]->$dm) : () ) };
-      };
-      $meta->add_around_method_modifier('_default_action_args_for', $act_args);
     }
 
     $meta->make_immutable if $make_immutable;
@@ -261,34 +622,64 @@ class DBIC, which {
   };
 
   # needs class, attribute_name domain_model_name
-  implements reflect_submodel_attribute => as {
+  implements reflect_source_object_attribute => as {
     my ($self, %opts) = @_;
+    unless( $opts{attribute_name} && $opts{class} && $opts{parent_class}
+            && ( $opts{source_class} || $opts{domain_model_name} ) ){
+      confess( "Error: class, parent_class, attribute_name, and either " .
+               "domain_model_name or source_class are required parameters" );
+    }
+
     my $meta =  $opts{class}->meta;
-    my $attr_opts = $self->parameters_for_submodel_attr(%opts);
+    my $attr_opts = $self->parameters_for_source_object_attribute(%opts);
 
     my $make_immutable = $meta->is_immutable;
     $meta->make_mutable if $meta->is_immutable;
+
     my $attr = $meta->add_attribute($opts{attribute_name}, %$attr_opts);
-    $meta->make_immutable if $make_immutable;
 
+    $meta->make_immutable if $make_immutable;
     return $attr;
   };
 
   # needs class, attribute_name domain_model_name
-  implements parameters_for_submodel_attr => as {
+  implements parameters_for_source_object_attribute => as {
     my ($self, %opts) = @_;
 
-    my $attr_name = $opts{attribute_name};
-    my $dm_name   = $opts{domain_model_name};
-    my $domain    = $opts{domain_model_class};
-    $domain ||= $opts{class}->meta->find_attribute_by_name($dm_name)->_isa_metadata;
-    my $from_attr = $domain->meta->find_attribute_by_name($attr_name);
-    my $source    = $domain->result_source_instance;
+    my $class        = delete $opts{class};
+    my $attr_name    = delete $opts{attribute_name};
+    my $dm_name      = delete $opts{domain_model_name};
+    my $source_class = delete $opts{source_class};
+    my $parent_class = delete $opts{parent_class};
+    confess("parent_class is a required argument") unless $parent_class;
+    confess("You must supply at least one of domain_model_name and source_class")
+      unless $dm_name || $source_class;
+
+    my $source;
+    $source = $source_class->result_source_instance if $source_class;
+    #puke! dwimery
+    if( !$source_class ){
+      my $dm = $class->meta->find_attribute_by_name($dm_name);
+      $source_class = $dm->_isa_metadata;
+      $source = $source_class->result_source_instance;
+    } elsif( !$dm_name ){
+      ($dm_name) = map{$_->name} grep{$_->_isa_metadata eq $source_class}
+        $class->meta->domain_models;
+      if( !$dm_name ){   #last resort guess
+        my $tentative = $self->dm_name_from_source_name($source->source_name);
+        ($dm_name) = $tentative if grep{$_->name eq $tentative} $class->domain_models;
+      }
+    }
+
+    my $from_attr = $source_class->meta->find_attribute_by_name($attr_name);
 
     #default options. lazy build but no outsider method
     my %attr_opts = ( is => 'ro', lazy => 1, required => 1,
                       clearer   => "_clear_${attr_name}",
-                      predicate => "has_${attr_name}",
+                      predicate => {
+                          "has_${attr_name}" =>
+                              sub { defined(shift->$dm_name->$attr_name) }
+                      },
                       domain_model   => $dm_name,
                       orig_attr_name => $attr_name,
                     );
@@ -298,15 +689,17 @@ class DBIC, which {
       $from_attr->type_constraint->name eq 'ArrayRef' ||
         $from_attr->type_constraint->is_subtype_of('ArrayRef');
 
+
+
     if( my $rel_info = $source->relationship_info($attr_name) ){
       my $rel_accessor = $rel_info->{attrs}->{accessor};
-      my $rel_moniker  = $rel_info->{class}->source_name;
+      my $rel_moniker  = $rel_info->{class}->result_source_instance->source_name;
 
       if($rel_accessor eq 'multi' && $constraint_is_ArrayRef) {
         #has_many
-        my $sm = $self->submodel_classname_from_source_name($rel_moniker);
+        my $sm = $self->class_name_from_source_name($parent_class, $rel_moniker);
         #type constraint is a collection, and default builds it
-        $attr_opts{isa} = $self->classname_for_collection_of($sm);
+        $attr_opts{isa} = $self->class_name_for_collection_of($sm);
         $attr_opts{default} = sub {
           my $rs = shift->$dm_name->related_resultset($attr_name);
           return $attr_opts{isa}->new(_source_resultset => $rs);
@@ -314,13 +707,16 @@ class DBIC, which {
       } elsif( $rel_accessor eq 'single') {
         #belongs_to
         #type constraint is the foreign IM object, default inflates it
-        $attr_opts{isa} = $self->submodel_classname_from_source_name($rel_moniker);
+        $attr_opts{isa} = $self->class_name_from_source_name($parent_class, $rel_moniker);
         $attr_opts{default} = sub {
-          shift->$dm_name
-            ->find_related($attr_name, {},{result_class => $attr_opts{isa}});
+          if (defined(my $o = shift->$dm_name->$attr_name)) {
+            return $attr_opts{isa}->inflate_result($o->result_source, { $o->get_columns });
+          }
+          return undef;
+            #->find_related($attr_name, {},{result_class => $attr_opts{isa}});
         };
       }
-    } elsif( $constraint_is_ArrayRef && $attr_name =~ m/^(.*)_list$/) {
+    } elsif( $constraint_is_ArrayRef && $attr_name =~ m/^(.*)_list$/ ) {
       #m2m magic
       my $mm_name = $1;
       my $link_table = "links_to_${mm_name}_list";
@@ -331,15 +727,22 @@ class DBIC, which {
         || confess "Can't find ${mm_name} belongs_to on ".$hm_source->result_class
           ." traversing many-many for ${mm_name}_list";
 
-      my $sm = $self->submodel_classname_from_source_name($far_side->source_name);
-      $attr_opts{isa} = $self->classname_for_collection_of($sm);
+      my $sm = $self->class_name_from_source_name($parent_class,$far_side->source_name);
+      $attr_opts{isa} = $self->class_name_for_collection_of($sm);
 
       #proper collections will remove the result_class uglyness.
       $attr_opts{default} = sub {
-        my $rs = shift->$dm_name->result_source->related_source($link_table)
-          ->related_source($mm_name)->resultset;
+        my $rs = shift->$dm_name->related_resultset($link_table)->related_resultset($mm_name);
         return $attr_opts{isa}->new(_source_resultset => $rs);
       };
+    #} elsif( $constraint_is_ArrayRef ){
+      #test these to see if rel is m2m
+      #my $meth = $attr_name;
+      #if( $source->can("set_${meth}") && $source->can("add_to_${meth}") &&
+      #    $source->can("${meth}_rs") && $source->can("remove_from_${meth}") ){
+
+
+      #}
     } else {
       #no rel
       my $reader = $from_attr->get_read_method;
@@ -350,45 +753,71 @@ class DBIC, which {
   };
 
 
-  #XXX change superclasses to "base" ?
-  implements reflect_submodel_action => as{
+  implements reflect_source_action => as{
     my($self, %opts) = @_;
-    my $im_class = delete $opts{submodel_class};
-    my $base     = delete $opts{base} || Action;
-    my $attrs    = delete $opts{attributes};
-    my $name     = delete $opts{name};
-    my $class    = delete $opts{class} || $im_class->_default_action_class_for($name);
+    my $name   = delete $opts{name};
+    my $class  = delete $opts{class};
+    my $base   = delete $opts{base} || Action;
+    my $object = delete $opts{object_class};
+    my $source = delete $opts{source_class};
+
+    confess("name, object_class and source_class are required arguments")
+      unless $source && $name && $object;
+
+    my $attr_rules = delete $opts{attributes};
+    $class ||= $object->_default_action_class_for($name);
+
+    Class::MOP::load_class( $base   );
+    Class::MOP::load_class( $object );
+    Class::MOP::load_class( $source );
+
+    #print STDERR "\n\t", ref $attr_rules eq 'ARRAY' ? @$attr_rules : $attr_rules,"\n";
+    # attributes => undef,              #default to qr/./
+    # attributes => [],                 #default to nothing
+    # attributes => qr//,               #DWIM, treated as [qr//]
+    # attributes => [{...}]             #DWIM, treat as [qr/./, {...} ]
+    # attributes => [[-exclude => ...]] #DWIM, treat as [qr/./, [-exclude => ...]]
+    my $attr_haystack = [ map { $_->name } $object->meta->parameter_attributes ];
+    if(!defined $attr_rules){
+      $attr_rules = [qr/./];
+    } elsif( (!ref $attr_rules && $attr_rules) || (ref $attr_rules eq 'Regexp') ){
+      $attr_rules = [ $attr_rules ];
+    } elsif( ref $attr_rules eq 'ARRAY' && @$attr_rules){
+      #don't add a qr/./ rule if we have at least one match rule
+      push(@$attr_rules, qr/./) unless
+        grep {(ref $_ eq 'ARRAY' && $_->[0] ne '-exclude')
+                || !ref $_  || ref $_ eq 'Regexp'} @$attr_rules;
+    }
 
-    print STDERR "... Reflecting action $name for $im_class as $class\n"
-      if $self->debug_mode;
+    #print STDERR "${name}\t${class}\t${base}\n";
+    #print STDERR "\t${object}\t${source}\n";
+    #print STDERR "\t",@$attr_rules,"\n";
 
-    Class::MOP::load_class($_) for($base, $im_class);
-    $attrs = [ map{$_->name} $im_class->parameter_attributes] unless ref $attrs;
-    my $im_meta = $im_class->meta;
+    my $o_meta = $object->meta;
+    my $s_meta = $source->meta;
+    my $attributes  = $self->parse_reflect_rules($attr_rules, $attr_haystack);
 
     #create the class
-    my $meta = eval { Class::MOP::load_class($class) } ?
-      $class->meta : $base->meta->create($class, superclasses => [$base]);
-    my $make_immutable = $self->make_classes_immutable || $meta->is_immutable;
+    my $meta = $self->_load_or_create($class, $base);
+    my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;
     $meta->make_mutable if $meta->is_immutable;
 
-    foreach my $attr_name (@$attrs){
-      my $im_attr   = $im_meta->find_attribute_by_name($attr_name);
-      my $dm_attr   = $im_meta->find_attribute_by_name($im_attr->domain_model);
-      my $dm_meta   = $dm_attr->_isa_metadata->meta;
-      my $from_attr = $dm_meta->find_attribute_by_name($im_attr->orig_attr_name);
-
-      #Don't reflect read-only attributes to actions
-      unless( $from_attr->get_write_method ) {
-        print STDERR "..... not relecting read-only attribute ${attr_name} to ${class}"
-          if $self->debug_mode;
-        next;
-      }
-
-      my $attr_params = $self->parameters_for_submodel_action_attribute
-        ( submodel_class => $im_class, attribute_name => $attr_name );
-
-      #add the attribute to the class
+    for my $attr_name (keys %$attributes){
+      my $attr_opts   = $attributes->{$attr_name} || {};
+      my $o_attr      = $o_meta->find_attribute_by_name($attr_name);
+      my $s_attr_name = $o_attr->orig_attr_name || $attr_name;
+      my $s_attr      = $s_meta->find_attribute_by_name($s_attr_name);
+      confess("Unable to find attribute for '${s_attr_name}' via '${source}'")
+        unless defined $s_attr;
+      next unless $s_attr->get_write_method
+        && $s_attr->get_write_method !~ /^_/; #only rw attributes!
+
+      my $attr_params = $self->parameters_for_source_object_action_attribute
+        (
+         object_class   => $object,
+         source_class   => $source,
+         attribute_name => $attr_name
+        );
       $meta->add_attribute( $attr_name => %$attr_params);
     }
 
@@ -396,20 +825,21 @@ class DBIC, which {
     return $meta;
   };
 
-
-  implements parameters_for_submodel_action_attribute => as {
+  implements parameters_for_source_object_action_attribute => as {
     my ($self, %opts) = @_;
 
-    #XXX we need the domain model name so we can do valid_values correcty....
-    #otherwise we could do away with submodel_class and use domain_model_class instead
-    #we need for domain_model to be set on the attr which we may not be sure of
-    my $submodel  = delete $opts{submodel_class};
-    my $sm_meta   = $submodel->meta;
-    my $attr_name = delete $opts{attribute_name};
-    my $dm_name   = $sm_meta->find_attribute_by_name($attr_name)->domain_model;
-    my $domain    = $sm_meta->find_attribute_by_name($dm_name)->_isa_metadata;
-    my $from_attr = $domain->meta->find_attribute_by_name($attr_name);
-    my $source    = $domain->result_source_instance;
+    my $object       = delete $opts{object_class};
+    my $attr_name    = delete $opts{attribute_name};
+    my $source_class = delete $opts{source_class};
+    confess("object_class and attribute_name are required parameters")
+      unless $attr_name && $object;
+
+    my $o_meta  = $object->meta;
+    my $dm_name = $o_meta->find_attribute_by_name($attr_name)->domain_model;
+    $source_class ||= $o_meta->find_attribute_by_name($dm_name)->_isa_metadata;
+    my $from_attr = $source_class->meta->find_attribute_by_name($attr_name);
+
+    #print STDERR "$attr_name is type: " . $from_attr->meta->name . "\n";
 
     confess("${attr_name} is not writeable and can not be reflected")
       unless $from_attr->get_write_method;
@@ -418,13 +848,18 @@ class DBIC, which {
                      is        => 'rw',
                      isa       => $from_attr->_isa_metadata,
                      required  => $from_attr->is_required,
+                     ($from_attr->is_required
+                       ? () : (clearer => "clear_${attr_name}")),
                      predicate => "has_${attr_name}",
                     );
 
     if ($attr_opts{required}) {
-      $attr_opts{lazy} = 1;
-      $attr_opts{default} = $from_attr->has_default ? $from_attr->default :
-        sub{confess("${attr_name} must be provided before calling reader")};
+        if($from_attr->has_default) {
+          $attr_opts{lazy} = 1;
+          $attr_opts{default} = $from_attr->default;
+        } else {
+          $attr_opts{lazy_fail} = 1;
+        }
     }
 
     #test for relationships
@@ -432,6 +867,7 @@ class DBIC, which {
       $from_attr->type_constraint->name eq 'ArrayRef' ||
         $from_attr->type_constraint->is_subtype_of('ArrayRef');
 
+    my $source = $source_class->result_source_instance;
     if (my $rel_info = $source->relationship_info($attr_name)) {
       my $rel_accessor = $rel_info->{attrs}->{accessor};
 
@@ -454,314 +890,208 @@ class DBIC, which {
 
       $attr_opts{default} = sub { [] };
       $attr_opts{valid_values} = sub {
-        shift->$dm_name->result_source->related_source($link_table)
+        shift->target_model->result_source->related_source($link_table)
           ->related_source($mm_name)->resultset;
       };
     }
+    #use Data::Dumper;
+    #print STDERR "\n" .$attr_name ." - ". $object . "\n";
+    #print STDERR Dumper(\%attr_opts);
     return \%attr_opts;
   };
 
+  implements _load_or_create => as {
+    my ($self, $class, $base) = @_;
+    my $meta = $self->_maybe_load_class($class) ?
+      $class->meta : $base->meta->create($class, superclasses => [ $base ]);
+    return $meta;
+  };
+
+  implements _maybe_load_class => as {
+    my ($self, $class) = @_;
+    my $file = $class . '.pm';
+    $file =~ s{::}{/}g;
+    my $ret = eval { Class::MOP::load_class($class) };
+    if ($INC{$file} && $@) {
+      confess "Error loading ${class}: $@";
+    }
+    return $ret;
+  };
+
 };
 
 1;
 
+#--------#---------#---------#---------#---------#---------#---------#---------#
+__END__;
 
 =head1 NAME
 
-Reaction::InterfaceModel::Reflector::DBIC - Autogenerate an Interface Model from
-a DBIx::Class Schema.
+Reaction::InterfaceModel::Reflector::DBIC -
+Automatically Generate InterfaceModels from DBIx::Class models
 
 =head1 DESCRIPTION
 
-This class will reflect a L<DBIx::Class::Schema> to a C<Reaction::InterfaceModel::Object>.
-It can aid you in creating interface models, collections, and associated actions rooted
-in DBIC storage.
-
-=head1 SYNOPSYS
-
-  #model_class is the namespace where our reflected interface model will be created
-  my $reflector = Reaction::InterfaceModel::Reflector::DBIC
-    ->new(model_class => 'RTest::TestIM');
-
-  #Example 1: Reflect all submodels (result sources / tables)
-  #domain_model_class ISA DBIx::Class::Schema
-  $reflector->reflect_model(domain_model_class => 'RTest::TestDB');
-  #the '_RTest_TestDB' attribute is created automatically to store the domain model
-  RTest::TestIM->new(_RTest_TestDB => RTest::TestDB->connect(...) );
-
-  #Example 2: Don't reflect the FooBaz submodel
-  $reflector->reflect_model(
-                            domain_model_class => 'RTest::TestDB',
-                            exclude_submodels  => ['FooBaz'],
-                           );
-  RTest::TestIM->new(_RTest_TestDB => RTest::TestDB->connect(...) );
-
-  #Example 3: Only reflect Foo, Bar, and Baz
-  $reflector->reflect_model(
-                            domain_model_class => 'RTest::TestDB',
-                            reflect_submodels  => [qw/Foo Bar Baz/],
-                           );
-  RTest::TestIM->new(_RTest_TestDB => RTest::TestDB->connect(...) );
-
-  #Example 4: Explicit domain_model_name
-  $reflector->reflect_model(
-                            domain_model_class => 'RTest::TestDB',
-                            domain_model_name  => '_rtest_testdb',
-                           );
-  RTest::TestIM->new(_rtest_testdb => RTest::TestDB->connect(...) );
-
-=head1 A NOTE ABOUT REFLECTION
-
-This class is meant as an aid in rapid prototyping and CRUD functionality creation.
-While parts of it should be useful for projects of any size, any non-trivial
-application will likely require some hand-coding or tweaking to get the most out of
-this tool. Reflection, like CRUD, is not a magic bullet. It's just a way to help you
-eliminate repetitive and unnecessary coding.
+The InterfaceModel reflectors are classes that are meant to aid you in easily
+generating Reaction::InterfaceModel classes that represent their underlying
+DBIx::Class domain models by introspecting your L<DBIx::Class::ResultSource>s
+and creating a collection of L<Reaction::InterfaceModel::Object> and
+L<Reaction::InterfaceModel::Collection> classes for you to use.
 
-=head1 OVERVIEW & DEFAULT NAMING CONVENTIONS
+The default base class of all Object classes will be
+ L<Reaction::InterfaceModel::Object> and the default Collection type will be
+L<Reaction::InterfaceModel::Collection::Virtual::ResultSet>.
 
-By default (you can override this behavior later), The top-level model (the one
-corresponding to your schema) will be reflected to the class name you provide at
-instantiation, submodels to the model name plus the name of the source, and collections
-to the name of the submodel plus "Collection". Action names, if not specified directly
-will be determined by using the submodel's "_action_name_for" method.
+Additionally, the reflector can create InterfaceModel actions that interact
+with the supplied L<Reaction::UI::Controller::Collection::CRUD>, allowing you
+to easily set up a highly customizable CRUD interface in minimal time.
 
-=head2 A Note about Immutable
-
-The methods that modify classes will check for class immutability and unlock classes
-for modification if they are immutable. Classes will be locked again after they are
-modified if they were locked at the start.
-
-=head1 ATTRIBUTES
-
-=head2 model_class
-
-Required, Read-only. This is the name of the class where your top model will be created
-and the namespace under which all your submodels, actions, collections will be
-created.
-
-=head2 make_classes_immutable
-
-Read-Write boolean, defaults to false. If this is set to true, after classes are
-created they will be made immutable.
-
-=head2 default_object_actions
-
-=head2 default_collection_actions
-
-These hold an ArrayRef of action prototypes. An Action prototype is a hashref
-with at least 2 keys, "name" and "base" the latter which is an otional superclass
-for this action. By default a "Create" action is reflected for Collections and
-"Update" and "Delete" actions for IM Objects. You may add here any
-attribute that reflect_submodel_action takes, i.e. for an action that doesn't need
-any reflected attributes, like Delete, use C<attributes =E<gt> []>.
-
-=head2 debug_mode
-
-Read-Write boolean, defaults to false. In the future this will provide valuable
-information at runtime, however that has not yet been implemented.
-
-=head1 METHODS
-
-=head2 submodel_classname_from_source_name $source_name
-
-Generate the classname for a submodel from the result source's name.
-
-=head2 classname_for_collection_for $object_class
-
-Returns the classname for a collection of a certain submodel. Currently it just appends
-"::Collection"
-
-=head2 reflect_model %args
+At this time, supported collection actions consist of:
 
 =over 4
 
-=item C<domain_model_class> - Required, this is the classname of your Schema
+=item B<> L<Reaction::INterfaceModel::Action::DBIC::ResultSet::Create>
 
-=item C<domain_model_name>  - The name to use when creating the domain model attribute
-If you don't supply this one will automatically be generated by prefacing the domain_model_class
-with an underscore and replacing all instances of "::", with "_"
+Creates a new item in the collection and underlying ResultSet.
 
-=item C<domain_model_args>  - Any other optional arguments suitable for passing to C<add_attribute>
+=item B<> L<Reaction::INterfaceModel::Action::DBIC::ResultSet::DeleteAll>
 
-=item C<reflect_submodels>  - An ArrayRef of the source names of the submodels to reflect.
-If the value is not a reference it will attempt to reflect all sources. In the future
-there may be regex support
-
-=item C<exclude_submodels>  - ArrayRef of submodels to exclude from reflection. In the
-future there may be regex support
+Deletes all the items in a collection and it's underlying resultset using
+C<delete_all>
 
 =back
 
-This method will query the schema given to it and reflect all appropriate submodels as
-well as calling C<add_submodel_to_model> to create an attribute in the reflected model
-which returns an appropriate collection.
-
-=head2 add_submodel_to_model %args
+And supported object actions are :
 
 =over 4
 
-=item C<source_name> - The DBIC source name for this submodel
-
-=item C<collection_class> - The classname for the collection type for this submodel.
+=item B<Update> - via L<Reaction::INterfaceModel::Action::DBIC::Result::Update>
 
-=item C<attribute_name> - The name of the attribute to create in the model to represent
-this submodel. If one is not supplied the source name will be used.
+Updates an existing object.
 
-=item C<domain_model_name> - The attribute name of the domain model where the schema is
-located. In the future this may be optional since it can be detected, but it needs to
-wait until some changes are made to the attribute metaclasses.
+=item B<Delete> - via L<Reaction::INterfaceModel::Action::DBIC::Result::Delete>
 
-=item C<reader> - The read method for the submodel attribute. If one is not provided,
-a lower case version of the source name with underscores separating previous cases
-of a camel-case word change and "_collection" appended will be used.  Examples:
-"FooBar" becomes C<foo_bar_collection> and "Foo" becomes C<foo_collection>.
+Deletes an existing object.
 
 =back
 
-This will create a read-only attribute in your main model that will return a
-collection of the submodel type when the reader is called. This will return the same
-collection every time, not a fresh one. This may change in the future, but I really
-see no need for it right now.
-
-=head2 reflect_collection_for \%args
-
-=over 4
-
-=item C<object_class> - Required. The class ob objects this collection will be representing
-
-=item C<base> - Optional, if you'd like to use a different base for the Collection other
-than L<Reaction::InterfaceModel::Collection::Virtual::ResultSet> you can set it here
-
-=item C<reflect_actions> - Action prototypes for the actions you wish to reflect for
-this collection. If nothing is specified then C<default_collection_actions> is used.
-An Action prototype is a hashref with at least 2 keys, "name" and "base" the latter
-is the superclass for this action. Using an empty array reference would reflect nothing.
-
-=item C<class> - The desired classname for this collection. If none is provided, then
-the value returned by C<classname_for_collection_of> is used.
-
-=back
-
-This method will create a new collection class that inherits from C<base> and overrides
-C<_build_im_class> to return C<object_class>. Additionally it will automatically
-override C<_default_action_args_for> as needed for reflected actions.
-
-=head2 reflect_submodel \%args
-
-=over 4
-
-=item C<domain_model_class> - The class from which the submodel will be created, or your
-source class, e.g. MyApp::Schema::Foo
-
-=item C<base> - Optional, if you'd like to use a different base other than
-L<Reaction::InterfaceModel::Object>
-
-=item C<domain_model_name> - the name to use for your domain model attribute. If one
-is not provided, a lower case version of the source name begining with an underscore
-and with underscores separating previous cases of a camel-case word change and
-"_store" appended will be used.
-Examples: "FooBar" becomes C<_foo_bar_store> and "Foo" becomes C<_foo_store>.
+=head1 SYNOPSIS
 
-=item C<domain_model_args> - Any additional arguments you may want to pass to the domain
-model when it is created e.g. C<handles>
+    package MyApp::IM::TestModel;
+    use base 'Reaction::InterfaceModel::Object';
+    use Reaction::Class;
+    use Reaction::InterfaceModel::Reflector::DBIC;
+    my $reflector = Reaction::InterfaceModel::Reflector::DBIC->new;
 
-=item C<inflate> - unless this is set to zero an inflate_result method will be created.
-
-=item C<class> - the name of the submodel class created, if you don't specify it the
-value returned by C<submodel_classname_from_source_name> will be used
+    #Reflect everything
+    $reflector->reflect_schema
+      (
+       model_class  => __PACKAGE__,
+       schema_class => 'MyApp::Schema',
+      );
 
-=item C<reflect_actions> - Action prototypes for the actions you wish to reflect for
-this collection. If nothing is specified then C<default_object_actions> is used.
-An Action prototype is a hashref with at least 2 keys, "name" and "base" the latter
-is the superclass for this action. Using an empty array reference would reflect nothing.
+=head2 Selectively including and excluding sources
 
-=item C<reflect_attributes> - an arrayref of the names of the attributes you want to
-reflect, if this is not an arrayref it will attempt to reflect all attributes,
-if you wish to not reflect anything pass it an empty arrayref
+    #reflect everything except for the FooBar and FooBaz classes
+    $reflector->reflect_schema
+      (
+       model_class  => __PACKAGE__,
+       schema_class => 'MyApp::Schema',
+       sources => [-exclude => [qw/FooBar FooBaz/] ],
+       # you could also do:
+       sources => [-exclude => qr/(?:FooBar|FooBaz)/,
+       # or even
+       sources => [-exclude => [qr/FooBar/, qr/FooBaz/],
+      );
 
-=item C<exclude_attributes> - an arrayref of the names of the attributes to exclude.
+    #reflect only the Foo family of sources
+    $reflector->reflect_schema
+      (
+       model_class  => __PACKAGE__,
+       schema_class => 'MyApp::Schema',
+       sources => qr/^Foo/,
+      );
 
-=back
+=head2 Selectively including and excluding fields in sources
 
-This method will create the submodel class, copy the applicable attributes and create
-the appropriate domain model attribute as well as create the necessary actions and
-perform the necessary overrides to C<_default_action_args_for>
+    #Reflect Foo and Baz in their entirety and exclude the field 'avatar' in the Bar ResultSource
+    $reflector->reflect_schema
+      (
+       model_class  => __PACKAGE__,
+       schema_class => 'MyApp::Schema',
+       sources => [qw/Foo Baz/,
+                   [ Bar => {attributes => [[-exclude => 'avatar']] } ],
+                   # or exclude by regex
+                   [ Bar => {attributes => [-exclude => qr/avatar/] } ],
+                   # or simply do not include it...
+                   [ Bar => {attributes => [qw/id name description/] } ],
+                  ],
+      );
 
-=head2 reflect_submodel_attribute \%args
+=head1 ATTRIBUTES
 
-Takes the same arguments as C<parameters_for_submodel_attribute>.
+=head2 make_classes_immutable
 
-Reflect this attribute and add it to the submodel class.
+=head2 object_actions
 
-=head2 parameters_for_submodel_attribute \%args
+=head2 collection_actions
 
-=over 4
+=head2 default_object_actions
 
-=item C<class> - the submodel class
+=head2 default_collection_actions
 
-=item C<attribute_name> - the name of the attribute you want to reflect
+=head2 builtin_object_actions
 
-=item C<domain_model_class> - the class where we are copying the attribute from.
-If not specified, the type constraint on the domain model attribute will be used
+=head2 builtin_collection_actions
 
-=item C<domain_model_name> - the name of the domain model attribute.
+=head1 METHODS
 
-=back
+=head2 new
 
-This method determines the parameters necessary for reflecting the argument. Most
-of the magic here is so that relations can be accurately reflected so that many-to-one
-relationships can return submodel objects and one-to-many and many-to-many
-relationships can return collections. By default all reflected attributes will be built
-lazily from their parent domain model.
+=head2 _all_object_actions
 
-=head2 reflect_submodel_action \%args
+=head2 _all_collection_actions
 
-=over 4
+=head2 dm_name_from_class_name
 
-=item C<submodel_class> - the submodel class this action will be associated with
+=head2 dm_name_from_source_name
 
-=item C<base> - superclass for the action class created
+=head2 class_name_from_source_name
 
-=item C<attributes> - a list of the names of attributes to mirror from the submodel.
-A blank list signifies nothing, and a non list value will cause it to reflect all
-writeable parameter attributes from the submodel.
+=head2 class_name_for_collection_of
 
-=item C<name> - the name of the action, required.
+=head2 merge_hashes
 
-=item C<class> - optional, the name of the action class. By default it will query the
-submodel class through the method C<_default_action_class_for>
+=head2 parse_reflect_rules
 
-=back
+=head2 merge_reflect_rules
 
-Create an action class that acts on the submodel from a base class. This is most useful
-for CRUD and similar actions.
+=head2 reflect_schema
 
-=head2 parameters_for_submodel_action_attribute \ %args
+=head2 _compute_source_options
 
-=over 4
+=head2 add_source
 
-=item C<attribute_name> - name of the attribute being reflected
+=head2 reflect_source
 
-=item C<submodel_class> - the submodel where this attribute is located
+=head2 reflect_source_collection
 
-=back
+=head2 reflect_source_object
 
-Create the correct parameters for the attribute being created in the action, including
-valid_values, and correct handling of relationships and defaults.
+=head2 reflect_source_object_attribute
 
-=head1 PRIVATE METHODS
+=head2 parameters_for_source_object_attribute
 
-=head2 BUILD
+=head2 reflect_source_action
 
-Load the C<model_class> if it exists or create one if it does not.
+=head2 parameters_for_source_object_action_attribute
 
 =head1 TODO
 
-Allow reflect_* and exclude_* methods to take compiled regular expressions, tidy up
-argument names and method names, mace docs decent, make more tests, try to figure out
-more through introspection to require less arguments, proper checking of values passed
-and throwing of errors when garbage is passed in.
+Allow the reflector to dump the generated code out as files, eliminating the need to
+reflect on startup every time. This will likely take quite a bit of work though. The
+main work is already in place, but the grunt work is still left. At the moment there
+is no closures that can't be dumped out as code with a little bit of work.
 
 =head1 AUTHORS