2010-12-13 11 views
5

これは私のprevious questionムース構造型について続きます。私は質問の長さについてお詫び申し上げます。私は、必要なすべての詳細が含まれていることを確認したかったのです。ムース強制とビルダー

MyApp::Type::Fieldは、構造化タイプを定義します。私は強制的にそのvalue属性をPersonクラス(下記の例を参照)から設定できるようにしています。私の実際のアプリケーションでは、Field型が人の名前だけではなく、HashRefから強制的に強制されることに注意してください。

また、作成時にMyApp::Type::Fieldsizerequiredの読み取り専用属性をMyApp::Personに設定する必要があります。ビルダーメソッドを使用してこれを行うことができますが、強制的に使用する場合は呼び出されません。強制的に新しいオブジェクトが作成されるため、ビルダーメソッドを使用しません。

aroundメソッド修飾子をMyApp::Personに追加することで、これを回避することができます(下記の例を参照)。これは面倒です。 aroundメソッド修飾子が頻繁に呼び出されますが、読み取り専用属性を1回設定する必要があります。

これを行うにはより良い方法がありますが、強制は可能ですか? MyApp::Type::Fieldクラスは、デフォルト値またはビルダーを使用してsizeおよびrequiredを初期化することはできません。値の意味を知る方法がないためです。

私は、around修飾子を持たないことに賛成して強制することはできません。

MyApp::Type::Field

coerce 'MyApp::Type::Field' 
    => from 'Str' 
     => via { MyApp::Type::Field->new(value => $_) }; 

has 'value' => (is => 'rw'); 
has 'size'  => (is => 'ro', isa => 'Int', writer => '_set_size',  predicate => 'has_size'); 
has 'required' => (is => 'ro', isa => 'Bool', writer => '_set_required', predicate => 'has_required'); 

MyApp::Person

has name => (is => 'rw', isa => 'MyApp::Type::Field', lazy => 1, builder => '_build_name', coerce => 1);  

sub _build_name { 
    print "Building name\n"; 
    return MyApp::Type::Field->new(size => 255, required => 1); 
} 

MyApp::Test

print "Create new person with coercion\n"; 
my $person = MyApp::Person->new(); 
print "Set name\n"; 
$person->name('Joe Bloggs'); 
print "Name set\n"; 
printf ("Name: %s [%d][%d]\n\n", $person->name->value, $person->name->size, $person->name->required); 

print "Create new person without coercion\n"; 
$person = MyApp::Person->new(); 
print "Set name\n"; 
$person->name->value('Joe Bloggs'); 
print "Name set\n"; 
printf ("Name: %s [%d][%d]\n\n", $person->name->value, $person->name->size, $person->name->required); 

プリント:

Create new person with coercion 
Set name 
Name set 
Name: Joe Bloggs [0][0] 

Create new person without coercion 
Set name 
Building name 
Name set 
Name: Joe Bloggs [255][2] 

MyApp::Personaroundメソッドモディファイアを追加し、それがsizerequiredを設定しないように、ビルダーを変更:

around 'name' => sub { 
    my $orig = shift; 
    my $self = shift; 

    print "Around name\n"; 

    unless ($self->$orig->has_size) { 
     print "Setting size\n"; 
     $self->$orig->_set_size(255); 
    }; 

    unless ($self->$orig->has_required) { 
     print "Setting required\n"; 
     $self->$orig->_set_required(1); 
    }; 

    $self->$orig(@_); 
}; 

sub _build_name { 
    print "Building name\n"; 
    return MyApp::Type::Field->new(); 
} 
MyApp::Testが実行される

sizerequiredです2回設定する。各MyApp::Person属性のサブタイプを作成し、非常によくMyApp::Type::Field作品へStrからそのサブタイプを強制するの

Create new person with coercion 
Set name 
Around name 
Building name 
Setting size 
Setting required 
Name set 
Around name 
Setting size 
Setting required 
Around name 
Around name 
Name: Joe Bloggs [255][3] 

Create new person without coercion 
Set name 
Around name 
Building name 
Name set 
Around name 
Around name 
Around name 
Name: Joe Bloggs [255][4] 

提案するソリューション

​​提案。私は、forループ内でロット全体をラップすることによって、複数のサブタイプ、強制、属性を作成することもできます。これは、同様のプロパティを持つ複数の属性を作成する場合に非常に便利です。

以下の例では、handlesを使用して委任を設定しているため、$person->get_first_name$person->first_name->valueに変換されています。

package MyApp::Type::Field; 

