Extending an Object in Perl - perl

I've got a base object called RuleObject and an object that inherits from that called RuleObjectString. I have a new method in RuleObjectString that I want to call in my code that uses that object. But I get the error. 'Can't locate object method "compare" via package "RuleObject" at ./testobject.pl line 10.' But I'm not creating a RuleObject. I'm creating a RuleObjectString. What am I doing wrong here?
testobject.pl
1 #! /usr/bin/perl
2
3 use strict;
4
5 use RuleObjectString;
6
7 my $s = RuleObjectString->new();
8 $s->value('stuff goes here');
9
10 if ($s->compare('stuff')){
11 print "MATCH!\n";
12 }else{
13 print "no match :(\n";
14 }
RuleObject.pm
package RuleObject;
our #ISA = qw/Exporter/;
our #EXPORT = qw/new/;
use strict;
sub new{
my $class = shift;
my $self;
$self->{value} = undef;
bless $self;
return $self;
}
sub value{
my $self = shift;
my $value = shift;
if ($value){
$self->{value} = $value;
}else{
return $self->{value};
}
}
RuleObjectString.pm
package RuleObjectString;
our #ISA = qw/RuleObject/;
our #EXPORT = qw/compare/;
use strict;
sub compare{
my $self = shift;
my $compareto = shift;
return $self->value() =~ /$compareto/;
}

I think jmcneirney is on the right track. In your RuleObject constructor, you say
bless $self;
which is the same as
bless $self, __PACKAGE__;
or
bless $self, 'RuleObject'
but what you want is for the object to blessed as a RuleObjectString. So what you want to do is say
bless $self, $class
Now
RuleObject->new()
RuleObjectString->new()
will both call the same constructor, but the object returned by the first call will be blessed as a RuleObject and the second object will be blessed as a RuleObjectString.

This is 2012, so you should consider using proper OOP solutions instead of reinventing the wheel all over again.
By using Moose, the solution would look something like this (untested):
RuleObject.pm
package RuleObject;
use Moose;
has 'value' => ( isa => 'Str', is => 'rw', required => 0, default => '' );
1;
RuleObjectString.pm
package RuleObjectString;
use Moose;
extends 'RuleObject';
sub compare {
my $self = shift;
my $compareto = shift;
return $self->value =~ /$compareto/;
}
1;
Simple! :)

Try dumping the object and see what it is.
print Dumper( $s )
It's going to be a RuleObject.
You might need to define a new() in RuleObjectString
and have it call Super::new().

Related

Perl - Can't locate object method via "Module::SUPER"

