Как я могу создать несколько атрибутов с помощью одного компоновщика в Moose? - PullRequest
6 голосов
/ 20 октября 2010

Используя Moose, возможно ли создать построитель, который создает несколько атрибутов одновременно?

У меня есть проект, в котором у объекта есть несколько «наборов» полей - если запрашивается какой-либо член набораЯ хочу пойти дальше и заселить их всех.Я предполагаю, что если мне понадобится имя, мне также понадобится дата рождения, и, поскольку они находятся в одной таблице, быстрее получить оба в одном запросе.

Я не уверен, что мойвопрос достаточно ясен, но, надеюсь, некоторый пример кода прояснит это.

Что у меня есть:

Package WidgetPerson;
use Moose;

has id => (is => 'ro', isa => 'Int' );
has name => (is => 'ro', lazy => 1, builder => '_build_name');
has birthdate => (is => 'ro', lazy => 1, builder => '_build_birthdate');
has address => (is => 'ro', lazy => 1, builder => '_build_address');

sub _build_name {
 my $self = shift;
 my ($name) = $dbh->selectrow_array("SELECT name FROM people WHERE id = ?", {}, $self->id);
 return $name;
}
sub _build_birthdate {
 my $self = shift;
 my ($date) = $dbh->selectrow_array("SELECT birthdate FROM people WHERE id = ?", {}, $self->id);
 return $date;
}
sub _build_address {
 my $self = shift;
 my ($date) = $dbh->selectrow_array("SELECT address FROM addresses WHERE person_id = ?", {}, $self->id);
 return $date;
}

Но я хочу:

has name => (is => 'ro', isa => 'Str', lazy => 1, builder => '_build_stuff');
has birthdate => (is => 'ro', isa => 'Date', lazy => 1, builder => '_build_stuff');
has address => (is => 'ro', isa => 'Address', lazy => 1, builder => '_build_address');
sub _build_stuff {
 my $self = shift;
 my ($name, $date) = $dbh->selectrow_array("SELECT name, birthdate FROM people WHERE id = ?", {}, $self->id);
 $self->name($name);
 $self->birthdate($date);
}
sub _build_address { 
 #same as before 
}

Ответы [ 2 ]

7 голосов
/ 22 октября 2010

Что я делаю в этом случае, когда я не хочу иметь отдельный объект, как в ответе Эфира, это иметь ленивый атрибут для промежуточного состояния. Так, например:

has raw_row => (is => 'ro', init_arg => undef, lazy => 1, builder => '_build_raw_row');
has birthdate => (is => 'ro', lazy => 1, builder => '_build_birthdate');

sub _build_raw_row {
   $dbh->selectrow_hashref(...);
}

sub _build_birthdate {
    my $self = shift;
    return $self->raw_row->{birthdate};
}

Повторите тот же шаблон, что и birthdate для имени и т. Д.

При чтении любого из отдельных атрибутов будет пытаться получить данные от raw_row, ленивый конструктор которого будет запускать SQL только один раз. Поскольку все ваши атрибуты доступны только для чтения, вам не нужно беспокоиться об обновлении какого-либо состояния объекта при изменении одного из них.

Этот шаблон полезен и для таких вещей, как XML-документы - промежуточное состояние, которое вы сохраняете, может быть, например, DOM с отдельными атрибутами, лениво создаваемыми из выражений XPath или что у вас есть.

5 голосов
/ 20 октября 2010

Нет, построитель атрибутов может возвращать только одно значение за раз. Вы могли бы построить оба, если бы каждый строитель установил значение другого атрибута перед возвратом, но это довольно быстро становится уродливым ...

Однако , если у вас обычно есть две части данных, которые каким-то образом объединяются (например, поступают из одного и того же запроса к БД, как в вашем случае), вы можете хранить эти значения вместе в одном атрибуте объект:

has birth_info => (
    is => 'ro', isa => 'MyApp::Data::BirthInfo',
    lazy => 1,
    default => sub {
         MyApp::Data::BirthInfo->new(shift->some_id)
    },
    handles => [ qw(birthdate name) ],
);

package MyApp::Data::BirthInfo;
use Moose;
has some_id => (
    is => 'ro', isa => 'Int',
    trigger => sub {
        # perhaps this object self-populates from the DB when you assign its id?
        # or use some other mechanism to load the row in an ORMish way (perhaps BUILD)
    }
);
has birthdate => (
    is => 'ro', isa => 'Str',
);
has name => (
    is => 'ro', isa => 'Str',
);
...