2016-08-10 5 views
2

das folgende Fragment von Perl-Code anzeigen, die auf Moose basiert:Perl Moose Accessoren on the fly erzeugt

$BusinessClass->meta->add_attribute($Key => { is  => $rorw, 
               isa  => $MooseType, 
               lazy  => 0, 
               required => 0, 
               reader => sub { $_[0]->ORM->{$Key} }, 
               writer => sub { $_[0]->ORM->newVal($Key, $_[1]) }, 
               predicate => "has_$Key", 
              }); 

erhalte ich die Fehlermeldung:

bad accessor/reader/writer/predicate/clearer format, must be a HASH ref at /usr/local/lib/perl5/site_perl/mach/5.20/Class/MOP/Class.pm line 899

Der Grund des Fehlers ist clear: reader und writer müssen String-Namen von Funktionen sein.

Aber was in diesem speziellen Fall zu tun? Ich möchte keine neue Funktion für jedes der hundert ORM-Felder erstellen (ORM-Attribut ist hier ein gebundener Hash). Also kann ich hier keine Schnur weitergeben, ich brauche eine Schließung.

So führte meine Codierung Bedürfnisse in einen Widerspruch. Ich weiß nicht, was ich machen soll.


Das obige war ein Fragment von echtem Code. Jetzt stelle ich ein minimales Beispiel:

#!/usr/bin/perl 

my @Fields = qw(af sdaf gdsg ewwq fsf); # pretend that we have 100 fields 

# Imagine that this is a tied hash with 100 fields 
my %Data = map { $_ => rand } @Fields; 

package Test; 
use Moose; 

foreach my $Key (@Fields) { 
    __PACKAGE__->meta->add_attribute($Key => { is  => 'rw', 
              isa  => 'Str', 
              lazy  => 0, 
              required => 0, 
              reader => sub { $Data{$Key} }, 
              writer => sub { $Data{$Key} = $_[1] }, 
              }); 
} 

Rennen führt dies zu:

$ ./test.pl 
bad accessor/reader/writer/predicate/clearer format, must be a HASH ref at /usr/lib/i386-linux-gnu/perl5/5.22/Class/MOP/Class.pm line 899 
    Class::MOP::Class::try {...} at /usr/share/perl5/Try/Tiny.pm line 92 
    eval {...} at /usr/share/perl5/Try/Tiny.pm line 83 
    Try::Tiny::try('CODE(0x9dc6cec)', 'Try::Tiny::Catch=REF(0x9ea0c60)') called at /usr/lib/i386-linux-gnu/perl5/5.22/Class/MOP/Class.pm line 904 
    Class::MOP::Class::_post_add_attribute('Moose::Meta::Class=HASH(0x9dc13f4)', 'Moose::Meta::Attribute=HASH(0x9dc6b5c)') called at /usr/lib/i386-linux-gnu/perl5/5.22/Class/MOP/Mixin/HasAttributes.pm line 39 
    Class::MOP::Mixin::HasAttributes::add_attribute('Moose::Meta::Class=HASH(0x9dc13f4)', 'Moose::Meta::Attribute=HASH(0x9dc6b5c)') called at /usr/lib/i386-linux-gnu/perl5/5.22/Moose/Meta/Class.pm line 572 
    Moose::Meta::Class::add_attribute('Moose::Meta::Class=HASH(0x9dc13f4)', 'af', 'HASH(0x9ea13a4)') called at test.pl line 18 

Ich weiß nicht, was zu tun ist (wie „dynamisch“ (Verschluss-like) Accessoren erstellen, ohne zu schreiben eine individuelle Funktion für jedes der 100 Felder?)

+0

Können Sie bitte ein [mcve] erstellen, damit wir das ausführen können? – simbabque

+0

@simbabque Fertig – porton

+1

Der Writer soll direkt in den Hash schreiben, nicht in ein Objekt? – simbabque

Antwort

3

Ich denke, Ändern der Reader und Writer-Methoden wie das erfordert ein ungesundes Niveau von Wahnsinn. Wenn Sie möchten, werfen Sie einen Blick auf the source code of Class::MOP::Method::Accessor, die unter der Haube verwendet wird, um die Accessoren zu erstellen.

Stattdessen schlage ich vor, einfach die Funktionalität zu den Moose-generierten Lesern mit einem around Methodenmodifikator überschreiben (oder anhängen). Um das mit Sub-Klassen zu arbeiten, können Sie Class::Method::Modifiers anstelle der Moose around verwenden.

package Foo::Subclass; 
use Moose; 
extends 'Foo'; 

package Foo; 
use Moose; 

package main; 
require Class::Method::Modifiers; # no import because it would overwrite Moose 

my @Fields = qw(af sdaf gdsg ewwq fsf); # pretend that we have 100 fields 

# Imagine that this is a tied hash with 100 fields 
my %Data = map { $_ => rand } @Fields; 

my $class = 'Foo::Subclass'; 
foreach my $Key (@Fields) { 
    $class->meta->add_attribute(
     $Key => { 
      is  => 'rw', 
      isa  => 'Str', 
      lazy  => 0, 
      required => 0, 
     } 
    ); 

    Class::Method::Modifiers::around("${class}::$Key", sub { 
     my $orig = shift; 
     my $self = shift; 

     $self->$orig(@_); # just so Moose is up to speed 

     # writer 
     $Data{$Key} = $_[0] if @_; 

     return $Data{$Key}; 
    }); 
} 

Und dann einen Test durchführen.

package main; 
use Data::Printer; 
use v5.10; 

my $foo = Test->new; 
say $foo->sdaf; 
$foo->sdaf('foobar'); 
say $foo->sdaf; 

p %Data; 
p $foo; 

Hier ist der STDOUT/STDERR von meiner Maschine.

{ 
    af  0.972962507120432, 
    ewwq 0.959195914302605, 
    fsf 0.719139421719849, 
    gdsg 0.140205658312095, 
    sdaf "foobar" 
} 
Foo::Subclass { 
    Parents  Foo 
    Linear @ISA Foo::Subclass, Foo, Moose::Object 
    public methods (6) : af, ewwq, fsf, gdsg, meta, sdaf 
    private methods (0) 
    internals: { 
     sdaf "foobar" 
    } 
} 
0.885114977459551 
foobar 

Wie Sie sehen können, ist Moose nicht wirklich wissen, über die Werte innerhalb des Hash, aber wenn Sie die Zugriffsmethoden verwenden, wird es lesen und schreiben. Das Moose-Objekt wird langsam mit neuen Werten gefüllt, wenn Sie den Writer verwenden, aber ansonsten sind die Werte innerhalb des Moose-Objekts nicht wirklich wichtig.

+0

Große Lösung, aber es funktioniert nicht für mich, weil ich im realen Code '$ BusinessClass-> meta-> add_attribute (...);' in einer Basisklasse, die für eine abgeleitete Klasse dient. Ihr vorgeschlagener 'around $ Key' wird in der Basisklasse aufgerufen und schlägt mit einem Fehler fehl, da' add_attribute() 'für eine andere (abgeleitete) Klasse aufgerufen wurde und somit kein $ Key-Attribut in der Basisklasse existiert. Was ist zu tun? – porton

+0

Das sollte in untergeordneten Klassen funktionieren. Ich werde es versuchen, wenn ich einen Computer habe. – simbabque

+0

@porton Ich habe die Antwort angepasst. 'Test' wurde umbenannt in' Foo' und es gibt jetzt eine Unterklasse. Der Trick ist nicht, den Elch 'herum' zu benutzen. – simbabque