File Coverage

lib/UR/Role/Param.pm
Criterion Covered Total %
statement 75 80 93.7
branch 23 26 88.4
condition 4 6 66.6
subroutine 19 20 95.0
pod 0 8 0.0
total 121 140 86.4


line stmt bran cond sub pod time code
1             package UR::Role::Param;
2              
3 266     266   948 use strict;
  266         330  
  266         5908  
4 266     266   844 use warnings;
  266         307  
  266         5355  
5              
6 266     266   910 use Carp qw();
  266         316  
  266         3732  
7 266     266   860 use Scalar::Util qw(blessed);
  266         343  
  266         229333  
8              
9             our $VERSION = "0.46"; # UR $VERSION;;
10              
11             my %all_params;
12              
13             sub _constructor {
14 2     2   9 my($class, %params) = @_;
15 2         5 foreach my $param_name ( qw( role_name name varref state ) ) {
16 8 50       15 Carp::croak("$param_name is a required param") unless exists $params{$param_name};
17             }
18 2         13 $all_params{$params{role_name}}->{$params{name}} = bless \%params, $class;
19             }
20              
21             sub new {
22 1     1 0 2 my $class = shift;
23 1         4 return $class->_constructor(@_, state => 'unbound');
24             }
25            
26             sub TIESCALAR {
27 1     1   1 my $class = shift;
28 1         4 return $class->_constructor(@_, state => 'bound');
29             }
30              
31 9     9 0 16 sub name { shift->{name} }
32 3     3 0 6 sub role_name { shift->{role_name} }
33 3     3 0 7 sub varref { shift->{varref} }
34 2     2 0 36 sub state { shift->{state} }
35              
36             sub FETCH {
37 6     6   40 my $self = shift;
38 6         13 my $param_name = $self->name;
39              
40 6         12 my $role_instance = $self->_search_for_invocant_role_instance();
41 6 100       15 unless ($role_instance) {
42 2         29 Carp::confess("Role param '$param_name' is not bound to a value in this call frame");
43             }
44 4         9 my $params = $role_instance->role_params();
45 4         19 return $params->{$param_name};
46             }
47              
48             sub STORE {
49 0     0   0 my $self = shift;
50 0         0 my $name = $self->name;
51 0         0 Carp::croak("Role param '$name' is read-only");
52             }
53              
54             sub _search_for_invocant_role_instance {
55 6     6   6 my $self = shift;
56              
57 6         7 local $@;
58 6         7 for (my $frame = 1; ; $frame++) {
59 30         24 my($role_package, $invocant) = do {
60             package DB;
61 30         44 my @caller = caller($frame);
62 30 100       478 last unless $caller[3];
63 28         89 my($function_package) = $caller[3] =~ m/^(.*)::\w+$/;
64 28 100       40 next unless $function_package;
65 24         24 eval { ($function_package, $DB::args[0]) };
  24         47  
66             };
67 24   66     96 my $invocant_class = blessed($invocant) || (!ref($invocant) && $invocant);
68 24 100       31 next unless $invocant_class;
69              
70 14         43 my $role_instance = UR::Role::Instance->get(role_name => $role_package, 'class_name isa' => $invocant_class);
71 14 100       34 return $role_instance if $role_instance;
72             }
73 2         3 return;
74             }
75              
76             sub clone_self {
77             # used by Clone:PP (UR::Util::deep_copy), to prevent recursing into
78             # these Param objects. Otherwise, the cloning done when a Role's
79             # property data is cloned before merging it into the class would point
80             # this object's varref to an anonymous scalar other than the original
81             # variable with The RoleParam attribute, and the cloning process doesn't
82             # properly re-tie the RoleParam variables afterward.
83 5     5 0 625 return $_[0];
84             }
85              
86             sub param_names_for_role {
87 52     52 0 74 my($class, $role_name) = @_;
88 52         45 return keys(%{ $all_params{$role_name} });
  52         191  
89             }
90              
91             sub replace_unbound_params_in_struct_with_values {
92 36     36 0 71 my($class, $struct, @role_objects) = @_;
93              
94 36         67 my %role_params = map { $_->role_name => $_->role_params } @role_objects;
  49         137  
95              
96             my $replacer = sub {
97 2     2   3 my $ref = shift;
98              
99 2         3 my $self = $$ref;
100 2         6 my $role_params = $role_params{$self->role_name};
101 2         6 $$ref = $role_params->{$self->name}; # replaces value in structure
102              
103             # replace the role param variable
104 2         5 my $role_param_ref = $self->varref;
105 2 100       9 unless (tied($$role_param_ref)) {
106 1         3 tie $$role_param_ref, 'UR::Role::Param',
107             name => $self->name,
108             role_name => $self->role_name,
109             varref => $self->varref;
110             }
111 36         175 };
112              
113 36         94 _visit_params_with_values_in_struct($struct, $replacer);
114             }
115              
116             sub _is_unbound_param {
117 395     395   284 my $val = shift;
118 395   66     833 return (blessed($val) && $val->isa(__PACKAGE__) && $val->state eq 'unbound');
119             }
120              
121             sub _visit_params_with_values_in_struct {
122 429     429   343 my($struct, $cb) = @_;
123              
124 429 100       900 return unless my $reftype = ref($struct);
125 197 100       354 if ($reftype eq 'HASH') {
    100          
    50          
126 120         542 while(my($key, $val) = each %$struct) {
127 247 100       230 if (_is_unbound_param($val)) {
128 2         5 $cb->(\$struct->{$key});
129             } else {
130 245         210 _visit_params_with_values_in_struct($val, $cb);
131             }
132             }
133             } elsif ($reftype eq 'ARRAY') {
134 75         218 for(my $i = 0; $i < @$struct; $i++) {
135 148         134 my $val = $struct->[$i];
136 148 50       155 if (_is_unbound_param($val)) {
137 0         0 $cb->(\$struct->[$i]);
138             } else {
139 148         165 _visit_params_with_values_in_struct($val, $cb);
140             }
141             }
142             } elsif ($reftype eq 'SCALAR') {
143 0           _visit_params_with_values_in_struct($struct, $cb);
144             }
145             }
146              
147             1;
148              
149             =pod
150              
151             =head1 NAME
152              
153             UR::Role::Param - Role parameters as package variables
154              
155             =head1 SYNOPSIS
156              
157             package ProjectNamespace::LoggingRole;
158             use ProjectNamespace;
159              
160             our $logging_object : RoleParam(logging_obejct);
161             role ProjectNamespace::SomeParameterizedRole { };
162              
163             sub log {
164             my($self, $message) = @_;
165             $logging_object->log($message);
166             }
167              
168             package ThingThatLogs;
169             my $logger = create_a_logging_object();
170             class ThingThatLogs {
171             roles => [ ProjectNamespace::SomeParameterizedRole->create(logging_object => $logger) ],
172             };
173              
174             =head1 DESCRIPTION
175              
176             Roles can be configured by declaring variables with the C attribute.
177             These variables acquire values by calling C on the role's name and
178             giving values for all the role's parameters. More information about declaring
179             and using these parameters is described in the "Parameterized Roles" section of
180             L.
181              
182             When the variables are initially declared, their value is initialized to a
183             reference to a UR::Role::Param. This represents a placeholder value to be
184             filled in later. The value may be used in a role definition or in any
185             subroutine.
186              
187             When the role is composed into a class, the placeholder values are replaced
188             with the actual values given in the C call on the role's name. The
189             original RoleParam variable is then tied to the UR::Role::Param class; it's
190             C method returns the proper value by searching the call stack for the
191             first method whose invocant class has composed the role where the FETCH
192             originated from. It returns the value given when the role was composed
193             into the class.
194              
195             These role param variables are read-only.
196              
197             Each variable with the RoleParam attribute becomes a required argument when
198             the role is instantiated .
199              
200             =head1 SEE ALSO
201              
202             L, L, L