How can a attribute (which is a class) access the class to which it is an attribute? - class

A confusing title, I know. But the code should be clear:
class A {
has $.foo = 1;
# how can I access B::bar from here?
}
class B {
has $.bar = 2;
has A $.a;
submethod TWEAK {
$!a = A.new;
}
}
my $b = B.new;
say $b; # B.new(bar => 2, a => A.new(foo => 1))
say $b.a; # A.new(foo => 1)
my $test := $b.a;
say $test; # A.new(foo => 1)
Given $test, how can I access B::bar (which is on the same "level" as $test)?

Personally if I had to do this I'd state that A can have an attribute parent that fulfils a Role HasBar like so.
role HasBar {
has $.bar = 2;
}
class A {
has $.foo = 1;
has HasBar $.parent;
}
class B does HasBar {
has A $.a;
submethod TWEAK {
$!a = A.new( parent => self );
}
}
Attributes generally don't (and shouldn't) know if they are contained in another object. So if you want to be able to do that you need to tell them the connection exists. I prefer using Roles to do this as then your A class could be an attribute in lots of different things as long as the container has a bar accessible.

You could try this:
class B { ... } #need to decl B before you use it
class A {
has $.foo is rw = 1; #foo is already =1
has B $.b;
} #no TWEAK needed
class B {
has $.bar is rw = 2;
has A $.a;
}
say my $a = A.new(b => B.new);
say my $b = B.new(a => A.new);
say my $a2 = A.new(b => $b);
say my $b2 = B.new(a => $a2);
say $a2.b.bar; #2
say $b2.a.foo; #1
$b2.a.b.a.foo = 42; #added is rw trait to show this
say $b2;
#B.new(bar => 2, a => A.new(foo => 1, b => B.new(bar => 2, a => A.new(foo => 42, b => B))))
If you look at the output you will see that the $b2 is a nested recursion. Not sure if that is what you want! My bet is that #scimon s answer is a better one...

Related

Hash reference - Reuse inner hash to store and retrieve next values

I am new to Perl programming and have to work on Hash of Hashes. I am trying to reuse innerHash variables to be dynamic in nature. And I expect the inner items to be stored in outerHash how ever many I have added. Below is my test program snippet.
use warnings;
sub testHash {
my %outerHash = ();
my %innerHash1 = ();
my %innerHash2 = ();
$innerHash1{"key1"} = "value1";
$innerHash1{"key2"} = "value2";
$innerHash1{"key3"} = "value3";
$outerHash{"Master1"} = \%innerHash1;
$innerHash2{"key4"} = "value4";
$innerHash2{"key5"} = "value5";
$innerHash2{"key6"} = "value6";
$outerHash{"Master2"} = \%innerHash2;
#delete $innerHash1{$_};
%innerHash1 = ();
#undef %innerHash1;
$innerHash1{"key7"} = "value7";
$innerHash1{"key8"} = "value8";
$innerHash1{"key9"} = "value9";
$outerHash{"Master3"} = \%innerHash1;
foreach $outerItem (keys %outerHash){
print "\n$outerItem: ";
foreach $innerItem (keys %{$outerHash{$outerItem}}){
print "\t $innerItem = $outerHash{$outerItem}{$innerItem}";
}
print "\n-------------------------------------------------------";
}
print "\n";
}
testHash;
Output:
Master3: key8 = value8 key7 = value7 key9 = value9
-----------------------------------------------------------------
Master2: key5 = value5 key6 = value6 key4 = value4
-----------------------------------------------------------------
Master1: key8 = value8 key7 = value7 key9 = value9
-----------------------------------------------------------------
I understand it's taking the newer reference of innerHash1 while printing the items. What is the right way to have all the right elements in outerHash? In a real programming scenario I cannot declare n variables in advance.
You may be new to perl, but you understand the concept of pointers don't you? The code $outerHash{"Master1"} = \%innerHash1; means that "$outerHash{"Master1"} is assigned a pointer to %innerHash1". So what you're observing is correct and expected.
If you want to keep recreating %innerHash and adding it to %outerHash, you will need to do one of two things:
Assign by value. $outerHash{"Master1"} = {}; %{$outerHash{"Master1"}} = %innerHash1;
Only add %innerHash to %outerHash once per loop. Since %innerHash is redeclared within the loop, not outside of it, it goes out of scope every loop, but its contents will be kept in outerHash due to perl's reference counting. Depending on your perspective, this could be considered "trying to be too clever and potentially dangerous", but I think it's fine.
The "right" way, at least in my opinion, is by never declaring any intermediary "innerhashes" at all. Just use references directly:
my %outerHash;
$outerHash{Master1} = { key1 => 'value1', key2 => 'value2', key3 => 'value3'};
$outerHash{Master2} = { key4 => 'value4', key5 => 'value5', key6 => 'value6'};
$outerHash{Master3} = { key7 => 'value7', key8 => 'value8', key9 => 'value9'};
# or....
my %outerHash2;
$outerHash2{Master1}{key1} = 'value1';
$outerHash2{Master1}{key2} = 'value2';
$outerHash2{Master1}{key3} = 'value3';
$outerHash2{Master2}{key4} = 'value4';
$outerHash2{Master2}{key5} = 'value5';
$outerHash2{Master2}{key6} = 'value6';
$outerHash2{Master3}{key7} = 'value7';
$outerHash2{Master3}{key8} = 'value8';
$outerHash2{Master3}{key9} = 'value9';
# or...
my %outerHash3 = (
Master1 => {
key1 => 'value1',
key2 => 'value2',
key3 => 'value3'
}
Master2 => {
key4 => 'value4',
key5 => 'value5',
key6 => 'value6'
}
Master3 => {
key7 => 'value7',
key8 => 'value8',
key9 => 'value9'
}
);

