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';
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_object_actions => as { {} };
+ implements _build_collection_actions => as { {} };
- implements build_default_object_actions => as { [ qw/Update Delete/ ] };
- implements build_default_collection_actions => as { [ 'Create' ] };
+ implements _build_default_object_actions => as { [ qw/Update Delete/ ] };
+ implements _build_default_collection_actions => as { [ qw/Create DeleteAll/ ] };
- implements build_builtin_object_actions => as {
+ 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 } };
+ 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;
+ my $self = shift;
return $self->merge_hashes
($self->builtin_object_actions, $self->object_actions);
};
unless($model && $schema);
Class::MOP::load_class( $base );
Class::MOP::load_class( $schema );
- my $meta = eval {Class::MOP::load_class($model); } ?
- $model->meta : $base->meta->create($model, superclasses => [ $base ]);
+ my $meta = $self->_load_or_create($model, $base);
# sources => undef, #default to qr/./
# sources => [], #default to nothing
};
};
+ 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))
+ };
implements add_source => as {
my ($self, %opts) = @_;
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;
required => 1,
isa => $collection,
reader => $reader,
- predicate => "has_${name}",
+ predicate => "has_" . $self->_class_to_attribute_name($name) ,
domain_model => $dm_name,
orig_attr_name => $source,
default => sub {
- $collection->new(_source_resultset => shift->$dm_name->resultset($source));
+ my $self = $_[0];
+ return $collection->new(
+ _source_resultset => $self->$dm_name->resultset($source),
+ _parent => $self,
+ );
},
);
+# 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);
Class::MOP::load_class( $base );
Class::MOP::load_class( $object );
- my $meta = eval { Class::MOP::load_class($class) } ?
- $class->meta : $base->meta->create( $class, superclasses => [ $base ]);
+ 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;
- $meta->add_method(_build_im_class => sub{ $object } );
+ $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 {
Class::MOP::load_class($schema) if $schema;
Class::MOP::load_class($source_class);
- my $meta = eval { Class::MOP::load_class($class) } ?
- $class->meta : $base->meta->create($class, superclasses => [ $base ]);
+ my $meta = $self->_load_or_create($class, $base);
#create the domain model
$dm_name ||= $self->dm_name_from_source_name($source_name);
#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 ActionForm, ChooseOne and ChooseMany need this shit
+ #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');
$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}->result_source_instance->source_name;
#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;
# 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 ];
+ 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') ){
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 $meta = $self->_load_or_create($class, $base);
my $make_immutable = $meta->is_immutable || $self->make_classes_immutable;
$meta->make_mutable if $meta->is_immutable;
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);
- next unless $s_attr->get_write_method; #only rw attributes!
+ 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
(
$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;
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
};
}
#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 -
+Automatically Generate InterfaceModels from DBIx::Class models
+
+=head1 DESCRIPTION
+
+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.
+
+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>.
+
+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.
+
+At this time, supported collection actions consist of:
+
+=over 4
+
+=item B<> L<Reaction::INterfaceModel::Action::DBIC::ResultSet::Create>
+
+Creates a new item in the collection and underlying ResultSet.
+
+=item B<> L<Reaction::INterfaceModel::Action::DBIC::ResultSet::DeleteAll>
+
+Deletes all the items in a collection and it's underlying resultset using
+C<delete_all>
+
+=back
+
+And supported object actions are :
+
+=over 4
+
+=item B<Update> - via L<Reaction::INterfaceModel::Action::DBIC::Result::Update>
+
+Updates an existing object.
+
+=item B<Delete> - via L<Reaction::INterfaceModel::Action::DBIC::Result::Delete>
+
+Deletes an existing object.
+
+=back
+
+=head1 SYNOPSIS
+
+ package MyApp::IM::TestModel;
+ use base 'Reaction::InterfaceModel::Object';
+ use Reaction::Class;
+ use Reaction::InterfaceModel::Reflector::DBIC;
+ my $reflector = Reaction::InterfaceModel::Reflector::DBIC->new;
+
+ #Reflect everything
+ $reflector->reflect_schema
+ (
+ model_class => __PACKAGE__,
+ schema_class => 'MyApp::Schema',
+ );
+
+=head2 Selectively including and excluding sources
+
+ #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/],
+ );
+
+ #reflect only the Foo family of sources
+ $reflector->reflect_schema
+ (
+ model_class => __PACKAGE__,
+ schema_class => 'MyApp::Schema',
+ sources => qr/^Foo/,
+ );
+
+=head2 Selectively including and excluding fields in sources
+
+ #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/] } ],
+ ],
+ );
+
+=head1 ATTRIBUTES
+
+=head2 make_classes_immutable
+
+=head2 object_actions
+
+=head2 collection_actions
+
+=head2 default_object_actions
+
+=head2 default_collection_actions
+
+=head2 builtin_object_actions
+
+=head2 builtin_collection_actions
+
+=head1 METHODS
+
+=head2 new
+
+=head2 _all_object_actions
+
+=head2 _all_collection_actions
+
+=head2 dm_name_from_class_name
+
+=head2 dm_name_from_source_name
+
+=head2 class_name_from_source_name
+
+=head2 class_name_for_collection_of
+
+=head2 merge_hashes
+
+=head2 parse_reflect_rules
+
+=head2 merge_reflect_rules
+
+=head2 reflect_schema
+
+=head2 _compute_source_options
+
+=head2 add_source
+
+=head2 reflect_source
+
+=head2 reflect_source_collection
+
+=head2 reflect_source_object
+
+=head2 reflect_source_object_attribute
+
+=head2 parameters_for_source_object_attribute
+
+=head2 reflect_source_action
+
+=head2 parameters_for_source_object_action_attribute
+
+=head1 TODO
+
+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
+
+See L<Reaction::Class> for authors.
+
+=head1 LICENSE
+
+See L<Reaction::Class> for the license.
+
+=cut