From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

use strict;
require UR;
our $VERSION = "0.47"; # UR $VERSION;
class UR::Object::View::Aspect {
id_by => [
parent_view => { is => 'UR::Object::View', id_by => 'parent_view_id',
doc => "the id of the view object this is an aspect-of" },
number => { is => 'Integer',
doc => "aspects of a view are numbered" },
],
has => [
name => { is => 'Text',
is_mutable => 0,
doc => 'the name of the property/method on the subject which returns the value to be viewed' },
],
has_optional => [
label => { is => 'Text',
doc => 'display name for this aspect' },
position => { is => 'Scalar',
doc => 'position of this aspect within the parent view (meaning is view and toolkit dependent)' },
delegate_view => { is => 'UR::Object::View', id_by => 'delegate_view_id',
doc => "This aspect gets rendered via another view" },
],
};
sub create {
my $class = shift;
my($bx,%extra) = $class->define_boolexpr(@_);
# TODO: it would be nice to have this in the class definition:
# increment_for => 'parent_view'
unless ($bx->value_for('number')) {
if (my $parent_view_id = $bx->value_for('parent_view_id')) {
my $parent_view = UR::Object::View->get($parent_view_id);
my @previous_aspects = $parent_view->aspects;
$bx = $bx->add_filter(number => scalar(@previous_aspects)+1);
}
}
unless ($bx->value_for('label')) {
if (my $label = $bx->value_for('name')) {
$label =~ s/_/ /g;
$bx = $bx->add_filter(label => $label);
}
}
if (keys %extra) {
# This is a sub-view
my $delegate_subject_class_name;
if (exists $extra{'subject_class_name'}) {
$delegate_subject_class_name = $extra{'subject_class_name'};
} else {
# FIXME This duplicates functionality below in generate_delegate_view, but generate_delegate_view()
# doesn't take any args to tweak the properties of that delegated view :(
# Try to figure it out based on the name of the aspect...
my $parent_view;
if (my $view_id = $bx->value_for('parent_view_id')) {
$parent_view = UR::Object::View->get($view_id);
} elsif ($bx->specifies_value_for('parent_view')) {
$parent_view = $bx->value_for('parent_view');
}
unless ($parent_view) {
Carp::croak("Can't determine parent view from keys/values: ",join(', ', map { sprintf("%s => '%s'", $_, $extra{$_}) } keys %extra));
}
my $class_meta = $parent_view->subject_class_name->__meta__;
unless ($class_meta) {
Carp::croak("No class metadata for class "
. $parent_view->subject_class_meta
. ". Can't create delegate view on aspect named "
. $bx->value_for('name') );
}
my $property_meta = $class_meta->property_meta_for_name($bx->value_for('name'));
unless ($property_meta) {
Carp::croak("No property metadata for class " . $class_meta->class_name
. " property " . $bx->value_for('name')
. ". Can't create delegate view on aspect named " . $bx->value_for('name'));
}
unless ($property_meta->data_type) {
Carp::croak("Property metadata for class " . $class_meta->class_name
. " property " . $property_meta->property_name
. " has no data_type. Can't create delegate view on aspect named " . $bx->value_for('name'));
}
$delegate_subject_class_name = $property_meta->data_type;
}
unless ($delegate_subject_class_name) {
Carp::croak("Can't determine subject_class_name for delegate view on aspect named " . $bx->value_for('name'));
}
my $delegate_view = $delegate_subject_class_name->create_view(
perspective => $bx->value_for('perspective'),
toolkit => $bx->value_for('toolkit'),
%extra
);
unless ($delegate_view) {
Carp::croak("Can't create delegate view for aspect named " . $bx->value_for('name') . ": ".$delegate_subject_class_name->error_message);
}
#$bx->add_filter(delegate_view_id => $delegate_view->id);
$bx = $bx->add_filter(delegate_view => $delegate_view);
}
my $self = $class->SUPER::create($bx);
return unless $self;
my $name = $self->name;
unless ($name) {
$self->error_message("No name specified for aspect!");
$self->delete;
return;
}
return $self;
}
sub _look_for_recursion {
my $self = shift;
my $parent_view = $self->parent_view;
my $subject = $parent_view->subject;
$parent_view = $parent_view->parent_view;
while($parent_view) {
return 1 if ($parent_view->subject eq $subject);
$parent_view = $parent_view->parent_view;
}
return 0;
}
sub generate_delegate_view {
no warnings;
my $self = shift;
my $parent_view = $self->parent_view;
my $name = $self->name;
my $subject_class_name = $parent_view->subject_class_name;
my $retval;
my $property_meta = $subject_class_name->__meta__->property($name);
my $aspect_type;
if ($property_meta) {
$aspect_type = $property_meta->_data_type_as_class_name;
unless ($aspect_type) {
Carp::confess("Undefined aspect type. Set 'is' for $name in class " . $property_meta->class_name);
}
unless ($aspect_type->can("__meta__")) {
Carp::croak("$aspect_type has no meta data? cannot generate a view for $subject_class_name $name!");
}
}
else {
unless ($subject_class_name->can($name)) {
$self->error_message("No property/method $name found on $subject_class_name! Invalid aspect!");
$self->delete;
Carp::croak($self->error_message);
}
$aspect_type = 'UR::Value::Text'
}
my $aspect_meta = $aspect_type->__meta__;
my $delegate_view;
local $@;
eval {
$delegate_view = $aspect_type->create_view(
subject_class_name => $aspect_type,
perspective => $parent_view->perspective,
toolkit => $parent_view->toolkit,
parent_view => $parent_view,
aspects => [],
);
};
unless ($delegate_view) {
# try again using the "default" perspective
my $err1 = $@;
eval {
$delegate_view = $aspect_type->create_view(
subject_class_name => $aspect_type,
perspective => 'default',
toolkit => $parent_view->toolkit,
parent_view => $parent_view,
aspects => [],
);
};
my $err2 = $@;
unless ($delegate_view) {
$self->error_message(
"Error creating delegate view for $name ($aspect_type)! $err1\n"
. "Also failed to fall back to the default perspective for $name ($aspect_type)! $err2"
);
return;
}
}
my @default_aspects_params = $delegate_view->_resolve_default_aspects();
# add aspects which do not "go backward"
# no one wants to see an order, with a list of line items, which re-reprsent thier order on each
for my $aspect_params (@default_aspects_params) {
my $aspect_param_name = (ref($aspect_params) ? $aspect_params->{name} : $aspect_params);
my $aspect_property_meta = $aspect_meta->property($aspect_param_name);
no strict; no warnings;
next if (!$aspect_property_meta or !$property_meta);
if ($aspect_property_meta->reverse_as ne $name
and
$property_meta->reverse_as ne $aspect_param_name
) {
$delegate_view->add_aspect(ref($aspect_params) ? %$aspect_params : $aspect_params);
}
}
$self->delegate_view($delegate_view);
$retval = $delegate_view;
return $retval;
}
1;
=pod
=head1 NAME
UR::Object::View::Aspect - a specification for one aspect of a view
=head1 SYNOPSIS
my $v = $o->create_view(
perspective => 'default',
toolkit => 'xml',
aspects => [
'id',
'name',
'title',
{
name => 'department',
perspective => 'logo'
},
{
name => 'boss',
label => 'Supervisor',
aspects => [
'name',
'title',
{
name => 'subordinates',
perspective => 'graph by title'
}
]
}
]
);
=cut