views:

55

answers:

2

I recently upgraded Moose to v1.15 and found that a set of modules I use no longer worked. The error I get is:

You cannot coerce an attribute (source) unless its type (GOBO::Node) has a coercion at
/opt/local/lib/perl5/site_perl/5.12.0/darwin-multi-2level/Moose/Meta/Role/Application/ToClass.pm line 142

I can see several possible sources of error and would be grateful for advice on how to fix the problem.

The first bit of code for GOBO::Node looks like this:

package GOBO::Node;
[...]
extends 'GOBO::Base';
with 'GOBO::Labeled';
with 'GOBO::Attributed';

coerce 'GOBO::Node'
  => from 'Str'
  => via { new GOBO::Node(id=>$_) };

has 'source' => (is => 'rw', isa => 'GOBO::Node');

The roles used by this package also have attributes that are GOBO::Nodes, and the attribute 'source' mentioned in the error message is one of them.

  • part of the reason for having the coercion in GOBO::Node seems to be as a shortcut when creating a new node. Would it be better to use BUILDARGS rather than coerce?

  • where should I put the coercion if I want several packages to be able to use it? If I add the coercion to (e.g.) GOBO::Attributed, I get a warning that it already exists. Without the coercion, though, I get the warning above about not being able to coerce.

  • there is a separate package of subtypes; would it be better to create a subtype of GOBO::Node--e.g. GOBO::Node::ProtoNode--and a coercion, and use that for the attributes should be GOBO::Nodes?

Thank you for any help or advice on this problem!

+6  A: 

The example code you pasted doesn't actually trigger the error. The source attribute as written there won't try to coerce anything. I assume however that one of the roles you mention has an attribute with coerce => 1 defined.

In Moose types, and thus coercions, are global. When combined with the the fact that Moose builds a class dynamically you end up with the strange behavior you're seeing here. You'll need to move the definition of the coercion to someplace before the first use of the GOBO::Node type. Typically this is done by creating a package of subtypes (which you note you already have) and including that as early as possible (via use).

Simply moving the GOBO::Node coercion definition into this package of subtypes, and making sure it's used everywhere the coercion is needed should fix your problem.

To answer your other questions:

  • In general I would recommend using coercion over BUILDARGS because it is a much finer grained tool. The usage you're showing here is a text book example of proper coercion usage, so no real reason to change that.

  • As stated above, the typical answer is to build a Library package in a seperate namespace (MyApp::TypeLibrary) and then include that package at the top of the classes you want your types available. Perl won't re-compile a package that it has already compiled, meaning the error you got about the coercion already existing won't be triggered in this case.

  • Based on the examples you've provided there is no need to create a new subtype, GOBO::Node should already work, and without a new subtype this is effectively the same answer as the last one. Yes use the subtype library.

I hope that helps.

perigrin
That is great, thank you - and thank you for the answers to the other questions. :)
girlwithglasses
+5  A: 

The common solution is to have a separate file just to declare type constraints and their coercions. If you need to make sure that a particular class is loaded -- e.g. to coerce to it -- require it inside your coercion functions.

So, something like:

package GOBO::Types;
use Moose::Util::TypeConstraints;
class_type 'GOBO::Node';
coerce 'GOBO::Node',
    from 'Str',
    via { require GOBO::Node; GOBO::Node->new(id => $_) };

You can use this type library anywhere without worrying about loading order, and you can be sure that all your types will be defined already -- especially important if you have types that aren't class_type, because of how Moose tries to resolve type constraint names that it hasn't seen defined yet.

Using a coercion here instead of BUILDARGS is definitely the right thing to do; it's much more reusable.

hdp
Great, thank you! I have now fixed the problem code and all is running smoothly. :D
girlwithglasses