use Moose; 

has 'value'  => (
    is   => 'rw', 
); 

has 'size'  => (
    is   => 'ro', 
    isa   => 'Int', 
    writer  => '_set_size', 
); 

has 'required' => (
    is   => 'ro', 
    isa   => 'Bool', 
    writer  => '_set_required', 
); 

__PACKAGE__->meta->make_immutable; 
1; 

package MyApp::Person; 
use Moose; 
use Moose::Util::TypeConstraints; 
use namespace::autoclean; 

{ 
    my $attrs = { 
     title  => { size => 5, required => 0 }, 
     first_name => { size => 45, required => 1 }, 
     last_name => { size => 45, required => 1 }, 
    }; 

    foreach my $attr (keys %{$attrs}) { 

     my $subtype = 'MyApp::Person::' . ucfirst $attr; 

     subtype $subtype => as 'MyApp::Type::Field'; 

     coerce $subtype 
      => from 'Str' 
       => via { MyApp::Type::Field->new(
        value => $_, 
        size  => $attrs->{$attr}{'size'}, 
        required => $attrs->{$attr}{'required'}, 
       ) }; 

     has $attr => (
      is  => 'rw', 
      isa  => $subtype, 
      coerce => 1, 
      writer => "set_$attr", 
      handles => { "get_$attr" => 'value' }, 
      default => sub { 
       MyApp::Type::Field->new(
        size  => $attrs->{$attr}{'size'}, 
        required => $attrs->{$attr}{'required'}, 
       ) 
      }, 
     ); 
    } 
} 

__PACKAGE__->meta->make_immutable; 
1; 

package MyApp::Test; 

sub print_person { 
    my $person = shift; 

    printf "Title:  %s [%d][%d]\n" . 
      "First name: %s [%d][%d]\n" . 
      "Last name: %s [%d][%d]\n", 
      $person->title->value || '[undef]', 
      $person->title->size, 
      $person->title->required, 
      $person->get_first_name || '[undef]', 
      $person->first_name->size, 
      $person->first_name->required, 
      $person->get_last_name || '[undef]', 
      $person->last_name->size, 
      $person->last_name->required; 
} 

my $person; 

$person = MyApp::Person->new(
    title  => 'Mr', 
    first_name => 'Joe', 
    last_name => 'Bloggs', 
); 

print_person($person); 

$person = MyApp::Person->new(); 
$person->set_first_name('Joe'); 
$person->set_last_name('Bloggs'); 

print_person($person); 

1; 

版画:作家が与える非常にクリーンなクラスにインターフェースを作り、同等のセッターを提供して追加

Title:  Mr [5][0] 
First name: Joe [45][6] 
Last name: Bloggs [45][7] 
Title:  [undef] [5][0] 
First name: Joe [45][8] 
Last name: Bloggs [45][9] 

答えて

3

すべての人が、nameフィールドに異なる要件を持っているつもりですか?これは起こりそうにありません。

Fieldのパラメータはアプリケーションごとに異なる可能性が高いようです。したがって、タイプPersonNameをフィールドのサブタイプとして定義します。あなたの強制は文字列からPersonNameになります。その後、強制コードは、Field->new()を呼び出すときに、必要な長さと長さに適切な値を適用できます。

また、実際には、既に属性オブジェクトを提供しているメタオブジェクトシステムに基づくMooseオブジェクトの属性オブジェクトを構築しているようです。独自の属性オブジェクトを作成するのではなく、属性オブジェクトを拡張してみませんか?

このアプローチの詳細については、Moose Cookbook Meta Recipesを参照してください。

+1

フィールドは、メタ属性を持つ属性よりもMooseX :: Types :: Structuredに似ています。使用の1つの例は、各フィールドが値、最大長(サイズ)、および必要なフラグを必要とするWebフォームです。モデル(この例ではPersonクラス)は、サイズと必須フラグを設定します。 'Field'はかなり一般的であることを意図していますが、' Person'クラスはより具体的です。これまではメタ属性を調べていましたが、アクセスするのは少し面倒です( '$ person-> meta-> get_attribute( 'name') - > size()')。サブタイプはオプションです。私はこれを見ていきます... – Mike

+0

私はちょうどサブタイプを作成することで実験しました。そしてそれが素晴らしい解決策を提供するかもしれないと思います。私は明日のテストをもっとやるよ...ありがとう。 – Mike

+0

サブタイプ提案を使用した提案されたソリューションで自分の答えを更新しました。アドバイスありがとうございます。 – Mike