Приведение ArrayRef[MyClass] к ArrayRef[HashRef]

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

You cannot coerce an attribute (departments) unless its type (ArrayRef[Company::Department]) has a coercion at ./test.pl line 12.
You cannot coerce an attribute (employees) unless its type (ArrayRef[Company::Person]) has a coercion at ./test.pl line 23.

но потом удается.

#!/usr/bin/env perl

use warnings;
use strict;

package Company;
use Moose;
use Moose::Util::TypeConstraints;

has 'id'   => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'departments' => (is => 'ro', isa => 'ArrayRef[Company::Department]', coerce => 1);

coerce 'ArrayRef[Company::Department]',
  from 'ArrayRef[HashRef]',
  via { [ map { Company::Department->new($_) } @$_ ] };

package Company::Department;
use Moose;

has 'id'   => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'employees' => (is => 'ro', isa => 'ArrayRef[Company::Person]', coerce => 1);

package Company::Person;
use Moose;
use Moose::Util::TypeConstraints;

has 'id'         => (is => 'ro', isa => 'Num');
has 'name'  => (is => 'ro', isa => 'Str');
has 'age'        => (is => 'ro', isa => 'Num');

coerce 'ArrayRef[Company::Person]',
  from 'ArrayRef[HashRef]',
  via { [ map { Company::Person->new($_) } @$_ ] };

package main;

my %hash = (
    company => {
        id => 1,
        name => 'CorpInc',
        departments => [
            {
                id => 1,
                name => 'Sales',
                employees => [
                    {
                        id => 1,
                        name => 'John Smith',
                        age => '30',
                    },
                ],
            },
            {
                id => 2,
                name => 'IT',
                employees => [
                    {
                        id => 2,
                        name => 'Lucy Jones',
                        age => '28',
                    },
                    {
                        id => 3,
                        name => 'Miguel Cerveza',
                        age => '25',
                    },
                ],
            },
        ],
    }
);

my $company = Company->new($hash{company});
use Data::Dumper;
print Dumper $company;

Как это должно было быть сделано? P.S. Я пытался просто сделать

coerce 'Company::Department',
  from 'HashRef',
  via { Company::Department->new($_) };

но умер ужасно.


person Joel Berger    schedule 18.09.2012    source источник


Ответы (2)


Ну, полностью не получается, и вы должны это почувствовать, когда попытаетесь обновить эти поля с помощью coerce => 1. Вот почему:

Вы не можете передать coerce => 1, если ограничение типа атрибута не имеет приведения

Раньше это было принято, и это вроде как работало, за исключением того, что если вы пытались установить атрибут после того, как объект был создан, вы получали ошибку времени выполнения. Теперь вы получите сообщение об ошибке при попытке определить атрибут.

Тем не менее, я думаю, что найду способ исправить это, во-первых, введя подтипы, а во-вторых, изменив порядок пакетов:

package Company::Person;
use Moose;
use Moose::Util::TypeConstraints;

subtype 'ArrayRefCompanyPersons',
  as 'ArrayRef[Company::Person]';

coerce 'ArrayRefCompanyPersons',
  from 'ArrayRef[HashRef]',
  via { [ map { Company::Person->new($_) } @$_ ] };

has 'id'         => (is => 'ro', isa => 'Num');
has 'name'  => (is => 'ro', isa => 'Str');
has 'age'        => (is => 'ro', isa => 'Num');

package Company::Department;
use Moose;

has 'id'   => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'employees' => (is => 'ro', isa => 'ArrayRefCompanyPersons', coerce => 1);

package Company;
use Moose;
use Moose::Util::TypeConstraints;

subtype 'ArrayRefCompanyDepartments',
  as 'ArrayRef[Company::Department]';

coerce 'ArrayRefCompanyDepartments',
  from 'ArrayRef[HashRef]',
  via { [ map { Company::Department->new($_) } @$_ ] };

has 'id'   => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'departments' => (is => 'ro', isa => 'ArrayRefCompanyDepartments', coerce => 1);

Остальной код такой же, как и в вашей версии. Это работает без каких-либо предупреждений и более-менее ведет себя так, как (опять же, я думаю), так и должно быть.

person raina77ow    schedule 18.09.2012
comment
да, я должен был видеть, что это проблема порядка загрузки. Я больше привык объявлять все в отдельных файлах, поэтому use (с его неявным блоком BEGIN) исправляет ситуацию за меня. Думаю, я ехал слишком быстро. Спасибо! - person Joel Berger; 21.09.2012

Из документов Moose::Manual::Type:

ЗАГРУЗИТЬ ПРОБЛЕМЫ С ЗАКАЗОМ

Поскольку типы Moose определяются во время выполнения, вы можете столкнуться с проблемами порядка загрузки. В частности, вы можете захотеть использовать ограничение типа класса до того, как этот тип будет определен.

Чтобы решить эту проблему, мы рекомендуем определить все ваши пользовательские типы в одном модуле MyApp::Types, а затем загрузить этот модуль во все другие ваши модули.


Итак, чтобы добавить к raina77ow подтипу и ответу на порядок пакетов (+1), я бы рекомендовал создать модуль Company::Types:

package Company::Types;
use Moose;
use Moose::Util::TypeConstraints;

subtype 'CompanyDepartments'
  => as 'ArrayRef[Company::Department]';

subtype 'CompanyPersons'
  => as 'ArrayRef[Company::Person]';

coerce 'CompanyDepartments'
  => from 'ArrayRef[HashRef]'
  => via { 
       require Company::Department; 
       [ map { Company::Department->new($_) } @$_ ]; 
     };

coerce 'CompanyPersons'
  => from 'ArrayRef[HashRef]'
  => via { require Company::Person; [ map { Company::Person->new($_) } @$_ ] };

1;

А затем добавьте use Company::Types во все ваши Company:: классы.

person draegtun    schedule 20.09.2012