This is my first time using OOP with perl. I am in the processes of refactoring a 4k line procedural program at work. It seems pretty straight forward but I am having an issue with inheritance and SUPER.
Error message:
"Can't locate object method "New" via package "Module::SUPER" at Module.pm line 10"
I have tried, use base, parent and setting #ISA but they all get the same error. I'm sure I have to be overlooking something.
(This is not code from the program I am working on. Just an example that produces the same error)
All .pm and .pl files are in the same directory in this example. In the program I am working on the main program is in bin and the modules will be in ../modules(relative to bin).
I would assume this would be all I need to make that work:
use lib "../modules";
If I am wrong in thinking that please let me know.
Parent Module
package BaseModule;
use strict;
use warnings;
sub new {
my $class = shift;
my $self = {
ARRAY => shift,
DIVIDER => ","
};
bless ($self, $class);
return $self;
}
sub array {
my $self = shift;
if(#_) { $self->{ARRAY} = shift };
return $self->{ARRAY};
}
sub divider {
my $self = shift;
if(#_) { $self->{DIVIDER} = shift };
return $self->{DIVIDER};
}
sub testSub {
my $self = shift;
print join($self->{DIVIDER}, #{ $self->{ARRAY} } );
return 1;
}
1;
Child Module
package Module;
use strict;
use warnings;
#use base qw(BaseModule);
#require BaseModule;
#our #ISA = qw(BaseModule);
use parent qw(BaseModule);
sub new {
my $class = shift;
my $self = $class->SUPER::New(#_);
$self->{STRING} = shift;
bless ($self, $class);
return $self;
}
sub string {
my $self = shift;
if(#_) { $self->{STRING} = shift };
return $self->{STRING};
}
sub testSub {
my $self = shift;
print "$self->{STRING}:\n";
$self->SUPER::testSub();
return 1;
}
1;
Do I need to bless the child class if the parent class returns an already blessed $self?
Main Script
#!/usr/bin/perl
use strict;
use warnings;
use Module;
my $module = Module->new([1, 2, 3, 4, 5], "Example");
$module->divider(" | "); # Test Changing divider;
$module->testSub();
Any help is greatly appreciated.
"Can't locate object method "New" via package "Module::SUPER" at Module.pm line 10"
You try to call BaseModule::New whis hasn't been defined (did you mean BaseModule::new? Perl is case sensitive).
Do I need to bless the child class if the parent class returns an
already blessed $self?
No, $self at that point is already blesses (you could check that by means of Scalar::Util::blessed().

Is there a way to set inheritance in the same module with __PACKAGE__->?

For example I want to store the data for a dbi connection on startup so I do not have to initialize it through an object, is their a way to do this in the same package?
Initializing through my object would be:
my $obj = foo->new;
my $dbh = $obj->connect('dbi', 'user', 'pw');
But I want to store it at startup to where I can just use
my $obj = foo->new;
my $blah = $obj->selectall_arrayref(...);
package foo;
use strict;
use warnings;
__PACKAGE__->connect('dbi', 'user', 'pw');
sub new {
my $class = shift;
my $self = {};
bless ($self, $class);
return $self;
}
sub connect {
my $class = shift;
my $self = ref $class || $class;
return $self->(#_); # Is this possible?
}
An alternative to using a proxy object is to simply compose DBI into your class.
package foo;
use DBI;
sub new {
my $class = shift;
my $self = {DBH => DBI->connect(#_)};
bless ($self, $class);
return $self;
}
# defer method call to DBH
sub selectall_arrayref {shift->{DBH}->selectall_arrayref(#_)}
package main;
my $obj = foo->new('dbi:...', 'user', 'password');
my $blah = $obj->selectall_arrayref(...);
Subclassing DBI class is not quite simple, just read the documentation.
Other way may be declare a proxy object, and call the wrapped object with the help of AUTOLOAD.

Inheritance and child methods

For some reason I'm not able to access the child methods on the boundary object. I would appreciate as much detail with an answer as possible as I'm still a bit confused on inheritance with perl, especially the bless portion. Also any constructive criticism would be great about overall design.
Generic.pm (Base Class)
package AccessList::Generic;
use strict;
use warnings;
sub new {
my $class = shift;
my $self = {
rules => [],
#_
};
bless $self, $class;
return $self;
}
sub get_line_count {
my $self = shift;
return scalar #{$self->{rules}};
}
1;
Extended.pm
package AccessList::Extended;
use strict;
use warnings;
use AccessList::Generic;
use base qw(AccessList::Generic);
sub new {
my ($class, #args) = #_;
my $self = $class->SUPER::new(#args);
return $self;
}
1;
Boundary.pm
package AccessList::Extended::Boundary;
use strict;
use warnings;
use AccessList::Extended;
use base qw(AccessList::Extended);
sub new {
my ($class, #args) = #_;
my $self = $class->SUPER::new(#args);
return $self;
}
sub get_acl_information {
my ($self) = #_;
return;
}
1;
Failing Test
can_ok('AccessList::Extended::Boundary', 'get_acl_information');
Error Message
# Failed test 'AccessList::Extended::Boundary->can('get_acl_information')'
# at t/b1.t line 42.
# AccessList::Extended::Boundary->can('get_acl_information') failed
# Looks like you failed 1 test of 2.
I don't see any problems in what you posted. The problem is surely in what you didn't post. Did you forget to load AccessList::Extended::Boundary?
$ find -type f
./AccessList/Extended/Boundary.pm
./AccessList/Extended.pm
./AccessList/Generic.pm
$ perl -E'
use Test::More tests => 1;
use AccessList::Extended::Boundary;
can_ok("AccessList::Extended::Boundary", "get_acl_information");
'
1..1
ok 1 - AccessList::Extended::Boundary->can('get_acl_information')

Can one pass Perl object references between modules?

Example code:
testClass1.pm
package testClass1;
{
my $testClass2Ref;
sub new
{
my($class) = shift;
$testClass2Ref= shift;
bless $self, $class;
return $self;}
}
sub testRef
{
$testClass2Ref->testRef;
}
}
testClass2.pm
package testClass2;
{
sub new
{
my($class) = shift;
bless $self, $class;
return $self;}
}
sub testRef
{
print "Test 2";
}
}
test.pl
use testClass1;
use testClass2;
my $testClass2 = testClass2->new();
my $testClass1 = testClass2->new($testClass2);
$testClass1->testRef;
When I try call $testClass1->testRef, $testClass2Ref=undef.
How can I pass reference on the object from parent?
Update
Oh, sorry, I missed string in example's constructors.
sub new
{
my($class) = shift;
$testClass2Ref = shift;
my $self = {name=>'testClass1'};
bless $self, $class;
return $self;
}
This test is working, but Eclipse debugger show this variables as 'undef'.
Thanks for your help.
Besides the syntax errors, you aren't using strict mode. Turning it on will reveal that $self isn't being declared in either package. By replacing:
bless $self, $class;
with:
my $self = bless {}, $class;
Everything goes through as expected.
When you fix the syntax errors it works.
> ./test.pl
> Test 2
You were missing
my $self = {};
in both new methods.
A useful tool is
perl -wc testClass1.pm
Can one pass Perl object references between modules?
Absolutely!
In this test script I make two classes, one that tests and one to be tested. Remember objects are just references and methods are just subroutines; use them in the same way.
#!/usr/bin/env perl
use strict;
use warnings;
package Tester;
sub new {
my $class = shift;
my ($other) = #_;
my $self = { other => $other };
bless $self, $class;
return $self;
}
sub examine {
my $self = shift;
print "I'm holding a: ", ref( $self->{other} ), "\n";
}
package Candidate;
sub new { return bless {}, shift }
package main;
my $candidate = Candidate->new();
my $tester = Tester->new( $candidate );
$tester->examine();
EDIT: Now using a more modern system, MooseX::Declare (which is based on Moose) with Method::Signatures. This saves a lot of the boilerplate and lets you focus on what you want the objects to do, rather then how they are implemented.
#!/usr/bin/env perl
#technically Moose adds strict and warnings, but ...
use strict;
use warnings;
use MooseX::Declare;
use Method::Signatures::Modifiers;
class Tester {
has 'other' => ( isa => 'Object', is => 'rw', required => 1 );
method examine () {
print "I'm holding a: ", ref( $self->other() ), "\n";
}
}
class Candidate { }
no MooseX::Declare;
package main;
my $candidate = Candidate->new();
my $tester = Tester->new( other => $candidate );
$tester->examine();
For more realistic cases, see how some larger module systems pass object representing complex concepts. Off the top of my head, HTTP::Response object get passed around all through the LWP system

Define the method in the constructor of class in perl

I am reading code snippets like below:
sub new {
my $pkg = shift;
my $args = shift;
my #keys = keys %$args;
my $self = bless \%{$args}, $pkg;
$self->{'__properties'} = \#keys;
my $class = ref($self);
foreach my $meth (#keys) {
if (! $self->can($meth)) {
no strict "refs";
*{ $class . "::" . $meth } = sub {
my $instance = shift;
return $instance->{$meth};
};
}
}
return $self;
}
In the foreach loop, it seems that it creates some methods according to the parameters. There are two lines which I don't understand.Could someone help me? What's the * and {} used for?
no strict "refs";
*{ $class . "::" . $meth }
Best Regards,
This creates a symbol table alias.
The right side contains a reference to a function, so Perl will alias it to the subroutine $meth in the package $class.
See Symbol Tables in perlmod.
As eugene y have already explained, those lines manipulate the symbol table. In practical terms, they do so in order to create read-only accessor methods in the class based on whatever arbitrary list of attributes get passed into the constructor:
#!/usr/bin/env perl
use strict;
use warnings;
use 5.10.0;
package SomeClass;
sub new {
my $pkg = shift;
my $args = shift;
my #keys = keys %$args;
my $self = bless \%{$args}, $pkg;
$self->{'__properties'} = \#keys;
my $class = ref($self);
foreach my $meth (#keys) {
if (!$self->can($meth)) {
no strict "refs";
*{$class . "::" . $meth} = sub {
my $instance = shift;
return $instance->{$meth};
};
}
}
return $self;
}
package main;
my $foo = SomeClass->new({foo => 5}); # Creates SomeClass::foo
say $foo->foo; # 5
my $bar = SomeClass->new({foo => 3, bar => 7}); # Creates SomeClass::bar
say $bar->foo; # 3
say $bar->bar; # 7
say $foo->bar; # undef - ::bar was added to all instances of SomeClass
say $foo->baz; # Boom! No such method.
Personally, I think this is questionable OO practice (a class should generally have a known set of attributes instead of potentially adding new ones each time an instance is constructed), but that's what it does...