Using map in a void context?

I have this code:
my %events = ();
map { $events{$_} => 1 } #event_ids;
map { $events{$_} => 2 } #teen_event_ids;
map { $events{$_} => 3 } #kids_event_ids;
Although the arrays have data, the content of %events is turning up empty.
%events wasn't changed because you never even tried to change %events (except for emptying it when it was already empty)! I think you meant to do
my %events;
map { $events{$_} = 1 } #event_ids;
map { $events{$_} = 2 } #teen_event_ids;
map { $events{$_} = 3 } #kids_event_ids;
Using map in void context is frowned-upon. The following are more familiar:
my %events = (
( map { $_ => 1 } #event_ids ),
( map { $_ => 2 } #teen_event_ids ),
( map { $_ => 3 } #kids_event_ids ),
);
or
my %events;
$events{$_} = 1 for #event_ids;
$events{$_} = 2 for #teen_event_ids;
$events{$_} = 3 for #kids_event_ids;
Technically, you can use parens around map's operands (map( BLOCK LIST )), but it looks weird, so I put the parens around then entire map.
That's... not really the way map is intended to be used. It transforms a list into a different list and returns the resulting list. It's not meant to change a list (or hash) in-place.
Try this instead:
my %events = ();
$events{$_} = 1 for #event_ids;
$events{$_} = 2 for #teen_event_ids;
$events{$_} = 3 for #kids_event_ids;
or, if you specifically want to include a map:
my %events = map { $_ => 1 } #event_ids;
$events{$_} = 2 for #teen_event_ids;
$events{$_} = 3 for #kids_event_ids;
or even:
my %events = map { $_ => find_event_category($_) } #event_ids, #teen_event_ids, #kids_event_ids;
where find_event_category is a function which determines whether an id is for a teen or kids event and returns 1, 2, or 3 as appropriate.
OK, it's time to get some sleep. Should just be using = not the fat arrow, =>.

How do I add more than one over method to a mojolicious route?

I have the following code:
$r->find('user')->via('post')->over(authenticated => 1);
Given that route I can get to the user route passing through the authenticated check
that is setup using Mojolicious::Plugin::Authentication.
I want add another 'over' to that route.
$r->find('user')->via('post')->over(authenticated => 1)->over(access => 1);
That appears to override authenticated 'over' though.
I tried breaking up the routes with names like:
my $auth = $r->route('/')->over(authenticated => 1)
->name('Authenticated Route');
$access = $auth->route('/user')->over(access => 1)->name('USER_ACCESS');
That didn't work at all though. Neither of the 'over's are being accessed.
My routes are things like /user, /item, set up using MojoX::JSON::RPC::Service.
So, I don't have things like /user/:id to set up sub routes.( not sure that matters )
All routes are like /user, sent with parameters.
I've got a condition like:
$r->add_condition(
access => sub {
# do some stuff
},
);
that is the 'access' in $r->route('/user')->over(access => 1);
In short, the routes work fine when using:
$r->find('user')->via('post')->over(authenticated => 1);
But I'm unable to add a 2nd route.
So, what am I missing in setting up these routes with multiple conditions?
Is it possible to add multiple conditions to a single route /route_name?
You can just use both conditions in over like in this test:
use Mojolicious::Lite;
# dummy conditions storing their name and argument in the stash
for my $name (qw(foo bar)) {
app->routes->add_condition($name => sub {
my ($route, $controller, $to, #args) = #_;
$controller->stash($name => $args[0]);
});
}
# simple foo and bar dump action
sub dump {
my $self = shift;
$self->render_text(join ' ' => map {$self->stash($_)} qw(foo bar));
}
# traditional route with multiple 'over'
app->routes->get('/frst')->over(foo => 'yo', bar => 'works')->to(cb => \&dump);
# lite route with multiple 'over'
get '/scnd' => (foo => 'hey', bar => 'cool') => \&dump;
# test the lite app above
use Test::More tests => 4;
use Test::Mojo;
my $t = Test::Mojo->new;
# test first route
$t->get_ok('/frst')->content_is('yo works');
$t->get_ok('/scnd')->content_is('hey cool');
__END__
1..4
ok 1 - get /frst
ok 2 - exact match for content
ok 3 - get /scnd
ok 4 - exact match for content
Works fine here with Mojolicious 3.38 on perl 5.12.1 - #DavidO is right, maybe bridges can do the job better. :)
In my case I use two under methods:
$r = $app->routes;
$guest = $r->under->to( 'auth#check_level' );
$user = $r->under->to( 'auth#check_level', { required_level => 100 } );
$admin = $r->under->to( 'auth#check_level', { required_level => 200 } );
$guest->get( '/' )->to( 'main#index' );
$user->get( '/user' )->to( 'user#show' );
$super_admin = $admin->under->to( 'manage#check_level', { super_admin => 100 } );
$super_admin->get( '/delete_everything' )->to( 'system#shutdown' );
In this example when any of routes match some under will be called
'/' -> auth#check_level -> main_index
'/user' -> auth#check_level { required_level => 100 } -> 'user#show'
'/delete_everything' -> auth#check_level { required_level => 200 } -> 'manage#check_level', { super_admin => 100 } -> 'system#shutdown'
As you can see before target action in your controller will be run another action called: auth#check_level and manage#check_level
In each those extra actions you just compare stash->{ required_level } with session->{ required_level } you have set when authorize user
package YourApp::Controller::Manage;
sub check_level {
my $self = shift;
my $user_have = $self->session->{ required_level };
my $we_require = $self->stash->{ required_level };
# 'system#shutdown' will be called if user has required level
return 1 if $user_have >= $we_require;
$self->redirect_to( '/you_have_no_access_rights' );
return 0; #This route will not match. 'system#shutdown' will not be called
}
PS Of course I may use cb or just CODEREF which are "close same" to controller action:
$r->under({ cb => \&YourApp::Controller::auth::check_level });
$r->under( \&YourApp::Controller::auth::check_level ); # "same"
But I prefer ->to( 'controller#action' ) syntax. It looks much better
What if we use this approach?
# register condition
$r->add_condition(
chain => sub {
my ($route, $controller, $captures, $checkers) = #_;
for my $checker (#$checkers) {
return 0 unless $checker->($route, $controller, $captures);
}
return 1;
},
);
# ...
# example of using
$r->get('/')->over(chain => [\&checker1, \&checker2])->to(cb => \&foo)->name('bar');

How do I access a specific return value from the CDBI::Search function?

I am using a DB::CDBI class for accessing the database in our application. Our project is in object-oriented Perl.
package LT::LanguageImport;
use strict;
use warnings;
use base 'Misk5::CDBI';
__PACKAGE__->table( 'english_text_translation' );
__PACKAGE__->columns( Primary => qw/english language translation/ );
__PACKAGE__->columns( Essential => qw/english language translation/ );
__PACKAGE__->has_a( english => 'LT::EnglishLanguage' );
In one such scenario, I am supposed to check if a row in a table exists. I am using the built-in search API in a CDBI call.
sub find_translation {
my $translation_exists_r_not = $class->search(
english => $english,
language => $language,
translation => $translation
);
return;
}
$translation_exists_r_not is getting the expected values depending on the inputs given in the search. If the row exists, then the _data is updated with the row details.
$translation_exists_r_not = bless({
'_data' => [
{
'language' => 'polish',
'translation' => 'Admin',
'english' => 'admin'
}
],
'_place' => 0,
'_mapper' => [],
'_class' => 'LT::LanguageImport'
},
'Class::DBI::Iterator'
);
If the row desn't exist, then I get a return value like this.
$translation_exists_r_not = bless({
'_data' => [],
'_place' => 0,
'_mapper' => [],
'_class' => 'LT::LanguageImport'
},
'Class::DBI::Iterator'
);
I want to return the value of translation from this sub find_translation depending on the search result. I am not able to get a best condition for this.
I tried copying the _data into an array, but I'm not sure how to proceed further. As _data will be an empty arrayref and another condition it will have a hashref inside the arrayref.
my #Arr = $translation_exists_r_not->{'_data'};
CDBI's search method will return an iterator, because there may be multiple rows returned depending on your criteria.
If you know there can be only one row that matches your criteria, you want to use the retrieve method, i.e.:
if (my $translation_exists_r_not = $class->retrieve(
english => $english,
language => $language,
translation => $translation
)){
return [$translation_exists_r_not->translation,
'Misk5::TranslationAlreadyExists']
}
else {
return [ '', undef ]
}
And if multiple rows can be returned from your search, and you're only interested in the truthiness, then again, don't be rummaging around inside the CDBI::Iterator, but use its methods:
my $translation_exists_r_not = $class->search(
english => $english,
language => $language,
translation => $translation
); # returns an iterator
if ($translation_exists_r_not){
my $first = $translation_exists_r_not->first;
return [ $first->translation, 'Misk5::TranslationAlreadyExists' ]
}
Have a look at perldoc Class::DBI and perldoc Class::DBI::Iterator. CDBI has excellent documentation.
I think I got the solution. Thanks to whoever has tried to solve it.
my #req_array = %$translation_exists_r_not->{_data};
my $length_of_data = '9';
foreach my $elem (#req_array) {
$length_of_data = #{$elem};
}
Now check the length of the array.
if ($length_of_data == 0) {
$error = '';
$result = [undef, $error];
}
Now check if it is one.
if ($length_of_data == 1) {
my #result_array = #{%$translation_exists_r_not->{_data}};
my $translation = $result_array[0]{'translation'};
$error = 'Misk5::TranslationAlreadyExists';
$result = [$translation, $error];
}
return #$result;

represent allowed status transitions graph in Perl

There is something like status changes check logic in our app.
Currently checking is being handled by ugly if statement
I want to replace it by transition matrix:
my %allowed_status_changes = (
1 => (2,5),
2 => (1,2,3,4,5),
3 => (4,2),
4 => (3,2),
5 => (),
);
my $is_allowed_transition =
$submitted_status ~~ $allowed_status_changes {$original_status};
if ($prerequestsites && !$is_allowed_transition) {
return;
}
certain transitions can be allowed only on additional condition, therefore I will need something like
2 => (
(target => 1)
(target => 2, condition => $some_condition)
(target => (3,4), condition => $other_condition),
(target => 5)
),
(in my opinion it is too long)
What structure would you use in this situation if you should focus on readability and maintainability?
How you will parse it to check if transition is allowed?
If the conditions are very prevalent (e.g. almost every allowed transition has them) then your latter structure is perfectly fine, other than your syntax error of representing a hashref with "()" instead of "{}".
If the conditions are rare, I'd suggest going with #1, augmented by optional constructs similar to your #2.
Please note that readability of checking code is IMHO very clear though voluminous and not very idiomatic.
OTOH, maintainability of the matrix is high - you have terse yet readable syntax from #1 where no conditions are needed and a clear though longer syntax for conditions which is flexible enough for many conditions per many settings like your #2.
my %allowed_status_changes = (
1 => [2,5],
2 => [1,5,{targets=>[2], conditions=>[$some_condition]}
,{targets=>[3,4], conditions=>[$other_condition, $more_cond]}]
3 => [4,2],
4 => [3,2],
5 => [],
);
sub is_allowed_transition {
my ($submitted_status, $original_status ) = #_;
foreach my $alowed_status (#$allowed_status_changes{$original_status}) {
return 1 if !ref $alowed_status && $alowed_status == $submitted_status;
if (ref $alowed_status) {
foreach my $target (#$alowed_status{targets}) {
foreach my $condition (#$alowed_status{conditions}) {
return 1 if check_condition($submitted_status
, $original_status, $condition);
}
}
}
}
return 0;
}
if ($prerequestsites
&& !$is_allowed_transition($submitted_status, $original_status )) {
return;
}
Although I agree with DVK for the most part, I have to say, once you start delving into arrays of arrays of hashes, you're reaching a code complexity level that is hard to maintain without much spinning of heads and bugs.
At this point, I'd probably reach for an object and a class, for a bit of syntactic sugar.
my $transitions = TransitionGraph->new();
$transition->add( 1, { targets => [ 2, 5 ] });
$transition->add( 2, { targets => [ 1, 5 ] });
$transition->add( 2, { targets => [ 2 ], conditions => [ $some_condition ] });
$transition->add( 2, { targets => [ 3, 4 ], conditions => [ $other_condition, $more_cond ]});
$transition->add( 3, { targets => [4,2] } );
$transition->add( 4, { targets => [3,2] } );
$transition->add( 5, { targets => [] } );
if( $transition->allowed( 1 , 3 )){
}
Class implementation is up to the user, but I'd use Moose.
The primary benefits of this is you're encapsulating how the state graph works so you can Just Use it and worry about how the graph works seperate from where its used.
nb. in the above proposed API, add() creates a new record if one does not exist, and updates that record if it does exist. This turned out to be simpler than having "upgrade" methods or "get this item and then modify it" techniques.
Internally, it could do this, or something like it:
sub add {
my ( $self , $input_state, $rules ) = #_;
my $state;
if ( $self->has_state( $input_state ) ) {
$state = $self->get_state( $input_state );
} else {
$state = TransitionGraphState->new( source_id => $input_state );
$self->add_state( $input_state, $state );
}
my $targets = delete $rules{targets};
for my $target ( #$targets ) {
$state->add_target( $target, $rules );
}
return $self;
}
sub allowed {
my ( $self, $from, $to ) = #_;
if ( not $self->has_state( $from ) ){
croak "NO source state $from in transition graph";
}
my $state = $self->get_state( $from );
return $state->allowed_to( $to );
}
This also has the cool perk of not requiring one particular code set to work on the sub-nodes, you can create seperate instances with their own behaviour just in case you want one source state to be treated differently.
$transition->add_state( 2, $some_other_class_wich_works_like_transitiongraphstate );
Hope this is helpful =).
There is nothing wrong with the second form. You are not going to be able to get around the fact that you must encode the state machine somewhere. In fact, I think having the entire machine encoded in one place like that is far easier to understand that something with too many layers of abstraction where you need to look in n different places to understand the flow of the machine.