From: Yuval Kogman Date: Thu, 4 Sep 2008 00:07:33 +0000 (+0000) Subject: merge trunk to pluggable errors X-Git-Tag: 0.58~54^2~3 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=e606ae5f848070d889472329819c95f5ba763ca3;p=gitmo%2FMoose.git merge trunk to pluggable errors --- diff --git a/.shipit b/.shipit new file mode 100644 index 0000000..3af1b04 --- /dev/null +++ b/.shipit @@ -0,0 +1,7 @@ +# auto-generated shipit config file. +steps = FindVersion, ChangeVersion, CheckChangeLog, DistTest, Commit, Tag, MakeDist, DistClean + +svn.tagpattern = %v +svn.tagpattern = http://code2.0beta.co.uk/moose/svn/Moose/tags/%v + +CheckChangeLog.files = Changes diff --git a/Changes b/Changes index cc8cc7f..010f1c8 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,249 @@ Revision history for Perl extension Moose -0.51 +0.57 Wed September 3, 2008 + * Moose::Intro + - A new bit of doc intended to introduce folks familiar with + "standard" Perl 5 OO to Moose concepts. (Dave Rolsky) + + * Moose::Unsweetened + - Shows examples of two classes, each done first with and then + without Moose. This makes a nice parallel to + Moose::Intro. (Dave Rolsky) + + * Moose::Util::TypeConstraints + - Fixed a bug in find_or_parse_type_constraint so that it + accepts a Moose::Meta::TypeConstraint object as the parent + type, not just a name (jnapiorkowski) + - added tests (jnapiorkowski) + + * Moose::Exporter + - If Sub::Name was not present, unimporting failed to actually + remove some sugar subs, causing test failures (Dave Rolsky) + +0.56 Mon September 1, 2008 + For those not following the series of dev releases, there are + several major changes in this release of Moose. + ! Moose::init_meta should now be called as a method. See the + docs for details. + + - Major performance improvements by nothingmuch. + + - New modules for extension writers, Moose::Exporter and + Moose::Util::MetaRole by Dave Rolsky. + + - Lots of doc improvements and additions, especially in the + cookbook sections. + + - Various bug fixes. + + * Removed all references to the experimental-but-no-longer-needed + Moose::Meta::Role::Application::ToMetaclassInstance. + + * Require Class::MOP 0.65. + +0.55_04 Sat August 30, 2008 + * Moose::Util::MetaRole + * Moose::Cookbook::Extending::Recipe2 + - This simplifies the application of roles to any meta class, as + well as the base object class. Reimplemented metaclass traits + using this module. (Dave Rolsky) + + * Moose::Cookbook::Extending::Recipe1 + - This a new recipe, an overview of various ways to write Moose + extensions (Dave Rolsky) + + * Moose::Cookbook::Extending::Recipe3 + * Moose::Cookbook::Extending::Recipe4 + - These used to be Extending::Recipe1 and Extending::Recipe2, + respectively. + +0.55_03 Fri August 29, 2008 + * No changes from 0.55_02 except increasing the Class::MOP + dependency to 0.64_07. + +0.55_02 Fri August 29, 2008 + * Makefile.PL and Moose.pm + - explicitly require Perl 5.8.0+ (Dave Rolsky) + + * Moose::Util::TypeConstraints + - Fix warnings from find_type_constraint if the type is not + found (t0m). + + * Moose::Meta::TypeConstraint + - Predicate methods (equals/is_a_type_of/is_subtype_of) now + return false if the type you specify cannot be found in the + type registry, rather than throwing an unhelpful and + coincidental exception. (t0m). + - added docs & test for this (t0m) + + * Moose::Meta::TypeConstraint::Registry + - add_type_constraint now throws an exception if a parameter is + not supplied (t0m). + - added docs & test for this (t0m) + + * Moose::Cookbook::FAQ + - Added a faq entry on the difference between "role" and "trait" + (t0m) + + * Moose::Meta::Role + - Fixed a bug that caused role composition to not see a required + method when that method was provided by another role being + composed at the same time. (Dave Rolsky) + - test and bug finding (tokuhirom) + +0.55_01 Wed August 20, 2008 + + !! Calling Moose::init_meta as a function is now !! + !! deprecated. Please see the Moose.pm docs for details. !! + + * Moose::Meta::Method::Constructor + - Fix inlined constructor so that values produced by default + or builder methods are coerced as required. (t0m) + - added test for this (t0m) + + * Moose::Meta::Attribute + - A lazy attribute with a default or builder did not attempt to + coerce the default value. The immutable code _did_ + coerce. (t0m) + - added test for this (t0m) + + * Moose::Exporter + - This is a new helper module for writing "Moose-alike" + modules. This should make the lives of MooseX module authors + much easier. (Dave Rolsky) + + * Moose + * Moose::Cookbook::Meta::Recipe5 + - Implemented metaclass traits (and wrote a recipe for it): + + use Moose -traits => 'Foo' + + This should make writing small Moose extensions a little + easier (Dave Rolsky) + + * Moose::Cookbook::Basics::Recipe1 + - Removed any examples of direct hashref access, and applied an + editorial axe to reduce verbosity. (Dave Rolsky) + + * Moose::Cookbook::Basics::Recipe1 + - Also applied an editorial axe here. (Dave Rolsky) + + * Moose + * Moose::Cookbook::Extending::Recipe1 + * Moose::Cookbook::Extending::Recipe2 + - Rewrote extending and embedding moose documentation and + recipes to use Moose::Exporter (Dave Rolsky) + + * Moose + * Moose::Role + - These two modules now warn when you load them from the main + package "main" package, because we will not export sugar to + main. Previously it just did nothing. (Dave Rolsky) + + * Moose::Role + - Now provide an init_meta method just like Moose.pm, and you + can call this to provide an alternate role metaclass. (Dave + Rolsky and nothingmuch) + - get_method_map now respects the package cache flag (nothingmuch) + + * Moose::Meta::Role + - Two new methods - add_method and wrap_method_body + (nothingmuch) + + * many modules + - Optimizations including allowing constructors to accept hash + refs, making many more classes immutable, and making + constructors immutable. (nothingmuch) + +0.55 Sun August 3, 2008 + * Moose::Meta::Attribute + - breaking down the way 'handles' methods are + created so that the process can be more easily + overridden by subclasses (stevan) + + * Moose::Meta::TypeConstraint + - fixing what is passed into a ->message with + the type constraints (RT #37569) + - added tests for this (Charles Alderman) + + * Moose::Util::TypeConstraints + - fix coerce to accept anon types like subtype can (mst) + + * Moose::Cookbook + - reorganized the recipes into sections - Basics, Roles, Meta, + Extending - and wrote abstracts for each section (Dave Rolsky) + + * Moose::Cookbook::Basics::Recipe10 + - A new recipe that demonstrates operator overloading + in combination with Moose. (bluefeet) + + * Moose::Cookbook::Meta::Recipe1 + - an introduction to what meta is and why you'd want to make + your own metaclass extensions (Dave Rolsky) + + * Moose::Cookbook::Meta::Recipe4 + - a very simple metaclass example (Dave Rolsky) + + * Moose::Cookbook::Extending::Recipe1 + - how to write a Moose-alike module to use your own object base + class (Dave Rolsky) + + * Moose::Cookbook::Extending::Recipe2 + - how to write modules with an API just like C (Dave + Rolsky) + + * all documentation + - Tons of fixes, both syntactical and grammatical (Dave + Rolsky, Paul Fenwick) + +0.54 Thurs. July 3, 2008 + ... this is not my day today ... + + * Moose::Meta::Attribute + - fixed legal_options_for_inheritance such that + clone_and_inherit options still works for + Class::MOP::Attribute objects and therefore + does not break MooseX::AttributeHelpers + (stevan) + +0.53 Thurs. July 3, 2008 + * Whoops, I guess I should run 'make manifest' before + actually releasing the module. No actual changes + in this release, except the fact that it includes + the changes that I didn't include in the last + release. (stevan--) + +0.52 Thurs. July 3, 2008 + * Moose + - added "FEATURE REQUESTS" section to the Moose docs + to properly direct people (stevan) (RT #34333) + - making 'extends' croak if it is passed a Role since + this is not ever something you want to do + (fixed by stevan, found by obra) + - added tests for this (stevan) + + * Moose::Object + - adding support for DOES (as in UNIVERSAL::DOES) + (nothingmuch) + - added test for this + + * Moose::Meta::Attribute + - added legal_options_for_inheritance (wreis) + - added tests for this (wreis) + + * Moose::Cookbook::Snacks::* + - removed some of the unfinished snacks that should + not have been released yet. Added some more examples + to the 'Keywords' snack. (stevan) + + * Moose::Cookbook::Style + - added general Moose "style guide" of sorts to the + cookbook (nothingmuch) (RT #34335) + + * t/ + - added more BUILDARGS tests (stevan) + +0.51 Thurs. Jun 26, 2008 * Moose::Role - add unimport so "no Moose::Role" actually does something (sartak) @@ -25,11 +268,22 @@ Revision history for Perl extension Moose * Moose::Meta::Attribute - added support for meta_attr->does("ShortAlias") (sartak) - added tests for this (sartak) + - moved the bulk of the `handles` handling to the new + install_delegation method (Stevan) * Moose::Object + - Added BUILDARGS, a new step in new() + * Moose::Meta::Role::Application::RoleSummation - fix typos no one ever sees (sartak) + * Moose::Util::TypeConstraints + * Moose::Meta::TypeConstraint + * Moose::Meta::TypeCoercion + - Attempt to work around the ??{ } vs. threads issue + (not yet fixed) + - Some null_constraint optimizations + 0.50 Thurs. Jun 11, 2008 - Fixed a version number issue by bumping all modules to 0.50. @@ -114,7 +368,7 @@ Revision history for Perl extension Moose 0.45 Saturday, May 24, 2008 * Moose - - Because of work in Class::MOP 0.56, all + - Because of work in Class::MOP 0.57, all XS based functionality is now optional and a Pure Perl version is supplied - the CLASS_MOP_NO_XS environment variable @@ -151,7 +405,7 @@ Revision history for Perl extension Moose * Moose::Meta::Class - added same 'add_package_symbol' fix as in - Class::MOP 0.56 + Class::MOP 0.57 * Moose::Util - does_role now handles non-Moose classes diff --git a/MANIFEST b/MANIFEST index ba59067..5f4b7db 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,38 +1,41 @@ Changes doap.rdf -inc/Module/AutoInstall.pm inc/Module/Install.pm -inc/Module/Install/AutoInstall.pm inc/Module/Install/Base.pm inc/Module/Install/Can.pm inc/Module/Install/Fetch.pm -inc/Module/Install/Include.pm inc/Module/Install/Makefile.pm inc/Module/Install/Metadata.pm inc/Module/Install/Win32.pm inc/Module/Install/WriteAll.pm lib/Moose.pm lib/Moose/Cookbook.pod +lib/Moose/Cookbook/Basics/Recipe1.pod +lib/Moose/Cookbook/Basics/Recipe10.pod +lib/Moose/Cookbook/Basics/Recipe2.pod +lib/Moose/Cookbook/Basics/Recipe3.pod +lib/Moose/Cookbook/Basics/Recipe4.pod +lib/Moose/Cookbook/Basics/Recipe5.pod +lib/Moose/Cookbook/Basics/Recipe6.pod +lib/Moose/Cookbook/Basics/Recipe7.pod +lib/Moose/Cookbook/Basics/Recipe9.pod +lib/Moose/Cookbook/Extending/Recipe1.pod +lib/Moose/Cookbook/Extending/Recipe2.pod +lib/Moose/Cookbook/Extending/Recipe3.pod +lib/Moose/Cookbook/Extending/Recipe4.pod lib/Moose/Cookbook/FAQ.pod -lib/Moose/Cookbook/Recipe1.pod -lib/Moose/Cookbook/Recipe10.pod -lib/Moose/Cookbook/Recipe11.pod -lib/Moose/Cookbook/Recipe2.pod -lib/Moose/Cookbook/Recipe21.pod -lib/Moose/Cookbook/Recipe22.pod -lib/Moose/Cookbook/Recipe3.pod -lib/Moose/Cookbook/Recipe4.pod -lib/Moose/Cookbook/Recipe5.pod -lib/Moose/Cookbook/Recipe6.pod -lib/Moose/Cookbook/Recipe7.pod -lib/Moose/Cookbook/Recipe9.pod -lib/Moose/Cookbook/Snack/ArrayRef.pod -lib/Moose/Cookbook/Snack/BUILD.pod -lib/Moose/Cookbook/Snack/HashRef.pod +lib/Moose/Cookbook/Meta/Recipe1.pod +lib/Moose/Cookbook/Meta/Recipe2.pod +lib/Moose/Cookbook/Meta/Recipe3.pod +lib/Moose/Cookbook/Meta/Recipe4.pod +lib/Moose/Cookbook/Meta/Recipe5.pod +lib/Moose/Cookbook/Roles/Recipe1.pod +lib/Moose/Cookbook/Roles/Recipe2.pod lib/Moose/Cookbook/Snack/Keywords.pod -lib/Moose/Cookbook/Snack/Perl5ObjsVsMooseObjs.pod lib/Moose/Cookbook/Snack/Types.pod +lib/Moose/Cookbook/Style.pod lib/Moose/Cookbook/WTF.pod +lib/Moose/Exporter.pm lib/Moose/Meta/Attribute.pm lib/Moose/Meta/Class.pm lib/Moose/Meta/Instance.pm @@ -65,6 +68,7 @@ lib/Moose/Object.pm lib/Moose/Role.pm lib/Moose/Spec/Role.pod lib/Moose/Util.pm +lib/Moose/Util/MetaRole.pm lib/Moose/Util/TypeConstraints.pm lib/Moose/Util/TypeConstraints/OptimizedConstraints.pm lib/oose.pm @@ -75,16 +79,19 @@ MANIFEST.SKIP META.yml README t/000_load.t -t/000_recipes/001_point.t -t/000_recipes/002_bank_account.t -t/000_recipes/003_binary_tree.t -t/000_recipes/004_company.t -t/000_recipes/005_coercion.t -t/000_recipes/006_augment_inner.t -t/000_recipes/010_roles.t -t/000_recipes/011_advanced_role_composition.t -t/000_recipes/021_meta_attribute.t -t/000_recipes/022_attribute_trait.t +t/000_recipes/basics/001_point.t +t/000_recipes/basics/002_bank_account.t +t/000_recipes/basics/003_binary_tree.t +t/000_recipes/basics/004_company.t +t/000_recipes/basics/005_coercion.t +t/000_recipes/basics/006_augment_inner.t +t/000_recipes/basics/010_genes.t +t/000_recipes/extending/001_base_class.t +t/000_recipes/extending/002_metaclass_and_sugar.t +t/000_recipes/meta/002_meta_attribute.t +t/000_recipes/meta/003_attribute_trait.t +t/000_recipes/roles/001_roles.t +t/000_recipes/roles/002_advanced_role_composition.t t/010_basics/001_basic_class_setup.t t/010_basics/002_require_superclasses.t t/010_basics/003_super_and_override.t @@ -99,6 +106,8 @@ t/010_basics/011_moose_respects_type_constraints.t t/010_basics/012_rebless.t t/010_basics/013_create.t t/010_basics/014_create_anon.t +t/010_basics/015_buildargs.t +t/010_basics/016_load_into_main.t t/020_attributes/001_attribute_reader_generation.t t/020_attributes/002_attribute_writer_generation.t t/020_attributes/003_attribute_accessor_generation.t @@ -120,6 +129,7 @@ t/020_attributes/018_no_init_arg.t t/020_attributes/019_attribute_lazy_initializer.t t/020_attributes/020_trigger_and_coerce.t t/020_attributes/021_method_generation_rules.t +t/020_attributes/022_legal_options_for_inheritance.t t/030_roles/001_meta_role.t t/030_roles/002_role.t t/030_roles/003_apply_role.t @@ -138,6 +148,7 @@ t/030_roles/015_runtime_roles_and_attrs.t t/030_roles/016_runtime_roles_and_nonmoose.t t/030_roles/017_extending_role_attrs.t t/030_roles/018_runtime_roles_w_params.t +t/030_roles/019_build.t t/030_roles/020_role_composite.t t/030_roles/021_role_composite_exclusion.t t/030_roles/022_role_composition_req_methods.t @@ -146,6 +157,7 @@ t/030_roles/024_role_composition_methods.t t/030_roles/025_role_composition_override.t t/030_roles/026_role_composition_method_mods.t t/030_roles/030_role_parameterized.t +t/030_roles/031_roles_applied_in_create.t t/040_type_constraints/001_util_type_constraints.t t/040_type_constraints/002_util_type_constraints_export.t t/040_type_constraints/003_util_std_type_constraints.t @@ -170,12 +182,18 @@ t/040_type_constraints/021_maybe_type_constraint.t t/040_type_constraints/022_custom_type_errors.t t/040_type_constraints/023_types_and_undef.t t/040_type_constraints/024_role_type_constraint.t +t/040_type_constraints/025_type_coersion_on_lazy_attributes.t t/050_metaclasses/001_custom_attr_meta_with_roles.t t/050_metaclasses/002_custom_attr_meta_as_role.t t/050_metaclasses/003_moose_w_metaclass.t t/050_metaclasses/004_moose_for_meta.t -t/050_metaclasses/010_extending_and_embedding.t +t/050_metaclasses/010_extending_and_embedding_back_compat.t t/050_metaclasses/011_init_meta.t +t/050_metaclasses/012_moose_exporter.t +t/050_metaclasses/013_metaclass_traits.t +t/050_metaclasses/014_goto_moose_import.t +t/050_metaclasses/015_metarole.t +t/050_metaclasses/016_metarole_w_metaclass_pm.t t/060_compat/001_module_refresh_compat.t t/060_compat/002_moose_respects_base.t t/060_compat/003_foreign_inheritence.t @@ -193,6 +211,9 @@ t/100_bugs/011_DEMOLISH_eats_exceptions.t t/100_bugs/012_DEMOLISH_eats_mini.t t/100_bugs/013_lazybuild_required_undef.t t/100_bugs/014_DEMOLISHALL.t +t/100_bugs/016_inheriting_from_roles.t +t/100_bugs/017_type_constraint_messages.t +t/100_bugs/018_immutable_metaclass_does_role.t t/200_examples/001_example.t t/200_examples/002_example_Moose_POOP.t t/200_examples/003_example.t @@ -208,6 +229,8 @@ t/300_immutable/004_inlined_constructors_n_types.t t/300_immutable/005_multiple_demolish_inline.t t/300_immutable/006_immutable_nonmoose_subclass.t t/300_immutable/007_immutable_trigger_from_constructor.t +t/300_immutable/008_immutable_constructor_error.t +t/300_immutable/009_buildargs.t t/400_moose_util/001_moose_util.t t/400_moose_util/002_moose_util_does_role.t t/400_moose_util/003_moose_util_search_class_by_role.t @@ -215,10 +238,18 @@ t/500_test_moose/001_test_moose.t t/500_test_moose/002_test_moose_does_ok.t t/500_test_moose/003_test_moose_has_attribute_ok.t t/500_test_moose/004_test_moose_meta_ok.t +t/600_todo_tests/001_exception_reflects_failed_constraint.t +t/600_todo_tests/002_various_role_shit.t +t/600_todo_tests/003_immutable_n_around.t +t/600_todo_tests/004_inlined_constructor_modified_new.t +t/600_todo_tests/005_moose_and_threads.t t/lib/Bar.pm t/lib/Foo.pm t/lib/MyMooseA.pm t/lib/MyMooseB.pm t/lib/MyMooseObject.pm +t/lib/Role/Child.pm +t/lib/Role/Interface.pm +t/lib/Role/Parent.pm t/pod.t t/pod_coverage.t diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index d276d58..97affd5 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -17,5 +17,8 @@ cover_db ^\.# ^TODO$ ^PLANS$ +^doc/ ^benchmarks -^\._.*$ \ No newline at end of file +^\._.*$ +^t\/600_todo_tests\/$ +\.shipit diff --git a/Makefile.PL b/Makefile.PL index 0619502..11bef51 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,6 +1,6 @@ use strict; use warnings; -use inc::Module::Install 0.75; +use inc::Module::Install; name 'Moose'; all_from 'lib/Moose.pm'; @@ -10,10 +10,12 @@ license 'perl'; my $win32 = !! ( $^O eq 'Win32' or $^O eq 'cygwin' ); # prereqs +requires 'perl' => '5.008'; requires 'Scalar::Util' => $win32 ? '1.17' : '1.18'; requires 'Carp'; -requires 'Class::MOP' => '0.59'; -requires 'Sub::Exporter' => '0.972'; +requires 'Class::MOP' => '0.65'; +requires 'List::MoreUtils'; +requires 'Sub::Exporter' => '0.972'; # only used by oose.pm, not Moose.pm :P requires 'Filter::Simple' => '0'; @@ -25,7 +27,5 @@ build_requires 'Test::LongString'; tests_recursive; -auto_install; - WriteAll(); diff --git a/README b/README index 951b04b..3605d9c 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Moose version 0.50 +Moose version 0.57 =========================== See the individual module documentation for more information diff --git a/doc/moosex-compile b/doc/moosex-compile new file mode 100644 index 0000000..7d72f81 --- /dev/null +++ b/doc/moosex-compile @@ -0,0 +1,176 @@ +MooseX-Compile, wherein Yuval explains how MooseX::Compile is supposed to work and what needs doing. + +TODO: PLEASE EDIT ME + +19:11 hiya +19:12 hola +19:13 so, my empty mail was an attempted abort +19:13 but was going to be "MX::Compile doesn't depend on MX::Compile::CLI. should it?" +19:13 ah, ok =) +19:13 but i'm without my laptop, so i couldn't actually check my assumption +19:14 no, because MX::Compile::CLI is "just a a frontend" and at the time the dependencies were a little sketchy +19:14 they've since matured, so maybe it should dep +19:21 * obra nods +19:21 I was on a plane and was trying to see if MX::Compile was at the point where I could try trivial tests +19:22 ah +19:22 so the answer is definitely maybe ;-) +19:22 i haven't been able to make time for it in the past week +19:23 if you guys hand me small, targetted test cases (just commit to it) of code that passes under plain Moose and should pass with MX::Compile i can probably do that stuff pretty quickly +19:23 but the biggest barrier MXC has right now is testing, in order for it to progress towards something production worthy it basically needs to pass the Moose test suite +19:23 except without the Moose test suite's assumptions +19:23 about state and module loading, and all that +19:24 and doing that is a much more daunting prospect than hacking on MXC itself +19:24 understood. the problem is that I still don't have a good sense of how to get it going, even manually +19:24 ah +19:24 none of the test files seem to show off what I need +19:24 i can walk you through thjat +19:25 the assumptions of the system are: +19:25 the class you are compiling is in its own .pm using standard moose sugar +19:25 there is one package in that file +19:26 the compiler object takes the metaclass and the .pm file as args +19:26 it serializes the metaclass to a .mopc file, and the generated code into a .pmc +19:26 the .pmc contains the original .pm verbatim +19:26 except that all the moose sugar does nothing +19:27 meta is overriden to lazy load .mopc +19:27 and the class is supposed to be usable without loading Moose at all +19:27 what is the point of containing the original pm verbatim? +19:27 the user code +19:28 could open and slurp and eval +19:28 but this is a little more flexible +19:28 basically any subroutines the user has written, global/lexical variable initialization, loading of assorted modules etc all must work +19:28 are you using the flexibility? +19:28 (open, slurp, eval sounds suspiciously like "do") +19:29 can't use do/require/etc because it will go to the .pmc +19:29 instead of the .pm +19:29 the flexibility is helpful because you get a lexical set if the code is compiled +19:29 for when you need to do trickery +19:29 see Moose/Object.pm +19:29 I didn't think 'do' had that logic. but ok :) +19:30 anyway +19:30 do go on +19:30 now that we have Devel::Declare that might prove even simpler +19:30 simply replacing has() etc to export the subs inline +19:30 and write the resulting buffers to a .pmc +19:30 but that's for Later™ +19:30 The fact that the TM shows up in my terminal scare me +19:30 but only a bit less than that you typed it ;) +19:30 utf8++ +19:31 ubuntu++ +19:31 most linuxes seem to get that refreshingly right +19:31 so, erm +19:31 yeah. it's pleasant. +19:31 mxcompile +19:31 anyway +19:31 that is a nice frontend to the compiler object +19:31 I guess "what do I need to do to try MX::Compile for prophet+sd?" +19:31 it can recurse through a directory of modules, or take a list of classes +19:31 for starters, role support +19:31 i know how to do it +19:31 but haven't yet +19:32 type constraint support is very primitive +19:32 is that essentially the same code sartak needs to write to give Mouse roles? +19:32 i don't know what that is but doesn't sound likely +19:32 in MXC moose has already done the role composition +19:32 i just need to figure where the data came from, load that file and realias the subs +19:33 (at bootstrap time) +19:33 no role composition per se +19:33 it's nice to make clear that MXC has two "levels" of awesome +19:33 so you can figure out what you can hope to achieve +19:34 100% compiled everything means you don't load Moose or Class::MOP +19:34 until you need runtime reflection +19:34 no codegen at compile time +19:34 it should load as fast as hand written code +19:34 i've had it beating Object::Tiny in some benchmarks =) +19:35 oo +19:35 Moose::XS should aid in making MooseX::Compile's supported feature set easier +19:35 the less awesome level of awesome is just some classes +19:35 you don't pay for those classes' compilation (Role composition, etc) +19:35 (especially since for me perl -MMoose -e1 takes up 50% of "sd help"'s runtime +19:36 (.4s here) +19:36 5.8.8/ +19:36 ? +19:36 yeah +19:36 "that's what's in the wild" +19:36 i'm just curious if it makes a dfif +19:36 * obra nods +19:36 I don't have my macbook right now or I'd test +19:36 trunk moose loads slower +19:36 how much slower? +19:36 but 5.10 loads faster +19:36 negligiably +19:36 i think like 10% +19:36 this was trunk moose as of friday +19:36 but we can fix that +19:36 ah +19:36 my tests aren't scientific. +19:36 trunk moose as of you sending me nytprofs +19:37 actually that's CPAN moose now +19:37 0.35 - 0.45 +19:37 ouch +19:37 well, part of the problem is that it loads *EVERYTHING* +19:37 every type of meta method class, meta type constraint, the role system, etc +19:37 for a big app these probably will get loaded +19:38 but for a small app, especially if you load the various sub modules only as needed, you shouldn't pay for these +19:38 that's a trivial fix that perigrin started working on +19:38 yeah. I played with his branch and saw no change as of last night +19:39 so yeah, we're using roles. if roles aren't ready yet, I won't get far at all. +19:39 (Also, I do really appreciate all the work you're doing. That I'm not paying for, even ;) +19:39 Thank you. +19:39 i will try shaving Moose's own load time with a profile based approach +19:39 It's SO MUCH better than it was +19:39 well, everybody wins =) +19:39 a. you're a friend +19:40 b. part of my job is making Moose work well +19:40 c. your using Moose helps moose directly and indirectly +19:40 d. I LIKE TACOS +19:40 erm, i mean sushi +19:40 so no worries on that +19:41 so, long term goals: +19:41 App::SD etc has all the meta calculations already cached in .mopc and .pmc +19:41 moose is not loaded +19:41 all generated code is cached +19:41 at worst Moose::XS is loaded to install subs with newXS +19:41 that would be really cool +19:41 depending on which actually fairs better +19:42 that goal is realistic, but involves a lot of work +19:42 more realistic short term goals: +19:42 I started playing with try to dump the symbol table, etc +19:42 MooseX::Compile partly speeding up SD +19:42 we can incrementally improve on that +19:42 and found that DD::Streamer is a lot closer than anything has ever been, but it craps out around not being able to dump lvalue subs +19:43 Moose::XS replacing some code gen +19:43 yes, the initial approach was to to try and marshall Moose classes into DDS +19:43 but it wasn't stable enough +19:43 and also there's the problem of imports +19:43 you must serialize the whole table at once +19:43 or manage an intricate web of inter dependencies +19:43 * obra nods +19:44 i sort of work around that by making all the require()/use() statements stay verbatim +19:44 also it doesn't handle xsubs +19:44 how hard would it be to get moose's codegen to write out source code instead of blowing subs into memory? +19:44 so there's guesswork for where ::bootstrap was called +19:44 i was just getting to that = +19:44 =) +19:44 pretty trivial +19:44 heh +19:44 just grunt work +19:44 is that a more viable approach? +19:44 it's one of the limiting parts of MooseX::Compile +19:45 if we clean up that code it will be easier to add support for more features +19:45 but it's not a huge hurdle since it's a very contained problem +19:45 it doesn't directly affect the design of MXC +19:45 is this stuff written down anywhere other than this buffer? +19:45 i don't think so +19:46 where should it get pasted? +19:46 good question =) +19:46 i think #moose-dev is pretty aware +19:46 is there a moose wiki? +19:46 but documenting is good for people to help out +19:46 no, there should be +19:46 yeah. but the goal is to turn it into written docs. +19:46 ok. for now, it should end up in MooseX-Compile/doc/design +19:46 sounds good +19:46 . o O { Thank god I don't have a moose commit bit } +19:47 though most of this affects moose itself though +19:47 * obra nods +19:47 Moose/doc/moosex-compile, then diff --git a/lib/Moose.pm b/lib/Moose.pm index c4e6f5a..69dbc63 100644 --- a/lib/Moose.pm +++ b/lib/Moose.pm @@ -4,15 +4,18 @@ package Moose; use strict; use warnings; -our $VERSION = '0.50'; +use 5.008; + +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use Scalar::Util 'blessed'; use Carp 'confess', 'croak', 'cluck'; -use Sub::Exporter; +use Moose::Exporter; -use Class::MOP; +use Class::MOP 0.65; use Moose::Meta::Class; use Moose::Meta::TypeConstraint; @@ -20,264 +23,262 @@ use Moose::Meta::TypeCoercion; use Moose::Meta::Attribute; use Moose::Meta::Instance; +use Moose::Object; + use Moose::Meta::Role; +use Moose::Meta::Role::Composite; +use Moose::Meta::Role::Application; +use Moose::Meta::Role::Application::RoleSummation; +use Moose::Meta::Role::Application::ToClass; +use Moose::Meta::Role::Application::ToRole; +use Moose::Meta::Role::Application::ToInstance; -use Moose::Object; use Moose::Util::TypeConstraints; use Moose::Util (); -{ - my $CALLER; - - sub init_meta { - my ( $class, $base_class, $metaclass ) = @_; - $base_class = 'Moose::Object' unless defined $base_class; - $metaclass = 'Moose::Meta::Class' unless defined $metaclass; - - confess - "The Metaclass $metaclass must be a subclass of Moose::Meta::Class." - unless $metaclass->isa('Moose::Meta::Class'); - - # make a subtype for each Moose class - class_type($class) - unless find_type_constraint($class); - - my $meta; - if ( $class->can('meta') ) { - # NOTE: - # this is the case where the metaclass pragma - # was used before the 'use Moose' statement to - # override a specific class - $meta = $class->meta(); - ( blessed($meta) && $meta->isa('Moose::Meta::Class') ) - || confess "You already have a &meta function, but it does not return a Moose::Meta::Class"; - } - else { - # NOTE: - # this is broken currently, we actually need - # to allow the possiblity of an inherited - # meta, which will not be visible until the - # user 'extends' first. This needs to have - # more intelligence to it - $meta = $metaclass->initialize($class); - $meta->add_method( - 'meta' => sub { - # re-initialize so it inherits properly - $metaclass->initialize( blessed( $_[0] ) || $_[0] ); - } - ); - } +sub extends { + my $class = shift; - # make sure they inherit from Moose::Object - $meta->superclasses($base_class) - unless $meta->superclasses(); - - return $meta; + croak "Must derive at least one class" unless @_; + + my @supers = @_; + foreach my $super (@supers) { + Class::MOP::load_class($super); + croak "You cannot inherit from a Moose Role ($super)" + if $super->can('meta') && + blessed $super->meta && + $super->meta->isa('Moose::Meta::Role') } - my %exports = ( - extends => sub { - my $class = $CALLER; - return Class::MOP::subname('Moose::extends' => sub (@) { - croak "Must derive at least one class" unless @_; - - my @supers = @_; - foreach my $super (@supers) { - Class::MOP::load_class($super); - } - # this checks the metaclass to make sure - # it is correct, sometimes it can get out - # of sync when the classes are being built - my $meta = $class->meta->_fix_metaclass_incompatability(@supers); - $meta->superclasses(@supers); - }); - }, - with => sub { - my $class = $CALLER; - return Class::MOP::subname('Moose::with' => sub (@) { - Moose::Util::apply_all_roles($class->meta, @_) - }); - }, - has => sub { - my $class = $CALLER; - return Class::MOP::subname('Moose::has' => sub ($;%) { - my $name = shift; - croak 'Usage: has \'name\' => ( key => value, ... )' if @_ == 1; - my %options = @_; - my $attrs = ( ref($name) eq 'ARRAY' ) ? $name : [ ($name) ]; - $class->meta->add_attribute( $_, %options ) for @$attrs; - }); - }, - before => sub { - my $class = $CALLER; - return Class::MOP::subname('Moose::before' => sub (@&) { - Moose::Util::add_method_modifier($class, 'before', \@_); - }); - }, - after => sub { - my $class = $CALLER; - return Class::MOP::subname('Moose::after' => sub (@&) { - Moose::Util::add_method_modifier($class, 'after', \@_); - }); - }, - around => sub { - my $class = $CALLER; - return Class::MOP::subname('Moose::around' => sub (@&) { - Moose::Util::add_method_modifier($class, 'around', \@_); - }); - }, - super => sub { - return Class::MOP::subname('Moose::super' => sub { - return unless our $SUPER_BODY; $SUPER_BODY->(our @SUPER_ARGS) - }); - }, - override => sub { - my $class = $CALLER; - return Class::MOP::subname('Moose::override' => sub ($&) { - my ( $name, $method ) = @_; - $class->meta->add_override_method_modifier( $name => $method ); - }); - }, - inner => sub { - return Class::MOP::subname('Moose::inner' => sub { - my $pkg = caller(); - our ( %INNER_BODY, %INNER_ARGS ); - - if ( my $body = $INNER_BODY{$pkg} ) { - my @args = @{ $INNER_ARGS{$pkg} }; - local $INNER_ARGS{$pkg}; - local $INNER_BODY{$pkg}; - return $body->(@args); - } else { - return; - } - }); - }, - augment => sub { - my $class = $CALLER; - return Class::MOP::subname('Moose::augment' => sub (@&) { - my ( $name, $method ) = @_; - $class->meta->add_augment_method_modifier( $name => $method ); - }); - }, - make_immutable => sub { - my $class = $CALLER; - return Class::MOP::subname('Moose::make_immutable' => sub { - cluck "The make_immutable keyword has been deprecated, " . - "please go back to __PACKAGE__->meta->make_immutable\n"; - $class->meta->make_immutable(@_); - }); - }, - confess => sub { - return \&Carp::confess; - }, - blessed => sub { - return \&Scalar::Util::blessed; - }, - ); - - my $exporter = Sub::Exporter::build_exporter( - { - exports => \%exports, - groups => { default => [':all'] } - } - ); - - # 1 extra level because it's called by import so there's a layer of indirection - sub _get_caller{ - my $offset = 1; - return - (ref $_[1] && defined $_[1]->{into}) - ? $_[1]->{into} - : (ref $_[1] && defined $_[1]->{into_level}) - ? caller($offset + $_[1]->{into_level}) - : caller($offset); - } - sub import { - $CALLER = _get_caller(@_); + # this checks the metaclass to make sure + # it is correct, sometimes it can get out + # of sync when the classes are being built + my $meta = Moose::Meta::Class->initialize($class)->_fix_metaclass_incompatability(@supers); + $meta->superclasses(@supers); +} - # this works because both pragmas set $^H (see perldoc perlvar) - # which affects the current compilation - i.e. the file who use'd - # us - which is why we don't need to do anything special to make - # it affect that file rather than this one (which is already compiled) +sub with { + my $class = shift; + Moose::Util::apply_all_roles(Class::MOP::Class->initialize($class), @_); +} - strict->import; - warnings->import; +sub has { + my $class = shift; + my $name = shift; + croak 'Usage: has \'name\' => ( key => value, ... )' if @_ == 1; + my %options = @_; + my $attrs = ( ref($name) eq 'ARRAY' ) ? $name : [ ($name) ]; + Class::MOP::Class->initialize($class)->add_attribute( $_, %options ) for @$attrs; +} + +sub before { + my $class = shift; + Moose::Util::add_method_modifier($class, 'before', \@_); +} - # we should never export to main - return if $CALLER eq 'main'; +sub after { + my $class = shift; + Moose::Util::add_method_modifier($class, 'after', \@_); +} - init_meta( $CALLER, 'Moose::Object' ); +sub around { + my $class = shift; + Moose::Util::add_method_modifier($class, 'around', \@_); +} + +sub super { + return unless our $SUPER_BODY; $SUPER_BODY->(our @SUPER_ARGS); +} - goto $exporter; +sub override { + my $class = shift; + my ( $name, $method ) = @_; + Class::MOP::Class->initialize($class)->add_override_method_modifier( $name => $method ); +} + +sub inner { + my $pkg = caller(); + our ( %INNER_BODY, %INNER_ARGS ); + + if ( my $body = $INNER_BODY{$pkg} ) { + my @args = @{ $INNER_ARGS{$pkg} }; + local $INNER_ARGS{$pkg}; + local $INNER_BODY{$pkg}; + return $body->(@args); + } else { + return; } - - # NOTE: - # This is for special use by - # some modules and stuff, I - # dont know if it is sane enough - # to document actually. - # - SL - sub __CURRY_EXPORTS_FOR_CLASS__ { - $CALLER = shift; - ($CALLER ne 'Moose') - || croak "_import_into must be called a function, not a method"; - ($CALLER->can('meta') && $CALLER->meta->isa('Class::MOP::Class')) - || croak "Cannot call _import_into on a package ($CALLER) without a metaclass"; - return map { $_ => $exports{$_}->() } (@_ ? @_ : keys %exports); +} + +sub augment { + my $class = shift; + my ( $name, $method ) = @_; + Class::MOP::Class->initialize($class)->add_augment_method_modifier( $name => $method ); +} + +sub make_immutable { + my $class = shift; + cluck "The make_immutable keyword has been deprecated, " . + "please go back to __PACKAGE__->meta->make_immutable\n"; + Class::MOP::Class->initialize($class)->make_immutable(@_); +} + +Moose::Exporter->setup_import_methods( + with_caller => [ + qw( extends with has before after around override augment make_immutable ) + ], + as_is => [ + qw( super inner ), + \&Carp::confess, + \&Scalar::Util::blessed, + ], +); + +sub init_meta { + # This used to be called as a function. This hack preserves + # backwards compatibility. + if ( $_[0] ne __PACKAGE__ ) { + return __PACKAGE__->init_meta( + for_class => $_[0], + base_class => $_[1], + metaclass => $_[2], + ); } - sub unimport { - no strict 'refs'; - my $class = _get_caller(@_); + shift; + my %args = @_; + + my $class = $args{for_class} + or confess "Cannot call init_meta without specifying a for_class"; + my $base_class = $args{base_class} || 'Moose::Object'; + my $metaclass = $args{metaclass} || 'Moose::Meta::Class'; + + confess + "The Metaclass $metaclass must be a subclass of Moose::Meta::Class." + unless $metaclass->isa('Moose::Meta::Class'); - # loop through the exports ... - foreach my $name ( keys %exports ) { + # make a subtype for each Moose class + class_type($class) + unless find_type_constraint($class); + + my $meta; + + if ( $meta = Class::MOP::get_metaclass_by_name($class) ) { + unless ( $meta->isa("Moose::Meta::Class") ) { + confess "$class already has a metaclass, but it does not inherit $metaclass ($meta)"; + } + } else { + # no metaclass, no 'meta' method - # if we find one ... - if ( defined &{ $class . '::' . $name } ) { - my $keyword = \&{ $class . '::' . $name }; + # now we check whether our ancestors have metaclass, and if so borrow that + my ( undef, @isa ) = @{ $class->mro::get_linear_isa }; - # make sure it is from Moose - my ($pkg_name) = Class::MOP::get_code_info($keyword); - next if $pkg_name ne 'Moose'; + foreach my $ancestor ( @isa ) { + my $ancestor_meta = Class::MOP::get_metaclass_by_name($ancestor) || next; - # and if it is from Moose then undef the slot - delete ${ $class . '::' }{$name}; + my $ancestor_meta_class = ($ancestor_meta->is_immutable + ? $ancestor_meta->get_mutable_metaclass_name + : ref($ancestor_meta)); + + # if we have an ancestor metaclass that inherits $metaclass, we use + # that. This is like _fix_metaclass_incompatability, but we can do it now. + + # the case of having an ancestry is not very common, but arises in + # e.g. Reaction + unless ( $metaclass->isa( $ancestor_meta_class ) ) { + if ( $ancestor_meta_class->isa($metaclass) ) { + $metaclass = $ancestor_meta_class; + } } } + + $meta = $metaclass->initialize($class); + } + + if ( $class->can('meta') ) { + # check 'meta' method + + # it may be inherited + + # NOTE: + # this is the case where the metaclass pragma + # was used before the 'use Moose' statement to + # override a specific class + my $method_meta = $class->meta; + + ( blessed($method_meta) && $method_meta->isa('Moose::Meta::Class') ) + || confess "$class already has a &meta function, but it does not return a Moose::Meta::Class ($meta)"; + + $meta = $method_meta; + } + + unless ( $meta->has_method("meta") ) { # don't overwrite + # also check for inherited non moose 'meta' method? + # FIXME also skip this if the user requested by passing an option + $meta->add_method( + 'meta' => sub { + # re-initialize so it inherits properly + $metaclass->initialize( ref($_[0]) || $_[0] ); + } + ); } + # make sure they inherit from Moose::Object + $meta->superclasses($base_class) + unless $meta->superclasses(); + + return $meta; +} + +# This may be used in some older MooseX extensions. +sub _get_caller { + goto &Moose::Exporter::_get_caller; } ## make 'em all immutable $_->meta->make_immutable( - inline_constructor => 0, + inline_constructor => 1, + constructor_name => "_new", inline_accessors => 1, # these are Class::MOP accessors, so they need inlining ) - for ( - 'Moose::Meta::Attribute', - 'Moose::Meta::Class', - 'Moose::Meta::Instance', - - 'Moose::Meta::TypeConstraint', - 'Moose::Meta::TypeConstraint::Union', - 'Moose::Meta::TypeConstraint::Parameterized', - 'Moose::Meta::TypeCoercion', - - 'Moose::Meta::Method', - 'Moose::Meta::Method::Accessor', - 'Moose::Meta::Method::Constructor', - 'Moose::Meta::Method::Destructor', - 'Moose::Meta::Method::Overriden', - - 'Moose::Meta::Role', - 'Moose::Meta::Role::Method', - 'Moose::Meta::Role::Method::Required', - ); + for (qw( + Moose::Meta::Attribute + Moose::Meta::Class + Moose::Meta::Instance + + Moose::Meta::TypeConstraint + Moose::Meta::TypeConstraint::Union + Moose::Meta::TypeConstraint::Parameterized + Moose::Meta::TypeConstraint::Parameterizable + Moose::Meta::TypeConstraint::Enum + Moose::Meta::TypeConstraint::Class + Moose::Meta::TypeConstraint::Role + Moose::Meta::TypeConstraint::Registry + Moose::Meta::TypeCoercion + Moose::Meta::TypeCoercion::Union + + Moose::Meta::Method + Moose::Meta::Method::Accessor + Moose::Meta::Method::Constructor + Moose::Meta::Method::Destructor + Moose::Meta::Method::Overriden + Moose::Meta::Method::Augmented + + Moose::Meta::Role + Moose::Meta::Role::Method + Moose::Meta::Role::Method::Required + + Moose::Meta::Role::Composite + + Moose::Meta::Role::Application + Moose::Meta::Role::Application::RoleSummation + Moose::Meta::Role::Application::ToClass + Moose::Meta::Role::Application::ToRole + Moose::Meta::Role::Application::ToInstance +)); 1; @@ -321,20 +322,31 @@ Moose is an extension of the Perl 5 object system. The main goal of Moose is to make Perl 5 Object Oriented programming easier, more consistent and less tedious. With Moose you can to think -more about what you want to do and less about the mechanics of OOP. +more about what you want to do and less about the mechanics of OOP. + +Additionally, Moose is built on top of L, which is a +metaclass system for Perl 5. This means that Moose not only makes +building normal Perl 5 objects better, but it provides the power of +metaclass programming as well. + +=head2 New to Moose? -Additionally, Moose is built on top of L, which is a -metaclass system for Perl 5. This means that Moose not only makes -building normal Perl 5 objects better, but it provides the power of -metaclass programming as well. +If you're new to Moose, the best place to start is the L +docs, followed by the L. The intro will show you what +Moose is, and how it makes Perl 5 OO better. + +The cookbook recipes on Moose basics will get you up to speed with +many of Moose's features quickly. Once you have an idea of what Moose +can do, you can use the API documentation to get more detail on +features which interest you. =head2 Moose Extensions -The L namespace is the official place to find Moose extensions. -There are a number of these modules out on CPAN right now the best way to -find them is to search for MooseX:: on search.cpan.org or to look at the -latest version of L which aims to keep an up to date, easily -installable list of these extensions. +The C namespace is the official place to find Moose extensions. +These extensions can be found on the CPAN. The easiest way to find them +is to search for them (L), +or to examine L which aims to keep an up-to-date, easily +installable list of Moose extensions. =head1 BUILDING CLASSES WITH MOOSE @@ -418,7 +430,7 @@ for information on how to define a new type, and how to retrieve type meta-data) This will attempt to use coercion with the supplied type constraint to change the value passed into any accessors or constructors. You B have supplied -a type constraint in order for this to work. See L +a type constraint in order for this to work. See L for an example. =item I $role_name> @@ -503,7 +515,7 @@ want installed locally, and its value is the name of the original method in the class being delegated to. This can be very useful for recursive classes like trees. Here is a -quick example (soon to be expanded into a Moose::Cookbook::Recipe): +quick example (soon to be expanded into a Moose::Cookbook recipe): package Tree; use Moose; @@ -567,7 +579,7 @@ This tells the class to use a custom attribute metaclass for this particular attribute. Custom attribute metaclasses are useful for extending the capabilities of the I keyword: they are the simplest way to extend the MOP, but they are still a fairly advanced topic and too much to cover here, see -L for more information. +L for more information. The default behavior here is to just load C<$metaclass_name>; however, we also have a way to alias to a shorter name. This will first look to see if @@ -581,16 +593,13 @@ B as the metaclass name. This tells Moose to take the list of C<@role_names> and apply them to the attribute meta-object. This is very similar to the I option, but -allows you to use more than one extension at a time. This too is an advanced -topic, we don't yet have a cookbook for it though. +allows you to use more than one extension at a time. -As with I, the default behavior is to just load C<$role_name>; however, -we also have a way to alias to a shorter name. This will first look to see if -B exists. If it does, Moose -will then check to see if that has the method C, which -should return the actual name of the custom attribute trait. If there is no -C method, it will fall back to using -B as the trait name. +See L for details on how a trait name is +resolved to a class name. + +Also see L for a metaclass trait +example. =back @@ -624,18 +633,18 @@ Here is another example, but within the context of a role: package Foo::Role; use Moose::Role; - + has 'message' => ( is => 'rw', isa => 'Str', default => 'Hello, I am a Foo' ); - + package My::Foo; use Moose; - + with 'Foo::Role'; - + has '+message' => (default => 'Hello I am My::Foo'); In this case, we are basically taking the attribute which the role supplied @@ -729,13 +738,13 @@ method call and the C pseudo-package; it is really your choice. The keyword C, much like C, is a no-op outside of the context of an C method. You can think of C as being the inverse of C; the details of how C and C work is best described in -the L. +the L. =item B An C method, is a way of explicitly saying "I am augmenting this method from my superclass". Once again, the details of how C and -C work is best described in the L. +C work is best described in the L. =item B @@ -750,6 +759,36 @@ C anywhere you need to test for an object's class name. =back +=head1 METACLASS TRAITS + +When you use Moose, you can also specify traits which will be applied +to your metaclass: + + use Moose -traits => 'My::Trait'; + +This is very similar to the attribute traits feature. When you do +this, your class's C object will have the specified traits +applied to it. See L for more details. + +=head1 TRAIT NAME RESOLUTION + +By default, when given a trait name, Moose simply tries to load a +class of the same name. If such a class does not exist, it then looks +for for a class matching +B. The C<$type> +variable here will be one of B or B, depending on +what the trait is being applied to. + +If a class with this long name exists, Moose checks to see if it has +the method C. This method is expected to +return the I class name of the trait. If there is no +C method, it will fall back to using +B as the trait name. + +If all this is confusing, take a look at +L, which demonstrates how to create an +attribute trait. + =head1 UNIMPORTING FUNCTIONS =head2 B @@ -773,42 +812,36 @@ to work. Here is an example: =head1 EXTENDING AND EMBEDDING MOOSE -Moose also offers some options for extending or embedding it into your own -framework. The basic premise is to have something that sets up your class' -metaclass and export the moose declarators (C, C, C,...). -Here is an example: +Moose also offers some options for extending or embedding it into your +own framework. To learn more about extending Moose, we recommend +checking out the "Extending" recipes in the L, +starting with L, which provides +an overview of all the different ways you might extend Moose. - package MyFramework; - use Moose; +=head2 B<< Moose->init_meta(for_class => $class, base_class => $baseclass, metaclass => $metaclass) >> - sub import { - my $CALLER = caller(); +The C method sets up the metaclass object for the class +specified by C. This method injects a a C accessor +into the class so you can get at this object. It also sets the class's +superclass to C, with L as the default. - strict->import; - warnings->import; +You can specify an alternate metaclass with the C parameter. - # we should never export to main - return if $CALLER eq 'main'; - Moose::init_meta( $CALLER, 'MyFramework::Base' ); - Moose->import({into => $CALLER}); +For more detail on this topic, see L. - # Do my custom framework stuff - - return 1; - } +This method used to be documented as a function which accepted +positional parameters. This calling style will still work for +backwards compatibility, but is deprecated. =head2 B Moose's C method supports the L form of C<{into =E $pkg}> -and C<{into_level =E 1}> - -=head2 B +and C<{into_level =E 1}>. -Moose does some boot strapping: it creates a metaclass object for your class, -and then injects a C accessor into your class to retrieve it. Then it -sets your baseclass to Moose::Object or the value you pass in unless you already -have one. This is all done via C which takes the name of your class -and optionally a baseclass and a metaclass as arguments. +B: Doing this is more or less deprecated. Use L +instead, which lets you stack multiple C-alike modules +sanely. It handles getting the exported functions into the right place +for you. =head1 CAVEATS @@ -830,75 +863,6 @@ two features separate (yet interoperable) actually makes them easy to use, since their behavior is then easier to predict. Time will tell whether I am right or not (UPDATE: so far so good). -=item * - -It is important to note that we currently have no simple way of combining -multiple extended versions of Moose (see L above), -and that in many cases they will conflict with one another. We are working on -developing a way around this issue, but in the meantime, you have been warned. - -=back - -=head1 JUSTIFICATION - -In case you are still asking yourself "Why do I need this?", then this -section is for you. This used to be part of the main DESCRIPTION, but -I think Moose no longer actually needs justification, so it is included -(read: buried) here for those who are still not convinced. - -=over 4 - -=item Another object system!?!? - -Yes, I know there has been an explosion recently of new ways to -build objects in Perl 5, most of them based on inside-out objects -and other such things. Moose is different because it is not a new -object system for Perl 5, but instead an extension of the existing -object system. - -Moose is built on top of L, which is a metaclass system -for Perl 5. This means that Moose not only makes building normal -Perl 5 objects better, but it also provides the power of metaclass -programming. - -=item Is this for real? Or is this just an experiment? - -Moose is I on the prototypes and experiments I did for the Perl 6 -meta-model. However, Moose is B an experiment/prototype; it is for B. - -=item Is this ready for use in production? - -Yes, I believe that it is. - -Moose has been used successfully in production environemnts by several people -and companies (including the one I work for). There are Moose applications -which have been in production with little or no issue now for well over two years. -I consider it highly stable and we are commited to keeping it stable. - -Of course, in the end, you need to make this call yourself. If you have -any questions or concerns, please feel free to email me, or even the list -or just stop by #moose and ask away. - -=item Is Moose just Perl 6 in Perl 5? - -No. While Moose is very much inspired by Perl 6, it is not itself Perl 6. -Instead, it is an OO system for Perl 5. I built Moose because I was tired of -writing the same old boring Perl 5 OO code, and drooling over Perl 6 OO. So -instead of switching to Ruby, I wrote Moose :) - -=item Wait, I modern, I thought it was just I? - -So I was reading Larry Wall's talk from the 1999 Linux World entitled -"Perl, the first postmodern computer language" in which he talks about how -he picked the features for Perl because he thought they were cool and he -threw out the ones that he thought sucked. This got me thinking about how -we have done the same thing in Moose. For Moose, we have "borrowed" features -from Perl 6, CLOS (LISP), Smalltalk, Java, BETA, OCaml, Ruby and more, and -the bits we didn't like (cause they sucked) we tossed aside. So for this -reason (and a few others) I have re-dubbed Moose a I object system. - -Nuff Said. - =back =head1 ACKNOWLEDGEMENTS @@ -932,6 +896,14 @@ This is the official web home of Moose, it contains links to our public SVN repo as well as links to a number of talks and articles on Moose and Moose related technologies. +=item L - How to cook a Moose + +=item The Moose is flying, a tutorial by Randal Schwartz + +Part 1 - L + +Part 2 - L + =item L documentation =item The #moose channel on irc.perl.org @@ -940,7 +912,9 @@ technologies. =item Moose stats on ohloh.net - L -=item Several Moose extension modules in the L namespace. +=item Several Moose extension modules in the C namespace. + +See L for extensions. =back @@ -973,6 +947,15 @@ All complex software has bugs lurking in it, and this module is no exception. If you find a bug please either email me, or add the bug to cpan-RT. +=head1 FEATURE REQUESTS + +We are very strict about what features we add to the Moose core, especially +the user-visible features. Instead we have made sure that the underlying +meta-system of Moose is as extensible as possible so that you can add your +own features easily. That said, occasionally there is a feature needed in the +meta-system to support your planned extension, in which case you should +either email the mailing list or join us on irc at #moose to discuss. + =head1 AUTHOR Stevan Little Estevan@iinteractive.comE @@ -1011,8 +994,12 @@ Yuval (nothingmuch) Kogman Chris (perigrin) Prather +Wallace (wreis) Reis + Jonathan (jrockway) Rockway +Dave (autarch) Rolsky + Piotr (dexter) Roszatycki Sam (mugwump) Vilain diff --git a/lib/Moose/Cookbook.pod b/lib/Moose/Cookbook.pod index 7ea1cb2..5a9d225 100644 --- a/lib/Moose/Cookbook.pod +++ b/lib/Moose/Cookbook.pod @@ -19,73 +19,85 @@ for common questions and problems people have with Moose. =head2 Basic Moose +These recipes will give you a good idea of what Moose is capable, +starting with simple attribute declaration, and moving on to more +powerful features like laziness, types, type coercion, method +modifiers, and more. + =over 4 -=item L - The (always classic) B example +=item L - The (always classic) B example A simple Moose-based class. Demonstrated Moose attributes and subclassing. -=item L - A simple B example +=item L - A simple B example A slightly more complex Moose class. Demonstrates using a method modifier in a subclass. -=item L - A lazy B example +=item L - A lazy B example Demonstrates several attribute features, including types, weak references, predicates ("does this object have a foo?"), defaults, and -lazy attribute construction. +lazy attribute uction. -=item L - Subtypes, and modeling a simple B class hierarchy +=item L - Subtypes, and modeling a simple B class hierarchy Introduces the creation and use of custom types, a C method, and the use of C in a subclass. -=item L - More subtypes, coercion in a B class +=item L - More subtypes, coercion in a B class More type examples, including the use of type coercions. -=item L - The augment/inner example +=item L - The augment/inner example Demonstrates the use of C method modifiers, a way of turning the usual method overriding style "inside-out". -=item L - Making Moose fast with immutable +=item L - Making Moose fast with immutable Making a class immutable greatly increases the speed of accessors and object construction. -=item L - Managing complex relations with trigger (TODO) +=item L - Managing complex relations with trigger (TODO) I Work off of this http://code2.0beta.co.uk/moose/svn/Moose/trunk/t/200_examples/007_Child_Parent_attr_inherit.t -=item L - Builder methods and lazy_build +=item L - Builder methods and lazy_build The builder feature provides an inheritable and role-composable way to provide a default attribute value. +=item L - Operator overloading, subtypes, and coercion + +Demonstrates using operator overloading, coercion, and subtypes to +model how eye color is determined during reproduction. + =back =head2 Moose Roles +These recipes will show you how to use Moose roles. + =over 4 -=item L - The Moose::Role example +=item L - The Moose::Role example Demonstrates roles, which are also sometimes known as traits or mix-ins. Roles provide a method of code re-use which is orthogonal to subclassing. -=item L - Advanced Role Composition - method exclusion and aliasing +=item L - Advanced Role Composition - method exclusion and aliasing Sometimes you just want to include part of a role in your class. Sometimes you want the whole role but one if its methods conflicts with one in your class. With method exclusion and aliasing, you can work around these problems. -=item L - Runtime Role Composition (TODO) +=item L - Runtime Role Composition (TODO) I @@ -93,40 +105,105 @@ I =head2 Meta Moose +These recipes show you how to write your own meta classes, which lets +you extend the object system provide by Moose. + =over 4 -=item L - Welcome to the meta-world (TODO) +=item L - Welcome to the meta-world (Why Go Meta?) -I +If you're wondering what all this "meta" stuff is, and why you should +care about it, read this "recipe". -=item L - The meta-attribute example +=item L - A meta-attribute, attributes with labels One way to extend Moose is to provide your own attribute metaclasses. Attribute metaclasses let you extend attribute declarations (with C) and behavior to provide additional attribute functionality. -=item L - The meta-attribute trait example +=item L - Labels implemented via attribute traits Extending Moose's attribute metaclass is a great way to add functionality. However, attributes can only have one metaclass. Applying roles to the attribute metaclass lets you provide composable attribute functionality. -=item L - The meta-instance example (TODO) +=item L - Adding a "table" attribute to the metaclass -I +If you want to store more information about your classes, you'll have +to extend C. Doing so is simple, but you'll +probably also want to provide some sugar, so see +L as well. -=item L - The meta-class example (TODO) +=item L - The "table" attribute implemented as a metaclass trait + +This example takes the class metaclass we saw in the previous recipe +and reimplements it as a metaclass trait. + +=item L - Hooking into the immutabilization system (TODO) + +Moose has a feature known as "immutabilization". By calling C<< +__PACKAGE__->meta()->make_immutable() >> after defining your class +(attributes, roles, etc), you tell Moose to optimize things like +object creation, attribute access, and so on. + +If you are creating your own metaclasses, you may need to hook into +the immutabilization system. This cuts across a number of spots, +including the metaclass class, meta method classes, and possibly the +meta-instance class as well. + +This recipe shows you how to write extensions which immutabilize +properly. + +=item L - I (TODO) I =back +=head2 Extending Moose + +These recipes cover some more ways to extend Moose, and will be useful +if you plan to write your own C module. + +=over 4 + +=item L - Moose extension overview + +There are quite a number of ways to extend Moose. This recipe explains +provides an overview of each method, and provides recommendations for +when each is appropriate. + +=item L - Providing a base object class role + +Many base object class extensions can be implemented as roles. This +example shows how to provide a base object class debugging role that +is applied to any class that uses a notional C +module. + +=item L - Providing an alternate base object class + +You may find that you want to provide an alternate base object class +along with a meta extension, or maybe you just want to add some +functionality to all your classes without typing C over and over. + +=item L - Acting like Moose.pm and providing sugar Moose-style + +This recipe shows how to provide a replacement for C. You +may want to do this as part of the API for a C module, +especially if you want to default to a new metaclass class or base +object class. + +=back + =head1 SNACKS =over 4 +=item L + =item L =back diff --git a/lib/Moose/Cookbook/Basics/Recipe1.pod b/lib/Moose/Cookbook/Basics/Recipe1.pod new file mode 100644 index 0000000..2c19b78 --- /dev/null +++ b/lib/Moose/Cookbook/Basics/Recipe1.pod @@ -0,0 +1,240 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Basics::Recipe1 - The (always classic) B example. + +=head1 SYNOPSIS + + package Point; + use Moose; + + has 'x' => (isa => 'Int', is => 'rw', required => 1); + has 'y' => (isa => 'Int', is => 'rw', required => 1); + + sub clear { + my $self = shift; + $self->x(0); + $self->y(0); + } + + package Point3D; + use Moose; + + extends 'Point'; + + has 'z' => (isa => 'Int', is => 'rw', required => 1); + + after 'clear' => sub { + my $self = shift; + $self->z(0); + }; + + .... + + # hash or hashrefs are ok for the constructor + my $point1 = Point->new(x => 5, y => 7); + my $point2 = Point->new({x => 5, y => 7}); + + my $point3d = Point3D->new(x => 5, y => 42, z => -5); + +=head1 DESCRIPTION + +This is the classic Point example. It is taken directly from the Perl +6 Apocalypse 12 document, and is similar to the example found in the +classic K&R C book as well. + +As with all Perl 5 classes, a Moose class is defined in a package. +Moose handles turning on C and C for us, so all we +need to do is say C, and no kittens will die. + +When Moose is loaded, it exports a set of sugar functions into our +package. This means that we import some functions which serve as Moose +"keywords". These aren't real language keywords, they're just Perl +functions exported into our package. + +Moose automatically makes our package a subclass of L. +The L class provides us with a constructor that +respects our attributes, as well other features. See L +for details. + +Now, onto the keywords. The first one we see here is C, which +defines an instance attribute in our class: + + has 'x' => (isa => 'Int', is => 'rw', required => 1); + +This will create an attribute named C. The C parameter says +that we expect the value stored in this attribute to pass the type +constraint for C (1). The accessor generated for this attribute +will be read-write. + +The C<< requires => 1 >> parameter means that this attribute must be +provided when a new object is created. A point object without +coordinates doesn't make much sense, so we don't allow it. + +We have defined our attributes; next we define our methods. In Moose, +as with regular Perl 5 OO, a method is just a subroutine defined +within the package: + + sub clear { + my $self = shift; + $self->x(0); + $self->y(0); + } + +That concludes the B class. + +Next we have a subclass of B, B. To declare our +superclass, we use the Moose keyword C: + + extends 'Point'; + +The C keyword works much like C. First, it will +attempt to load your class if needed. However, unlike C, the +C keyword will I any previous values in your +package's C<@ISA>, where C will C values onto the +package's C<@ISA>. + +It is my opinion that the behavior of C is more intuitive. +(2). + +Next we create a new attribute for B called C. + + has 'z' => (isa => 'Int', is => 'rw', required => 1); + +This attribute is just like B's C and C attributes. + +The C keyword demonstrates a Moose feature called "method +modifiers" (or "advice" for the AOP inclined): + + after 'clear' => sub { + my $self = shift; + $self->z(0); + }; + +When C is called on a B object, our modifier method +gets called as well. Unsurprisingly, the modifier is called I +the real method. + +In this case, the real C method is inherited from B. Our +modifier method receives the same arguments as those passed to the +modified method (just C<$self> here). + +Of course, using the C modifier is not the only way to +accomplish this. This B Perl, right? You can get the same results +with this code: + + sub clear { + my $self = shift; + $self->SUPER::clear(); + $self->z(0); + } + +You could also use another Moose method modifier, C: + + override 'clear' => sub { + my $self = shift; + super(); + $self->z(0); + }; + +The C modifier allows you to use the C keyword to +dispatch to the superclass's method in a very Ruby-ish style. + +The choice of whether to use a method modifier, and which one to use, +is often a question of style as much as functionality. + +Since B inherits from L, it will also inherit +the default L constructor: + + my $point1 = Point->new(x => 5, y => 7); + my $point2 = Point->new({x => 5, y => 7}); + + my $point3d = Point3D->new(x => 5, y => 42, z => -5); + +The C constructor accepts a named argument pair for each +attribute defined by the class, which you can provide as a hash or +hash reference. In this particular example, the attributes are +required, and calling C without them will throw an error. + + my $point = Point->new( x => 5 ); # no y, kaboom! + +From here on, we can use C<$point> and C<$point3d> just as you would +any other Perl 5 object. For a more detailed example of what can be +done, you can refer to the F test +file. + +=head2 Moose Objects are Just Hashrefs + +While this all may appear rather magical, it's important to realize +that Moose objects are just hash references under the hood (3). For +example, you could pass C<$self> to C and you'd get +exactly what you'd expect. + +You could even poke around inside the object's data structure, but +that is strongly discouraged. + +The fact that Moose objects are hashrefs means it is easy to use Moose +to extend non-Moose classes, as long as they too are hash +references. If you want to extend a non-hashref class, check out +C. + +=head1 CONCLUSION + +This recipe demonstrates some basic Moose concepts. The next recipe +will build upon the basics shown here with more complex attributes and +methods. Please read on :) + +=head1 FOOTNOTES + +=over 4 + +=item (1) + +Moose provides a number of builtin type constraints are provided by, +of which C is one. For more information on the type constraint +system, see L. + +=item (2) + +The C keyword support multiple inheritance. Simply pass all +of your superclasses to C as a list: + + extends 'Foo', 'Bar', 'Baz'; + +=item (3) + +Moose supports using instance structures other than blessed hash +references (such as in a glob reference - see +L). + +=back + +=head1 SEE ALSO + +=over 4 + +=item Method Modifiers + +The concept of method modifiers is directly ripped off from CLOS. A +great explanation of them can be found by following this link. + +L + +=back + +=head1 AUTHOR + +Stevan Little Estevan@iinteractive.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Cookbook/Basics/Recipe10.pod b/lib/Moose/Cookbook/Basics/Recipe10.pod new file mode 100644 index 0000000..d8bd36a --- /dev/null +++ b/lib/Moose/Cookbook/Basics/Recipe10.pod @@ -0,0 +1,308 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Basics::Recipe10 - Operator overloading, subtypes, and coercion + +=head1 SYNOPSIS + + package Human; + + use Moose; + use Moose::Util::TypeConstraints; + + subtype 'Gender' + => as 'Str' + => where { $_ =~ m{^[mf]$}s }; + + has 'gender' => ( is => 'ro', isa => 'Gender', required => 1 ); + + has 'mother' => ( is => 'ro', isa => 'Human' ); + has 'father' => ( is => 'ro', isa => 'Human' ); + + use overload '+' => \&_overload_add, fallback => 1; + + sub _overload_add { + my ($one, $two) = @_; + + die('Only male and female humans may create children') + if ($one->gender() eq $two->gender()); + + my ( $mother, $father ) = ( $one->gender eq 'f' ? ($one, $two) : ($two, $one) ); + + my $gender = 'f'; + $gender = 'm' if (rand() >= 0.5); + + return Human->new( + gender => $gender, + mother => $mother, + father => $father, + ); + } + +=head1 DESCRIPTION + +This Moose cookbook recipe shows how operator overloading, coercion, +and sub types can be used to mimic the human reproductive system +(well, the selection of genes at least). Assumes a basic +understanding of Moose. + +=head1 INTRODUCTION + +The example in the SYNOPSIS outlines a very basic use of +operator overloading and Moose. The example creates a class +that allows you to add together two humans and produce a +child from them. + +The two parents must be of the opposite gender, as to do +otherwise wouldn't be biologically possible no matter how much +I might want to allow it. + +While this example works and gets the job done, it really isn't +all that useful. To take this a step further let's play around +with genes. Particularly the genes that dictate eye color. Why +eye color? Because it is simple. There are two genes that have +the most affect on eye color and each person carries two of each +gene. Now that will be useful! + +Oh, and don't forget that you were promised some coercion goodness. + +=head1 TECHNIQUES + +First, let's quickly define the techniques that will be used. + +=head2 Operator Overloading + +Overloading operators takes a simple declaration of which operator +you want to overload and what method to call. See the perldoc for +overload to see some good, basic, examples. + +=head2 Subtypes + +Moose comes with 21 default type constraints, as documented in +L. Int, Str, and CodeRef are +all examples. Subtypes give you the ability to inherit the +constraints of an existing type, and adding additional +constraints on that type. An introduction to type constraints +is available in the L. + +=head2 Coercion + +When an attribute is assigned a value its type constraint +is checked to validate the value. Normally, if the value +does not pass the constraint, an exception will be thrown. +But, it is possible with Moose to define the rules to coerce +values from one type to another. A good introduction to +this can be found in L. + +=head1 GENES + +As I alluded to in the introduction, there are many different +genes that affect eye color. But, there are 2 genes that play +the most prominent role: gey and bey2. To get started let us +make classes for these genes. + +=head2 bey2 + + package Human::Gene::bey2; + + use Moose; + use Moose::Util::TypeConstraints; + + type 'bey2Color' => where { $_ =~ m{^(?:brown|blue)$}s }; + + has 'color' => ( is => 'ro', isa => 'bey2Color' ); + +This class is really simple. All we need to know about the bey2 +gene is whether it is of the blue or brown variety. As you can +see a type constraint for the color attribute has been created +which validates for the two possible colors. + +=head2 gey + + package Human::Gene::gey; + + use Moose; + use Moose::Util::TypeConstraints; + + type 'geyColor' => where { $_ =~ m{^(?:green|blue)$}s }; + + has 'color' => ( is => 'ro', isa => 'geyColor' ); + +The gey gene is nearly identical to the bey2, except that it +has a green or blue variety. + +=head1 EYE COLOR + +Rather than throwing the 4 gene object (2xbey, 2xgey2) straight +on to the Human class, let's create an intermediate class that +abstracts the logic behind eye color. This way the Human class +won't get all cluttered up with the details behind the different +characteristics that makes up a Human. + + package Human::EyeColor; + + use Moose; + use Moose::Util::TypeConstraints; + + subtype 'bey2Gene' + => as 'Object' + => where { $_->isa('Human::Gene::bey2') }; + + coerce 'bey2Gene' + => from 'Str' + => via { Human::Gene::bey2->new( color => $_ ) }; + + subtype 'geyGene' + => as 'Object' + => where { $_->isa('Human::Gene::gey') }; + + coerce 'geyGene' + => from 'Str' + => via { Human::Gene::gey->new( color => $_ ) }; + + has 'bey2_1' => ( is => 'ro', isa => 'bey2Gene', coerce => 1 ); + has 'bey2_2' => ( is => 'ro', isa => 'bey2Gene', coerce => 1 ); + + has 'gey_1' => ( is => 'ro', isa => 'geyGene', coerce => 1 ); + has 'gey_2' => ( is => 'ro', isa => 'geyGene', coerce => 1 ); + +So, we now have a class that can hold the four genes that dictate +eye color. This isn't quite enough, as we also need to calculate +what the human's actual eye color is as a result of the genes. + +As with most genes there are recessive and dominant genes. The bey2 +brown gene is dominant to both blue and green. The gey green gene is +recessive to the brown bey gene and dominant to the blues. Finally, +the bey and gey2 blue genes are recessive to both brown and green. + + sub color { + my ( $self ) = @_; + + return 'brown' if ($self->bey2_1->color() eq 'brown' or $self->bey2_2->color() eq 'brown'); + return 'green' if ($self->gey_1->color() eq 'green' or $self->gey_2->color() eq 'green'); + return 'blue'; + } + +To top it off, if I want to access color(), I want to be really lazy +about it. Perl overloading supports the ability to overload the +stringification of an object. So, normally if I did "$eye_color" +I'd get something like "Human::EyeColor=HASH(0xba9348)". What I +really want is "brown", "green", or "blue". To do this you overload +the stringification of the object. + + use overload '""' => \&color, fallback => 1; + +That's all and good, but don't forget the spawn! Our +humans have to have children, and those children need to inherit +genes from their parents. Let's use operator overloading so +that we can add (+) together two EyeColor characteristics to +create a new EyeColor that is derived in a similar manner as +the gene selection in human reproduction. + + use overload '+' => \&_overload_add, fallback => 1; + + sub _overload_add { + my ($one, $two) = @_; + + my $one_bey2 = 'bey2_' . _rand2(); + my $two_bey2 = 'bey2_' . _rand2(); + + my $one_gey = 'gey_' . _rand2(); + my $two_gey = 'gey_' . _rand2(); + + return Human::EyeColor->new( + bey2_1 => $one->$one_bey2->color(), + bey2_2 => $two->$two_bey2->color(), + gey_1 => $one->$one_gey->color(), + gey_2 => $two->$two_gey->color(), + ); + } + + sub _rand2 { + return 1 + int( rand(2) ); + } + +What is happening here is we are overloading the addition +operator. When two eye color objects are added together +the _overload_add() method will be called with the two +objects on the left and right side of the + as arguments. +The return value of this method should be the expected +result of the addition. I'm not going to go in to the +details of how the gene's are selected as it should be +fairly self-explanatory. + +=head1 HUMAN EVOLUTION + +Our original human class in the SYNOPSIS requires very little +change to support the new EyeColor characteristic. All we +need to do is define a new subtype called EyeColor, a new +attribute called eye_color, and just for the sake of simple code +we'll coerce an arrayref of colors in to an EyeColor object. + + use List::MoreUtils qw( zip ); + + subtype 'EyeColor' + => as 'Object' + => where { $_->isa('Human::EyeColor') }; + + coerce 'EyeColor' + => from 'ArrayRef' + => via { + my @genes = qw( bey2_1 bey2_2 gey_1 gey_2 ); + return Human::EyeColor->new( zip( @genes, @$_ ) ); + }; + + has 'eye_color' => ( is => 'ro', isa => 'EyeColor', coerce => 1, required => 1 ); + +And then in the _overload_add() of the Human class we modify +the creation of the child object to include the addition of +the mother and father's eye colors. + + return Human->new( + gender => $gender, + eye_color => ( $one->eye_color() + $two->eye_color() ), + mother => $mother, + father => $father, + ); + +=head1 CONCLUSION + +The three techniques used in this article - overloading, subtypes, +and coercion - provide the power to produce simple, flexible, powerful, +explicit, inheritable, and enjoyable interfaces. + +If you want to get your hands on this code all combined together, and +working, download the Moose tarball and look at "t/000_recipes/012_genes.t". + +=head1 NEXT STEPS + +Has this been a real project we'd probably want to: + +=over 4 + +=item Better Randomization with Crypt::Random + +=item Characteristic Base Class + +=item Mutating Genes + +=item More Characteristics + +=item Artificial Life + +=back + +=head1 AUTHOR + +Aran Clary Deltac + +=head1 LICENSE + +This work is licensed under a Creative Commons Attribution 3.0 Unported License. + +License details are at: L + +=cut + diff --git a/lib/Moose/Cookbook/Basics/Recipe2.pod b/lib/Moose/Cookbook/Basics/Recipe2.pod new file mode 100644 index 0000000..56d2909 --- /dev/null +++ b/lib/Moose/Cookbook/Basics/Recipe2.pod @@ -0,0 +1,230 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Basics::Recipe2 - A simple B example + +=head1 SYNOPSIS + + package BankAccount; + use Moose; + + has 'balance' => (isa => 'Int', is => 'rw', default => 0); + + sub deposit { + my ($self, $amount) = @_; + $self->balance($self->balance + $amount); + } + + sub withdraw { + my ($self, $amount) = @_; + my $current_balance = $self->balance(); + ($current_balance >= $amount) + || confess "Account overdrawn"; + $self->balance($current_balance - $amount); + } + + package CheckingAccount; + use Moose; + + extends 'BankAccount'; + + has 'overdraft_account' => (isa => 'BankAccount', is => 'rw'); + + before 'withdraw' => sub { + my ($self, $amount) = @_; + my $overdraft_amount = $amount - $self->balance(); + if ($self->overdraft_account && $overdraft_amount > 0) { + $self->overdraft_account->withdraw($overdraft_amount); + $self->deposit($overdraft_amount); + } + }; + +=head1 DESCRIPTION + +The first recipe demonstrated how to build very basic Moose classes, +focusing on creating and manipulating attributes. The objects in that +recipe very data-oriented, and did not have much in the way of +behavior (i.e. methods). In this recipe, we expand upon the concepts +from the first recipe to include some real behavior. In particular, we +should how you can use a method modifier to implement new behavior for +a method. + +The classes in the SYNOPSIS show two kinds of bank account. A simple +bank account has one attribute, the balance, and two behaviors, +depositing and withdrawing money. + +We then extend the basic bank account in the CheckingAccount +class. This class adds another attribute, an overdraft account. It +also adds overdraft protection to the withdraw method. If you try to +withdraw more than you have, the checking account attempts to +reconcile the difference by withdrawing money from the overdraft +account. (1) + +The first class, B, introduces a new attribute feature, a +default value: + + has 'balance' => (isa => 'Int', is => 'rw', default => 0); + +This says that a B has a C attribute, which has +a C type constraint, a read/write accessor, and a default value +of C<0>. This means that every instance of B that is +created will have its C slot initialized to C<0>, unless some +other value is provided to the constructor. + +The C and C methods should be fairly +self-explanatory, as they are just plain old Perl 5 OO. + +As you know from the first recipe, the keyword C sets a +class's superclass. Here we see that B C +B. The next line introduces yet another new attribute +feature, class-based type constraints: + + has 'overdraft_account' => (isa => 'BankAccount', is => 'rw'); + +Up until now, we have only seen the C type constraint, which (as +we saw in the first recipe) is a builtin type constraint. The +C type constraint is new, and was actually defined the +moment we created the B class itself. In fact, Moose +creates a corresponding type constraint for every class in your +program (2). + +This means that in the first recipe, constraints for both C and +C were created. In this recipe, both C and +C type constraints are created automatically. Moose +does this as a convenience so that your classes and type constraint +can be kept in sync with one another. In short, Moose makes sure that +it will just DWIM (3). + +In B, we see another method modifier, the C +modifier. + + before 'withdraw' => sub { + my ($self, $amount) = @_; + my $overdraft_amount = $amount - $self->balance(); + if ($self->overdraft_account && $overdraft_amount > 0) { + $self->overdraft_account->withdraw($overdraft_amount); + $self->deposit($overdraft_amount); + } + }; + +Just as with the C modifier from the first recipe, Moose will +handle calling the superclass method (in this case C<< +BankAccount->withdraw >>). + +The C modifier will (obviously) run I the code from +the superclass is run. Here, C modifier implements overdraft +protection by first checking if there are available funds in the +checking account. If not (and if there is an overdraft account +available), it transfers the amount needed into the checking +account (4). + +As with the method modifier in the first recipe, we could use +C to get the same effect: + + sub withdraw { + my ($self, $amount) = @_; + my $overdraft_amount = $amount - $self->balance(); + if ($self->overdraft_account && $overdraft_amount > 0) { + $self->overdraft_account->withdraw($overdraft_amount); + $self->deposit($overdraft_amount); + } + $self->SUPER::withdraw($amount); + } + +The benefit of taking the method modifier approach is we do not need +to remember to call C and pass it the C<$amount> +argument when writing C<< CheckingAccount->withdraw >>. + +This is actually more than just a convenience for forgetful +programmers. Using method modifiers helps isolate subclasses from +changes in the superclasses. For instance, if B<< +BankAccount->withdraw >> were to add an additional argument of some +kind, the version of B<< CheckingAccount->withdraw >> which uses +C would not pass that extra argument correctly, +whereas the method modifier version would automatically pass along all +arguments correctly. + +Just as with the first recipe, object instantiation uses the C +method, which accepts named parameters. + + my $savings_account = BankAccount->new( balance => 250 ); + + my $checking_account = CheckingAccount->new( + balance => 100, + overdraft_account => $savings_account, + ); + +And as with the first recipe, a more in-depth example can be found in +the F test file. + +=head1 CONCLUSION + +The aim of this recipe was to take the knowledge gained in the first +recipe and expand upon it with a more realistic use case. The next +recipe will expand on Moose attributes to create a behaviorally +sophisticated class defined almost entirely by its attributes. + +=head1 FOOTNOTES + +=over 4 + +=item (1) + +If you're paying close attention, you might realize that there's a +circular loop waiting to happen here. A smarter example would have to +make sure that we don't accidentally create a loop between the +checking account and its overdraft account. + +=item (2) + +In reality, this creation is sensitive to the order in which modules +are loaded. In more complicated cases, you may find that you need to +explicitly declare a class type before the corresponding is loaded. + +=item (3) + +Moose does not attempt to encode a class's is-a relationships within +the type constraint hierarchy. Instead, Moose just considers the class +type constraint to be a subtype of C, and specializes the +constraint check to allow for subclasses. This means that an instance +of B will pass a C type constraint +successfully. For more details, please refer to the +L documentation. + +=item (4) + +If the overdraft account does not have the amount needed, it will +throw an error. Of course, the overdraft account could also have +overdraft protection. See note 1. + +=back + +=head1 SEE ALSO + +=over 4 + +=item Acknowledgment + +The BankAccount example in this recipe is directly taken from the +examples in this chapter of "Practical Common Lisp": + +L + +=back + +=head1 AUTHOR + +Stevan Little Estevan@iinteractive.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Cookbook/Recipe3.pod b/lib/Moose/Cookbook/Basics/Recipe3.pod similarity index 98% rename from lib/Moose/Cookbook/Recipe3.pod rename to lib/Moose/Cookbook/Basics/Recipe3.pod index 16836bb..9782548 100644 --- a/lib/Moose/Cookbook/Recipe3.pod +++ b/lib/Moose/Cookbook/Basics/Recipe3.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe3 - A lazy B example +Moose::Cookbook::Basics::Recipe3 - A lazy B example =head1 SYNOPSIS @@ -220,7 +220,7 @@ You I use the C option without the C option if you like, as we showed in the second recipe. And actually, you can use C instead of C. See -L for details. +L for details. =back diff --git a/lib/Moose/Cookbook/Recipe4.pod b/lib/Moose/Cookbook/Basics/Recipe4.pod similarity index 99% rename from lib/Moose/Cookbook/Recipe4.pod rename to lib/Moose/Cookbook/Basics/Recipe4.pod index ad088fd..3b8b0e6 100644 --- a/lib/Moose/Cookbook/Recipe4.pod +++ b/lib/Moose/Cookbook/Basics/Recipe4.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe4 - Subtypes, and modeling a simple B class hierarchy +Moose::Cookbook::Basics::Recipe4 - Subtypes, and modeling a simple B class hierarchy =head1 SYNOPSIS diff --git a/lib/Moose/Cookbook/Recipe5.pod b/lib/Moose/Cookbook/Basics/Recipe5.pod similarity index 98% rename from lib/Moose/Cookbook/Recipe5.pod rename to lib/Moose/Cookbook/Basics/Recipe5.pod index f0bac8a..a54af7a 100644 --- a/lib/Moose/Cookbook/Recipe5.pod +++ b/lib/Moose/Cookbook/Basics/Recipe5.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe5 - More subtypes, coercion in a B class +Moose::Cookbook::Basics::Recipe5 - More subtypes, coercion in a B class =head1 SYNOPSIS diff --git a/lib/Moose/Cookbook/Recipe6.pod b/lib/Moose/Cookbook/Basics/Recipe6.pod similarity index 96% rename from lib/Moose/Cookbook/Recipe6.pod rename to lib/Moose/Cookbook/Basics/Recipe6.pod index 6381776..bce383d 100644 --- a/lib/Moose/Cookbook/Recipe6.pod +++ b/lib/Moose/Cookbook/Basics/Recipe6.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe6 - The augment/inner example +Moose::Cookbook::Basics::Recipe6 - The augment/inner example =head1 SYNOPSIS diff --git a/lib/Moose/Cookbook/Recipe7.pod b/lib/Moose/Cookbook/Basics/Recipe7.pod similarity index 96% rename from lib/Moose/Cookbook/Recipe7.pod rename to lib/Moose/Cookbook/Basics/Recipe7.pod index 44b5c62..fdb0f1c 100644 --- a/lib/Moose/Cookbook/Recipe7.pod +++ b/lib/Moose/Cookbook/Basics/Recipe7.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe7 - Making Moose fast with immutable +Moose::Cookbook::Basics::Recipe7 - Making Moose fast with immutable =head1 SYNOPSIS diff --git a/lib/Moose/Cookbook/Recipe9.pod b/lib/Moose/Cookbook/Basics/Recipe9.pod similarity index 97% rename from lib/Moose/Cookbook/Recipe9.pod rename to lib/Moose/Cookbook/Basics/Recipe9.pod index 9ccf92a..a73748e 100644 --- a/lib/Moose/Cookbook/Recipe9.pod +++ b/lib/Moose/Cookbook/Basics/Recipe9.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe9 - Builder methods and lazy_build +Moose::Cookbook::Basics::Recipe9 - Builder methods and lazy_build =head1 SYNOPSIS @@ -48,7 +48,7 @@ Moose::Cookbook::Recipe9 - Builder methods and lazy_build =head1 DESCRIPTION -If you've already read L, then this example +If you've already read L, then this example should look awfully familiar. In fact, all we've done here is replace the attribute C with a C method. diff --git a/lib/Moose/Cookbook/Extending/Recipe1.pod b/lib/Moose/Cookbook/Extending/Recipe1.pod new file mode 100644 index 0000000..e94fc01 --- /dev/null +++ b/lib/Moose/Cookbook/Extending/Recipe1.pod @@ -0,0 +1,327 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Extending::Recipe1 - Moose extension overview + +=head1 DESCRIPTION + +Moose has quite a number of ways in which extensions can hook into +Moose and change its behavior. Moose also has a lot of behavior that +can be changed. This recipe will provide an overview of each extension +method and give you some recommendations on what tools to use. + +If you haven't yet read the recipes on metaclasses, go read those +first. You can't really write Moose extensions without understanding +the metaclasses, and those recipes also demonstrate some basic +extensions mechanisms such as metaclass subclasses and traits. + +=head2 Playing Nice With Others + +One of the goals of this overview is to help you build extensions that +cooperate well with other extensions. This is especially important if +you plan to release your extension to CPAN. + +Moose comes with several modules that exist to help your write +cooperative extensions. These are L and +L. By using these two modules to implement your +extensions, you will ensure that your extension works with both the +Moose core features and any other CPAN extension using those modules. + +=head1 PARTS OF Moose YOU CAN EXTEND + +The types of things you might want to do in Moose extensions broadly +fall into a few categories. + +=head2 Metaclass Extensions + +One way of extending Moose is by extending one or more Moose +metaclasses. For example, in L we saw +a metaclass subclass that added a C attribute to the +metaclass. If you were writing an ORM, this would be a logical +extension. + +Many of the Moose extensions on CPAN work by providing an attribute +metaclass extension. For example, the C +distro provides a new attribute metaclass that lets you delegate +behavior to a non-object attribute (a hashref or simple number). + +A metaclass extension can be packaged as a subclass or a +role/trait. If you can, we recommend using traits instead of +subclasses, since it's generally much easier to combine disparate +traits then it is to combine a bunch of subclasses. + +When your extensions are implemented as roles, you can apply them with +the L module. + +=head2 Providing Sugar Subs + +As part of a metaclass extension, you may also want to provide some +sugar subroutines, much like C does. Moose provides a helper +module called L that makes this much simpler. This +will be used in several of the extension recipes. + +=head2 Object Class Extensions + +Another common Moose extension is to change the default object class +behavior. For example, the C extension changes the +behavior of your objects so that they are singletons. The +C extension makes the constructor reject +arguments which don't match its attributes. + +Object class extensions often also include metaclass extensions. In +particular, if you want your object extension to work when a class is +made immutable, you may need to extend some or all of the +C, C, and +C objects. + +The L module lets you apply roles to the base +object class, as well as the meta classes just mentioned. + +=head2 Providing a Role + +Some extensions come in the form of a role for you to consume. The +C extension is a great example of this. In +fact, despite the C name, it does not actually change anything +about Moose's behavior. Instead, it is just a role that an object +which wants to be pluggable can consume. + +If you are implementing this sort of extension, you don't need to do +anything special. You simply create a role and document that it should +be used via the normal C sugar: + + package RoleConsumer; + + use Moose; + + with 'MooseX::My::Role'; + +=head2 New Types + +Another common Moose extension is a new type for the Moose type +system. In this case, you simply create a type in your module. When +people load your module, the type is created, and they can refer to it +by name after that. The C and +C distros are two good examples of how this +works. + +=head1 ROLES VS TRAITS VS SUBCLASSES + +It is important to understand that B. A role can be used as a trait, and a trait is a role. The only +thing that distinguishes the two is that a trait is packaged in a way +that lets Moose resolve a short name to a class name. In other words, +with a trait, the caller can specify it by a short name like "Big", +and Moose will resolve it to a class like +C. + +See L and +L for examples of traits in action. In +particular, both of these recipes demonstrate the trait resolution +mechanism. + +Implementing an extension as a (set of) metaclass or base object +role(s) will make your extension more cooperative. It is hard for an +end-user to effectively combine together multiple metaclass +subclasses, but it can be very easy to combine roles. + +=head1 USING YOUR EXTENSION + +There are a number of ways in which an extension can be applied. In +some cases you can provide multiple ways of consuming your extension. + +=head2 Extensions as Metaclass Traits + +If your extension is available as a trait, you can ask end users to +simply specify it in a list of traits. Currently, this only works for +metaclass and attribute metaclass traits: + + use Moose -traits => [ 'Big', 'Blue' ]; + + has 'animal' => + ( traits => [ 'Big', 'Blue' ], + ... + ); + +If your extension applies to any other metaclass, or the object base +class, you cannot use the trait mechanism. + +The benefit of the trait mechanism is that is very easy to see where a +trait is applied in the code, and consumers have fine-grained control +over what the trait applies to. This is especially true for attribute +traits, where you can apply the trait to just one attribute in a +class. + +=head2 Extensions as Metaclass (and Base Object) Subclasses + +Moose does not provide any simple APIs for consumers to use a subclass +extension, except for attribute metaclasses. The attribute declaration +parameters include a C parameter a consumer of your +extension can use to specify your subclass. + +This is one reason why implementing an extension as a subclass can be +a poor choice. However, you can force the use of certain subclasses at +import time by calling C<< Moose->init_meta >> for the caller, and +providing an alternate metaclass or base object class. + +If you do want to do this, you should look at using C +to re-export the C sugar subroutines. When you use +L and your exporting class has an C +method, L makes sure that this C method +gets called when your class is imported. + +Then in your C you can arrange for the caller to use your +subclasses: + + package MooseX::Embiggen; + + use Moose (); + use Moose::Exporter; + + use MooseX::Embiggen::Meta::Class; + use MooseX::Embiggen::Object; + + Moose::Exporter->setup_import_methods( also => 'Moose' ); + + sub init_meta { + shift; # just your package name + my %options = @_; + + return Moose->init_meta( + for_class => $options{for_class}, + metaclass => 'MooseX::Embiggen::Meta::Class', + base_class => 'MooseX::Embiggen::Object', + ); + } + +=head2 Extensions as Metaclass (and Base Object) Roles + +Implementing your extensions as metaclass roles makes your extensions +easy to apply, and cooperative with other metaclass role-based extensions. + +Just as with a subclass, you will probably want to package your +extensions for consumption with a single module that uses +L. However, in this case, you will use +L to apply all of your roles. The advantage of +using this module is that I. This means that your +extension is cooperative I, and consumers of your +extension can easily use it with other role-based extensions. + + package MooseX::Embiggen; + + use Moose (); + use Moose::Exporter; + use Moose::Util::MetaRole; + + use MooseX::Embiggen::Role::Meta::Class; + use MooseX::Embiggen::Role::Meta::Attribute; + use MooseX::Embiggen::Role::Meta::Method::Constructor + use MooseX::Embiggen::Role::Object; + + Moose::Exporter->setup_import_methods( also => 'Moose' ); + + sub init_meta { + shift; # just your package name + my %options = @_; + + Moose->init_meta(%options); + + my $meta = Moose::Util::MetaRole::apply_metaclass_roles( + for_class => $options{for_class}, + metaclass_roles => ['MooseX::Embiggen::Role::Meta::Class'], + attribute_metaclass_roles => + ['MooseX::Embiggen::Role::Meta::Attribute'], + constructor_class_roles => + ['MooseX::Embiggen::Role::Meta::Method::Constructor'], + ); + + Moose::Util::MetaRole::apply_base_class_roles( + for_class => $options{for_class}, + roles => ['MooseX::Embiggen::Role::Object'], + ); + + return $meta; + } + +As you can see from this example, you can use C +to apply roles to any metaclass, as well as the base object class. If +some other extension has already applied its own roles, they will be +preserved when your extension applies its roles, and vice versa. + +=head2 Providing Sugar + +With L, you can also export your own sugar subs, as +well as those from other sugar modules: + + package MooseX::Embiggen; + + use Moose (); + use Moose::Exporter; + + Moose::Exporter->setup_import_methods( + with_caller => ['embiggen'], + also => 'Moose', + ); + + sub init_meta { ... } + + sub embiggen { + my $caller = shift; + $caller->meta()->embiggen(@_); + } + +And then the consumer of your extension can use your C sub: + + package Consumer; + + use MooseX::Embiggen; + + extends 'Thing'; + + embiggen ...; + +This can be combined with metaclass and base class roles quite easily. + +=head1 LEGACY EXTENSION METHODOLOGIES + +Before the existence of L and +L, there were a number of other ways to extend +Moose. In general, these methods were less cooperative, and only +worked well with a single extension. + +These methods include C, C (which uses +C under the hood), and various hacks to do what +L does. Please do not use these for your own +extensions. + +Note that if you write a cooperative extension, it should cooperate +with older extensions, though older extensions generally do not +cooperate with each other. + +=head1 CONCLUSION + +If you can write your extension as one or more metaclass and base +object roles, please consider doing so. Make sure to read the docs for +L and L as well. + +=head2 Caveat + +The L API is still considered an experiment, +and could go away or change in the future. + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Cookbook/Extending/Recipe2.pod b/lib/Moose/Cookbook/Extending/Recipe2.pod new file mode 100644 index 0000000..0bdeae5 --- /dev/null +++ b/lib/Moose/Cookbook/Extending/Recipe2.pod @@ -0,0 +1,68 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Extending::Recipe2 - Providing a role for the base object class + +=head1 SYNOPSIS + + package MooseX::Debugging; + + use strict; + use warnings; + + use Moose::Exporter; + use Moose::Util::MetaRole; + use MooseX::Debugging::Role::Object; + + Moose::Exporter->setup_import_methods(); + + sub init_meta { + shift; + my %options = @_; + + Moose::Util::MetaRole::apply_base_object_roles( + for_class => $options{for_class}, + role => ['MooseX::Debugging::Role::Object'], + ); + } + + + package MooseX::Debugging::Role::Object; + + after 'BUILD' => sub { + my $self = shift; + + warn "Made a new " . ref $self . " object\n"; + } + +=head1 DESCRIPTION + +In this example, we provide a role for the base object class that adds +some simple debugging output. Every time an object is created, it +spits out a warning saying what type of object it was. + +Obviously, a real debugging role would do something more interesting, +but this recipe is all about how we apply that role. + +In this case, with the combination of L and +L, we ensure that when a module does "S", it automatically gets the debugging role applied +to its base object class. + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + diff --git a/lib/Moose/Cookbook/Extending/Recipe3.pod b/lib/Moose/Cookbook/Extending/Recipe3.pod new file mode 100644 index 0000000..3abe740 --- /dev/null +++ b/lib/Moose/Cookbook/Extending/Recipe3.pod @@ -0,0 +1,85 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Extending::Recipe3 - Providing an alternate base object class + +=head1 SYNOPSIS + + package MyApp::Base; + use Moose; + + extends 'Moose::Object'; + + before 'new' => sub { warn "Making a new " . $_[0] }; + + no Moose; + + package MyApp::UseMyBase; + use Moose (); + use Moose::Exporter; + + Moose::Exporter->setup_import_methods( also => 'Moose' ); + + sub init_meta { + shift; + Moose->init_meta( @_, base_class => 'MyApp::Object' ); + } + +=head1 DESCRIPTION + +Often you find that you want to share some behavior between all your +classes. One way to do that is to make a base class and simply add +C> to every class in your +application. However, that can get tedious. Instead, you can simply +create your Moose-alike module that sets the base object class to +C for you. + +Then, instead of writing C> you can write C>. + +In this particular example, our base class issues some debugging +output every time a new object is created, but you can surely think of +some more interesting things to do with your own base class. + +This all works because of the magic of L. When we +call C<< Moose::Exporter->setup_import_methods( also => 'Moose' ) >> +it builds an C and C method for you. The C<< also => +'Moose' >> bit says that we want to export everything that Moose does. + +The C method that gets created will call our C +method, passing it C<< for_caller => $caller >> as its arguments. The +C<$caller> is set to the class that actually imported us in the first +place. + +See the L docs for more details on its API. + +=head1 USING MyApp::UseMyBase + +To actually use our new base class, we simply use C +I of C. We get all the Moose sugar plus our new base +class. + + package Foo; + + use MyApp::UseMyBase; + + has 'size' => ( is => 'rw' ); + + no MyApp::UseMyBase; + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Cookbook/Extending/Recipe4.pod b/lib/Moose/Cookbook/Extending/Recipe4.pod new file mode 100644 index 0000000..a6b9c3f --- /dev/null +++ b/lib/Moose/Cookbook/Extending/Recipe4.pod @@ -0,0 +1,85 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Extending::Recipe4 - Acting like Moose.pm and providing sugar Moose-style + +=head1 SYNOPSIS + + package MyApp::Mooseish; + + use strict; + use warnings; + + use Moose (); + use Moose::Exporter; + + Moose::Exporter->setup_import_methods( + with_caller => ['has_table'], + also => 'Moose', + ); + + sub init_meta { + shift; + Moose->init_meta( @_, metaclass => 'MyApp::Meta::Class' ); + } + + sub has_table { + my $caller = shift; + $caller->meta()->table(shift); + } + +=head1 DESCRIPTION + +This recipe expands on the use of L we saw in +L. Instead of providing our own +object base class, we provide our own metaclass class, and we also +export a sugar subroutine C. + +Given the above code, you can now replace all instances of C with C. Similarly, C is now +replaced with C. + +The C parameter specifies a list of functions that should +be wrapped before exporting. The wrapper simply ensures that the +importing package name is the first argument to the function, so we +can do C>. + +See the L docs for more details on its API. + +=head1 USING MyApp::Mooseish + +The purpose of all this code is to provide a Moose-like +interface. Here's what it would look like in actual use: + + package MyApp::User; + + use MyApp::Mooseish; + + has_table 'User'; + + has 'username' => ( is => 'ro' ); + has 'password' => ( is => 'ro' ); + + sub login { ... } + + no MyApp::Mooseish; + +All of the normal Moose sugar (C, C, etc) is available +when you C. + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=pod diff --git a/lib/Moose/Cookbook/FAQ.pod b/lib/Moose/Cookbook/FAQ.pod index c33896c..74f06d1 100644 --- a/lib/Moose/Cookbook/FAQ.pod +++ b/lib/Moose/Cookbook/FAQ.pod @@ -78,26 +78,17 @@ because it makes sub classing your class much easier. If you need to affect the constructor's parameters prior to the instance actually being constructed, you have a number of options. -First, there are I (See the L -for a complete example and explaination of coercions). With -coercions it is possible to morph argument values into the correct -expected types. This approach is the most flexible and robust, but -does have a slightly higher learning curve. - -Second, using an C method modifier on C can be an -effective way to affect the contents of C<@_> prior to letting -Moose deal with it. This carries with it the extra burden for -your subclasses, in that they have to be sure to explicitly -call your C and/or work around your C to get to the -version from L. - -The last approach is to use the standard Perl technique of calling -the C within your own custom version of C. This, -of course, brings with it all the issues of the C solution -as well as any issues C might add. - -In short, try to use C and coercions, they are your best -bets. +To change the parameter processing as a whole, you can use +the C method. The default implementation accepts key/value +pairs or a hash reference. You can override it to take positional args, +or any other format + +To change the handling of individual parameters, there are I +(See the L for a complete example and +explanation of coercions). With coercions it is possible to morph +argument values into the correct expected types. This approach is the +most flexible and robust, but does have a slightly higher learning +curve. =head3 How do I make non-Moose constructors work with Moose? @@ -106,7 +97,7 @@ delegation. Moose makes this easy using the C keyword, coercions, and C, so subclassing is often not the ideal route. -That said, the default Moose constructors is inherited from +That said, the default Moose constructor is inherited from L. When inheriting from a non-Moose class, the inheritance chain to L is broken. The simplest way to fix this is to simply explicitly inherit from L @@ -181,7 +172,7 @@ L. This will allow you to write: is => 'rw', ); -And have Moose create seperate C and C methods +And have Moose create separate C and C methods instead of a single C method. NOTE: This B be set globally in Moose, as that would break @@ -213,7 +204,7 @@ coerce the value into a C object using the code in found in the C block. For a more comprehensive example of using coercions, see the -L. +L. If you need to deflate your attribute, the current best practice is to add an C modifier to your accessor. Here is some example code: @@ -291,6 +282,21 @@ release. See L and specifically the B question in the B section. +=head3 What are Traits, and how are they different to Roles? + +In Moose, a trait is almost exactly the same thing as a role, except +that traits typically register themselves, which allows you to refer +to them by a short name ("Big" vs "MyApp::Role::Big"). + +In Moose-speak, a I is usually composed into a I at +compile time, whereas a I is usually composed into an instance +of a class at runtime to add or modify the behavior of B. + +Outside the context of Moose, traits and roles generally mean exactly the +same thing. The original paper called them Traits, however Perl 6 will call +them Roles. + =head1 AUTHOR Stevan Little Estevan@iinteractive.comE diff --git a/lib/Moose/Cookbook/Meta/Recipe1.pod b/lib/Moose/Cookbook/Meta/Recipe1.pod new file mode 100644 index 0000000..37aac8c --- /dev/null +++ b/lib/Moose/Cookbook/Meta/Recipe1.pod @@ -0,0 +1,67 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Meta::Recipe1 - Welcome to the meta world (Why Go Meta?) + +=head1 SUMMARY + +If you've ever found yourself thinking "Moose is great, but I wish it +did X differently", then you've gone meta. The meta recipes are all +about how to change and extend the way Moose does its thing, by +changing how the various meta classes (C, +C, etc) work. + +The metaclass system is a set of classes that describe classes, roles, +attributes, etc. The metaclass API lets you ask questions about a +class, like "what attributes does it have?", or "what roles does the +class do?" + +The metaclass system also lets you actively make changes to a class, +for example by adding new methods. + +The interface with which you normally use Moose (C, C, +C) is just a thin layer of syntactic sugar over the +underlying metaclass system. + +By extending and changing how this metaclass system works, you can in +effect create a modified object implementation for your classes. + +=head2 Examples + +Let's say that you want to additional properties to +attributes. Specifically, we want to add a "label" property to each +attribute, so we can write C<< +My::Class->meta()->get_attribute('size')->label() >>. The first two +recipes show two different ways to do this, one with a full +meta-attribute subclass, and the other with an attribute trait. + +You might also want to add additional properties to your +metaclass. For example, if you were writing an ORM based on Moose, you +could associate a table name with each class via the class's metaclass +object, letting you write C<< My::Class->meta()->table_name() >>. + +=head1 SEE ALSO + +Many of the MooseX modules on CPAN implement metaclass extensions. A +couple good examples include C and +C. For a more complex example see +C. + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.org + +=head1 COPYRIGHT AND LICENSE + +Copyright 2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + + diff --git a/lib/Moose/Cookbook/Recipe21.pod b/lib/Moose/Cookbook/Meta/Recipe2.pod similarity index 99% rename from lib/Moose/Cookbook/Recipe21.pod rename to lib/Moose/Cookbook/Meta/Recipe2.pod index 7196f0f..d5f3754 100644 --- a/lib/Moose/Cookbook/Recipe21.pod +++ b/lib/Moose/Cookbook/Meta/Recipe2.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe21 - The meta-attribute example +Moose::Cookbook::Meta::Recipe2 - A meta-attribute, attributes with labels =head1 SYNOPSIS diff --git a/lib/Moose/Cookbook/Recipe22.pod b/lib/Moose/Cookbook/Meta/Recipe3.pod similarity index 89% rename from lib/Moose/Cookbook/Recipe22.pod rename to lib/Moose/Cookbook/Meta/Recipe3.pod index a0868a6..1faeaca 100644 --- a/lib/Moose/Cookbook/Recipe22.pod +++ b/lib/Moose/Cookbook/Meta/Recipe3.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe22 - The attribute trait example +Moose::Cookbook::Meta::Recipe3 - Labels implemented via attribute traits =head1 SYNOPSIS @@ -64,18 +64,19 @@ Moose::Cookbook::Recipe22 - The attribute trait example =head1 BUT FIRST -This recipe is a continuation of L. Please read that -first. +This recipe is a continuation of +L. Please read that recipe first. =head1 MOTIVATION -In Recipe 21, we created an attribute metaclass that gives attributes a "label" -that can be set in L. That works well until you want a second -meta-attribute, or until you want to adjust the behavior of the attribute. You -could define a specialized attribute metaclass to use in every attribute. -However, you may want different attributes to have different behaviors. You -might end up with a unique attribute metaclass for B, -with a lot of code copying and pasting! +In L, we created an attribute +metaclass that gives attributes a "label" that can be set in +L. That works well until you want a second meta-attribute, +or until you want to adjust the behavior of the attribute. You could +define a specialized attribute metaclass to use in every attribute. +However, you may want different attributes to have different +behaviors. You might end up with a unique attribute metaclass for +B, with a lot of code copying and pasting! Or, if you've been drinking deeply of the Moose kool-aid, you'll have a role for each of the behaviors. One role would give a label meta-attribute. Another @@ -105,7 +106,7 @@ user-level class. =head1 DISSECTION -A side-by-side look of the code examples in this recipe and recipe 21 should +A side-by-side look of the code examples in this recipe and recipe 2 should indicate that defining and using a trait is very similar to defining and using a new attribute metaclass. @@ -132,7 +133,7 @@ C to find the full name of the trait. Now we begin writing our application logic. I'll only cover what has changed -since recipe 21. +since recipe 2. has url => ( traits => [qw/Labeled/], diff --git a/lib/Moose/Cookbook/Meta/Recipe4.pod b/lib/Moose/Cookbook/Meta/Recipe4.pod new file mode 100644 index 0000000..fbc4593 --- /dev/null +++ b/lib/Moose/Cookbook/Meta/Recipe4.pod @@ -0,0 +1,67 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Meta::Recipe4 - Adding a "table" attribute to the metaclass + +=head1 SYNOPSIS + + package MyApp::Meta::Class; + use Moose; + extends 'Moose::Meta::Class'; + + has table => + ( is => 'rw', + isa => 'Str', + ); + +=head1 DESCRIPTION + +In this recipe, we'll create a new metaclass which has a "table" +attribute. This metaclass is for classes associated with a DBMS table, +as one might do for an ORM. + +In this example, the table name is just a string, but in a real ORM +the table might be an object describing the table. + +=head1 THE METACLASS + +The metaclass example really is as simple as the one in the +synopsis. The trick is getting your classes to use this metaclass, and +providing some sort of sugar for declaring the table. This is covered +in L, which shows how to make a +module like C itself, with sugar like C. + +=head2 Using this Metaclass in Practice + +Using this new "table" attribute is quite simple. Let's say we have a +class named C, we could simply write the following: + + my $table = MyApp::User->meta()->table(); + +As long as MyApp::User has arranged to use C as +its metaclass, this method call just works. + +=head1 SEE ALSO + +L - The "table" attribute implemented +as a metaclass trait + +L - Acting like Moose.pm and +providing sugar Moose-style + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=pod diff --git a/lib/Moose/Cookbook/Meta/Recipe5.pod b/lib/Moose/Cookbook/Meta/Recipe5.pod new file mode 100644 index 0000000..b0d54b7 --- /dev/null +++ b/lib/Moose/Cookbook/Meta/Recipe5.pod @@ -0,0 +1,68 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Meta::Recipe5 - The "table" attribute as a metaclass trait + +=head1 SYNOPSIS + + package MyApp::Meta::Class::Trait::HasTable; + use Moose::Role; + + has table => + ( is => 'rw', + isa => 'Str', + ); + + package Moose::Meta::Class::Custom::Trait::HasTable; + sub register_implementation { 'MyApp::Meta::Class::Trait::HasTable' } + + package MyApp::User; + use Moose -traits => 'HasTable'; + + __PACKAGE__->table('User'); + +=head1 DESCRIPTION + +This recipe takes the metaclass table attribute and reimplements it as +a metaclass trait. Traits are just roles that Moose applies to +something for you. In this case, that "something" is the class's +metaclass object. + +The advantage of using traits is that it's easy to combine multiple +traits, whereas combining multiple metaclasses can be tricky (which +subclasses which?). + +The disadvantage is that it's not easy to combine a trait with some +sort of sugar (like our notional C sugar). + +=head2 Using this Metaclass Trait in Practice + +Once this trait has been applied to a metaclass, it looks exactly like +the example we saw in L: + + my $table = MyApp::User->meta()->table(); + +=head1 SEE ALSO + +L - Labels implemented via attribute +traits + +L - Adding a "table" attribute to the +metaclass + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=pod diff --git a/lib/Moose/Cookbook/Recipe1.pod b/lib/Moose/Cookbook/Recipe1.pod deleted file mode 100644 index 04c2c80..0000000 --- a/lib/Moose/Cookbook/Recipe1.pod +++ /dev/null @@ -1,237 +0,0 @@ - -=pod - -=head1 NAME - -Moose::Cookbook::Recipe1 - The (always classic) B example. - -=head1 SYNOPSIS - - package Point; - use Moose; - - has 'x' => (isa => 'Int', is => 'ro'); - has 'y' => (isa => 'Int', is => 'rw'); - - sub clear { - my $self = shift; - $self->{x} = 0; - $self->y(0); - } - - package Point3D; - use Moose; - - extends 'Point'; - - has 'z' => (isa => 'Int'); - - after 'clear' => sub { - my $self = shift; - $self->{z} = 0; - }; - -=head1 DESCRIPTION - -This is the classic Point example. This one in particular I took -from the Perl 6 Apocalypse 12 document, but it is similar to the -example found in the classic K&R C book as well, and many other -places. And now, onto the code: - -As with all Perl 5 classes, a Moose class is defined in a package. -Moose now handles turning on C and C for you, so -all you need to do is say C, and no kittens will die. - -By loading Moose, we are enabling the loading of the Moose -"environment" into our package. This means that we import some -functions which serve as Moose "keywords". These aren't anything -fancy, just plain old exported functions. - -Another important thing happens at this stage as well. Moose will -automatically set your package's superclass to be L. -The reason we do this, is so that we can be sure that your class -will inherit from L and get the benefits that -provides (such as a constructor; see L for details). -However, you don't actually I to inherit from L -if you don't want to. All Moose features will still be accessible to -you. - -Now, onto the keywords. The first one we see here is C, which -defines an instance attribute in your class: - - has 'x' => (isa => 'Int', is => 'ro'); - -This will create an attribute named C, which will expect the -value stored in the attribute to pass the type constraint C (1), -and the accessor generated for this attribute will be read-only -(abbreviated as C). - -The next C line is very similar, with only one difference: - - has 'y' => (isa => 'Int', is => 'rw'); - -A read/write (abbreviated as C) accessor will be generated for -the C attribute. - -At this point the attributes have been defined, and it is time to -define our methods. In Moose, as with regular Perl 5 OO, a method -is just a subroutine defined within the package. So here we create -the C method. - - sub clear { - my $self = shift; - $self->{x} = 0; - $self->y(0); - } - -It is pretty standard, the only thing to note is that we are directly -accessing the C slot in the instance L<(2)>. This is because the -value was created with a read-only accessor. This also shows that Moose -objects are not anything out of the ordinary, but just regular old -blessed HASH references. This means they are very compatible with -other Perl 5 (non-Moose) classes as well. - -The next part of the code to review is the B subclass, -B. The first item you might notice is that we do not use -the standard C declaration here. Instead we use the Moose -keyword C like so: - - extends 'Point'; - -This keyword will function very much like C does in that -it will make an attempt to load your class if it has not already been -loaded. However, it differs on one important point. The C -keyword will overwrite any previous values in your package's C<@ISA>, -where C will C values onto the package's C<@ISA>. It -is my opinion that the behavior of C is more intuitive in -that it is more explicit about defining the superclass relationship. - -A small digression here: both Moose and C support multiple -inheritance. You simply pass all the superclasses to C, -like so: - - extends 'Foo', 'Bar', 'Baz'; - -Now, back to our B class. The next thing we do is to create -a new attribute for B called C. - - has 'z' => (isa => 'Int'); - -As with B's C and C attributes, this attribute has a -type constraint of C, but it differs in that it does B -ask for any autogenerated accessors. The result being (aside from -broken object encapsulation) that C is a private attribute. - -Next comes another Moose feature which we call method "modifiers" -(or method "advice" for the AOP inclined). The modifier used here -is the C modifier, and looks like this: - - after 'clear' => sub { - my $self = shift; - $self->{z} = 0; - }; - -This modifier tells Moose to install a C method for -B that will first run the C method for the -superclass (in this case C), and then run this -method I it (passing in the same arguments as the original -method). - -Now, of course using the C modifier is not the only way to -accomplish this. I mean, after all, this B Perl right? You -would get the same results with this code: - - sub clear { - my $self = shift; - $self->SUPER::clear(); - $self->{z} = 0; - } - -You could also use another Moose method modifier, C here, -and get the same results again. Here is how that would look: - - override 'clear' => sub { - my $self = shift; - super(); - $self->{z} = 0; - }; - -The C modifier allows you to use the C keyword -within it to dispatch to the superclass's method in a very Ruby-ish -style. - -Now, of course, what use is a class if you can't instantiate objects -with it? Since B inherits from L, it will also -inherit the default L constructor: C. Here -are two examples of how that is used: - - my $point = Point->new(x => 1, y => 2); - my $point3d = Point3D->new(x => 1, y => 2, z => 3); - -As you can see, C accepts named argument pairs for any of the -attributes. It does not I that you pass in the all the -attributes, and it will politely ignore any named arguments it does -not recognize. - -From here on, you can use C<$point> and C<$point3d> just as you would -any other Perl 5 object. For a more detailed example of what can be -done, you can refer to the F test file. - -=head1 CONCLUSION - -I hope this recipe has given you some explanation of how to use -Moose to build your Perl 5 classes. The next recipe will build upon -the basics shown here with more complex attributes and methods. -Please read on :) - -=head1 FOOTNOTES - -=over 4 - -=item (1) - -Several default type constraints are provided by Moose, of which -C is one. For more information on the builtin type constraints -and the type constraint system in general, see the -L documentation. - -=item (2) - -Moose supports using instance structures other than blessed hash -references (such as in a glob reference -- see -L). If you want your Moose classes to -be interchangeable, it is advisable to avoid direct instance -access, like that shown above. Moose does let you get and set -attributes directly without exposing the instance structure, but -that's an advanced topic (intrepid readers should refer to the -L). - -=back - -=head1 SEE ALSO - -=over 4 - -=item Method Modifiers - -The concept of method modifiers is directly ripped off from CLOS. A -great explanation of them can be found by following this link. - -L - -=back - -=head1 AUTHOR - -Stevan Little Estevan@iinteractive.comE - -=head1 COPYRIGHT AND LICENSE - -Copyright 2006-2008 by Infinity Interactive, Inc. - -L - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut \ No newline at end of file diff --git a/lib/Moose/Cookbook/Recipe2.pod b/lib/Moose/Cookbook/Recipe2.pod deleted file mode 100644 index 9bb2c93..0000000 --- a/lib/Moose/Cookbook/Recipe2.pod +++ /dev/null @@ -1,211 +0,0 @@ - -=pod - -=head1 NAME - -Moose::Cookbook::Recipe2 - A simple B example - -=head1 SYNOPSIS - - package BankAccount; - use Moose; - - has 'balance' => (isa => 'Int', is => 'rw', default => 0); - - sub deposit { - my ($self, $amount) = @_; - $self->balance($self->balance + $amount); - } - - sub withdraw { - my ($self, $amount) = @_; - my $current_balance = $self->balance(); - ($current_balance >= $amount) - || confess "Account overdrawn"; - $self->balance($current_balance - $amount); - } - - package CheckingAccount; - use Moose; - - extends 'BankAccount'; - - has 'overdraft_account' => (isa => 'BankAccount', is => 'rw'); - - before 'withdraw' => sub { - my ($self, $amount) = @_; - my $overdraft_amount = $amount - $self->balance(); - if ($self->overdraft_account && $overdraft_amount > 0) { - $self->overdraft_account->withdraw($overdraft_amount); - $self->deposit($overdraft_amount); - } - }; - -=head1 DESCRIPTION - -In the first recipe we demonstrated the construction of basic -Moose classes whose attributes had various accessor schemes and -builtin type constraints. However, our objects were very data- -oriented, and did not have many behavioral aspects (i.e. methods) -to them. In this recipe, we will expand upon the concepts from -the first recipe and give a more realistic scenario of more -behavior oriented classes. - -We are using the example of a bank account, which has a standard -account (you can deposit money, withdraw money and check your -current balance), and a checking account which has optional -overdraft protection. The overdraft protection will protect the -owner of the checking account by automatically withdrawing the -needed funds from the overdraft account to ensure that a check -will not bounce. - -Now, onto the code. The first class, B, introduces a -new attribute feature: a default value. - - has 'balance' => (isa => 'Int', is => 'rw', default => 0); - -This tells us that a B has a C attribute, -which has the C type constraint, a read/write accessor, -and a default value of C<0>. This means that every instance of -B that is created will have its C slot -initialized to C<0>. Very simple really :) - -Next come the methods. The C and C methods -should be fairly self-explanatory; they are nothing specific to -Moose, just your standard Perl 5 OO. - -Now, onto the B class. As you know from the -first recipe, the keyword C sets a class's superclass -relationship. Here we see that B is a -B. The next line introduces yet another new aspect -of Moose, that of class-based type-constraints: - - has 'overdraft_account' => (isa => 'BankAccount', is => 'rw'); - -Up until now, we have only had C type constraints, which -(as I said in the first recipe) is a builtin type constraint -that Moose provides for you. The C type constraint -is new, and was actually defined the moment we created the -B class itself. In fact, for every class in -your program, a corresponding type constraint will be created. This -means that in the first recipe, both C and C type -constraints were created, and in this recipe, both C -and C type constraints were created. Moose does -this as a convenience so that your class model and the type -constraint model can be kept in sync with one another. In short, -Moose makes sure that it will just DWIM (1). - -Next, we come to the behavioral part of B, and -again we see a method modifier, but this time it is a C -modifier. - - before 'withdraw' => sub { - my ($self, $amount) = @_; - my $overdraft_amount = $amount - $self->balance(); - if ($self->overdraft_account && $overdraft_amount > 0) { - $self->overdraft_account->withdraw($overdraft_amount); - $self->deposit($overdraft_amount); - } - }; - -Just as with the C modifier from the first recipe, Moose -will handle calling the superclass method (in this case the -C method). The C modifier shown -above will run (obviously) I the code from the superclass -with run. The C modifier here implements the overdraft -protection by first checking if there are enough available -funds in the checking account and if not (and if there is an overdraft -account available), it transfers the appropriate funds into the -checking account. - -As with the method modifier in the first recipe, there is another -way to accomplish this same thing using the built in C -pseudo-package. So the above method is equivalent to the one here. - - sub withdraw { - my ($self, $amount) = @_; - my $overdraft_amount = $amount - $self->balance(); - if ($self->overdraft_account && $overdraft_amount > 0) { - $self->overdraft_account->withdraw($overdraft_amount); - $self->deposit($overdraft_amount); - } - $self->SUPER::withdraw($amount); - } - -The benefits of taking the method modifier approach is that the -author of the B subclass does not need to remember -to call C and to pass it the C<$amount> argument. -Instead the method modifier ensures that all arguments make it -to the superclass method correctly. But this is actually more -than just a convenience for forgetful programmers, it also helps -isolate subclasses from changes in the superclasses. For instance, -if B were to add an additional argument -of some kind, the version of B which -uses C would not pass that extra argument -correctly, whereas the method modifier version would automatically -pass along all arguments correctly. - -Just as with the first recipe, object instantiation is a fairly -normal process, here is an example: - - my $savings_account = BankAccount->new(balance => 250); - my $checking_account = CheckingAccount->new( - balance => 100, - overdraft_account => $savings_account - ); - -And as with the first recipe, a more in-depth example of using -these classes can be found in the F test file. - -=head1 CONCLUSION - -The aim of this recipe was to take the knowledge gained in the -first recipe and expand upon it with a more realistic use case. I -hope that this recipe has accomplished this goal. The next recipe -will expand even more upon the capabilities of attributes in Moose -to create a behaviorally sophisticated class almost entirely -defined by attributes. - -=head1 FOOTNOTES - -=over 4 - -=item (1) - -Moose does not attempt to encode a class's is-a relationships -within the type constraint hierarchy. Instead, Moose just -considers the class type constraint to be a subtype of C, -and specializes the constraint check to allow for subclasses. This -means that an instance of B will pass a -C type constraint successfully. For more details, -please refer to the L documentation. - -=back - -=head1 SEE ALSO - -=over 4 - -=item Acknowledgment - -The BankAccount example in this recipe is directly taken from the -examples in this chapter of "Practical Common Lisp": - -L - -=back - -=head1 AUTHOR - -Stevan Little Estevan@iinteractive.comE - -=head1 COPYRIGHT AND LICENSE - -Copyright 2006-2008 by Infinity Interactive, Inc. - -L - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/lib/Moose/Cookbook/Recipe10.pod b/lib/Moose/Cookbook/Roles/Recipe1.pod similarity index 99% rename from lib/Moose/Cookbook/Recipe10.pod rename to lib/Moose/Cookbook/Roles/Recipe1.pod index ebdb439..eca8887 100644 --- a/lib/Moose/Cookbook/Recipe10.pod +++ b/lib/Moose/Cookbook/Roles/Recipe1.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe10 - The Moose::Role example +Moose::Cookbook::Roles::Recipe1 - The Moose::Role example =head1 SYNOPSIS diff --git a/lib/Moose/Cookbook/Recipe11.pod b/lib/Moose/Cookbook/Roles/Recipe2.pod similarity index 96% rename from lib/Moose/Cookbook/Recipe11.pod rename to lib/Moose/Cookbook/Roles/Recipe2.pod index 5f3734a..63836b1 100644 --- a/lib/Moose/Cookbook/Recipe11.pod +++ b/lib/Moose/Cookbook/Roles/Recipe2.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe11 - Advanced Role Composition - method exclusion and aliasing +Moose::Cookbook::Roles::Recipe2 - Advanced Role Composition - method exclusion and aliasing =head1 SYNOPSIS diff --git a/lib/Moose/Cookbook/Snack/ArrayRef.pod b/lib/Moose/Cookbook/Snack/ArrayRef.pod deleted file mode 100644 index 6dc2bab..0000000 --- a/lib/Moose/Cookbook/Snack/ArrayRef.pod +++ /dev/null @@ -1,162 +0,0 @@ - -=pod - -=head1 NAME - -Moose::Cookbook::Snack::ArrayRef - Using the ArrayRef type constraint - -=head1 SYNOPSIS - - package Fruit; - use Moose; - - has 'name' => (is => 'rw', required => 1); - has 'species' => (is => 'rw', required => 1); - - package ProduceStore; - use Moose; - use Moose::Util::TypeConstraints; - - has 'fruit_aisle' => (isa => 'ArrayRef[Fruit]', is => 'rw'); - - package main; - - # we need something to put in the fruit aisle - my $orange = Fruit->new(name => 'orange', species => 'C. sinensis'); - my $apple = Fruit->new(name => 'apple', species => 'M. domestica'); - my @fruit = ($apple, $orange); - my $store = ProduceStore->new(fruit_aisle => \@fruit); - -=head1 DESCRIPTION - -The ArrayRef type constraint is used to store a reference to a Perl list or -array variable as an attribute of a Moose object. - -=head2 Disclaimer - -The code in this document will work on Moose as advertised, but the developers -strongly recommend using something like L or -L when working with array references in order to -help keep your Moose objects nice and encapsulated. - -=head2 Assigning arrays to an ArrayRef attribute - -Once a Moose-based object with an C attribute has been created, you -can pass an array (by reference) to that object attribute using that -attribute's accessor. This is how we assign the apple and orange to the -store's C C attribute, we pass an array containing both -objects by reference to the C attribute: - - my @fruit = ($apple, $orange); - my $store = ProduceStore->new(fruit_aisle => \@fruit); - -Or you can pass an anonymous array to the C attribute as well. If -you created two new objects, C<$grape> and C<$tomato>, and assigned them to -the C, they would replace the apple and the orange in the store's -fruit aisle: - - $store->fruit_aisle( [$grape, $tomato] ); - -Our C C is parameterized, meaning, that the -C C can contain nothing but C objects as array -values. If you try to pass in a reference to a array using C objects as -array values for example, Moose will complain: - - Attribute (fruit_aisle) does not pass the type constraint (ArrayRef[Str]) - -=head2 Dumping the contents of an ArrayRef - -In order to dump the contents of a C object attribute, you must first -de-reference the C, and then enumerate over it's keys. You can add -this method for showing the store's inventory to the C -object shown in the SYNOPSIS: - - sub show_inventory { - my $self = shift; - foreach my $item ( @{$self->fruit_aisle} ) { - # ... access each Fruit object - } - } - -=head2 Assigning arrays to an ArrayRef will overwrite existing arrays - -Once you create an object containing a C attribute, if you assign a -new array reference to that attribute, it will replace any existing array -reference: - - # replace existing inventory - my $grape = Fruit->new(name => 'grape', species => 'V. vinifera'); - my $tomato = Fruit->new(name => 'tomato', species => 'S. lycopersicum'); - $store->fruit_aisle( [$grape, $tomato] ); - -=head2 Appending/Deleting values to/from an ArrayRef - -In order to append new elements to an array referred to by the C -attribute, you will need to make a copy of the array first, add your new array -elements, then assign your modified copy back to the C attribute: - - my @fruit_aisle_copy = @{$store->fruit_aisle}; - my $avocado = Fruit->new(name => 'avocado', species => 'P. americana'); - push(@fruit_aisle_copy, $avocado); - $store->fruit_aisle( \@fruit_aisle_copy ); - -And here's an example of deleting an object stored in an ArrayRef: - - my @fruit_aisle_copy = @{$store->fruit_aisle}; - # new array to hold the fruit objects that won't be deleted - my @reworked_fruit_aisle; - for my $fruit_obj ( @fruit_aisle_copy ) { - if ( $fruit_obj->name ne 'tomato' ) { - push(@reworked_fruit_aisle, $fruit_obj); - } - } - $store->fruit_aisle( \@reworked_fruit_aisle ); - -Putting the above code into their own object methods would make appending to or deleting from an C a trivial operation. - -=head2 Clearing an ArrayRef - -Assigning C to clear an C will not work because the attribute -was originally defined with a type constraint, meaning that attribute must have -0 or more of that type of value to be valid. C in Perl is not a value, -so it won't work for clearing the C. - -If you assign an empty anonymous hash to a C attribute, this will -clear out that attribute yet still satisfy the type constraint. - - # this clears the ArrayRef - $store->fruit_aisle( [ ] ); - -=head1 SEE ALSO - -=over 4 - -=item L - Subtypes, and modeling a simple Company -class hierarchy - -=item L - Snippets of code for using Types and -Type Constraints - -=item L - Type constraints that Moose can use -and the tools to extend them or create your own. - -=item L - Autoboxed wrappers for Native Perl datatypes - -=item L - Extends attribute interfaces - -=back - -=head1 AUTHOR - -Brian Manning - -=head1 COPYRIGHT AND LICENSE - -Copyright 2006-2008 by Infinity Interactive, Inc. - -L - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/lib/Moose/Cookbook/Snack/BUILD.pod b/lib/Moose/Cookbook/Snack/BUILD.pod deleted file mode 100644 index f16d075..0000000 --- a/lib/Moose/Cookbook/Snack/BUILD.pod +++ /dev/null @@ -1,134 +0,0 @@ - -=pod - -=head1 NAME - -Moose::Cookbook::Snack::BUILD - Custom initialization methods for Moose objects - -=head1 SYNOPSIS - - package Build::Demo; - use Moose; - - # once the object has been created, don't allow - # changes to 'example_file' - has 'example_file' => (is => 'ro', required => 1); - - sub BUILD { - my $self = shift; - # create the object only if the 'example_file' exists - if (-e $self->example_file) { - return $self; - } - else { - die('ERROR: file _' . $self->example_file . '_ does not exist'); - } - } - - package main; - use Moose; - - # the name of this script, which works - my $first_test = Build::Demo->new(example_file => $0); - # this should fail (unless there's a file named 'foo' - # in the current directory) - my $second_test = Build::Demo->new(example_file => 'foo'); - -=head1 DESCRIPTION - -The C method allows you to write your own initialization methods for -your Moose objects. - -=head2 Creating new objects in Perl and Moose - -By convention, most objects in Perl are created by calling a C method -inside that object: - - package My::Perl::Class; - - sub new { - # object blessing and initialization code goes here... - } - - package main; - my $object = My::Perl::Class->new(); - -Moose is no different in this respect. However, since Moose handles the -C method for you, how do you change the default behaivor of the -C method in Moose? This is what the C method was designed -for. - - package My::Moose::Class; - - sub BUILD { - # object initialization code goes here... - } - - package main; - my $object = My::Moose::Class->new(); - -=head2 Why would you want a custom constructor? - -If your object needs to verify some behaivor or internal state before it is -created, a good time to do that is when the object is being created. Why -waste resources (CPU, memory) on objects that won't work because of missing -resources? - -=head2 When would you use an Moose type constraint instead of a custom constructor? - -Using type constraints via L, you can verify -simple relationships when an object is created: - - package Triangle; - use Moose; - - has - -You would want to use the C method in order to verify more complex -relationships: - - package IsoscelesTriangle; - use Moose; - -=head2 BUILD method is run only if it is defined in the object - -If your object does not have a C method, then Moose will skip trying to -run it. - -=head2 What is 'BUILDALL'? - -(Taken from L) The C method will call every BUILD -method in the inheritance hierarchy, and pass it a hash-ref of the the -C<%params> passed to the C method. - -=head1 SEE ALSO - -=over 4 - -=item L - The base object for Moose (BUILDALL) - -=item L - Frequently asked questions about Moose -(How do I write custom constructors with Moose?) - -=item L - Subtypes, and modeling a simple -Company class heirarchy (Example usage of BUILD in action) - -=item L - For when things go wrong with Moose -('Roles' section describes BUILD/BUILDALL) - -=back - -=head1 AUTHOR - -Brian Manning - -=head1 COPYRIGHT AND LICENSE - -Copyright 2006-2008 by Infinity Interactive, Inc. - -L - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/lib/Moose/Cookbook/Snack/HashRef.pod b/lib/Moose/Cookbook/Snack/HashRef.pod deleted file mode 100644 index a6b28fa..0000000 --- a/lib/Moose/Cookbook/Snack/HashRef.pod +++ /dev/null @@ -1,167 +0,0 @@ - -=pod - -=head1 NAME - -Moose::Cookbook::Snack::HashRef - Using the HashRef type constraint - -=head1 SYNOPSIS - - package Fruit; - use Moose; - - has 'species' => ( is => 'rw', required => 1 ); - - package ProduceStore; - use Moose; - use Moose::Util::TypeConstraints; - - has 'fruit_aisle' => ( is => 'rw', isa => 'HashRef[Fruit]' ); - - package main; - use Moose; - - # we need something to put in the fruit aisle - my $orange = Fruit->new( species => 'C. sinensis' ); - my $apple = Fruit->new( species => 'M. domestica' ); - my %fruit = ( orange => $orange, apple => $apple ); - my $store = ProduceStore->new( fruit_aisle => \%fruit ); - -=head1 DESCRIPTION - -The HashRef type constraint is used to store a reference to a Perl hash -variable as an attribute of a Moose object. - -=head2 Disclaimer - -The code in this document will work on Moose as advertised, but the developers -strongly recommend using something like L or -L when working with hash references in order to -help keep your Moose objects nice and encapsulated. The reason why this POD -exists is to show potential users of L that Moose objects are just like -Plain Ol' Perl Objects (POPO), albeit with some extra metadata syntatic sugar. - -=head2 Assigning hashes to a HashRef attribute - -Once a Moose-based object with a C attribute has been created, you -can pass a hash (by reference) to that attribute using that attribute's -accessor. This is how we assign the apple and orange to the store's -C C attribute, we pass a hash containing both objects by -reference to the C attribute: - - my %fruit = ( orange => $orange, apple => $apple ); - my $store = ProduceStore->new( fruit_aisle => \%fruit ); - -Or you can pass an anonymous hash to the C attribute as well. If you -created two new objects, C<$grape> and C<$tomato>, and assigned them to the -C, they would replace the apple and the orange in the store's fruit -aisle: - - $store->fruit_aisle( { grape => $grape, tomato => $tomato } ); - -Our C C example is parameterized, meaning, that the -C C can contain nothing but C objects as hash -values. If you try to pass in a reference to a hash using C objects as -hash values for example, Moose will complain: - - Attribute (fruit_aisle) does not pass the type constraint (HashRef[Int]) - -=head2 Dumping the contents of the HashRef - -In order to dump the contents of a C object attribute, you must first -de-reference the C, and then enumerate over it's keys. - - foreach my $item ( keys(%{$self->fruit_aisle}) ) { - my $fruit = $self->{fruit_aisle}{$item}; - print "Item: $item, type: " . $fruit->meta->name - . " species: " . $fruit->species . "\n"; - } - -If the above de-referencing of the C C is a little too -noisy, you could create a copy of it, and then enumerate over that copy: - - my %fruit_aisle_copy = %{$self->fruit_aisle}; - foreach my $item ( keys(%fruit_aisle_copy) ) { - my $fruit = $fruit_aisle_copy{$item}; - print "Item: $item, type: " . $fruit->meta->name - . " species: " . $fruit->species . "\n"; - } - -=head2 Assigning to a HashRef attribute will overwrite - -Once you create an object containing a C attribute, if you assign a -new hash reference to that attribute, it will replace any existing hash -reference: - - # this replaces the existing HashRef contents - my $grape = Fruit->new( species => 'V. vinifera' ); - my $tomato = Fruit->new( species => 'S. lycopersicum'); - $store->fruit_aisle( { grape => $grape, tomato => $tomato } ); - -=head2 Appending/Deleting key/value pairs to a HashRef - -In order to append or delete key/value pairs to the hash referred to by the -C attribute, you will need to make a copy of the hash first, add or -delete the desired key/value pairs, then assign your modified copy back to the -C attribute. Here's an example of appending new key/value pars: - - my %fruit_aisle_copy = %{$store->fruit_aisle}; - my $avocado = Fruit->new( species => 'P. americana' ); - $fruit_aisle_copy{avocado} = $avocado; - $store->fruit_aisle( \%fruit_aisle_copy ); - $store->fruit_aisle->{avocado}; - -And here's an example of deleting existing key/value pairs: - - # delete an attribute from the HashRef - %fruit_aisle_copy = %{$store->fruit_aisle}; - delete($fruit_aisle_copy{tomato}); - $store->fruit_aisle( \%fruit_aisle_copy ); - delete $mooseObj->hashref->{foo}; - -Putting the above code into their own object methods would make appending to -and deleting from a C a trivial operation. - -=head2 Clearing the HashRef - -Assigning C to clear a C will not work because the attribute -was originally defined with a type constraint, meaning that attribute must have -0 or more of that type of value to be valid. B in Perl is not a value, -so it won't work for clearing the C. - -If you assign an empty anonymous hash to a C attribute, this will -clear out that attribute yet still satisfy the type constraint. - - # this clears the HashRef - $store->fruit_aisle( { } ); - -=head1 SEE ALSO - -=over 4 - -=item L - Snippets of code for using Types and -Type Constraints - -=item L - Type constraints that Moose can use -and the tools to extend them or create your own. - -=item L - Autoboxed wrappers for Native Perl datatypes - -=item L - Extends attribute interfaces - -=back - -=head1 AUTHOR - -Brian Manning - -=head1 COPYRIGHT AND LICENSE - -Copyright 2006-2008 by Infinity Interactive, Inc. - -L - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/lib/Moose/Cookbook/Snack/Keywords.pod b/lib/Moose/Cookbook/Snack/Keywords.pod index 11178a3..98af71c 100644 --- a/lib/Moose/Cookbook/Snack/Keywords.pod +++ b/lib/Moose/Cookbook/Snack/Keywords.pod @@ -4,23 +4,22 @@ Moose::Cookbook::Snack::Keywords - Restricted keywords in Moose -=cut - =head1 DESCRIPTION -There are several keywords exported in L that cause clashes against -any barewords such as attribute names, sub names, and globs. - +There are several keywords exported by L that can cause clashes +against other user-defined barewords. The following document provides +a list of those keywords in a single place for easy reference. =head2 The 'meta' keyword While most of the reserved keywords collisions can be avoided, however -I is the only one you B override. Do not attempt to override -I. +I is the only one you B override. Do not attempt to override +I, it will break the Moose internals. =head2 Moose Keywords -If you are using Moose its best to avoid these keywords +If you are using L or L its best to avoid these +keywords: =over 4 @@ -44,8 +43,6 @@ If you are using Moose its best to avoid these keywords =item augment -=item make_immutable - =item confess =item blessed @@ -54,7 +51,7 @@ If you are using Moose its best to avoid these keywords =head2 Moose::Util::TypeConstraints Keywords -If you are using Moose::Util::TypeConstraints its best to avoid +If you are using L its best to avoid these keywords =over 4 @@ -90,43 +87,82 @@ these keywords =back =head2 Avoiding collisions - + =head3 Turning off Moose -To remove the keywords Moose exports using no Moose at the bottom of your code +To remove the keywords L exports just add C at the bottom of +your code, like so: - package Thing; - use Moose; + package Thing; + use Moose; - # code here + # code here - no Moose; + no Moose; + +This will un-export the keywords that L originally exported. The same +will also work for L and L. It is +general L policy that this feature is used. =head3 Sub::Exporter -The L module can rename keywords +L, L and L all use +L to handle all their exporting needs. This means that all the +features that L provides are also available to them. - package LOL::Cat; - use Moose 'has' => { -as => 'i_can_haz' }; +For instance, with L you can rename keywords, like so: - i_can_haz 'cheeseburger' => ( - is => 'rw', - trigger => sub { print "NOM NOM" } - ); + package LOL::Cat; + use Moose 'has' => { -as => 'i_can_haz' }; + + i_can_haz 'cheeseburger' => ( + is => 'rw', + trigger => sub { print "NOM NOM" } + ); + + LOL::Cat->new->cheeseburger('KTHNXBYE'); - LOL::Cat->new->cheeseburger('KTHNXBYE');; +See the L docs for more information. =head3 namespace::clean -You can use L to clean up the namespace +You can also use L to clean up your namespace, but you must +be careful not to remove C with this. Here is an example of that usage: + + package Foo; + use Moose; + use namespace::clean -except => 'meta'; + # ... + +=head1 SEE ALSO + +=over 4 + +=item L + +=item L + +=item L + +=item L + +=item L -=head1 AUTHOR AND COPYRIGHT +=back + +=head1 AUTHOR John Goulah Cjgoulah@cpan.org> -=head1 LICENSE +Stevan Little Estevan@iinteractive.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2008 by Infinity Interactive, Inc. + +L -This program is free software; you can redistribute it and/or modify -it under the same terms as perl itself. +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. =cut diff --git a/lib/Moose/Cookbook/Snack/Perl5ObjsVsMooseObjs.pod b/lib/Moose/Cookbook/Snack/Perl5ObjsVsMooseObjs.pod deleted file mode 100644 index 2f75e4d..0000000 --- a/lib/Moose/Cookbook/Snack/Perl5ObjsVsMooseObjs.pod +++ /dev/null @@ -1,185 +0,0 @@ - -=pod - -=head1 NAME - -Moose::Cookbook::Snack::Perl5ObjsVsMooseObjs - Short comparison between Perl 5 -objects and Moose objects - -=head1 SYNOPSIS - - package Moose::Demo; - use Moose; # automagically sets 'strict' and 'warnings' - - has 'script_name' => ( is => 'rw', required => 1); - - package main; - - # '$0' is the name of this script, set automatically by Perl - my $demo = Moose::Demo->new( script_name => $0 ); - - print "My name is " . $demo->script_name . "\n"; - print "I am a " . $demo->meta->name . " type of object\n"; - -=head1 DESCRIPTION - -So what's the big stink about Moose? Perl 5 comes with objects and object -oriented programming already. Given the above Moose code, what would similar -code look like in the existing Perl 5 object-oriented style of programming? -Let's take a look and find out... - -=head2 Perl 5 OO Example - - # Perl 5 Object, as taught by the 'perltoot' POD page - package Perl5::Demo; - use strict; - use warnings; - - - sub new { - my $class = shift; - # assign the rest of the method arguments to a temp hash - my %args = @_; - - # create the object out of a blessed hash reference - my $self = bless ( {}, ref($class) || $class ); - # create the script_name attribute - $self->{script_name} = undef; - - # verify that the user passed in the 'script_name' attribute - if ( exists $args{script_name} ) { - $self->script_name($args{script_name}); - } - else { - die "ERROR: can't create object without 'script_name' "; - } - - # return the object reference back to the caller - return $self; - } - - sub script_name { - my $self = shift; - # check for arguments; use the argument - # if passed in, otherwise return the - # existing value (if any) - if (@_) { - $self->{script_name} = shift; - } - return $self->{script_name}; - } - - package main; - use strict; - use warnings; - - my $demo = Perl5::Demo->new( script_name => $0 ); - - print "My name is " . $demo->script_name . "\n"; - print "I am a " . ref($demo) . " type of object\n"; - -Looks more complex, right? Moose does a lot of the labor when working with -Perl objects, so that you don't have to. What are some of the specific -differences between Moose and Perl 5 Objects? - -=head3 Difference #1 - declaration of object attributes - -Both the Moose and Perl 5 objects have one attribute, C. It's a -good programming practice to always validate user input, so we have the Perl 5 -object check to make sure that the user passes in the C attribute -to it when the object is created. The Moose object automatically checks this -for us when we set C 1> in the C function for the Moose -object. - -In more advanced Moose usage, you can use something called 'type constraints' -when creating your Moose objects. Type constraints are used to validate what -the user passes in when setting Moose object attributes. If the user passes -in a type of data that Moose is not expecting, then the type constraints in -Moose (specifically, the L module) will let the -user know this in no uncertain terms. Type constraints in Moose can be as -simple as strings or numbers, or as complex as other Moose objects. - -=head3 Difference #2 - strict and warning pragmas - -Moose sets the 'strict' and 'warnings' pragmas for you automatically. We have -to do this for ourselves in the Perl 5 example. - -=head3 Difference #3 - Determining an object's class name - -The C function in Perl 5 is how you determine an object's class name. -The proper way to do this with Moose is C<$object-Emeta-Ename>; - - # an object's class name in Perl 5 OO - print "I am a " . ref($demo) . " type of object\n"; - - # an object's class name in Moose - print "I am a " . $demo->meta->name . " type of object\n"; - -Moose builds on C to provide a rich introspection API that -goes way beyond just getting the class name. Check out the -C documentation for more details. - -=head3 Difference #4 - Assigning values to Moose object attributes - -When you wish to assign a value directly to an object attribute for a Perl 5 -object, you can either create an object method that handles the value for you; - - package Perl5Object; - sub set_x { # some code here that sets 'x' } - package main; - # later on... - $self->set_x(0); - -or you can assign the value directly to the Perl 5 object attribute like this: - - $self->{x} = 0; - -Moose creates object methods for handling attributes for you, as long as you -specified C rw> for each C statement inside the object -declaration. This is mentioned in L, in the section -labeld B, but briefly: - - package MooseObject; - has 'x' => (is => 'rw'); - package main; - # later on... - $self->x(0); - -The syntax shown for the Perl 5 object (C<$self-E{x} = 0>) will -also work on the Moose object, as Moose objects are, by default, -blessed hashes just like the average Perl object is. However, if you -access the object's hash reference directly via the latter syntax you -will have several problems. - -First, Moose will no longer be able to enforce attribute constraints, -such as read-only or type constraints. Second, you've broken that -object's encapsulation, and encapsulation is one of the reasons you -want to use objects in the first place, right? - -=head1 SEE ALSO - -=over 4 - -=item L - The 'Point' object example - -=item L - Type constraints that Moose can use -and the tools to extend them or create your own. - -=item L - For when things go wrong with Moose - -=back - -=head1 AUTHOR - -Brian Manning - -=head1 COPYRIGHT AND LICENSE - -Copyright 2006-2008 by Infinity Interactive, Inc. - -L - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=cut diff --git a/lib/Moose/Cookbook/Snack/Types.pod b/lib/Moose/Cookbook/Snack/Types.pod index 172c972..c971155 100644 --- a/lib/Moose/Cookbook/Snack/Types.pod +++ b/lib/Moose/Cookbook/Snack/Types.pod @@ -36,7 +36,7 @@ Moose::Cookbook::Snack::Types - Snippets of code for using Types and Type Constr =head1 DESCRIPTION -This is the Point example from (L) with added +This is the Point example from (L) with added type checking. If we try to assign a string value to an attribute that is defined as @@ -51,7 +51,7 @@ check the value we are about to set, before we try and set it. =over 4 -=item L +=item L =item L @@ -72,4 +72,4 @@ L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. -=cut \ No newline at end of file +=cut diff --git a/lib/Moose/Cookbook/Style.pod b/lib/Moose/Cookbook/Style.pod new file mode 100644 index 0000000..67da29d --- /dev/null +++ b/lib/Moose/Cookbook/Style.pod @@ -0,0 +1,204 @@ +=pod + +=head1 NAME + +Moose::Cookbook::Style - The latest in trendy Moose cuisine + +=for authors + +Please annotate all bad examples with comments so that they won't be copied by +accident + +=cut + +=head1 Benefits of Good Style + +Good Moose style, as defined by this document, helps ensure your code has the +following desirable properties: + +=over 4 + +=item Play well with others + +Your code will be more reusable and easier to extend. + +=item Ease maintenance + +The code will be easier to understand because it follows an accepted set of +conventions and idioms. + +This will help others maintaining your code, and also help you to get support +on IRC for instance. + +=item Help Moose generate better code + +By using the most appropriate features, the generated code will be safer and +more efficient. + +=item Benefit from meta programming + +Code that operates on the metaclass will benefit from clean meta definitions. + +If you are manually converting argument types with C there is no +meta data explaining your intention. If on the other hand you use coercions, +there is introspectable meta data that makes this clear. + +This means that e.g. MooseX extensions that work by introspecting your class +will be able to do the right thing more often, because they don't need to +guess. + +=back + +=head1 Don't change C + +It is generally considered bad style to override L for a +number of reasons. + +The first reason is consistency. Subclasses of your class and code +instantiating your class would be simpler if your constructor works closer to +the default. + +The second reason is performance. By calling C on your metaclass: + + __PACKAGE__->meta->make_immutable; + +And opting out of any class definition changes from that point on, you allow +Moose to create more efficient versions of certain generic methods. Moose will +generate a tight, optimal C for you, based on the minimal set of features +you use. + +Moose provides many features that allow you to do common object construction +tasks at the right level of abstraction. + +When attributes have the ability to provide the necessary functionality, use +that. If that isn't sufficient, L has numerous features you can +use at construction time. + +=head2 Use C instead of custom initialization or overriding C + +Instead of changing C, do initialization in C. + +The construction parameters are passed in, so you don't need to replicate +C, and since C is called for each superclass that defines it, +you will never forget to invoke your initializers if you extend them. + +=head2 Use C, C or C + +To initialize attributes there is a plethora of methods preferable to assigning +the value at initialization time. + +If you want to translate parameter data, use coercions. + +If you want to ensure a parameter can't be overridden by the constructor, set +the C to C instead of overwriting it in C. + +=head2 Use C to alter C<@_> processing + +If you need to change the way C<@_> is processed, for example for +C<< Class->new( $single_param ) >>, use C instead of wrapping +C. This ensures the behavior is subclassible, it keeps this logic +independent of the other aspects of construction, and can be made efficient +using C. + +=head1 Don't pollute the global type registry + +=head2 Use fully qualified type names for your own data + +L provides a convenient method to do this. + +If you define + + # Bad style: + + subtype Person => ( + as 'Object', + where { $_->can("name") }, + ); + +Then the global name C is registered, and this could conflict with +other bad usage of the sort. + +Instead, prefix type name with your project namespace, or class name: + + subtype 'My::Foo::Person' => ( + as 'Object', + where { $_->can("name") }, + ); + +Or with L: + + use MooseX::Types::Moose qw(Object); + + use MooseX::Types ( + -declare => [qw(Person)], + ); + + subtype Person() => ( # note parenthesis, "Person" is a function, not a string + as Object, # MooseX::Types::Moose exported it + where { $_->can("name") }, + ); + +=head3 Coerce in a subtype + +Likewise use fully qualified subtypes of other types for defining coercions, so +that they won't affect unrelated code, causing action at a distance. + +This is important because the type registry is global, kind of like the symbol +table. + +This means that code like: + + # Bad style: + + coerce ArrayRef => ( + from Str => via { [ split /,/ ] }, + ); + +Will add a coercion to B attributes like: + + has foo => ( + isa => "ArrayRef", + coerce => 1, + ); + +when the actual coercion applies only to your specific cases. + +=head1 Clean up your package + +Use C or C to remove the sugar exports. + +This will make sure the sugar isn't accidentally called as methods on your objects. + +For instance: + + $obj->can("has"); + +will return true, even though C is not a method. + +=head1 Accept no substitutes + +By substitutes I mean hacks instead of "proper" solutions. + +When you have a tricky requirement, refrain from abusing Moose or MooseX:: or +whatever it is you are using. + +Instead, drop by IRC and discuss it. Most of the time a crazy idea can either +be simplified, or it will spawn a clean, reliable feature to whatever package +you are using. + +This will improve your code and also share the benefit with others. + +=head1 AUTHOR + +Yuval (nothingmuch) Kogman + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify it under +the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Exporter.pm b/lib/Moose/Exporter.pm new file mode 100644 index 0000000..b09de9e --- /dev/null +++ b/lib/Moose/Exporter.pm @@ -0,0 +1,471 @@ +package Moose::Exporter; + +use strict; +use warnings; + +use Carp qw( confess ); +use Class::MOP; +use List::MoreUtils qw( first_index uniq ); +use Moose::Util::MetaRole; +use Sub::Exporter; + + +my %EXPORT_SPEC; + +sub setup_import_methods { + my ( $class, %args ) = @_; + + my $exporting_package = $args{exporting_package} ||= caller(); + + my ( $import, $unimport ) = $class->build_import_methods(%args); + + no strict 'refs'; + *{ $exporting_package . '::import' } = $import; + *{ $exporting_package . '::unimport' } = $unimport; +} + +sub build_import_methods { + my ( $class, %args ) = @_; + + my $exporting_package = $args{exporting_package} ||= caller(); + + $EXPORT_SPEC{$exporting_package} = \%args; + + my @exports_from = $class->_follow_also( $exporting_package ); + + my $export_recorder = {}; + + my $exports = $class->_make_sub_exporter_params( + [ $exporting_package, @exports_from ], $export_recorder ); + + my $exporter = Sub::Exporter::build_exporter( + { + exports => $exports, + groups => { default => [':all'] } + } + ); + + # $args{_export_to_main} exists for backwards compat, because + # Moose::Util::TypeConstraints did export to main (unlike Moose & + # Moose::Role). + my $import = $class->_make_import_sub( $exporting_package, $exporter, + \@exports_from, $args{_export_to_main} ); + + my $unimport = $class->_make_unimport_sub( $exporting_package, $exports, + $export_recorder ); + + return ( $import, $unimport ) +} + +{ + my $seen = {}; + + sub _follow_also { + my $class = shift; + my $exporting_package = shift; + + local %$seen = ( $exporting_package => 1 ); + + return uniq( _follow_also_real($exporting_package) ); + } + + sub _follow_also_real { + my $exporting_package = shift; + + die "Package in also ($exporting_package) does not seem to use MooseX::Exporter" + unless exists $EXPORT_SPEC{$exporting_package}; + + my $also = $EXPORT_SPEC{$exporting_package}{also}; + + return unless defined $also; + + my @also = ref $also ? @{$also} : $also; + + for my $package (@also) + { + die "Circular reference in also parameter to MooseX::Exporter between $exporting_package and $package" + if $seen->{$package}; + + $seen->{$package} = 1; + } + + return @also, map { _follow_also_real($_) } @also; + } +} + +sub _make_sub_exporter_params { + my $class = shift; + my $packages = shift; + my $export_recorder = shift; + + my %exports; + + for my $package ( @{$packages} ) { + my $args = $EXPORT_SPEC{$package} + or die "The $package package does not use Moose::Exporter\n"; + + for my $name ( @{ $args->{with_caller} } ) { + my $sub = do { + no strict 'refs'; + \&{ $package . '::' . $name }; + }; + + my $fq_name = $package . '::' . $name; + + $exports{$name} = $class->_make_wrapped_sub( + $fq_name, + $sub, + $export_recorder, + ); + } + + for my $name ( @{ $args->{as_is} } ) { + my $sub; + + if ( ref $name ) { + $sub = $name; + $name = ( Class::MOP::get_code_info($name) )[1]; + } + else { + $sub = do { + no strict 'refs'; + \&{ $package . '::' . $name }; + }; + } + + $export_recorder->{$sub} = 1; + + $exports{$name} = sub {$sub}; + } + } + + return \%exports; +} + +{ + # This variable gets closed over in each export _generator_. Then + # in the generator we grab the value and close over it _again_ in + # the real export, so it gets captured each time the generator + # runs. + # + # In the meantime, we arrange for the import method we generate to + # set this variable to the caller each time it is called. + # + # This is all a bit confusing, but it works. + my $CALLER; + + sub _make_wrapped_sub { + shift; + my $fq_name = shift; + my $sub = shift; + my $export_recorder = shift; + + + # We need to set the package at import time, so that when + # package Foo imports has(), we capture "Foo" as the + # package. This lets other packages call Foo::has() and get + # the right package. This is done for backwards compatibility + # with existing production code, not because this is a good + # idea ;) + return sub { + my $caller = $CALLER; + + my $sub = Class::MOP::subname( $fq_name => sub { $sub->( $caller, @_ ) } ); + + $export_recorder->{$sub} = 1; + + return $sub; + }; + } + + sub _make_import_sub { + shift; + my $exporting_package = shift; + my $exporter = shift; + my $exports_from = shift; + my $export_to_main = shift; + + return sub { + # I think we could use Sub::Exporter's collector feature + # to do this, but that would be rather gross, since that + # feature isn't really designed to return a value to the + # caller of the exporter sub. + # + # Also, this makes sure we preserve backwards compat for + # _get_caller, so it always sees the arguments in the + # expected order. + my $traits; + ($traits, @_) = Moose::Exporter::_strip_traits(@_); + + # Normally we could look at $_[0], but in some weird cases + # (involving goto &Moose::import), $_[0] ends as something + # else (like Squirrel). + my $class = $exporting_package; + + $CALLER = Moose::Exporter::_get_caller(@_); + + # this works because both pragmas set $^H (see perldoc + # perlvar) which affects the current compilation - + # i.e. the file who use'd us - which is why we don't need + # to do anything special to make it affect that file + # rather than this one (which is already compiled) + + strict->import; + warnings->import; + + # we should never export to main + if ( $CALLER eq 'main' && ! $export_to_main ) { + warn + qq{$class does not export its sugar to the 'main' package.\n}; + return; + } + + my $did_init_meta; + for my $c ( grep { $_->can('init_meta') } $class, @{$exports_from} ) { + + $c->init_meta( for_class => $CALLER ); + $did_init_meta = 1; + } + + if ( $did_init_meta && @{$traits} ) { + _apply_meta_traits( $CALLER, $traits ); + } + elsif ( @{$traits} ) { + confess + "Cannot provide traits when $class does not have an init_meta() method"; + } + + goto $exporter; + }; + } +} + +sub _strip_traits { + my $idx = first_index { $_ eq '-traits' } @_; + + return ( [], @_ ) unless $idx >= 0 && $#_ >= $idx + 1; + + my $traits = $_[ $idx + 1 ]; + + splice @_, $idx, 2; + + $traits = [ $traits ] unless ref $traits; + + return ( $traits, @_ ); +} + +sub _apply_meta_traits { + my ( $class, $traits ) = @_; + + return unless @{$traits}; + + my $meta = $class->meta(); + + my $type = ( split /::/, ref $meta )[-1] + or confess + 'Cannot determine metaclass type for trait application . Meta isa ' + . ref $meta; + + my @resolved_traits + = map { Moose::Util::resolve_metatrait_alias( $type => $_ ) } + @$traits; + + return unless @resolved_traits; + + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => $class, + metaclass_roles => \@resolved_traits, + ); +} + +sub _get_caller { + # 1 extra level because it's called by import so there's a layer + # of indirection + my $offset = 1; + + return + ( ref $_[1] && defined $_[1]->{into} ) ? $_[1]->{into} + : ( ref $_[1] && defined $_[1]->{into_level} ) + ? caller( $offset + $_[1]->{into_level} ) + : caller($offset); +} + +sub _make_unimport_sub { + shift; + my $exporting_package = shift; + my $exports = shift; + my $export_recorder = shift; + + return sub { + my $caller = scalar caller(); + Moose::Exporter->_remove_keywords( + $caller, + [ keys %{$exports} ], + $export_recorder, + ); + }; +} + +sub _remove_keywords { + shift; + my $package = shift; + my $keywords = shift; + my $recorded_exports = shift; + + no strict 'refs'; + + foreach my $name ( @{ $keywords } ) { + + if ( defined &{ $package . '::' . $name } ) { + my $sub = \&{ $package . '::' . $name }; + + # make sure it is from us + next unless $recorded_exports->{$sub}; + + # and if it is from us, then undef the slot + delete ${ $package . '::' }{$name}; + } + } +} + +1; + +__END__ + +=head1 NAME + +Moose::Exporter - make an import() and unimport() just like Moose.pm + +=head1 SYNOPSIS + + package MyApp::Moose; + + use strict; + use warnings; + + use Moose (); + use Moose::Exporter; + + Moose::Exporter->setup_import_methods( + with_caller => [ 'sugar1', 'sugar2' ], + as_is => [ 'sugar3', \&Some::Random::thing ], + also => 'Moose', + ); + + # then later ... + package MyApp::User; + + use MyApp::Moose; + + has 'name'; + sugar1 'do your thing'; + thing; + + no MyApp::Moose; + +=head1 DESCRIPTION + +This module encapsulates the logic to export sugar functions like +C. It does this by building custom C and C +methods for your module, based on a spec your provide. + +It also lets your "stack" Moose-alike modules so you can export +Moose's sugar as well as your own, along with sugar from any random +C module, as long as they all use C. + +=head1 METHODS + +This module provides two public methods: + +=head2 Moose::Exporter->setup_import_methods(...) + +When you call this method, C build custom C +and C methods for your module. The import method will export +the functions you specify, and you can also tell it to export +functions exported by some other module (like C). + +The C method cleans the callers namespace of all the +exported functions. + +This method accepts the following parameters: + +=over 4 + +=item * with_caller => [ ... ] + +This a list of function I to be exported wrapped and then +exported. The wrapper will pass the name of the calling package as the +first argument to the function. Many sugar functions need to know +their caller so they can get the calling package's metaclass object. + +=item * as_is => [ ... ] + +This a list of function names or sub references to be exported +as-is. You can identify a subroutine by reference, which is handy to +re-export some other module's functions directly by reference +(C<\&Some::Package::function>). + +=item * also => $name or \@names + +This is a list of modules which contain functions that the caller +wants to export. These modules must also use C. The +most common use case will be to export the functions from C. + +C also makes sure all these functions get removed +when C is called. + +=back + +=head2 Moose::Exporter->build_import_methods(...) + +Returns two code refs, one for import and one for unimport. + +Used by C. + +=head1 IMPORTING AND init_meta + +If you want to set an alternative base object class or metaclass +class, simply define an C method in your class. The +C method that C generates for you will call +this method (if it exists). It will always pass the caller to this +method via the C parameter. + +Most of the time, your C method will probably just call C<< +Moose->init_meta >> to do the real work: + + sub init_meta { + shift; # our class name + return Moose->init_meta( @_, metaclass => 'My::Metaclass' ); + } + +=head1 METACLASS TRAITS + +The C method generated by C will allow the +user of your module to specify metaclass traits in a C<-traits> +parameter passed as part of the import: + + use Moose -traits => 'My::Meta::Trait'; + + use Moose -traits => [ 'My::Meta::Trait', 'My::Other::Trait' ]; + +These traits will be applied to the caller's metaclass +instance. Providing traits for an exporting class that does not create +a metaclass for the caller is an error. + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +This is largely a reworking of code in Moose.pm originally written by +Stevan Little and others. + +=head1 COPYRIGHT AND LICENSE + +Copyright 2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Intro.pod b/lib/Moose/Intro.pod new file mode 100644 index 0000000..df465de --- /dev/null +++ b/lib/Moose/Intro.pod @@ -0,0 +1,549 @@ +=pod + +=head1 NAME + +Moose::Intro - What is Moose, and how do I use it? + +=head1 WHAT IS MOOSE? + +Moose is a I object system for Perl 5. If you've used a +modern object-oriented language (which Perl 5 definitely isn't), you +know they provide keywords for attribute declaration, object +construction, and inheritance. These keywords are part of the +language, and you don't care how they are implemented. + +Moose aims to do the same thing for Perl 5 OO. We can't actually +create new keywords, but we do offer "sugar" that looks a lot like +them. More importantly, with Moose, you I your +class, without needing to know about blessed hashrefs, accessor +methods, and so on. + +Moose lets you focus on the I structure of your classes, so +you can focus on "what" rather than "how". With Moose, a class +definition should read like a list of very concise English sentences. + +Moose is built in top of C, a meta-object protocol (aka +MOP). Using the MOP, Moose provides complete introspection for all +Moose-using classes. This means you can ask classes about their +attributes, parents, children, methods, etc., all using a well-defined +API. The MOP abstracts away tedious digging about in the Perl symbol +table, looking at C<@ISA> vars, and all the other crufty Perl tricks +we know and love (?). + +Moose is based in large part on the Perl 6 object system, as well as +drawing on the best ideas from CLOS, Smalltalk, and many other +languages. + +=head1 WHY MOOSE? + +Moose makes Perl 5 OO both simpler and more powerful. It encapsulates +all the tricks of Perl 5 power users in high-level declarative APIs +which are easy to use, and don't require any special knowledge of how +Perl works under the hood. + +Moose makes Perl 5 OO fun, accessible, and powerful. And if you want +to dig about in the guts, Moose lets you do that too, by using and +extending its powerful introspection API. + +=head1 AN EXAMPLE + + package Person; + + use Moose; + + has 'first_name' => ( + is => 'rw', + isa => 'Str', + ); + + has 'last_name' => ( + is => 'rw', + isa => 'Str', + ); + +This is a I class definition! + + package User; + + use DateTime; + use Moose; + + extends 'Person'; + + has 'password' => ( + is => 'rw', + isa => 'Str', + ); + + has 'last_login' => ( + is => 'rw', + isa => 'DateTime', + handles => { 'date_of_last_login' => 'date' }, + ); + + sub login { + my $self = shift; + my $pw = shift; + + return 0 if $pw ne $self->password; + + $self->last_login( DateTime->now() ); + + return 1; + } + +We'll leave the line-by-line explanation of this code to other +documentation, but you can see how Moose reduces common OO idioms to +simple declarative constructs. + +=head2 Where's the Constructor? + +One point of confusion that might come up with Moose is how it handles +object construction. B method for +your classes!> + +Moose will provide one for you. It will accept a hash or hash +reference of named parameters matching your attributes. This is just +another way in which Moose keeps your from worrying I classes are +implemented. Simply define a class and you're ready to start creating +objects! + +=head1 MOOSE CONCEPTS (VS "OLD SCHOOL" Perl) + +In the past, you may not have thought too much about the difference +between packages and classes, attributes and methods, constructors vs +methods, etc. Part of what the MOP provides is well-defined +introspection features for each of those things, and in turn Moose +provides I sugar for each of them. Moose also introduces +concepts that are uncommon (or entirely new) like roles, method +modifiers, and declarative delegation. + +Knowing what these concepts mean in Moose-speak, and how they used to +be done in old school Perl 5 OO is a good way to start learning to use +Moose. + +=head2 Class + +When you say "use Moose" in a package, you are defining your package +as a class. At its simplest, a class will consist simply of attributes +and/or methods. It can also include roles, method modifiers, and more. + +A class I zero or more B. + +A class I zero or more B. + +A class I one or more superclasses (aka parent classes). A +class inherits from its superclass(es). + +A class may I B. These modifiers can apply to +its own methods or methods that are inherited from its ancestors. + +A class may I one or more B. + +A class I a B and a B. These are +provided for you "for free" by Moose. + +The B accepts named parameters corresponding to the +class's attributes and uses them to initialize an B. + +A class I a B, which in turn has B, +B, and B. This metaclass I the +class. + +A class is usually analogous to a category of nouns, like "People" or +"Users". + + package Person; + + use Moose; + # now it's a Moose class! + +=head2 Attribute + +An attribute is a property of the class that defines it. It I +has a name, and it I a number of other defining +characteristics. + +These characteristics may include a read/write flag, a B, +accessor method names, B, a default value, and more. + +Attributes I methods, but defining them causes various +accessor methods to be created. At a minimum, a normal attribute will +always have a reader accessor method. Many attributes have things like +a writer method, clearer method, and predicate method ("has it been +set?"). + +An attribute may also define Bs, which will create +additional methods based on the delegation specification. + +By default, Moose stores attributes in the object instance, which is a +hashref, I! +It is best to think of Moose attributes as "properties" of the +I B. These properties are accessed through +well-defined accessor methods. + +An attribute is usually analagous to specific feature of something in +the class's category. For example, People have first and last +names. Users have passwords and last login datetimes. + + has 'first_name' => ( + is => 'rw', + isa => 'Str', + ); + +=head2 Method + +A method is very straightforward. Any subroutine you define in your +class is a method. + +Methods correspond to verbs, and are what your objects can do. For +example, a User can login. + + sub login { ... } + +=head2 Roles + +A role is something that a class I. For example, a Machine class +might do the Breakable role, and a so could a Bone class. A role is +used to define some concept that cuts across multiple unrelated +classes, like "breakability", or "has a color". + +A role I zero or more B. + +A role I zero or more B. + +A role I zero or more B. + +A role I zero or more B. + +A required method is not implemented by the role. Instead, a required +method says "to use this Role you must implement this method". + +Roles are I into classes (or other roles). When a role is +composed into a class, its attributes and methods are "flattened" into +the class. Roles I show up in the inheritance hierarchy. When +a role is composed, it's attributes and methods appear as if they were +defined I. + +Role are somewhat like mixins or interfaces in other OO languages. + + package Breakable; + + use Moose::Role; + + has is_broken => ( + is => 'rw', + isa => 'Bool', + ); + + requires 'break'; + + before 'break' => { + my $self = shift; + + $self->is_broken(1); + }; + +=head2 Method Modifiers + +A method modifier is a way of defining an action to be taken when a +named method is called. Think of it as a hook on the named method. For +example, you could say "before calling C, call this modifier +first". Modifiers come in different flavors like "before", "after", +"around", and "augment", and you can apply more than one modifier to +a single method. + +Method modifiers are often used as an alternative to overriding a +method in a parent class. They are also used in roles as a way of +modifying methods in the consuming class. + +Under the hood, a method modifier is just a plain old Perl subroutine +that gets called before or after (or around, etc.) some named method. + + before 'login' => sub { + my $self = shift; + my $pw = shift; + + warn "Called login() with $pw\n"; + }; + +=head2 Type + +Moose also comes with a (miniature) type system. This allows you to +define types for attributes. Moose has a set of built-in types based +on what Perl provides, such as "Str", "Num", "Bool", "HashRef", etc. + +In addition, every class name in your application can also be used as +a type name. We saw an example using "DateTime" earlier. + +Finally, you can define your own types, either as subtypes or entirely +new types, with their own constraints. For example, you could define a +type "PosInt", a subtype of "Int" which only allows positive numbers. + +=head2 Delegation + +Moose attributes provide declarative syntax for defining +delegations. A delegation is a method which delegates the real work to +some attribute of the class. + +You saw this in the User example, where we defined a delegation for +the C method. Under the hood, this simple calls +C on the User object's C attribute. + +=head2 Constructor + +A constructor creates an B for the class. In old +school Perl, this was usually done by defining a method called +C which in turn called C on a reference. + +With Moose, this C method is created for you, and it simply +does the right thing. You should never need to define your own +constructor! + +Sometimes you want to do something whenever an object is created. In +those cases, you can provide a C method in your class. Moose +will call this for you after creating a new object. + +=head2 Destructor + +This is a special method called when an object instance goes out of +scope. You can specialize what your class does in this method if you +need to, but you usually don't. + +With old school Perl 5, this is the C method, but with +Moose it is the C method. + +=head2 Object Instance + +An object instance is a specific noun in the class's "category". For +example, one specific Person or User. An instance is created by the +class's B. + +An instance has values for its attributes. For example, a specific +person has a first and last name, + +In old school Perl 5, this is often a blessed hash reference. With +Moose, you should never need to know what your object instance +actually is. (ok, it's usually a blessed hashref with Moose too) + +=head2 Moose VS Old School Summary + +=over 4 + +=item * Class + +A package with no introspection other than mucking about in the symbol +table. + +With Moose, you get well-defined declaration and introspection. + +=item * Attributes + +Hand-written accessor methods, symbol table hackery, or a helper +module like C. + +With Moose, these are declaritively defined, and distinct from +methods. + +=item * Method + +These are pretty much the same in Moose as in old school Perl. + +=item * Roles + +C or C, or maybe C. + +With Moose, they're part of the core feature set, and are +introspectable like everything else. + +=item * Method Modifiers + +Could only be done through serious symbol table wizardry, and you +probably never saw this before (at least in Perl 5). + +=item * Type + +Hand-written parameter checking in your C method and accessors. + +With Moose, you define types declaratively, and then use them by name +in your attributes. + +=item * Delegation + +C or C, but probably even more +hand-written code. + +With Moose, this is also declarative. + +=item * Constructor + +A C method which calls C on a reference. + +Comes for free when you define a class with Moose. + +=item * Destructor + +A C method. + +With Moose, this is called C. + +=item * Object Instance + +A blessed reference, usually a hash reference. + +With Moose, this is an opaque thing which has a bunch of attributes +and methods, as defined by its class. + +=item * Immutabilization + +Moose comes with a feature called "immutabilization". When you make +your class immutable, it means you're done adding methods, attributes, +roles, etc. This lets Moose optimize your class with a bunch of +extremely dirty in-place code generation tricks that speed up things +like object construction and so on. + +=back + +=head1 META WHAT? + +A metaclass is a class that describes classes. With Moose, every class +you define gets a C method. It returns a L +object, which has an introspection API that can tell you about the +class it represents. + + my $meta = User->meta(); + + for my $attribute ( $meta->compute_all_applicable_attributes ) { + print $attribute->name(), "\n"; + + if ( $attribute->has_type_constraint ) { + print " type: ", $attribute->type_constraint->name, "\n"; + } + } + + for my $method ( $meta->compute_all_applicable_methods ) { + print $method->name, "\n"; + } + +Almost every concept we defined earlier has a meta class, so we have +L, L, +L, L, +L, L, and so on. + +=head1 BUT I NEED TO DO IT MY WAY! + +One of the great things about Moose, is that if you dig down and find +that it does something the "wrong way", you can change it by extending +a metaclass. For example, you can have arrayref based objects, you can +make your constructors strict (no unknown params allowed!), you can +define a naming scheme for attribute accessors, you can make a class a +Singleton, and much, much more. + +Many of these extensions require surprisingly small amounts of code, +and once you've done it once, you'll never have to hand-code "your way +of doing things" again. Instead you ll just load your favorite +extensions. + + package MyWay::User; + + use Moose; + use MooseX::StrictConstructor + use MooseX::MyWay; + + has ...; + + +=head1 JUSTIFICATION + +If you're still still asking yourself "Why do I need this?", then this +section is for you. + +=over 4 + +=item Another object system!?!? + +Yes, I know there has been an explosion recently of new ways to +build objects in Perl 5, most of them based on inside-out objects +and other such things. Moose is different because it is not a new +object system for Perl 5, but instead an extension of the existing +object system. + +Moose is built on top of L, which is a metaclass system +for Perl 5. This means that Moose not only makes building normal +Perl 5 objects better, but it also provides the power of metaclass +programming. + +=item Is this for real? Or is this just an experiment? + +Moose is I on the prototypes and experiments Stevan did for the +Perl 6 meta-model. However, Moose is B an experiment or +prototype; it is for B. + +=item Is this ready for use in production? + +Yes. + +Moose has been used successfully in production environments by several +people and companies. There are Moose applications which have been in +production with little or no issue now for well over two years. We +consider it highly stable and we are commited to keeping it stable. + +Of course, in the end, you need to make this call yourself. If you +have any questions or concerns, please feel free to email Stevan, the +moose@perl.org list, or just stop by irc.perl.org#moose and ask away. + +=item Is Moose just Perl 6 in Perl 5? + +No. While Moose is very much inspired by Perl 6, it is not itself Perl +6. Instead, it is an OO system for Perl 5. Stevan built Moose because +he was tired of writing the same old boring Perl 5 OO code, and +drooling over Perl 6 OO. So instead of switching to Ruby, he wrote +Moose :) + +=item Wait, I modern, I thought it was just I? + +Stevan read Larry Wall's talk from the 1999 Linux World entitled +"Perl, the first postmodern computer language" in which he talks about +how he picked the features for Perl because he thought they were cool +and he threw out the ones that he thought sucked. This got him +thinking about how we have done the same thing in Moose. For Moose, we +have "borrowed" features from Perl 6, CLOS (LISP), Smalltalk, Java, +BETA, OCaml, Ruby and more, and the bits we didn't like (cause they +sucked) we tossed aside. So for this reason (and a few others) Stevan +has re-dubbed Moose a I object system. + +Nuff Said. + +=back + +=head1 WHAT NEXT? + +So you're sold on Moose. Time to learn how to really use it. + +We recommend that you start with the L. If you work +your way through all the recipes under the basics section, you should +have a pretty good sense of how Moose works, and all of its basic OO +features. + +After that, check out the Role recipes. If you're really curious, go +on and read the Meta and Extending recipes, but those are mostly there +for people who want to be Moose wizards and change how Moose works. + +If you want to see how Moose would translate directly old school Perl +5 OO code, check out the L. + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE and Stevan Little +Estevan@iinteractive.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Meta/Attribute.pm b/lib/Moose/Meta/Attribute.pm index a4b6a10..7c481c0 100644 --- a/lib/Moose/Meta/Attribute.pm +++ b/lib/Moose/Meta/Attribute.pm @@ -7,7 +7,7 @@ use warnings; use Scalar::Util 'blessed', 'weaken'; use overload (); -our $VERSION = '0.50'; +our $VERSION = '0.57'; our $AUTHORITY = 'cpan:STEVAN'; use Moose::Meta::Method::Accessor; @@ -124,17 +124,54 @@ sub interpolate_class { return ( wantarray ? ( $class, @traits ) : $class ); } +# ... + +my @legal_options_for_inheritance = qw( + default coerce required + documentation lazy handles + builder type_constraint +); + +sub legal_options_for_inheritance { @legal_options_for_inheritance } + +# NOTE/TODO +# This method *must* be able to handle +# Class::MOP::Attribute instances as +# well. Yes, I know that is wrong, but +# apparently we didn't realize it was +# doing that and now we have some code +# which is dependent on it. The real +# solution of course is to push this +# feature back up into Class::MOP::Attribute +# but I not right now, I am too lazy. +# However if you are reading this and +# looking for something to do,.. please +# be my guest. +# - stevan sub clone_and_inherit_options { my ($self, %options) = @_; + my %copy = %options; - # you can change default, required, coerce, documentation, lazy, handles, builder, type_constraint (explicitly or using isa/does), metaclass and traits + my %actual_options; - foreach my $legal_option (qw(default coerce required documentation lazy handles builder type_constraint)) { + + # NOTE: + # we may want to extends a Class::MOP::Attribute + # in which case we need to be able to use the + # core set of legal options that have always + # been here. But we allows Moose::Meta::Attribute + # instances to changes them. + # - SL + my @legal_options = $self->can('legal_options_for_inheritance') + ? $self->legal_options_for_inheritance + : @legal_options_for_inheritance; + + foreach my $legal_option (@legal_options) { if (exists $options{$legal_option}) { $actual_options{$legal_option} = $options{$legal_option}; delete $options{$legal_option}; } - } + } if ($options{isa}) { my $type_constraint; @@ -242,7 +279,7 @@ sub _process_options { } } else { - $class->throw_error("I do not understand this option (is => " . $options->{is} . ") on attribute $name", data => $options->{is}); + $class->throw_error("I do not understand this option (is => " . $options->{is} . ") on attribute ($name)", data => $options->{is}); } } @@ -250,10 +287,10 @@ sub _process_options { if (exists $options->{does}) { if (eval { $options->{isa}->can('does') }) { ($options->{isa}->does($options->{does})) - || $class->throw_error("Cannot have an isa option and a does option if the isa does not do the does on attribute $name", data => $options); + || $class->throw_error("Cannot have an isa option and a does option if the isa does not do the does on attribute ($name)", data => $options); } else { - $class->throw_error("Cannot have an isa option which cannot ->does() on attribute $name", data => $options); + $class->throw_error("Cannot have an isa option which cannot ->does() on attribute ($name)", data => $options); } } @@ -277,26 +314,26 @@ sub _process_options { if (exists $options->{coerce} && $options->{coerce}) { (exists $options->{type_constraint}) - || $class->throw_error("You cannot have coercion without specifying a type constraint on attribute $name", data => $options); - $class->throw_error("You cannot have a weak reference to a coerced value on attribute $name", data => $options) + || $class->throw_error("You cannot have coercion without specifying a type constraint on attribute ($name)", data => $options); + $class->throw_error("You cannot have a weak reference to a coerced value on attribute ($name)", data => $options) if $options->{weak_ref}; } if (exists $options->{trigger}) { ('CODE' eq ref $options->{trigger}) - || $class->throw_error("Trigger must be a CODE ref", data => $options->{trigger}); + || $class->throw_error("Trigger must be a CODE ref on attribute ($name)", data => $options->{trigger}); } if (exists $options->{auto_deref} && $options->{auto_deref}) { (exists $options->{type_constraint}) - || $class->throw_error("You cannot auto-dereference without specifying a type constraint on attribute $name", data => $options); + || $class->throw_error("You cannot auto-dereference without specifying a type constraint on attribute ($name)", data => $options); ($options->{type_constraint}->is_a_type_of('ArrayRef') || $options->{type_constraint}->is_a_type_of('HashRef')) - || $class->throw_error("You cannot auto-dereference anything other than a ArrayRef or HashRef on attribute $name", data => $options); + || $class->throw_error("You cannot auto-dereference anything other than a ArrayRef or HashRef on attribute ($name)", data => $options); } if (exists $options->{lazy_build} && $options->{lazy_build} == 1) { - $class->throw_error("You can not use lazy_build and default for the same attribute $name", data => $options) + $class->throw_error("You can not use lazy_build and default for the same attribute ($name)", data => $options) if exists $options->{default}; $options->{lazy} = 1; $options->{required} = 1; @@ -347,13 +384,8 @@ sub initialize_instance_slot { $value_is_set = 1; } elsif ($self->has_builder) { - if (my $builder = $instance->can($self->builder)){ - $val = $instance->$builder; - $value_is_set = 1; - } - else { - $self->throw_error(blessed($instance)." does not support builder method '".$self->builder."' for attribute '" . $self->name . "'", object => $instance, data => $params); - } + $val = $self->_call_builder($instance); + $value_is_set = 1; } } @@ -376,6 +408,24 @@ sub initialize_instance_slot { if ref $val && $self->is_weak_ref; } +sub _call_builder { + my ( $self, $instance ) = @_; + + my $builder = $self->builder(); + + return $instance->$builder() + if $instance->can( $self->builder ); + + $self->throw_error( blessed($instance) + . " does not support builder method '" + . $self->builder + . "' for attribute '" + . $self->name + . "'", + object => $instance, + ); +} + ## Slot management # FIXME: @@ -460,25 +510,22 @@ sub get_value { if ($self->is_lazy) { unless ($self->has_value($instance)) { + my $value; if ($self->has_default) { - my $default = $self->default($instance); - $self->set_initial_value($instance, $default); + $value = $self->default($instance); } elsif ( $self->has_builder ) { - if (my $builder = $instance->can($self->builder)){ - $self->set_initial_value($instance, $instance->$builder); - } - else { - $self->throw_error(blessed($instance) - . " does not support builder method '" - . $self->builder - . "' for attribute '" - . $self->name - . "'", object => $instance); - } - } - else { - $self->set_initial_value($instance, undef); + $value = $self->_call_builder($instance); + } + if ($self->has_type_constraint) { + my $type_constraint = $self->type_constraint; + $value = $type_constraint->coerce($value) + if ($self->should_coerce); + $type_constraint->check($value) + || c$self->throw_error("Attribute (" . $self->name + . ") does not pass the type constraint because: " + . $type_constraint->get_message($value), type_constraint => $type_constraint, data => $value); } + $self->set_initial_value($instance, $value); } } @@ -514,72 +561,81 @@ sub accessor_metaclass { 'Moose::Meta::Method::Accessor' } sub install_accessors { my $self = shift; $self->SUPER::install_accessors(@_); + $self->install_delegation if $self->has_handles; + return; +} - if ($self->has_handles) { +sub install_delegation { + my $self = shift; - # NOTE: - # Here we canonicalize the 'handles' option - # this will sort out any details and always - # return an hash of methods which we want - # to delagate to, see that method for details - my %handles = $self->_canonicalize_handles(); - - # find the accessor method for this attribute - my $accessor = $self->get_read_method_ref; - # then unpack it if we need too ... - $accessor = $accessor->body if blessed $accessor; - - # install the delegation ... - my $associated_class = $self->associated_class; - foreach my $handle (keys %handles) { - my $method_to_call = $handles{$handle}; - my $class_name = $associated_class->name; - my $name = "${class_name}::${handle}"; + # NOTE: + # Here we canonicalize the 'handles' option + # this will sort out any details and always + # return an hash of methods which we want + # to delagate to, see that method for details + my %handles = $self->_canonicalize_handles; + + # find the accessor method for this attribute + my $accessor = $self->_get_delegate_accessor; + + # install the delegation ... + my $associated_class = $self->associated_class; + foreach my $handle (keys %handles) { + my $method_to_call = $handles{$handle}; + my $class_name = $associated_class->name; + my $name = "${class_name}::${handle}"; (!$associated_class->has_method($handle)) || $self->throw_error("You cannot overwrite a locally defined method ($handle) with a delegation", method_name => $handle); - # NOTE: - # handles is not allowed to delegate - # any of these methods, as they will - # override the ones in your class, which - # is almost certainly not what you want. + # NOTE: + # handles is not allowed to delegate + # any of these methods, as they will + # override the ones in your class, which + # is almost certainly not what you want. - # FIXME warn when $handle was explicitly specified, but not if the source is a regex or something - #cluck("Not delegating method '$handle' because it is a core method") and - next if $class_name->isa("Moose::Object") and $handle =~ /^BUILD|DEMOLISH$/ || Moose::Object->can($handle); + # FIXME warn when $handle was explicitly specified, but not if the source is a regex or something + #cluck("Not delegating method '$handle' because it is a core method") and + next if $class_name->isa("Moose::Object") and $handle =~ /^BUILD|DEMOLISH$/ || Moose::Object->can($handle); - if ('CODE' eq ref($method_to_call)) { - $associated_class->add_method($handle => Class::MOP::subname($name, $method_to_call)); - } - else { - # NOTE: - # we used to do a goto here, but the - # goto didn't handle failure correctly - # (it just returned nothing), so I took - # that out. However, the more I thought - # about it, the less I liked it doing - # the goto, and I prefered the act of - # delegation being actually represented - # in the stack trace. - # - SL - $associated_class->add_method($handle => Class::MOP::subname($name, sub { - my $instance = shift; - my $proxy = $instance->$accessor(); - (defined $proxy) - || $self->throw_error("Cannot delegate $handle to $method_to_call because " . - "the value of " . $self->name . " is not defined", method_name => $method_to_call, object => $instance); - $proxy->$method_to_call(@_); - })); - } + if ('CODE' eq ref($method_to_call)) { + $associated_class->add_method($handle => Class::MOP::subname($name, $method_to_call)); } - } - - return; + else { + # NOTE: + # we used to do a goto here, but the + # goto didn't handle failure correctly + # (it just returned nothing), so I took + # that out. However, the more I thought + # about it, the less I liked it doing + # the goto, and I prefered the act of + # delegation being actually represented + # in the stack trace. + # - SL + $associated_class->add_method($handle => Class::MOP::subname($name, sub { + my $instance = shift; + my $proxy = $instance->$accessor(); + (defined $proxy) + || $self->throw_error("Cannot delegate $handle to $method_to_call because " . + "the value of " . $self->name . " is not defined", method_name => $method_to_call, object => $instance); + $proxy->$method_to_call(@_); + })); + } + } } # private methods to help delegation ... +sub _get_delegate_accessor { + my $self = shift; + # find the accessor method for this attribute + my $accessor = $self->get_read_method_ref; + # then unpack it if we need too ... + $accessor = $accessor->body if blessed $accessor; + # return the accessor + return $accessor; +} + sub _canonicalize_handles { my $self = shift; my $handles = $self->handles; @@ -645,9 +701,9 @@ sub _get_delegate_method_list { my $self = shift; my $meta = $self->_find_delegate_metaclass; if ($meta->isa('Class::MOP::Class')) { - return map { $_->{name} } # NOTE: !never! delegate &meta - grep { $_->{class} ne 'Moose::Object' && $_->{name} ne 'meta' } - $meta->compute_all_applicable_methods; + return map { $_->name } # NOTE: !never! delegate &meta + grep { $_->package_name ne 'Moose::Object' && $_->name ne 'meta' } + $meta->get_all_methods; } elsif ($meta->isa('Moose::Meta::Role')) { return $meta->get_method_list; @@ -700,6 +756,8 @@ will behave just as L does. =item B +=item B + =item B =item B @@ -722,7 +780,7 @@ Any coercion to convert values is done before checking the type constraint. To check a value against a type constraint before setting it, fetch the attribute instance using L, fetch the type_constraint from the attribute using L -and call L. See L +and call L. See L for an example. =back @@ -751,6 +809,11 @@ This is to support the C feature, it clones an attribute from a superclass and allows a very specific set of changes to be made to the attribute. +=item B + +Whitelist with options you can change. You can overload it in your custom +metaclass to allow your options be inheritable. + =item B Returns true if this meta-attribute has a type constraint. @@ -793,14 +856,14 @@ and predicate options for you using the following convention. #If your attribute name starts with an underscore: has '_foo' => (lazy_build => 1); #is the same as - has '_foo' => (lazy => 1, required => 1, predicate => '_has_foo', clearer => '_clear_foo', builder => '_build__foo); + has '_foo' => (lazy => 1, required => 1, predicate => '_has_foo', clearer => '_clear_foo', builder => '_build__foo'); # or has '_foo' => (lazy => 1, required => 1, predicate => '_has_foo', clearer => '_clear_foo', default => sub{shift->_build__foo}); #If your attribute name does not start with an underscore: has 'foo' => (lazy_build => 1); #is the same as - has 'foo' => (lazy => 1, required => 1, predicate => 'has_foo', clearer => 'clear_foo', builder => '_build_foo); + has 'foo' => (lazy => 1, required => 1, predicate => 'has_foo', clearer => 'clear_foo', builder => '_build_foo'); # or has 'foo' => (lazy => 1, required => 1, predicate => 'has_foo', clearer => 'clear_foo', default => sub{shift->_build_foo}); diff --git a/lib/Moose/Meta/Class.pm b/lib/Moose/Meta/Class.pm index b5fb740..44b7972 100644 --- a/lib/Moose/Meta/Class.pm +++ b/lib/Moose/Meta/Class.pm @@ -9,7 +9,8 @@ use Class::MOP; use Carp (); use Scalar::Util 'weaken', 'blessed'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use Moose::Meta::Method::Overriden; @@ -22,6 +23,16 @@ __PACKAGE__->meta->add_attribute('roles' => ( default => sub { [] } )); +__PACKAGE__->meta->add_attribute('constructor_class' => ( + accessor => 'constructor_class', + default => sub { 'Moose::Meta::Method::Constructor' } +)); + +__PACKAGE__->meta->add_attribute('destructor_class' => ( + accessor => 'destructor_class', + default => sub { 'Moose::Meta::Method::Destructor' } +)); + __PACKAGE__->meta->add_attribute('error_builder' => ( reader => 'error_builder', default => 'confess', @@ -131,15 +142,16 @@ sub excludes_role { } sub new_object { - my ($class, %params) = @_; - my $self = $class->SUPER::new_object(%params); + my $class = shift; + my $params = @_ == 1 ? $_[0] : {@_}; + my $self = $class->SUPER::new_object($params); foreach my $attr ($class->compute_all_applicable_attributes()) { # if we have a trigger, then ... if ($attr->can('has_trigger') && $attr->has_trigger) { # make sure we have an init-arg ... if (defined(my $init_arg = $attr->init_arg)) { # now make sure an init-arg was passes ... - if (exists $params{$init_arg}) { + if (exists $params->{$init_arg}) { # and if get here, fire the trigger $attr->trigger->( $self, @@ -150,7 +162,7 @@ sub new_object { ? $attr->get_read_method_ref->($self) # otherwise, just get the value from # the constructor params - : $params{$init_arg}), + : $params->{$init_arg}), $attr ); } @@ -161,15 +173,16 @@ sub new_object { } sub construct_instance { - my ($class, %params) = @_; + my $class = shift; + my $params = @_ == 1 ? $_[0] : {@_}; my $meta_instance = $class->get_meta_instance; # FIXME: # the code below is almost certainly incorrect # but this is foreign inheritence, so we might # have to kludge it in the end. - my $instance = $params{'__INSTANCE__'} || $meta_instance->create_instance(); + my $instance = $params->{'__INSTANCE__'} || $meta_instance->create_instance(); foreach my $attr ($class->compute_all_applicable_attributes()) { - $attr->initialize_instance_slot($meta_instance, $instance, \%params); + $attr->initialize_instance_slot($meta_instance, $instance, $params); } return $instance; } @@ -179,12 +192,15 @@ sub construct_instance { sub get_method_map { my $self = shift; - if (defined $self->{'$!_package_cache_flag'} && - $self->{'$!_package_cache_flag'} == Class::MOP::check_package_cache_flag($self->meta->name)) { - return $self->{'%!methods'}; + my $current = Class::MOP::check_package_cache_flag($self->name); + + if (defined $self->{'_package_cache_flag'} && $self->{'_package_cache_flag'} == $current) { + return $self->{'methods'}; } - my $map = $self->{'%!methods'}; + $self->{_package_cache_flag} = $current; + + my $map = $self->{'methods'}; my $class_name = $self->name; my $method_metaclass = $self->method_metaclass; @@ -287,43 +303,74 @@ sub _find_next_method_by_name_which_is_not_overridden { return undef; } +# Right now, this method does not handle the case where two +# metaclasses differ only in roles applied against a common parent +# class. This can happen fairly easily when ClassA applies metaclass +# Role1, and then a subclass, ClassB, applies a metaclass Role2. In +# reality, the way to resolve the problem is to apply Role1 to +# ClassB's metaclass. However, we cannot currently detect this, and so +# we simply fail to fix the incompatibility. +# +# The algorithm for fixing it is not that complicated. +# +# First, we see if the two metaclasses share a common parent (probably +# Moose::Meta::Class). +# +# Second, we see if the metaclasses only differ in terms of roles +# applied. This second point is where things break down. There is no +# easy way to determine if the difference is from roles only. To do +# that, we'd need to able to reliably determine the origin of each +# method and attribute in each metaclass. If all the unshared methods +# & attributes come from roles, and there is no name collision, then +# we can apply the missing roles to the child's metaclass. +# +# Tracking the origin of these things will require some fairly +# invasive changes to various parts of Moose & Class::MOP. +# +# For now, the workaround is for ClassB to subclass ClassA _and then_ +# apply metaclass roles to its metaclass. sub _fix_metaclass_incompatability { my ($self, @superclasses) = @_; + foreach my $super (@superclasses) { # don't bother if it does not have a meta. - next unless $super->can('meta'); + my $super_meta = Class::MOP::Class->initialize($super) or next; + next unless $super_meta->isa("Class::MOP::Class"); + # get the name, make sure we take # immutable classes into account - my $super_meta_name = ($super->meta->is_immutable - ? $super->meta->get_mutable_metaclass_name - : blessed($super->meta)); - # if it's meta is a vanilla Moose, - # then we can safely ignore it. - next if $super_meta_name eq 'Moose::Meta::Class'; - # but if we have anything else, - # we need to check it out ... - unless (# see if of our metaclass is incompatible - ($self->isa($super_meta_name) && - # and see if our instance metaclass is incompatible - $self->instance_metaclass->isa($super->meta->instance_metaclass)) && - # ... and if we are just a vanilla Moose - $self->isa('Moose::Meta::Class')) { - # re-initialize the meta ... - my $super_meta = $super->meta; - # NOTE: - # We might want to consider actually - # transfering any attributes from the - # original meta into this one, but in - # general you should not have any there - # at this point anyway, so it's very - # much an obscure edge case anyway - $self = $super_meta->reinitialize($self->name => ( - 'attribute_metaclass' => $super_meta->attribute_metaclass, - 'method_metaclass' => $super_meta->method_metaclass, - 'instance_metaclass' => $super_meta->instance_metaclass, - )); + my $super_meta_name + = $super_meta->is_immutable + ? $super_meta->get_mutable_metaclass_name + : ref($super_meta); + + next if + # if our metaclass is compatible + $self->isa($super_meta_name) + and + # and our instance metaclass is also compatible then no + # fixes are needed + $self->instance_metaclass->isa( $super_meta->instance_metaclass ); + + next unless $super_meta->isa( ref($self) ); + + unless ( $self->is_pristine ) { + $self->throw_error("Not reinitializing metaclass for " + . $self->name + . ", it isn't pristine"); } + + $self = $super_meta->reinitialize( + $self->name, + attribute_metaclass => $super_meta->attribute_metaclass, + method_metaclass => $super_meta->method_metaclass, + instance_metaclass => $super_meta->instance_metaclass, + ); + + $self->$_( $super_meta->$_ ) + for qw( constructor_class destructor_class ); } + return $self; } @@ -379,41 +426,38 @@ use Moose::Meta::Method::Destructor; sub create_immutable_transformer { my $self = shift; my $class = Class::MOP::Immutable->new($self, { - read_only => [qw/ - superclasses - roles - error_class - error_builder - /], - cannot_call => [qw/ - add_method - alias_method - remove_method - add_attribute - remove_attribute - remove_package_symbol - add_role - /], - memoize => { - class_precedence_list => 'ARRAY', - compute_all_applicable_attributes => 'ARRAY', - get_meta_instance => 'SCALAR', - get_method_map => 'SCALAR', - # maybe .... - calculate_all_roles => 'ARRAY', - }, - # NOTE: - # this is ugly, but so are typeglobs, - # so whattayahgonnadoboutit - # - SL - wrapped => { - add_package_symbol => sub { - my $original = shift; - $self->throw_error("Cannot add package symbols to an immutable metaclass") - unless (caller(2))[3] eq 'Class::MOP::Package::get_package_symbol'; - goto $original->body; - }, - }, + read_only => [qw/superclasses/], + cannot_call => [qw/ + add_method + alias_method + remove_method + add_attribute + remove_attribute + remove_package_symbol + add_role + /], + memoize => { + class_precedence_list => 'ARRAY', + linearized_isa => 'ARRAY', # FIXME perl 5.10 memoizes this on its own, no need? + get_all_methods => 'ARRAY', + #get_all_attributes => 'ARRAY', # it's an alias, no need, but maybe in the future + compute_all_applicable_attributes => 'ARRAY', + get_meta_instance => 'SCALAR', + get_method_map => 'SCALAR', + calculate_all_roles => 'ARRAY', + }, + # NOTE: + # this is ugly, but so are typeglobs, + # so whattayahgonnadoboutit + # - SL + wrapped => { + add_package_symbol => sub { + my $original = shift; + $self->throw_error("Cannot add package symbols to an immutable metaclass") + unless (caller(2))[3] eq 'Class::MOP::Package::get_package_symbol'; + goto $original->body; + }, + }, }); return $class; } @@ -422,8 +466,8 @@ sub make_immutable { my $self = shift; $self->SUPER::make_immutable ( - constructor_class => 'Moose::Meta::Method::Constructor', - destructor_class => 'Moose::Meta::Method::Destructor', + constructor_class => $self->constructor_class, + destructor_class => $self->destructor_class, inline_destructor => 1, # NOTE: # no need to do this, @@ -620,6 +664,15 @@ cascade down the role hierarchy. This method does the same thing as L, but adds support for taking the C<$params> as a HASH ref. +=item B + +=item B + +These are the names of classes used when making a class +immutable. These default to L and +L respectively. These accessors are +read-write, so you can use them to change the class name. + =item B Throws the error created by C using C diff --git a/lib/Moose/Meta/Instance.pm b/lib/Moose/Meta/Instance.pm index 91c404d..1b0444f 100644 --- a/lib/Moose/Meta/Instance.pm +++ b/lib/Moose/Meta/Instance.pm @@ -4,7 +4,8 @@ package Moose::Meta::Instance; use strict; use warnings; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base "Class::MOP::Instance"; @@ -28,7 +29,7 @@ Moose::Meta::Instance - The Moose Instance metaclass This class provides the low level data storage abstractions for attributes. Using this API generally violates attribute encapsulation and is not -reccomended, instead look at L, +recommended, instead look at L, L, etc, as well as L for the recommended way to fiddle with attribute values in a generic way, independent of how/whether accessors have been defined. Accessors can be found diff --git a/lib/Moose/Meta/Method.pm b/lib/Moose/Meta/Method.pm index 8013f76..89135e6 100644 --- a/lib/Moose/Meta/Method.pm +++ b/lib/Moose/Meta/Method.pm @@ -3,7 +3,8 @@ package Moose::Meta::Method; use strict; use warnings; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Class::MOP::Method'; diff --git a/lib/Moose/Meta/Method/Accessor.pm b/lib/Moose/Meta/Method/Accessor.pm index c8cd7c2..718313e 100644 --- a/lib/Moose/Meta/Method/Accessor.pm +++ b/lib/Moose/Meta/Method/Accessor.pm @@ -4,7 +4,8 @@ package Moose::Meta::Method::Accessor; use strict; use warnings; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Method', @@ -52,7 +53,7 @@ sub generate_accessor_method_inline { . $self->_inline_store($inv, $value_name) . "\n" . $self->_inline_trigger($inv, $value_name) . "\n" . ' }' . "\n" - . $self->_inline_check_lazy . "\n" + . $self->_inline_check_lazy($inv) . "\n" . $self->_inline_post_body(@_) . "\n" . 'return ' . $self->_inline_auto_deref($self->_inline_get($inv)) . "\n" . ' }'); @@ -88,7 +89,7 @@ sub generate_reader_method_inline { $self->_eval_code('sub {' . $self->_inline_pre_body(@_) . $self->_inline_throw_error('"Cannot assign a value to a read-only accessor"', 'data => \@_') . ' if @_ > 1;' - . $self->_inline_check_lazy + . $self->_inline_check_lazy($inv) . $self->_inline_post_body(@_) . 'return ' . $self->_inline_auto_deref( $slot_access ) . ';' . '}'); @@ -142,53 +143,53 @@ sub _inline_check_required { } sub _inline_check_lazy { - my $self = $_[0]; + my ($self, $instance) = @_; + my $attr = $self->associated_attribute; return '' unless $attr->is_lazy; - my $inv = '$_[0]'; - my $slot_access = $self->_inline_access($inv, $attr->name); + my $slot_access = $self->_inline_access($instance, $attr->name); - my $slot_exists = $self->_inline_has($inv, $attr->name); + my $slot_exists = $self->_inline_has($instance, $attr->name); my $code = 'unless (' . $slot_exists . ') {' . "\n"; if ($attr->has_type_constraint) { if ($attr->has_default || $attr->has_builder) { if ($attr->has_default) { - $code .= ' my $default = $attr->default(' . $inv . ');'."\n"; + $code .= ' my $default = $attr->default(' . $instance . ');'."\n"; } elsif ($attr->has_builder) { $code .= ' my $default;'."\n". - ' if(my $builder = '.$inv.'->can($attr->builder)){ '."\n". - ' $default = '.$inv.'->$builder; '. "\n } else {\n" . - ' ' . $self->_inline_throw_error('Scalar::Util::blessed('.$inv.')." does not support builder method '. - '\'".$attr->builder."\' for attribute \'" . $attr->name . "\'"') . ';'. "\n }"; + ' if(my $builder = '.$instance.'->can($attr->builder)){ '."\n". + ' $default = '.$instance.'->$builder; '. "\n } else {\n" . + ' ' . $self->_inline_throw_error(q{sprintf "%s does not support builder method '%s' for attribute '%s'", ref(} . $instance . ') || '.$instance.', $attr->builder, $attr->name') . + ';'. "\n }"; } $code .= ' $default = $type_constraint_obj->coerce($default);'."\n" if $attr->should_coerce; $code .= ' ($type_constraint->($default))' . ' || ' . $self->_inline_throw_error('"Attribute (" . $attr_name . ") does not pass the type constraint ("' . ' . $type_constraint_name . ") with " . (defined($default) ? overload::StrVal($default) : "undef")' ) . ';' . "\n"; - $code .= ' ' . $self->_inline_init_slot($attr, $inv, $slot_access, '$default') . "\n"; + $code .= ' ' . $self->_inline_init_slot($attr, $instance, $slot_access, '$default') . "\n"; } else { - $code .= ' ' . $self->_inline_init_slot($attr, $inv, $slot_access, 'undef') . "\n"; + $code .= ' ' . $self->_inline_init_slot($attr, $instance, $slot_access, 'undef') . "\n"; } } else { if ($attr->has_default) { - $code .= ' ' . $self->_inline_init_slot($attr, $inv, $slot_access, ('$attr->default(' . $inv . ')')) . "\n"; + $code .= ' ' . $self->_inline_init_slot($attr, $instance, $slot_access, ('$attr->default(' . $instance . ')')) . "\n"; } elsif ($attr->has_builder) { - $code .= ' if (my $builder = '.$inv.'->can($attr->builder)) { ' . "\n" - . ' ' . $self->_inline_init_slot($attr, $inv, $slot_access, ($inv . '->$builder')) - . "\n } else {\n" . - ' ' . $self->_inline_throw_error('Scalar::Util::blessed('.$inv.')." does not support builder method '. - '\'".$attr->builder."\' for attribute \'" . $attr->name . "\'"') . ';'. "\n }"; + $code .= ' if (my $builder = '.$instance.'->can($attr->builder)) { ' . "\n" + . ' ' . $self->_inline_init_slot($attr, $instance, $slot_access, ($instance . '->$builder')) + . "\n } else {\n" + . ' ' . $self->_inline_throw_error(q{sprintf "%s does not support builder method '%s' for attribute '%s'", ref(} . $instance . ') || '.$instance.', $attr->builder, $attr->name') + . ';'. "\n }"; } else { - $code .= ' ' . $self->_inline_init_slot($attr, $inv, $slot_access, 'undef') . "\n"; + $code .= ' ' . $self->_inline_init_slot($attr, $instance, $slot_access, 'undef') . "\n"; } } $code .= "}\n"; diff --git a/lib/Moose/Meta/Method/Augmented.pm b/lib/Moose/Meta/Method/Augmented.pm index 7d86a77..c69f8bb 100644 --- a/lib/Moose/Meta/Method/Augmented.pm +++ b/lib/Moose/Meta/Method/Augmented.pm @@ -5,7 +5,8 @@ use warnings; use Carp 'confess'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Method'; diff --git a/lib/Moose/Meta/Method/Constructor.pm b/lib/Moose/Meta/Method/Constructor.pm index 8a523b2..8fdf3a7 100644 --- a/lib/Moose/Meta/Method/Constructor.pm +++ b/lib/Moose/Meta/Method/Constructor.pm @@ -6,7 +6,7 @@ use warnings; use Scalar::Util 'blessed', 'weaken', 'looks_like_number'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Method', @@ -26,21 +26,21 @@ sub new { my $self = bless { # from our superclass - '&!body' => undef, - '$!package_name' => $options{package_name}, - '$!name' => $options{name}, + 'body' => undef, + 'package_name' => $options{package_name}, + 'name' => $options{name}, # specific to this subclass - '%!options' => $options{options}, - '$!meta_instance' => $meta->get_meta_instance, - '@!attributes' => [ $meta->compute_all_applicable_attributes ], + 'options' => $options{options}, + 'meta_instance' => $meta->get_meta_instance, + 'attributes' => [ $meta->compute_all_applicable_attributes ], # ... - '$!associated_metaclass' => $meta, + 'associated_metaclass' => $meta, } => $class; # we don't want this creating # a cycle in the code, if not # needed - weaken($self->{'$!associated_metaclass'}); + weaken($self->{'associated_metaclass'}); $self->initialize_body; @@ -49,11 +49,11 @@ sub new { ## accessors -sub options { (shift)->{'%!options'} } -sub meta_instance { (shift)->{'$!meta_instance'} } -sub attributes { (shift)->{'@!attributes'} } +sub options { (shift)->{'options'} } +sub meta_instance { (shift)->{'meta_instance'} } +sub attributes { (shift)->{'attributes'} } -sub associated_metaclass { (shift)->{'$!associated_metaclass'} } +sub associated_metaclass { (shift)->{'associated_metaclass'} } ## method @@ -76,12 +76,9 @@ sub initialize_body { $source .= "\n" . 'return $class->Moose::Object::new(@_)'; $source .= "\n" . ' if $class ne \'' . $self->associated_metaclass->name . '\';'; - $source .= "\n" . $self->_inline_throw_error('"Single parameters to new() must be a HASH ref"', 'data => $_[0]'); - $source .= "\n" . ' if scalar @_ == 1 && ref($_[0]) ne q{HASH};'; + $source .= "\n" . 'my $params = ' . $self->_generate_BUILDARGS('$class', '@_'); - $source .= "\n" . 'my %params = (scalar @_ == 1) ? %{$_[0]} : @_;'; - - $source .= "\n" . 'my $instance = ' . $self->meta_instance->inline_create_instance('$class'); + $source .= ";\n" . 'my $instance = ' . $self->meta_instance->inline_create_instance('$class'); $source .= ";\n" . (join ";\n" => map { $self->_generate_slot_initializer($_) @@ -123,14 +120,32 @@ sub initialize_body { $code = eval $source; $self->throw_error("Could not eval the constructor :\n\n$source\n\nbecause :\n\n$@", error => $@, data => $source ) if $@; } - $self->{'&!body'} = $code; + $self->{'body'} = $code; +} + +sub _generate_BUILDARGS { + my ( $self, $class, $args ) = @_; + + my $buildargs = $self->associated_metaclass->find_method_by_name("BUILDARGS"); + + if ( $args eq '@_' and ( !$buildargs or $buildargs->body == \&Moose::Object::BUILDARGS ) ) { + return join("\n", + 'do {', + $self->_inline_throw_error('"Single parameters to new() must be a HASH ref"', 'data => $_[0]'), + ' if scalar @_ == 1 && defined $_[0] && ref($_[0]) ne q{HASH};', + '(scalar @_ == 1) ? {%{$_[0]}} : {@_};', + '}', + ); + } else { + return $class . "->BUILDARGS($args)"; + } } sub _generate_BUILDALL { my $self = shift; my @BUILD_calls; foreach my $method (reverse $self->associated_metaclass->find_all_methods_by_name('BUILD')) { - push @BUILD_calls => '$instance->' . $method->{class} . '::BUILD(\%params)'; + push @BUILD_calls => '$instance->' . $method->{class} . '::BUILD($params)'; } return join ";\n" => @BUILD_calls; } @@ -143,7 +158,7 @@ sub _generate_triggers { if ($attr->can('has_trigger') && $attr->has_trigger) { if (defined(my $init_arg = $attr->init_arg)) { push @trigger_calls => ( - '(exists $params{\'' . $init_arg . '\'}) && do {' . "\n " + '(exists $params->{\'' . $init_arg . '\'}) && do {' . "\n " . '$attrs->[' . $i . ']->trigger->(' . '$instance, ' . $self->meta_instance->inline_get_slot_value( @@ -172,35 +187,18 @@ sub _generate_slot_initializer { my $is_moose = $attr->isa('Moose::Meta::Attribute'); # XXX FIXME if ($is_moose && defined($attr->init_arg) && $attr->is_required && !$attr->has_default && !$attr->has_builder) { - push @source => ('(exists $params{\'' . $attr->init_arg . '\'}) ' . + push @source => ('(exists $params->{\'' . $attr->init_arg . '\'}) ' . '|| ' . $self->_inline_throw_error('"Attribute (' . $attr->name . ') is required"') .';'); } if (($attr->has_default || $attr->has_builder) && !($is_moose && $attr->is_lazy)) { if ( defined( my $init_arg = $attr->init_arg ) ) { - push @source => 'if (exists $params{\'' . $init_arg . '\'}) {'; - - push @source => ('my $val = $params{\'' . $init_arg . '\'};'); - - if ($is_moose && $attr->has_type_constraint) { - if ($attr->should_coerce && $attr->type_constraint->has_coercion) { - push @source => $self->_generate_type_coercion( - $attr, - '$type_constraints[' . $index . ']', - '$val', - '$val' - ); - } - push @source => $self->_generate_type_constraint_check( - $attr, - '$type_constraint_bodies[' . $index . ']', - '$type_constraints[' . $index . ']', - '$val' - ); - } - push @source => $self->_generate_slot_assignment($attr, '$val', $index); - + push @source => 'if (exists $params->{\'' . $init_arg . '\'}) {'; + push @source => ('my $val = $params->{\'' . $init_arg . '\'};'); + push @source => $self->_generate_type_constraint_and_coercion($attr, $index) + if $is_moose; + push @source => $self->_generate_slot_assignment($attr, '$val', $index); push @source => "} else {"; } my $default; @@ -214,22 +212,17 @@ sub _generate_slot_initializer { push @source => '{'; # wrap this to avoid my $val overwrite warnings push @source => ('my $val = ' . $default . ';'); - push @source => $self->_generate_type_constraint_check( - $attr, - ('$type_constraint_bodies[' . $index . ']'), - ('$type_constraints[' . $index . ']'), - '$val' - ) if ($is_moose && $attr->has_type_constraint); - + push @source => $self->_generate_type_constraint_and_coercion($attr, $index) + if $is_moose; push @source => $self->_generate_slot_assignment($attr, '$val', $index); push @source => '}'; # close - wrap this to avoid my $val overrite warnings push @source => "}" if defined $attr->init_arg; } elsif ( defined( my $init_arg = $attr->init_arg ) ) { - push @source => '(exists $params{\'' . $init_arg . '\'}) && do {'; + push @source => '(exists $params->{\'' . $init_arg . '\'}) && do {'; - push @source => ('my $val = $params{\'' . $init_arg . '\'};'); + push @source => ('my $val = $params->{\'' . $init_arg . '\'};'); if ($is_moose && $attr->has_type_constraint) { if ($attr->should_coerce && $attr->type_constraint->has_coercion) { push @source => $self->_generate_type_coercion( @@ -290,6 +283,29 @@ sub _generate_slot_assignment { return $source; } +sub _generate_type_constraint_and_coercion { + my ($self, $attr, $index) = @_; + + return unless $attr->has_type_constraint; + + my @source; + if ($attr->should_coerce && $attr->type_constraint->has_coercion) { + push @source => $self->_generate_type_coercion( + $attr, + '$type_constraints[' . $index . ']', + '$val', + '$val' + ); + } + push @source => $self->_generate_type_constraint_check( + $attr, + ('$type_constraint_bodies[' . $index . ']'), + ('$type_constraints[' . $index . ']'), + '$val' + ); + return @source; +} + sub _generate_type_coercion { my ($self, $attr, $type_constraint_name, $value_name, $return_value_name) = @_; return ($return_value_name . ' = ' . $type_constraint_name . '->coerce(' . $value_name . ');'); diff --git a/lib/Moose/Meta/Method/Destructor.pm b/lib/Moose/Meta/Method/Destructor.pm index a6f4f65..ef5660e 100644 --- a/lib/Moose/Meta/Method/Destructor.pm +++ b/lib/Moose/Meta/Method/Destructor.pm @@ -7,7 +7,8 @@ use warnings; use Carp 'confess'; use Scalar::Util 'blessed', 'weaken'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Method', @@ -25,18 +26,18 @@ sub new { my $self = bless { # from our superclass - '&!body' => undef, - '$!package_name' => $options{package_name}, - '$!name' => $options{name}, + 'body' => undef, + 'package_name' => $options{package_name}, + 'name' => $options{name}, # ... - '%!options' => $options{options}, - '$!associated_metaclass' => $options{metaclass}, + 'options' => $options{options}, + 'associated_metaclass' => $options{metaclass}, } => $class; # we don't want this creating # a cycle in the code, if not # needed - weaken($self->{'$!associated_metaclass'}); + weaken($self->{'associated_metaclass'}); $self->initialize_body; @@ -45,8 +46,8 @@ sub new { ## accessors -sub options { (shift)->{'%!options'} } -sub associated_metaclass { (shift)->{'$!associated_metaclass'} } +sub options { (shift)->{'options'} } +sub associated_metaclass { (shift)->{'associated_metaclass'} } ## method @@ -59,7 +60,7 @@ sub is_needed { || confess "When calling is_needed as a class method you must pass a class name"; return $_[0]->meta->can('DEMOLISH'); } - defined $self->{'&!body'} ? 1 : 0 + defined $self->{'body'} ? 1 : 0 } sub initialize_body { @@ -93,7 +94,7 @@ sub initialize_body { $code = eval $source; confess "Could not eval the destructor :\n\n$source\n\nbecause :\n\n$@" if $@; } - $self->{'&!body'} = $code; + $self->{'body'} = $code; } diff --git a/lib/Moose/Meta/Method/Overriden.pm b/lib/Moose/Meta/Method/Overriden.pm index 9b661d6..d87d0c5 100644 --- a/lib/Moose/Meta/Method/Overriden.pm +++ b/lib/Moose/Meta/Method/Overriden.pm @@ -3,7 +3,8 @@ package Moose::Meta::Method::Overriden; use strict; use warnings; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Method'; diff --git a/lib/Moose/Meta/Role.pm b/lib/Moose/Meta/Role.pm index 547dabb..13034ce 100644 --- a/lib/Moose/Meta/Role.pm +++ b/lib/Moose/Meta/Role.pm @@ -8,7 +8,8 @@ use metaclass; use Carp 'confess'; use Scalar::Util 'blessed'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use Moose::Meta::Class; @@ -240,6 +241,14 @@ sub get_method_modifier_list { keys %{$self->$accessor}; } +sub reset_package_cache_flag { (shift)->{'_package_cache_flag'} = undef } +sub update_package_cache_flag { + my $self = shift; + $self->{'_package_cache_flag'} = Class::MOP::check_package_cache_flag($self->name); +} + + + ## ------------------------------------------------------------------ ## subroles @@ -253,6 +262,7 @@ sub add_role { (blessed($role) && $role->isa('Moose::Meta::Role')) || confess "Roles must be instances of Moose::Meta::Role"; push @{$self->get_roles} => $role; + $self->reset_package_cache_flag; } sub calculate_all_roles { @@ -285,7 +295,16 @@ sub method_metaclass { 'Moose::Meta::Role::Method' } sub get_method_map { my $self = shift; - my $map = {}; + + my $current = Class::MOP::check_package_cache_flag($self->name); + + if (defined $self->{'_package_cache_flag'} && $self->{'_package_cache_flag'} == $current) { + return $self->{'methods'} ||= {}; + } + + $self->{_package_cache_flag} = $current; + + my $map = $self->{'methods'} ||= {}; my $role_name = $self->name; my $method_metaclass = $self->method_metaclass; @@ -295,6 +314,10 @@ sub get_method_map { foreach my $symbol (keys %all_code) { my $code = $all_code{$symbol}; + next if exists $map->{$symbol} && + defined $map->{$symbol} && + $map->{$symbol}->body == $code; + my ($pkg, $name) = Class::MOP::get_code_info($code); if ($pkg->can('meta') @@ -305,7 +328,7 @@ sub get_method_map { # loudly (in the case of Curses.pm) so we # just be a little overly cautious here. # - SL - && eval { no warnings; blessed($pkg->meta) } + && eval { no warnings; blessed($pkg->meta) } # FIXME calls meta && $pkg->meta->isa('Moose::Meta::Role')) { my $role = $pkg->meta->name; next unless $self->does_role($role); @@ -344,6 +367,59 @@ sub has_method { exists $self->get_method_map->{$name} ? 1 : 0 } +# FIXME this is copypasated from Class::MOP::Class +# refactor to inherit from some common base +sub wrap_method_body { + my ( $self, %args ) = @_; + + my $body = delete $args{body}; # delete is for compat + + ('CODE' eq ref($body)) + || confess "Your code block must be a CODE reference"; + + $self->method_metaclass->wrap( $body => ( + package_name => $self->name, + %args, + )); +} + +sub add_method { + my ($self, $method_name, $method) = @_; + (defined $method_name && $method_name) + || confess "You must define a method name"; + + my $body; + if (blessed($method)) { + $body = $method->body; + if ($method->package_name ne $self->name && + $method->name ne $method_name) { + warn "Hello there, got something for you." + . " Method says " . $method->package_name . " " . $method->name + . " Class says " . $self->name . " " . $method_name; + $method = $method->clone( + package_name => $self->name, + name => $method_name + ) if $method->can('clone'); + } + } + else { + $body = $method; + $method = $self->wrap_method_body( body => $body, name => $method_name ); + } + + $method->attach_to_class($self); + + $self->get_method_map->{$method_name} = $method; + + my $full_method_name = ($self->name . '::' . $method_name); + $self->add_package_symbol( + { sigil => '&', type => 'CODE', name => $method_name }, + Class::MOP::subname($full_method_name => $body) + ); + + $self->update_package_cache_flag; # still valid, since we just added the method to the map, and if it was invalid before that then get_method_map updated it +} + sub find_method_by_name { (shift)->get_method(@_) } sub get_method_list { @@ -560,6 +636,8 @@ probably not that much really). =item B +=item B + =item B =back @@ -608,6 +686,10 @@ probably not that much really). =item B +=item B + +=item B + =item B =item B diff --git a/lib/Moose/Meta/Role/Application.pm b/lib/Moose/Meta/Role/Application.pm index f59bda2..393adc7 100644 --- a/lib/Moose/Meta/Role/Application.pm +++ b/lib/Moose/Meta/Role/Application.pm @@ -4,7 +4,8 @@ use strict; use warnings; use metaclass; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; __PACKAGE__->meta->add_attribute('method_exclusions' => ( @@ -29,7 +30,7 @@ sub new { : [ $params{excludes} ]); } - $class->meta->new_object(%params); + $class->_new(\%params); } sub is_method_excluded { diff --git a/lib/Moose/Meta/Role/Application/RoleSummation.pm b/lib/Moose/Meta/Role/Application/RoleSummation.pm index 99b0b3b..7886a81 100644 --- a/lib/Moose/Meta/Role/Application/RoleSummation.pm +++ b/lib/Moose/Meta/Role/Application/RoleSummation.pm @@ -6,11 +6,11 @@ use metaclass; use Carp 'confess'; use Scalar::Util 'blessed'; -use Data::Dumper; use Moose::Meta::Role::Composite; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Role::Application'; diff --git a/lib/Moose/Meta/Role/Application/ToClass.pm b/lib/Moose/Meta/Role/Application/ToClass.pm index 8101d75..32b6fb2 100644 --- a/lib/Moose/Meta/Role/Application/ToClass.pm +++ b/lib/Moose/Meta/Role/Application/ToClass.pm @@ -7,9 +7,8 @@ use metaclass; use Carp 'confess'; use Scalar::Util 'blessed'; -use Data::Dumper; - -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Role::Application'; diff --git a/lib/Moose/Meta/Role/Application/ToInstance.pm b/lib/Moose/Meta/Role/Application/ToInstance.pm index f512a15..7513ceb 100644 --- a/lib/Moose/Meta/Role/Application/ToInstance.pm +++ b/lib/Moose/Meta/Role/Application/ToInstance.pm @@ -7,7 +7,8 @@ use metaclass; use Carp 'confess'; use Scalar::Util 'blessed'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Role::Application::ToClass'; diff --git a/lib/Moose/Meta/Role/Application/ToRole.pm b/lib/Moose/Meta/Role/Application/ToRole.pm index c3e90d1..29a7eb7 100644 --- a/lib/Moose/Meta/Role/Application/ToRole.pm +++ b/lib/Moose/Meta/Role/Application/ToRole.pm @@ -7,9 +7,8 @@ use metaclass; use Carp 'confess'; use Scalar::Util 'blessed'; -use Data::Dumper; - -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Role::Application'; diff --git a/lib/Moose/Meta/Role/Composite.pm b/lib/Moose/Meta/Role/Composite.pm index c68aff5..5900480 100644 --- a/lib/Moose/Meta/Role/Composite.pm +++ b/lib/Moose/Meta/Role/Composite.pm @@ -7,7 +7,8 @@ use metaclass; use Carp 'confess'; use Scalar::Util 'blessed'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Role'; @@ -39,7 +40,7 @@ sub new { # and the name is created from the # roles if one has not been provided $params{name} ||= (join "|" => map { $_->name } @{$params{roles}}); - $class->meta->new_object(%params); + $class->_new(\%params); } # NOTE: @@ -110,4 +111,4 @@ L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. -=cut \ No newline at end of file +=cut diff --git a/lib/Moose/Meta/Role/Method.pm b/lib/Moose/Meta/Role/Method.pm index 5b03e94..1ab49ec 100644 --- a/lib/Moose/Meta/Role/Method.pm +++ b/lib/Moose/Meta/Role/Method.pm @@ -4,7 +4,8 @@ package Moose::Meta::Role::Method; use strict; use warnings; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Class::MOP::Method'; @@ -44,4 +45,4 @@ L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. -=cut \ No newline at end of file +=cut diff --git a/lib/Moose/Meta/Role/Method/Required.pm b/lib/Moose/Meta/Role/Method/Required.pm index 92b0f91..ee0c1f6 100644 --- a/lib/Moose/Meta/Role/Method/Required.pm +++ b/lib/Moose/Meta/Role/Method/Required.pm @@ -4,7 +4,8 @@ package Moose::Meta::Role::Method::Required; use strict; use warnings; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::Role::Method'; @@ -40,4 +41,4 @@ L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. -=cut \ No newline at end of file +=cut diff --git a/lib/Moose/Meta/TypeCoercion.pm b/lib/Moose/Meta/TypeCoercion.pm index 426e2b6..3f0618b 100644 --- a/lib/Moose/Meta/TypeCoercion.pm +++ b/lib/Moose/Meta/TypeCoercion.pm @@ -10,7 +10,8 @@ use Carp 'confess'; use Moose::Meta::Attribute; use Moose::Util::TypeConstraints (); -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; __PACKAGE__->meta->add_attribute('type_coercion_map' => ( diff --git a/lib/Moose/Meta/TypeCoercion/Union.pm b/lib/Moose/Meta/TypeCoercion/Union.pm index 7161765..6291347 100644 --- a/lib/Moose/Meta/TypeCoercion/Union.pm +++ b/lib/Moose/Meta/TypeCoercion/Union.pm @@ -8,7 +8,8 @@ use metaclass; use Carp 'confess'; use Scalar::Util 'blessed'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::TypeCoercion'; @@ -100,4 +101,4 @@ L This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. -=cut \ No newline at end of file +=cut diff --git a/lib/Moose/Meta/TypeConstraint.pm b/lib/Moose/Meta/TypeConstraint.pm index 21834fe..d527edd 100644 --- a/lib/Moose/Meta/TypeConstraint.pm +++ b/lib/Moose/Meta/TypeConstraint.pm @@ -11,7 +11,10 @@ use overload '""' => sub { shift->name }, # stringify to tc name use Carp 'confess'; use Scalar::Util qw(blessed refaddr); -our $VERSION = '0.50'; +use base qw(Class::MOP::Object); + +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; __PACKAGE__->meta->add_attribute('name' => (reader => 'name')); @@ -57,7 +60,7 @@ __PACKAGE__->meta->add_attribute('package_defined_in' => ( sub new { my $class = shift; - my $self = $class->meta->new_object(@_); + my $self = $class->_new(@_); $self->compile_type_constraint() unless $self->_has_compiled_type_constraint; return $self; @@ -77,12 +80,12 @@ sub validate { sub get_message { my ($self, $value) = @_; - $value = (defined $value ? overload::StrVal($value) : 'undef'); if (my $msg = $self->message) { local $_ = $value; return $msg->($value); } else { + $value = (defined $value ? overload::StrVal($value) : 'undef'); return "Validation failed for '" . $self->name . "' failed with value $value"; } } @@ -92,7 +95,7 @@ sub get_message { sub equals { my ( $self, $type_or_name ) = @_; - my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name); + my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name) or return; return 1 if refaddr($self) == refaddr($other); @@ -115,7 +118,7 @@ sub equals { sub is_a_type_of { my ($self, $type_or_name) = @_; - my $type = Moose::Util::TypeConstraints::find_type_constraint($type_or_name); + my $type = Moose::Util::TypeConstraints::find_type_constraint($type_or_name) or return; ($self->equals($type) || $self->is_subtype_of($type)); } @@ -123,7 +126,7 @@ sub is_a_type_of { sub is_subtype_of { my ($self, $type_or_name) = @_; - my $type = Moose::Util::TypeConstraints::find_type_constraint($type_or_name); + my $type = Moose::Util::TypeConstraints::find_type_constraint($type_or_name) or return; my $current = $self; @@ -279,13 +282,24 @@ If you wish to use features at this depth, please come to the =item B +This checks the current type against the supplied type (only). +Returns false if the two types are not equal. It also returns false if +you provide the type as a name, and the type name isn't found in the +type registry. + =item B -This checks the current type name, and if it does not match, -checks if it is a subtype of it. +This checks the current type against the supplied type, or if the +current type is a sub-type of the type name or object supplied. It +also returns false if you provide the type as a name, and the type +name isn't found in the type registry. =item B +This checks the current type is a sub-type of the type name or object +supplied. It also returns false if you provide the type as a name, and +the type name isn't found in the type registry. + =item B =item B @@ -306,10 +320,16 @@ the C will be used to construct a custom error message. =item B +The name of the type in the global type registry. + =item B +This type's parent type. + =item B +Returns true if this type has a parent type. + =item B =item B diff --git a/lib/Moose/Meta/TypeConstraint/Class.pm b/lib/Moose/Meta/TypeConstraint/Class.pm index 0fc4ec9..888d1ae 100644 --- a/lib/Moose/Meta/TypeConstraint/Class.pm +++ b/lib/Moose/Meta/TypeConstraint/Class.pm @@ -7,7 +7,8 @@ use metaclass; use Scalar::Util 'blessed'; use Moose::Util::TypeConstraints (); -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::TypeConstraint'; @@ -20,7 +21,7 @@ sub new { my ( $class, %args ) = @_; $args{parent} = Moose::Util::TypeConstraints::find_type_constraint('Object'); - my $self = $class->meta->new_object(%args); + my $self = $class->_new(\%args); $self->_create_hand_optimized_type_constraint; $self->compile_type_constraint(); diff --git a/lib/Moose/Meta/TypeConstraint/Enum.pm b/lib/Moose/Meta/TypeConstraint/Enum.pm index 1d94095..76093c2 100644 --- a/lib/Moose/Meta/TypeConstraint/Enum.pm +++ b/lib/Moose/Meta/TypeConstraint/Enum.pm @@ -6,7 +6,8 @@ use metaclass; use Moose::Util::TypeConstraints (); -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::TypeConstraint'; @@ -20,7 +21,7 @@ sub new { $args{parent} = Moose::Util::TypeConstraints::find_type_constraint('Str'); - my $self = $class->meta->new_object(%args); + my $self = $class->_new(\%args); $self->compile_type_constraint() unless $self->_has_compiled_type_constraint; diff --git a/lib/Moose/Meta/TypeConstraint/Parameterizable.pm b/lib/Moose/Meta/TypeConstraint/Parameterizable.pm index 7f73004..95193eb 100644 --- a/lib/Moose/Meta/TypeConstraint/Parameterizable.pm +++ b/lib/Moose/Meta/TypeConstraint/Parameterizable.pm @@ -4,7 +4,8 @@ use strict; use warnings; use metaclass; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::TypeConstraint'; diff --git a/lib/Moose/Meta/TypeConstraint/Parameterized.pm b/lib/Moose/Meta/TypeConstraint/Parameterized.pm index a7d45ae..7ece248 100644 --- a/lib/Moose/Meta/TypeConstraint/Parameterized.pm +++ b/lib/Moose/Meta/TypeConstraint/Parameterized.pm @@ -8,7 +8,8 @@ use Scalar::Util 'blessed'; use Carp 'confess'; use Moose::Util::TypeConstraints; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::TypeConstraint'; diff --git a/lib/Moose/Meta/TypeConstraint/Registry.pm b/lib/Moose/Meta/TypeConstraint/Registry.pm index fd87fdf..19784e9 100644 --- a/lib/Moose/Meta/TypeConstraint/Registry.pm +++ b/lib/Moose/Meta/TypeConstraint/Registry.pm @@ -8,7 +8,8 @@ use metaclass; use Scalar::Util 'blessed'; use Carp 'confess'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Class::MOP::Object'; @@ -26,7 +27,7 @@ __PACKAGE__->meta->add_attribute('type_constraints' => ( sub new { my $class = shift; - my $self = $class->meta->new_object(@_); + my $self = $class->_new(@_); return $self; } @@ -37,11 +38,14 @@ sub has_type_constraint { sub get_type_constraint { my ($self, $type_name) = @_; + return unless defined $type_name; $self->type_constraints->{$type_name} } sub add_type_constraint { my ($self, $type) = @_; + confess("No type supplied / type is not a valid type constraint") + unless ($type && blessed $type && $type->isa('Moose::Meta::TypeConstraint')); $self->type_constraints->{$type->name} = $type; } @@ -93,8 +97,15 @@ base Moose registry and base Moose types will automagically be found too). =item B +Returns a type constraint object from the registry by name. Will return +false if the supplied type name cannot be found. + =item B +Adds a type constraint object to the registry. Will throw an exception if +no type is supplied, or the supplied object does not inherit from +L + =item B =back diff --git a/lib/Moose/Meta/TypeConstraint/Role.pm b/lib/Moose/Meta/TypeConstraint/Role.pm index 1cd13de..dc5029c 100644 --- a/lib/Moose/Meta/TypeConstraint/Role.pm +++ b/lib/Moose/Meta/TypeConstraint/Role.pm @@ -7,7 +7,8 @@ use metaclass; use Scalar::Util 'blessed'; use Moose::Util::TypeConstraints (); -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::TypeConstraint'; @@ -20,7 +21,7 @@ sub new { my ( $class, %args ) = @_; $args{parent} = Moose::Util::TypeConstraints::find_type_constraint('Role'); - my $self = $class->meta->new_object(%args); + my $self = $class->_new(\%args); $self->_create_hand_optimized_type_constraint; $self->compile_type_constraint(); diff --git a/lib/Moose/Meta/TypeConstraint/Union.pm b/lib/Moose/Meta/TypeConstraint/Union.pm index 293cdb2..f754834 100644 --- a/lib/Moose/Meta/TypeConstraint/Union.pm +++ b/lib/Moose/Meta/TypeConstraint/Union.pm @@ -7,7 +7,8 @@ use metaclass; use Moose::Meta::TypeCoercion::Union; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use base 'Moose::Meta::TypeConstraint'; @@ -111,7 +112,7 @@ Moose::Meta::TypeConstraint::Union - A union of Moose type constraints =head1 DESCRIPTION This metaclass represents a union of Moose type constraints. More -details to be explained later (possibly in a Cookbook::Recipe). +details to be explained later (possibly in a Cookbook recipe). This actually used to be part of Moose::Meta::TypeConstraint, but it is now better off in it's own file. diff --git a/lib/Moose/Object.pm b/lib/Moose/Object.pm index f0b8463..242425f 100644 --- a/lib/Moose/Object.pm +++ b/lib/Moose/Object.pm @@ -9,26 +9,33 @@ use if ( not our $__mx_is_compiled ), metaclass => 'Moose::Meta::Class'; use Carp 'confess'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; sub new { my $class = shift; - my %params; - my $meta = $class->meta; + my $params = $class->BUILDARGS(@_); + my $self = $class->meta->new_object($params); + $self->BUILDALL($params); + return $self; +} + +sub BUILDARGS { + my $class = shift; if (scalar @_ == 1) { if (defined $_[0]) { (ref($_[0]) eq 'HASH') - || $meta->throw_error("Single parameters to new() must be a HASH ref", data => $_[0]); - %params = %{$_[0]}; + || $class->throw_error("Single parameters to new() must be a HASH ref", data => $_[0]); + return {%{$_[0]}}; + } + else { + return {}; # FIXME this is compat behavior, but is it correct? } - } + } else { - %params = @_; + return {@_}; } - my $self = $meta->new_object(%params); - $self->BUILDALL(\%params); - return $self; } sub BUILDALL { @@ -67,6 +74,16 @@ sub DESTROY { $_[0]->DEMOLISHALL; } +# support for UNIVERSAL::DOES ... +BEGIN { + my $does = UNIVERSAL->can("DOES") ? "SUPER::DOES" : "isa"; + eval 'sub DOES { + my ( $self, $class_or_role_name ) = @_; + return $self->'.$does.'($class_or_role_name) + || $self->does($class_or_role_name); + }'; +} + # new does() methods will be created # as approiate see Moose::Meta::Role sub does { @@ -129,7 +146,12 @@ This will return the metaclass associated with the given class. =item B -This will create a new instance and call C. +This will call C, create a new instance and call C. + +=item B + +This method processes an argument list into a hash reference. It is used by +C. =item B @@ -145,6 +167,12 @@ This will call every C method in the inheritance hierarchy. This will check if the invocant's class C a given C<$role_name>. This is similar to C for object, but it checks the roles instead. +=item B + +A Moose Role aware implementation of L. + +C is equivalent to C or C. + =item B Cmon, how many times have you written the following code while debugging: diff --git a/lib/Moose/Role.pm b/lib/Moose/Role.pm index 4467519..981546a 100644 --- a/lib/Moose/Role.pm +++ b/lib/Moose/Role.pm @@ -10,7 +10,8 @@ use Carp 'confess', 'croak'; use Data::OptList; use Sub::Exporter; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; use Moose (); @@ -19,189 +20,136 @@ use Moose::Util (); use Moose::Meta::Role; use Moose::Util::TypeConstraints; -{ - my ( $CALLER, %METAS ); +sub extends { + croak "Roles do not currently support 'extends'"; +} + +sub with { + Moose::Util::apply_all_roles( Moose::Meta::Role->initialize(shift), @_ ); +} + +sub requires { + my $meta = Moose::Meta::Role->initialize(shift); + croak "Must specify at least one method" unless @_; + $meta->add_required_methods(@_); +} + +sub excludes { + my $meta = Moose::Meta::Role->initialize(shift); + croak "Must specify at least one role" unless @_; + $meta->add_excluded_roles(@_); +} - sub _find_meta { - my $role = $CALLER; +sub has { + my $meta = Moose::Meta::Role->initialize(shift); + my $name = shift; + croak 'Usage: has \'name\' => ( key => value, ... )' if @_ == 1; + my %options = @_; + my $attrs = ( ref($name) eq 'ARRAY' ) ? $name : [ ($name) ]; + $meta->add_attribute( $_, %options ) for @$attrs; +} - return $METAS{$role} if exists $METAS{$role}; +sub before { + my $meta = Moose::Meta::Role->initialize(shift); + my $code = pop @_; - # make a subtype for each Moose class - role_type $role unless find_type_constraint($role); + for (@_) { + croak "Moose::Role do not currently support " + . ref($_) + . " references for before method modifiers" + if ref $_; + $meta->add_before_method_modifier( $_, $code ); + } +} - my $meta; - if ($role->can('meta')) { - $meta = $role->meta(); - (blessed($meta) && $meta->isa('Moose::Meta::Role')) - || confess "You already have a &meta function, but it does not return a Moose::Meta::Role"; - } - else { - $meta = Moose::Meta::Role->initialize($role); - $meta->alias_method('meta' => sub { $meta }); - } +sub after { + my $meta = Moose::Meta::Role->initialize(shift); - return $METAS{$role} = $meta; + my $code = pop @_; + for (@_) { + croak "Moose::Role do not currently support " + . ref($_) + . " references for after method modifiers" + if ref $_; + $meta->add_after_method_modifier( $_, $code ); } +} +sub around { + my $meta = Moose::Meta::Role->initialize(shift); + my $code = pop @_; + for (@_) { + croak "Moose::Role do not currently support " + . ref($_) + . " references for around method modifiers" + if ref $_; + $meta->add_around_method_modifier( $_, $code ); + } +} - my %exports = ( - extends => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::extends' => sub { - croak "Roles do not currently support 'extends'" - }); - }, - with => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::with' => sub (@) { - Moose::Util::apply_all_roles($meta, @_) - }); - }, - requires => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::requires' => sub (@) { - croak "Must specify at least one method" unless @_; - $meta->add_required_methods(@_); - }); - }, - excludes => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::excludes' => sub (@) { - croak "Must specify at least one role" unless @_; - $meta->add_excluded_roles(@_); - }); - }, - has => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::has' => sub ($;%) { - my $name = shift; - croak 'Usage: has \'name\' => ( key => value, ... )' if @_ == 1; - my %options = @_; - my $attrs = ( ref($name) eq 'ARRAY' ) ? $name : [ ($name) ]; - $meta->add_attribute( $_, %options ) for @$attrs; - }); - }, - before => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::before' => sub (@&) { - my $code = pop @_; - do { - croak "Moose::Role do not currently support " - . ref($_) - . " references for before method modifiers" - if ref $_; - $meta->add_before_method_modifier($_, $code) - } for @_; - }); - }, - after => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::after' => sub (@&) { - my $code = pop @_; - do { - croak "Moose::Role do not currently support " - . ref($_) - . " references for after method modifiers" - if ref $_; - $meta->add_after_method_modifier($_, $code) - } for @_; - }); - }, - around => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::around' => sub (@&) { - my $code = pop @_; - do { - croak "Moose::Role do not currently support " - . ref($_) - . " references for around method modifiers" - if ref $_; - $meta->add_around_method_modifier($_, $code) - } for @_; - }); - }, - # see Moose.pm for discussion - super => sub { - return Class::MOP::subname('Moose::Role::super' => sub { - return unless $Moose::SUPER_BODY; $Moose::SUPER_BODY->(@Moose::SUPER_ARGS) - }); - }, - override => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::override' => sub ($&) { - my ($name, $code) = @_; - $meta->add_override_method_modifier($name, $code); - }); - }, - inner => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::inner' => sub { - croak "Moose::Role cannot support 'inner'"; - }); - }, - augment => sub { - my $meta = _find_meta(); - return Class::MOP::subname('Moose::Role::augment' => sub { - croak "Moose::Role cannot support 'augment'"; - }); - }, - confess => sub { - return \&Carp::confess; - }, - blessed => sub { - return \&Scalar::Util::blessed; - } - ); - - my $exporter = Sub::Exporter::build_exporter({ - exports => \%exports, - groups => { - default => [':all'] - } - }); - - sub import { - $CALLER = - ref $_[1] && defined $_[1]->{into} ? $_[1]->{into} - : ref $_[1] - && defined $_[1]->{into_level} ? caller( $_[1]->{into_level} ) - : caller(); - - # this works because both pragmas set $^H (see perldoc perlvar) - # which affects the current compilation - i.e. the file who use'd - # us - which is why we don't need to do anything special to make - # it affect that file rather than this one (which is already compiled) - - strict->import; - warnings->import; - - # we should never export to main - return if $CALLER eq 'main'; - - goto $exporter; - }; - - sub unimport { - no strict 'refs'; - my $class = Moose::_get_caller(@_); - - # loop through the exports ... - foreach my $name ( keys %exports ) { - - # if we find one ... - if ( defined &{ $class . '::' . $name } ) { - my $keyword = \&{ $class . '::' . $name }; - - # make sure it is from Moose::Role - my ($pkg_name) = Class::MOP::get_code_info($keyword); - next if $pkg_name ne 'Moose::Role'; - - # and if it is from Moose::Role then undef the slot - delete ${ $class . '::' }{$name}; +# see Moose.pm for discussion +sub super { + return unless $Moose::SUPER_BODY; + $Moose::SUPER_BODY->(@Moose::SUPER_ARGS); +} + +sub override { + my $meta = Moose::Meta::Role->initialize(shift); + my ( $name, $code ) = @_; + $meta->add_override_method_modifier( $name, $code ); +} + +sub inner { + croak "Moose::Role cannot support 'inner'"; +} + +sub augment { + croak "Moose::Role cannot support 'augment'"; +} + +my $exporter = Moose::Exporter->setup_import_methods( + with_caller => [ + qw( with requires excludes has before after around override make_immutable ) + ], + as_is => [ + qw( extends super inner augment ), + \&Carp::confess, + \&Scalar::Util::blessed, + ], +); + +sub init_meta { + shift; + my %args = @_; + + my $role = $args{for_class} + or confess + "Cannot call init_meta without specifying a for_class"; + + my $metaclass = $args{metaclass} || "Moose::Meta::Role"; + + # make a subtype for each Moose class + role_type $role unless find_type_constraint($role); + + # FIXME copy from Moose.pm + my $meta; + if ($role->can('meta')) { + $meta = $role->meta(); + (blessed($meta) && $meta->isa('Moose::Meta::Role')) + || confess "You already have a &meta function, but it does not return a Moose::Meta::Role"; + } + else { + $meta = $metaclass->initialize($role); + + $meta->add_method( + 'meta' => sub { + # re-initialize so it inherits properly + $metaclass->initialize( ref($_[0]) || $_[0] ); } - } + ); } + + return $meta; } 1; @@ -277,6 +225,15 @@ Moose::Role offers a way to remove the keywords it exports, through the C method. You simply have to say C at the bottom of your code for this to work. +=head2 B<< Moose::Role->init_meta(for_class => $role, metaclass => $metaclass) >> + +The C method sets up the metaclass object for the role +specified by C. It also injects a a C accessor into +the role so you can get at this object. + +The default metaclass is L. You can specify an +alternate metaclass with the C parameter. + =head1 CAVEATS Role support has only a few caveats: diff --git a/lib/Moose/Unsweetened.pod b/lib/Moose/Unsweetened.pod new file mode 100644 index 0000000..bfaa3b7 --- /dev/null +++ b/lib/Moose/Unsweetened.pod @@ -0,0 +1,332 @@ +=pod + +=head1 NAME + +Moose::Unsweetened - Moose idioms in plain old Perl 5 without the sugar + +=head1 DESCRIPTION + +If you're trying to figure out just what the heck Moose does, and how +it saves you time, you might find it helpful to see what Moose is +I doing for you. This document shows you the translation from +Moose sugar back to plain old Perl 5. + +=head1 CLASSES AND ATTRIBUTES + +First, we define two very small classes the Moose way. + + package Person; + + use DateTime; + use DateTime::Format::Natural; + use Moose; + use Moose::Util::TypeConstraints; + + has name => ( + is => 'rw', + isa => 'Str', + required => 1, + ); + + # Moose doesn't know about non-Moose-based classes. + class_type 'DateTime'; + + my $en_parser = DateTime::Format::Natural->new( + lang => 'en', + time_zone => 'UTC', + ); + + coerce 'DateTime' + => from 'Str' + => via { $en_parser->parse_datetime($_) }; + + has birth_date => ( + is => 'rw', + isa => 'DateTime', + ); + + subtype 'ShirtSize' + => as 'Str' + => where { /^(?:s|m|l|xl|xxl)$/i } + => message { "$_ is not a valid shirt size (s, m, l, xl, xxl)" }; + + has shirt_size => ( + is => 'rw', + isa => 'ShirtSize', + default => 'l', + ); + +This is a fairly simple class with three attributes. We also define a +type to validate t-shirt sizes because we don't want to end up with +something like "blue" for the shirt size! + + package User; + + use Email::Valid; + use Moose; + use Moose::Util::TypeConstraints; + + extends 'Person'; + + subtype 'Email' + => as 'Str' + => where { Email::Valid->address($_) } + => message { "$_ is not a valid email address" }; + + has email_address => ( + is => 'rw', + isa => 'Email', + required => 1, + ); + +This class subclasses Person to add a single attribute, email address. + +Now we will show what these classes would look like in plain old Perl +5. For the sake of argument, we won't use any base classes or any +helpers like C. + + package Person; + + use strict; + use warnings; + + use Carp qw( confess ); + use DateTime; + use DateTime::Format::Natural; + + + sub new { + my $class = shift; + my %p = ref $_[0] ? %{ $_[0] } : @_; + + exists $p{name} + or confess 'name is a required attribute'; + $class->_validate_name( $p{name} ); + + exists $p{birth_date} + or confess 'birth_date is a required attribute'; + + $p{birth_date} = $class->_coerce_birth_date($date ); + $class->_validate_birth_date( $date ); + + $p{shirt_size} = 'l' + unless exists $p{shirt_size}: + + $class->_validate_shirt_size( $p{shirt_size} ); + + my %self = map { $_ => $p{$_} } qw( name shirt_size ); + $self{birth_date} = $date; + + return bless \%self, $class; + } + + sub _validate_name { + shift; + my $name = shift; + + local $Carp::CarpLevel = $Carp::CarpLevel + 1; + + defined $name + or confess 'name must be a string'; + } + + { + my $en_parser = DateTime::Format::Natural->new( + lang => 'en', + time_zone => 'UTC', + ); + + sub _coerce_birth_date { + shift; + my $date = shift; + + return $date unless defined $date && ! ref $date; + + my $dt = $en_parser->parse_datetime($date); + + return $dt ? $dt : undef; + } + } + + sub _validate_birth_date { + shift; + my $birth_date = shift; + + local $Carp::CarpLevel = $Carp::CarpLevel + 1; + + $birth_date->isa('DateTime') ) + or confess 'birth_date must be a DateTime object'; + } + + sub _validate_shirt_size { + shift; + my $shirt_size = shift; + + local $Carp::CarpLevel = $Carp::CarpLevel + 1; + + defined $shirt_size + or confess 'shirt_size cannot be undef'; + + $shirt_size =~ /^(?:s|m|l|xl|xxl)$/ + or confess "$shirt_size is not a valid shirt size (s, m, l, xl, xxl)"; + } + + sub name { + my $self = shift; + + if (@_) { + $self->_validate_name( $_[0] ); + $self->{name} = $_[0]; + } + + return $self->{name}; + } + + sub birth_date { + my $self = shift; + + if (@_) { + my $date = $self->_coerce_birth_date( $_[0] ); + $self->_validate_birth_date( $date ); + + $self->{birth_date} = $date; + } + + return $self->{birth_date}; + } + + sub shirt_size { + my $self = shift; + + if (@_) { + $self->_validate_shirt_size( $_[0] ); + $self->{shirt_size} = $_[0]; + } + + return $self->{shirt_size}; + } + +Wow, that was a mouthful! One thing to note is just how much space the +data validation code consumes. As a result, it's pretty common for +Perl 5 programmers to just not bother, which results in much more +fragile code. + +Did you spot the (intentional) bug? + +It's in the C<_validate_birth_date()> method. We should check that +that value in C<$birth_date> is actually defined and object before we +go and call C on it! Leaving out those checks means our data +validation code could actually cause our program to die. Oops. + +There's one bit of code in there worth explaining, which is the +handling of the birth date for coercion. In both the constructor and +accessor, we first take a copy of the birth date before passing it to +the coercion routine. This is to avoid changing the value as it was +passed to those methods, which could cause problems for the caller. + +Also note that if we add a superclass to Person we'll have to change +the constructor to account for that. + +(As an aside, getting all the little details of what Moose does for +you just right in this code was not easy, which just emphasizes the +point, that Moose saves you a lot of work!) + +Now let's see User: + + package User; + + use strict; + use warnings; + + use Carp qw( confess ); + use Email::Valid; + use Scalar::Util qw( blessed ); + + use base 'Person'; + + + sub new { + my $class = shift; + my %p = ref $_[0] ? %{ $_[0] } : @_; + + exists $p{email_address} + or confess 'email_address is a required attribute'; + $class->_validate_email_address( $p{email_address} ); + + my $self = $class->SUPER::new(%p); + + $self->{email_address} = $p{email_address}; + + return $self; + } + + sub _validate_email_address { + shift; + my $email_address = shift; + + local $Carp::CarpLevel = $Carp::CarpLevel + 1; + + defined $email_address + or confess 'email_address must be a string'; + + Email::Valid->address($email_address) + or confess "$email_address is not a valid email address"; + } + + sub email_address { + my $self = shift; + + if (@_) { + $self->_validate_email_address( $_[0] ); + $self->{email_address} = $_[0]; + } + + return $self->{email_address}; + } + +That one was shorter, but it only has one attribute. + +Between the two classes, we have a whole lot of code that doesn't do +much. We could probably simplify this by defining some sort of +"attribute and validation" hash, like this: + + package Person; + + my %Attr = ( + name => { + required => 1, + validate => sub { defined $_ }, + }, + birth_date => { + required => 1, + validate => sub { blessed $_ && $_->isa('DateTime') }, + }, + shirt_size => { + required => 1, + validate => sub { defined $_ && $_ =~ /^(?:s|m|l|xl|xxl)$/i }, + } + ); + +Then we could define a base class that would accept such a definition, +and do the right thing. Keep that sort of thing up and we're well on +our way to writing a half-assed version of Moose! + +Of course, there are CPAN modules that do some of what Moose does, +like C, C, and so on. But none of them +put together all of Moose's features along with a layer of declarative +sugar. + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Util.pm b/lib/Moose/Util.pm index f0ee00c..8a1517c 100644 --- a/lib/Moose/Util.pm +++ b/lib/Moose/Util.pm @@ -6,9 +6,10 @@ use warnings; use Sub::Exporter; use Scalar::Util 'blessed'; use Carp 'confess'; -use Class::MOP 0.56; +use Class::MOP 0.57; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; my @exports = qw[ @@ -71,33 +72,30 @@ sub search_class_by_role { sub apply_all_roles { my $applicant = shift; - + confess "Must specify at least one role to apply to $applicant" unless @_; - - my $roles = Data::OptList::mkopt([ @_ ]); - - #use Data::Dumper; - #warn Dumper $roles; - - my $meta = (blessed $applicant ? $applicant : find_meta($applicant)); - + + my $roles = Data::OptList::mkopt( [@_] ); + + my $meta = ( blessed $applicant ? $applicant : find_meta($applicant) ); + foreach my $role_spec (@$roles) { - Class::MOP::load_class($role_spec->[0]); + Class::MOP::load_class( $role_spec->[0] ); } - - ($_->[0]->can('meta') && $_->[0]->meta->isa('Moose::Meta::Role')) - || confess "You can only consume roles, " . $_->[0] . " is not a Moose role" - foreach @$roles; - if (scalar @$roles == 1) { - my ($role, $params) = @{$roles->[0]}; - $role->meta->apply($meta, (defined $params ? %$params : ())); + ( $_->[0]->can('meta') && $_->[0]->meta->isa('Moose::Meta::Role') ) + || confess "You can only consume roles, " + . $_->[0] + . " is not a Moose role" + foreach @$roles; + + if ( scalar @$roles == 1 ) { + my ( $role, $params ) = @{ $roles->[0] }; + $role->meta->apply( $meta, ( defined $params ? %$params : () ) ); } else { - Moose::Meta::Role->combine( - @$roles - )->apply($meta); - } + Moose::Meta::Role->combine( @$roles )->apply($meta); + } } # instance deconstruction ... @@ -151,10 +149,10 @@ sub add_method_modifier { my $add_modifier_method = 'add_' . $modifier_name . '_method_modifier'; if ( my $method_modifier_type = ref( @{$args}[0] ) ) { if ( $method_modifier_type eq 'Regexp' ) { - my @all_methods = $meta->compute_all_applicable_methods; + my @all_methods = $meta->get_all_methods; my @matched_methods - = grep { $_->{name} =~ @{$args}[0] } @all_methods; - $meta->$add_modifier_method( $_->{name}, $code ) + = grep { $_->name =~ @{$args}[0] } @all_methods; + $meta->$add_modifier_method( $_->name, $code ) for @matched_methods; } } diff --git a/lib/Moose/Util/MetaRole.pm b/lib/Moose/Util/MetaRole.pm new file mode 100644 index 0000000..4724da7 --- /dev/null +++ b/lib/Moose/Util/MetaRole.pm @@ -0,0 +1,257 @@ +package Moose::Util::MetaRole; + +use strict; +use warnings; + +use List::MoreUtils qw( all ); + +sub apply_metaclass_roles { + my %options = @_; + + my $for = $options{for_class}; + + my $meta = _make_new_metaclass( $for, \%options ); + + for my $tor_class ( grep { $options{ $_ . '_roles' } } + qw( constructor_class destructor_class ) ) { + + my $class = _make_new_class( + $meta->$tor_class(), + $options{ $tor_class . '_roles' } + ); + + $meta->$tor_class($class); + } + + return $meta; +} + +sub _make_new_metaclass { + my $for = shift; + my $options = shift; + + return $for->meta() + unless grep { exists $options->{ $_ . '_roles' } } + qw( + metaclass + attribute_metaclass + method_metaclass + instance_metaclass + ); + + my $new_metaclass + = _make_new_class( ref $for->meta(), $options->{metaclass_roles} ); + + my $old_meta = $for->meta(); + + # This could get called for a Moose::Meta::Role as well as a Moose::Meta::Class + my %classes = map { + $_ => _make_new_class( $old_meta->$_(), $options->{ $_ . '_roles' } ) + } + grep { $old_meta->can($_) } + qw( + attribute_metaclass + method_metaclass + instance_metaclass + ); + + return $new_metaclass->reinitialize( $for, %classes ); +} + +sub apply_base_class_roles { + my %options = @_; + + my $for = $options{for_class}; + + my $meta = $for->meta(); + + my $new_base = _make_new_class( + $for, + $options{roles}, + [ $meta->superclasses() ], + ); + + $meta->superclasses($new_base) + if $new_base ne $meta->name(); +} + +sub _make_new_class { + my $existing_class = shift; + my $roles = shift; + my $superclasses = shift || [$existing_class]; + + return $existing_class unless $roles; + + my $meta = $existing_class->meta(); + + return $existing_class + if $meta->can('does_role') && all { $meta->does_role($_) } @{$roles}; + + return Moose::Meta::Class->create_anon_class( + superclasses => $superclasses, + roles => $roles, + cache => 1, + )->name(); +} + +1; + +__END__ + +=head1 NAME + +Moose::Util::MetaRole - Apply roles to any metaclass, as well as the object base class + +=head1 SYNOPSIS + + package MyApp::Moose; + + use strict; + use warnings; + + use Moose (); + use Moose::Exporter; + use Moose::Util::Meta::Role; + + use MyApp::Role::Meta::Class; + use MyApp::Role::Meta::Method::Constructor; + use MyApp::Role::Object; + + Moose::Exporter->setup_import_methods( also => 'Moose' ); + + sub init_meta { + shift; + my %options = @_; + + Moose->init_meta(%options); + + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => $options{for_class}, + metaclass_roles => ['MyApp::Role::Meta::Class'], + constructor_class_roles => ['MyApp::Role::Meta::Method::Constructor'], + ); + + Moose::Util::MetaRole::apply_base_class_roles( + for_class => $options{for_class}, + roles => ['MyApp::Role::Object'], + ); + + return $options{for_class}->meta(); + } + +=head1 DESCRIPTION + +B + +This utility module is designed to help authors of Moose extensions +write extensions that are able to cooperate with other Moose +extensions. To do this, you must write your extensions as roles, which +can then be dynamically applyied to the caller's metaclasses. + +This module makes sure to preserve any existing superclasses and roles +already set for the meta objects, which means that any number of +extensions can apply roles in any order. + +=head1 USAGE + +B. The process of applying roles +to the metaclass reinitializes the metaclass object, which wipes out +any existing attributes already defined. However, as long as you do +this when your module is imported, the caller should not have any +attributes defined yet. + +The easiest way to ensure that this happens is to use +L and provide an C method that will be +called when imported. + +=head1 FUNCTIONS + +This module provides two functions. + +=head2 apply_metaclass_roles( ... ) + +This function will apply roles to one or more metaclasses for the +specified class. It accepts the following parameters: + +=over 4 + +=item * for_class => $name + +This specifies the class for which to alter the meta classes. + +=item * metaclass_roles => \@roles + +=item * attribute_metaclass_roles => \@roles + +=item * method_metaclass_roles => \@roles + +=item * instance_metaclass_roles => \@roles + +=item * constructor_class_roles => \@roles + +=item * destructor_class_roles => \@roles + +These parameter all specify one or more roles to be applied to the +specified metaclass. You can pass any or all of these parameters at +once. + +=back + +=head2 apply_base_class_roles( for_class => $class, roles => \@roles ) + +This function will apply the specified roles to the object's base class. + +=head1 PROBLEMS WITH METACLASS ROLES AND SUBCLASS + +Because of the way this module works, there is an ordering problem +which occurs in certain situations. This sequence of events causes an +error: + +=over 4 + +=item 1. + +There is a class (ClassA) which uses some extension(s) that apply +roles to the metaclass. + +=item 2. + +You have another class (ClassB) which wants to subclass ClassA and +apply some more extensions. + +=back + +Normally, the call to C will happen at run time, I the +additional extensions are applied. This causes an error when we try to +make the metaclass for ClassB compatible with the metaclass for +ClassA. + +We hope to be able to fix this in the future. + +For now the workaround is for ClassB to make sure it extends ClassA +I it loads extensions: + + package ClassB; + + use Moose; + + BEGIN { extends 'ClassA' } + + use MooseX::SomeExtension; + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Util/TypeConstraints.pm b/lib/Moose/Util/TypeConstraints.pm index adbb77c..5bb95c2 100644 --- a/lib/Moose/Util/TypeConstraints.pm +++ b/lib/Moose/Util/TypeConstraints.pm @@ -6,9 +6,10 @@ use warnings; use Carp (); use Scalar::Util 'blessed'; -use Sub::Exporter; +use Moose::Exporter; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; ## -------------------------------------------------------- @@ -62,38 +63,17 @@ use Moose::Meta::TypeCoercion::Union; use Moose::Meta::TypeConstraint::Registry; use Moose::Util::TypeConstraints::OptimizedConstraints; -my @exports = qw/ - type subtype class_type role_type as where message optimize_as - coerce from via - enum - find_type_constraint - register_type_constraint -/; - -Sub::Exporter::setup_exporter({ - exports => \@exports, - groups => { default => [':all'] } -}); - -sub unimport { - no strict 'refs'; - my $class = caller(); - # loop through the exports ... - foreach my $name (@exports) { - # if we find one ... - if (defined &{$class . '::' . $name}) { - my $keyword = \&{$class . '::' . $name}; - - # make sure it is from Moose - my ($pkg_name) = Class::MOP::get_code_info($keyword); - next if $@; - next if $pkg_name ne 'Moose::Util::TypeConstraints'; - - # and if it is from Moose then undef the slot - delete ${$class . '::'}{$name}; - } - } -} +Moose::Exporter->setup_import_methods( + as_is => [ + qw( + type subtype class_type role_type as where message optimize_as + coerce from via + enum + find_type_constraint + register_type_constraint ) + ], + _export_to_main => 1, +); ## -------------------------------------------------------- ## type registry and some useful functions for it @@ -245,21 +225,18 @@ sub find_or_create_does_type_constraint ($) { sub find_or_parse_type_constraint ($) { my $type_constraint_name = shift; - - return $REGISTRY->get_type_constraint($type_constraint_name) - if $REGISTRY->has_type_constraint($type_constraint_name); - my $constraint; - - if (_detect_type_constraint_union($type_constraint_name)) { + + if ($constraint = find_type_constraint($type_constraint_name)) { + return $constraint; + } elsif (_detect_type_constraint_union($type_constraint_name)) { $constraint = create_type_constraint_union($type_constraint_name); - } - elsif (_detect_parameterized_type_constraint($type_constraint_name)) { + } elsif (_detect_parameterized_type_constraint($type_constraint_name)) { $constraint = create_parameterized_type_constraint($type_constraint_name); } else { return; } - + $REGISTRY->add_type_constraint($constraint); return $constraint; } @@ -273,7 +250,9 @@ sub find_type_constraint ($) { if ( blessed $type and $type->isa("Moose::Meta::TypeConstraint") ) { return $type; - } else { + } + else { + return unless $REGISTRY->has_type_constraint($type); return $REGISTRY->get_type_constraint($type); } } @@ -361,7 +340,7 @@ sub enum ($;@) { sub create_enum_type_constraint ($$) { my ( $type_name, $values ) = @_; - + Moose::Meta::TypeConstraint::Enum->new( name => $type_name || '__ANON__', values => $values, @@ -399,7 +378,7 @@ sub _create_type_constraint ($$$;$$) { # FIXME should probably not be a special case if ( defined $parent and $parent = find_or_parse_type_constraint($parent) ) { - $class = "Moose::Meta::TypeConstraint::Parameterizable" + $class = "Moose::Meta::TypeConstraint::Parameterizable" if $parent->isa("Moose::Meta::TypeConstraint::Parameterizable"); } @@ -436,7 +415,7 @@ sub _create_type_constraint ($$$;$$) { sub _install_type_coercions ($$) { my ($type_name, $coercion_map) = @_; - my $type = $REGISTRY->get_type_constraint($type_name); + my $type = find_type_constraint($type_name); (defined $type) || confess "Cannot find type '$type_name', perhaps you forgot to load it."; if ($type->has_coercion) { @@ -679,7 +658,7 @@ Moose::Util::TypeConstraints - Type constraint system for Moose type 'Num' => where { Scalar::Util::looks_like_number($_) }; subtype 'Natural' - => as 'Num' + => as 'Int' => where { $_ > 0 }; subtype 'NaturalLessThanTen' @@ -705,7 +684,7 @@ and they are not used by Moose unless you tell it to. No type inference is performed, expression are not typed, etc. etc. etc. This is simply a means of creating small constraint functions which -can be used to simplify your own type-checking code, with the added +can be used to simplify your own type-checking code, with the added side benefit of making your intentions clearer through self-documentation. =head2 Slightly Less Important Caveat @@ -735,7 +714,7 @@ yet to have been created yet, is to simply do this: =head2 Default Type Constraints -This module also provides a simple hierarchy for Perl 5 types, here is +This module also provides a simple hierarchy for Perl 5 types, here is that hierarchy represented visually. Any @@ -767,6 +746,10 @@ parameterized, this means you can say: HashRef[CodeRef] # a hash of str to CODE ref mappings Maybe[Str] # value may be a string, may be undefined +B Unless you parameterize a type, then it is invalid to +include the square brackets. I.e. C will be +literally interpreted as a type name. + B The C type constraint for the most part works correctly now, but edge cases may still exist, please use it sparringly. @@ -776,10 +759,10 @@ existence check. This means that your class B be loaded for this type constraint to pass. I know this is not ideal for all, but it is a saner restriction than most others. -=head2 Type Constraint Naming +=head2 Type Constraint Naming -Since the types created by this module are global, it is suggested -that you namespace your types just as you would namespace your +Since the types created by this module are global, it is suggested +that you namespace your types just as you would namespace your modules. So instead of creating a I type for your B module, you would call the type I instead. @@ -800,7 +783,7 @@ L to declare a completely new type. -keys => HasLength, -values => IsArrayRef( IsObject )); -For more examples see the F +For more examples see the F test file. Here is an example of using L and it's non-test @@ -815,7 +798,7 @@ related C function. }))) }; -For a complete example see the +For a complete example see the F test file. =head1 FUNCTIONS @@ -883,10 +866,20 @@ This is just sugar for the type constraint construction syntax. This is just sugar for the type constraint construction syntax. +Takes a block/code ref as an argument. When the type constraint is +tested, the supplied code is run with the value to be tested in +$_. This block should return true or false to indicate whether or not +the constraint check passed. + =item B This is just sugar for the type constraint construction syntax. +Takes a block/code ref as an argument. When the type constraint fails, +then the code block is run (with the value provided in $_). This code +ref should return a string, which will be used in the text of the +exception thrown. + =item B This can be used to define a "hand optimized" version of your diff --git a/lib/Moose/Util/TypeConstraints/OptimizedConstraints.pm b/lib/Moose/Util/TypeConstraints/OptimizedConstraints.pm index a580686..3f41d93 100644 --- a/lib/Moose/Util/TypeConstraints/OptimizedConstraints.pm +++ b/lib/Moose/Util/TypeConstraints/OptimizedConstraints.pm @@ -5,7 +5,8 @@ use warnings; use Scalar::Util 'blessed', 'looks_like_number'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; sub Value { defined($_[0]) && !ref($_[0]) } @@ -18,15 +19,12 @@ sub Num { !ref($_[0]) && looks_like_number($_[0]) } sub Int { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ } -{ - no warnings 'uninitialized'; - sub ScalarRef { ref($_[0]) eq 'SCALAR' } - sub ArrayRef { ref($_[0]) eq 'ARRAY' } - sub HashRef { ref($_[0]) eq 'HASH' } - sub CodeRef { ref($_[0]) eq 'CODE' } - sub RegexpRef { ref($_[0]) eq 'Regexp' } - sub GlobRef { ref($_[0]) eq 'GLOB' } -} +sub ScalarRef { ref($_[0]) eq 'SCALAR' } +sub ArrayRef { ref($_[0]) eq 'ARRAY' } +sub HashRef { ref($_[0]) eq 'HASH' } +sub CodeRef { ref($_[0]) eq 'CODE' } +sub RegexpRef { ref($_[0]) eq 'Regexp' } +sub GlobRef { ref($_[0]) eq 'GLOB' } sub FileHandle { ref($_[0]) eq 'GLOB' && Scalar::Util::openhandle($_[0]) or blessed($_[0]) && $_[0]->isa("IO::Handle") } diff --git a/lib/Test/Moose.pm b/lib/Test/Moose.pm index 2a46ce7..2916ef1 100644 --- a/lib/Test/Moose.pm +++ b/lib/Test/Moose.pm @@ -8,7 +8,8 @@ use Test::Builder; use Moose::Util 'does_role', 'find_meta'; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; my @exports = qw[ diff --git a/lib/oose.pm b/lib/oose.pm index 5d02aee..ca6463a 100644 --- a/lib/oose.pm +++ b/lib/oose.pm @@ -5,7 +5,8 @@ use warnings; use Class::MOP; -our $VERSION = '0.50'; +our $VERSION = '0.57'; +$VERSION = eval $VERSION; our $AUTHORITY = 'cpan:STEVAN'; BEGIN { diff --git a/t/000_load.t b/t/000_load.t index 0c59d05..697464c 100644 --- a/t/000_load.t +++ b/t/000_load.t @@ -5,6 +5,10 @@ use warnings; use Test::More tests => 1; -BEGIN { - use_ok('Moose'); -} +package Foo; + +# Moose will issue a warning if we try to load it from the main +# package. +::use_ok('Moose'); + + diff --git a/t/000_recipes/001_point.t b/t/000_recipes/basics/001_point.t similarity index 82% rename from t/000_recipes/001_point.t rename to t/000_recipes/basics/001_point.t index 771a795..6c71552 100644 --- a/t/000_recipes/001_point.t +++ b/t/000_recipes/basics/001_point.t @@ -3,41 +3,39 @@ use strict; use warnings; -use Test::More tests => 58; +use Test::More tests => 57; use Test::Exception; -BEGIN { - use_ok('Moose'); +{ + package Point; + use Moose; + + has 'x' => (isa => 'Int', is => 'rw', required => 1); + has 'y' => (isa => 'Int', is => 'rw', required => 1); + + sub clear { + my $self = shift; + $self->x(0); + $self->y(0); + } + + __PACKAGE__->meta->make_immutable( debug => 0 ); } { - package Point; - use Moose; - - has 'x' => (isa => 'Int', is => 'ro'); - has 'y' => (isa => 'Int', is => 'rw'); - - sub clear { - my $self = shift; - $self->{x} = 0; - $self->y(0); - } - - __PACKAGE__->meta->make_immutable(debug => 0); -}{ - package Point3D; - use Moose; - - extends 'Point'; - - has 'z' => (isa => 'Int'); - - after 'clear' => sub { - my $self = shift; - $self->{z} = 0; - }; - - __PACKAGE__->meta->make_immutable(debug => 0); + package Point3D; + use Moose; + + extends 'Point'; + + has 'z' => (isa => 'Int', is => 'rw', required => 1); + + after 'clear' => sub { + my $self = shift; + $self->z(0); + }; + + __PACKAGE__->meta->make_immutable( debug => 0 ); } my $point = Point->new(x => 1, y => 2); @@ -55,9 +53,8 @@ dies_ok { } '... cannot assign a non-Int to y'; dies_ok { - $point->x(1000); -} '... cannot assign to a read-only method'; -is($point->x, 1, '... got the right (un-changed) value for x'); + Point->new(); +} '... must provide required attributes to new'; $point->clear(); @@ -89,15 +86,11 @@ is($point3d->x, 10, '... got the right value for x'); is($point3d->y, 15, '... got the right value for y'); is($point3d->{'z'}, 3, '... got the right value for z'); -dies_ok { - $point3d->z; -} '... there is no method for z'; - $point3d->clear(); is($point3d->x, 0, '... got the right (cleared) value for x'); is($point3d->y, 0, '... got the right (cleared) value for y'); -is($point3d->{'z'}, 0, '... got the right (cleared) value for z'); +is($point3d->z, 0, '... got the right (cleared) value for z'); dies_ok { Point3D->new(x => 10, y => 'Foo', z => 3); @@ -111,6 +104,10 @@ dies_ok { Point3D->new(x => 0, y => 10, z => 'Bar'); } '... cannot assign a non-Int to z'; +dies_ok { + Point3D->new(x => 10, y => 3); +} '... z is a required attribute for Point3D'; + # test some class introspection can_ok('Point', 'meta'); @@ -160,7 +157,7 @@ is_deeply( [ 'Point' ], '... Point3D gets the parent given to it'); -my @Point3D_methods = qw(new meta clear); +my @Point3D_methods = qw(new meta z clear); my @Point3D_attrs = ('z'); is_deeply( diff --git a/t/000_recipes/002_bank_account.t b/t/000_recipes/basics/002_bank_account.t similarity index 74% rename from t/000_recipes/002_bank_account.t rename to t/000_recipes/basics/002_bank_account.t index 4e0b571..ff318cc 100644 --- a/t/000_recipes/002_bank_account.t +++ b/t/000_recipes/basics/002_bank_account.t @@ -3,51 +3,49 @@ use strict; use warnings; -use Test::More tests => 24; +use Test::More tests => 23; use Test::Exception; -BEGIN { - use_ok('Moose'); -} - { package BankAccount; use Moose; - - has 'balance' => (isa => 'Num', is => 'rw', default => 0); + + has 'balance' => ( isa => 'Num', is => 'rw', default => 0 ); sub deposit { - my ($self, $amount) = @_; - $self->balance($self->balance + $amount); + my ( $self, $amount ) = @_; + $self->balance( $self->balance + $amount ); } - + sub withdraw { - my ($self, $amount) = @_; + my ( $self, $amount ) = @_; my $current_balance = $self->balance(); - ($current_balance >= $amount) + ( $current_balance >= $amount ) || confess "Account overdrawn"; - $self->balance($current_balance - $amount); + $self->balance( $current_balance - $amount ); } - - __PACKAGE__->meta->make_immutable(debug => 0); -}{ - package CheckingAccount; - use Moose; - - extends 'BankAccount'; - - has 'overdraft_account' => (isa => 'BankAccount', is => 'rw'); - - before 'withdraw' => sub { - my ($self, $amount) = @_; - my $overdraft_amount = $amount - $self->balance(); - if ($self->overdraft_account && $overdraft_amount > 0) { - $self->overdraft_account->withdraw($overdraft_amount); - $self->deposit($overdraft_amount); - } - }; - - __PACKAGE__->meta->make_immutable(debug => 0); + + __PACKAGE__->meta->make_immutable( debug => 0 ); +} + +{ + package CheckingAccount; + use Moose; + + extends 'BankAccount'; + + has 'overdraft_account' => ( isa => 'BankAccount', is => 'rw' ); + + before 'withdraw' => sub { + my ( $self, $amount ) = @_; + my $overdraft_amount = $amount - $self->balance(); + if ( $self->overdraft_account && $overdraft_amount > 0 ) { + $self->overdraft_account->withdraw($overdraft_amount); + $self->deposit($overdraft_amount); + } + }; + + __PACKAGE__->meta->make_immutable( debug => 0 ); } my $savings_account = BankAccount->new(balance => 250); diff --git a/t/000_recipes/003_binary_tree.t b/t/000_recipes/basics/003_binary_tree.t similarity index 77% rename from t/000_recipes/003_binary_tree.t rename to t/000_recipes/basics/003_binary_tree.t index cc7afbd..ac03763 100644 --- a/t/000_recipes/003_binary_tree.t +++ b/t/000_recipes/basics/003_binary_tree.t @@ -3,50 +3,46 @@ use strict; use warnings; -use Test::More tests => 34; +use Test::More tests => 33; use Test::Exception; use Scalar::Util 'isweak'; -BEGIN { - use_ok('Moose'); -} - { package BinaryTree; use Moose; - has 'node' => (is => 'rw', isa => 'Any'); + has 'node' => ( is => 'rw', isa => 'Any' ); has 'parent' => ( - is => 'rw', - isa => 'BinaryTree', + is => 'rw', + isa => 'BinaryTree', predicate => 'has_parent', - weak_ref => 1, + weak_ref => 1, ); has 'left' => ( - is => 'rw', - isa => 'BinaryTree', - predicate => 'has_left', + is => 'rw', + isa => 'BinaryTree', + predicate => 'has_left', lazy => 1, - default => sub { BinaryTree->new(parent => $_[0]) }, + default => sub { BinaryTree->new( parent => $_[0] ) }, ); has 'right' => ( - is => 'rw', - isa => 'BinaryTree', - predicate => 'has_right', - lazy => 1, - default => sub { BinaryTree->new(parent => $_[0]) }, + is => 'rw', + isa => 'BinaryTree', + predicate => 'has_right', + lazy => 1, + default => sub { BinaryTree->new( parent => $_[0] ) }, ); before 'right', 'left' => sub { - my ($self, $tree) = @_; - $tree->parent($self) if defined $tree; - }; - - __PACKAGE__->meta->make_immutable(debug => 0); + my ( $self, $tree ) = @_; + $tree->parent($self) if defined $tree; + }; + + __PACKAGE__->meta->make_immutable( debug => 0 ); } my $root = BinaryTree->new(node => 'root'); diff --git a/t/000_recipes/004_company.t b/t/000_recipes/basics/004_company.t similarity index 99% rename from t/000_recipes/004_company.t rename to t/000_recipes/basics/004_company.t index 44bfa13..cc53a71 100644 --- a/t/000_recipes/004_company.t +++ b/t/000_recipes/basics/004_company.t @@ -8,15 +8,13 @@ use Test::More; BEGIN { eval "use Regexp::Common; use Locale::US;"; plan skip_all => "Regexp::Common & Locale::US required for this test" if $@; - plan tests => 66; + plan tests => 65; } use Test::Exception; use Scalar::Util 'isweak'; -BEGIN { - use_ok('Moose'); -} + { package Address; diff --git a/t/000_recipes/005_coercion.t b/t/000_recipes/basics/005_coercion.t similarity index 62% rename from t/000_recipes/005_coercion.t rename to t/000_recipes/basics/005_coercion.t index 7ea0371..7643abf 100644 --- a/t/000_recipes/005_coercion.t +++ b/t/000_recipes/basics/005_coercion.t @@ -8,61 +8,45 @@ use Test::More; BEGIN { eval "use HTTP::Headers; use Params::Coerce; use URI;"; plan skip_all => "HTTP::Headers & Params::Coerce & URI required for this test" if $@; - plan tests => 18; + plan tests => 17; } use Test::Exception; -BEGIN { - use_ok('Moose'); -} - { - package Request; - use Moose; + package Request; + use Moose; use Moose::Util::TypeConstraints; - - use HTTP::Headers (); - use Params::Coerce (); - use URI (); - - subtype Header - => as Object - => where { $_->isa('HTTP::Headers') }; - - coerce Header - => from ArrayRef - => via { HTTP::Headers->new( @{ $_ } ) } - => from HashRef - => via { HTTP::Headers->new( %{ $_ } ) }; - - subtype Uri - => as Object - => where { $_->isa('URI') }; - - coerce Uri - => from Object - => via { $_->isa('URI') ? $_ : Params::Coerce::coerce( 'URI', $_ ) } - => from Str - => via { URI->new( $_, 'http' ) }; - - subtype Protocol - => as Str - => where { /^HTTP\/[0-9]\.[0-9]$/ }; - - - has 'base' => (is => 'rw', isa => 'Uri', coerce => 1); - has 'url' => (is => 'rw', isa => 'Uri', coerce => 1); - has 'method' => (is => 'rw', isa => 'Str'); - has 'protocol' => (is => 'rw', isa => 'Protocol'); - has 'headers' => ( - is => 'rw', - isa => 'Header', - coerce => 1, - default => sub { HTTP::Headers->new } + + use HTTP::Headers (); + use Params::Coerce (); + use URI (); + + subtype Header => as Object => where { $_->isa('HTTP::Headers') }; + + coerce Header => from ArrayRef => via { HTTP::Headers->new( @{$_} ) } => + from HashRef => via { HTTP::Headers->new( %{$_} ) }; + + subtype Uri => as Object => where { $_->isa('URI') }; + + coerce Uri => from Object => + via { $_->isa('URI') ? $_ : Params::Coerce::coerce( 'URI', $_ ) } => + from Str => via { URI->new( $_, 'http' ) }; + + subtype Protocol => as Str => where {/^HTTP\/[0-9]\.[0-9]$/}; + + has 'base' => ( is => 'rw', isa => 'Uri', coerce => 1 ); + has 'url' => ( is => 'rw', isa => 'Uri', coerce => 1 ); + has 'method' => ( is => 'rw', isa => 'Str' ); + has 'protocol' => ( is => 'rw', isa => 'Protocol' ); + has 'headers' => ( + is => 'rw', + isa => 'Header', + coerce => 1, + default => sub { HTTP::Headers->new } ); - - __PACKAGE__->meta->make_immutable(debug => 0); + + __PACKAGE__->meta->make_immutable( debug => 0 ); } my $r = Request->new; diff --git a/t/000_recipes/006_augment_inner.t b/t/000_recipes/basics/006_augment_inner.t similarity index 95% rename from t/000_recipes/006_augment_inner.t rename to t/000_recipes/basics/006_augment_inner.t index 53d2b75..04f2f1d 100644 --- a/t/000_recipes/006_augment_inner.t +++ b/t/000_recipes/basics/006_augment_inner.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 3; +use Test::More tests => 2; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + ## Augment/Inner diff --git a/t/000_recipes/basics/010_genes.t b/t/000_recipes/basics/010_genes.t new file mode 100644 index 0000000..b638438 --- /dev/null +++ b/t/000_recipes/basics/010_genes.t @@ -0,0 +1,204 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +{ + package Human; + + use Moose; + use Moose::Util::TypeConstraints; + + subtype 'EyeColor' + => as 'Object' + => where { $_->isa('Human::EyeColor') }; + + coerce 'EyeColor' + => from 'ArrayRef' + => via { + return Human::EyeColor->new( + bey2_1 => $_->[0], + bey2_2 => $_->[1], + gey_1 => $_->[2], + gey_2 => $_->[3], + ); + }; + + subtype 'Gender' + => as 'Str' + => where { $_ =~ m{^[mf]$}s }; + + has 'gender' => ( is => 'ro', isa => 'Gender', required => 1 ); + + has 'eye_color' => ( is => 'ro', isa => 'EyeColor', coerce => 1, required => 1 ); + + has 'mother' => ( is => 'ro', isa => 'Human' ); + has 'father' => ( is => 'ro', isa => 'Human' ); + + use overload '+' => \&_overload_add, fallback => 1; + + sub _overload_add { + my ($one, $two) = @_; + + die('Only male and female humans may have children') + if ($one->gender() eq $two->gender()); + + my ( $mother, $father ) = ( $one->gender eq 'f' ? ($one, $two) : ($two, $one) ); + + my $gender = 'f'; + $gender = 'm' if (rand() >= 0.5); + + # Would be better to use Crypt::Random. + #use Crypt::Random qw( makerandom ); + #$gender = 'm' if (makerandom( Size => 1, Strength => 1, Uniform => 1 )); + + return Human->new( + gender => $gender, + eye_color => ( $one->eye_color() + $two->eye_color() ), + mother => $mother, + father => $father, + ); + } +} + +{ + package Human::EyeColor; + + use Moose; + use Moose::Util::TypeConstraints; + + subtype 'bey2Gene' + => as 'Object' + => where { $_->isa('Human::Gene::bey2') }; + + coerce 'bey2Gene' + => from 'Str' + => via { Human::Gene::bey2->new( color => $_ ) }; + + subtype 'geyGene' + => as 'Object' + => where { $_->isa('Human::Gene::gey') }; + + coerce 'geyGene' + => from 'Str' + => via { Human::Gene::gey->new( color => $_ ) }; + + has 'bey2_1' => ( is => 'ro', isa => 'bey2Gene', coerce => 1 ); + has 'bey2_2' => ( is => 'ro', isa => 'bey2Gene', coerce => 1 ); + + has 'gey_1' => ( is => 'ro', isa => 'geyGene', coerce => 1 ); + has 'gey_2' => ( is => 'ro', isa => 'geyGene', coerce => 1 ); + + use overload '+' => \&_overload_add, fallback => 1; + use overload '""' => \&color, fallback => 1; + + sub color { + my ( $self ) = @_; + + return 'brown' if ($self->bey2_1->color() eq 'brown' or $self->bey2_2->color() eq 'brown'); + return 'green' if ($self->gey_1->color() eq 'green' or $self->gey_2->color() eq 'green'); + return 'blue'; + } + + sub _overload_add { + my ($one, $two) = @_; + + my $one_bey2 = 'bey2_' . _rand2(); + my $two_bey2 = 'bey2_' . _rand2(); + + my $one_gey = 'gey_' . _rand2(); + my $two_gey = 'gey_' . _rand2(); + + return Human::EyeColor->new( + bey2_1 => $one->$one_bey2->color(), + bey2_2 => $two->$two_bey2->color(), + gey_1 => $one->$one_gey->color(), + gey_2 => $two->$two_gey->color(), + ); + } + + sub _rand2 { + # Would be better to use Crypt::Random. + #use Crypt::Random qw( makerandom ); + #return 1 + makerandom( Size => 1, Strength => 1, Uniform => 1 ); + return 1 + int( rand(2) ); + } +} + +{ + package Human::Gene::bey2; + + use Moose; + use Moose::Util::TypeConstraints; + + type 'bey2Color' => where { $_ =~ m{^(?:brown|blue)$}s }; + + has 'color' => ( is => 'ro', isa => 'bey2Color' ); +} + +{ + package Human::Gene::gey; + + use Moose; + use Moose::Util::TypeConstraints; + + type 'geyColor' => where { $_ =~ m{^(?:green|blue)$}s }; + + has 'color' => ( is => 'ro', isa => 'geyColor' ); +} + +use Test::More tests => 10; + +my $gene_color_sets = [ + [qw( blue blue blue blue ) => 'blue'], + [qw( blue blue green blue ) => 'green'], + [qw( blue blue blue green ) => 'green'], + [qw( blue blue green green ) => 'green'], + [qw( brown blue blue blue ) => 'brown'], + [qw( brown brown green green ) => 'brown'], + [qw( blue brown green blue ) => 'brown'], +]; + +foreach my $set (@$gene_color_sets) { + my $expected_color = pop( @$set ); + my $person = Human->new( + gender => 'f', + eye_color => $set, + ); + is( + $person->eye_color(), + $expected_color, + 'gene combination '.join(',',@$set).' produces '.$expected_color.' eye color', + ); +} + +my $parent_sets = [ + [ [qw( blue blue blue blue )], [qw( blue blue blue blue )] => 'blue' ], + [ [qw( blue blue blue blue )], [qw( brown brown green blue )] => 'brown' ], + [ [qw( blue blue green green )], [qw( blue blue green green )] => 'green' ], +]; + +foreach my $set (@$parent_sets) { + my $expected_color = pop( @$set ); + my $mother = Human->new( + gender => 'f', + eye_color => shift(@$set), + ); + my $father = Human->new( + gender => 'm', + eye_color => shift(@$set), + ); + my $child = $mother + $father; + is( + $child->eye_color(), + $expected_color, + 'mother '.$mother->eye_color().' + father '.$father->eye_color().' = child '.$expected_color, + ); +} + +# Hmm, not sure how to test for random selection of genes since +# I could theoretically run an infinite number of iterations and +# never find proof that a child has inherited a particular gene. + +# AUTHOR: Aran Clary Deltac + diff --git a/t/000_recipes/extending/001_base_class.t b/t/000_recipes/extending/001_base_class.t new file mode 100644 index 0000000..f140fd4 --- /dev/null +++ b/t/000_recipes/extending/001_base_class.t @@ -0,0 +1,62 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; +use Test::Exception; + +BEGIN { + unless ( eval 'use Test::Warn; 1' ) { + plan skip_all => 'These tests require Test::Warn'; + } + else { + plan tests => 4; + } +} + +{ + package MyApp::Base; + use Moose; + + extends 'Moose::Object'; + + before 'new' => sub { warn "Making a new " . $_[0] }; + + no Moose; +} + +{ + package MyApp::UseMyBase; + use Moose (); + use Moose::Exporter; + + Moose::Exporter->setup_import_methods( also => 'Moose' ); + + sub init_meta { + shift; + Moose->init_meta( @_, base_class => 'MyApp::Base' ); + } +} + +{ + package Foo; + + MyApp::UseMyBase->import; + + has( 'size' => ( is => 'rw' ) ); +} + +ok( Foo->isa('MyApp::Base'), + 'Foo isa MyApp::Base' ); + +ok( Foo->can('size'), + 'Foo has a size method' ); + +my $foo; +warning_is( sub { $foo = Foo->new( size => 2 ) }, + 'Making a new Foo', + 'got expected warning when calling Foo->new' ); + +is( $foo->size(), 2, '$foo->size is 2' ); + diff --git a/t/000_recipes/extending/002_metaclass_and_sugar.t b/t/000_recipes/extending/002_metaclass_and_sugar.t new file mode 100644 index 0000000..1ecbf17 --- /dev/null +++ b/t/000_recipes/extending/002_metaclass_and_sugar.t @@ -0,0 +1,60 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 3; + + +{ + package MyApp::Meta::Class; + use Moose; + + extends 'Moose::Meta::Class'; + + has 'table' => ( is => 'rw' ); + + no Moose; + + package MyApp::Mooseish; + + use strict; + use warnings; + + use Moose (); + use Moose::Exporter; + + Moose::Exporter->setup_import_methods( + with_caller => ['has_table'], + also => 'Moose', + ); + + sub init_meta { + shift; + Moose->init_meta( @_, metaclass => 'MyApp::Meta::Class' ); + } + + sub has_table { + my $caller = shift; + $caller->meta()->table(shift); + } +} + +{ + package MyApp::User; + + MyApp::Mooseish->import; + + has_table( 'User' ); + + has( 'username' => ( is => 'ro' ) ); + has( 'password' => ( is => 'ro' ) ); + + sub login { } +} + +isa_ok( MyApp::User->meta, 'MyApp::Meta::Class' ); +is( MyApp::User->meta->table, 'User', + 'MyApp::User->meta->table returns User' ); +ok( MyApp::User->can('username'), + 'MyApp::User has username method' ); diff --git a/t/000_recipes/021_meta_attribute.t b/t/000_recipes/meta/002_meta_attribute.t similarity index 96% rename from t/000_recipes/021_meta_attribute.t rename to t/000_recipes/meta/002_meta_attribute.t index 7e5abcf..efe675c 100644 --- a/t/000_recipes/021_meta_attribute.t +++ b/t/000_recipes/meta/002_meta_attribute.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 2; +use Test::More tests => 1; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + ## meta-attribute example { diff --git a/t/000_recipes/022_attribute_trait.t b/t/000_recipes/meta/003_attribute_trait.t similarity index 97% rename from t/000_recipes/022_attribute_trait.t rename to t/000_recipes/meta/003_attribute_trait.t index 223fb65..7b8157f 100644 --- a/t/000_recipes/022_attribute_trait.t +++ b/t/000_recipes/meta/003_attribute_trait.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 3; +use Test::More tests => 2; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + ## attribute trait example { diff --git a/t/000_recipes/010_roles.t b/t/000_recipes/roles/001_roles.t similarity index 97% rename from t/000_recipes/010_roles.t rename to t/000_recipes/roles/001_roles.t index 5b8f9d2..6f89b83 100644 --- a/t/000_recipes/010_roles.t +++ b/t/000_recipes/roles/001_roles.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 62; +use Test::More tests => 63; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + ## Roles @@ -91,6 +89,9 @@ ok(US::Currency->does('Printable'), '... US::Currency does Printable'); my $hundred = US::Currency->new(amount => 100.00); isa_ok($hundred, 'US::Currency'); +ok( $hundred->DOES("US::Currency"), "UNIVERSAL::DOES for class" ); +ok( $hundred->DOES("Comparable"), "UNIVERSAL::DOES for role" ); + can_ok($hundred, 'amount'); is($hundred->amount, 100, '... got the right amount'); diff --git a/t/000_recipes/011_advanced_role_composition.t b/t/000_recipes/roles/002_advanced_role_composition.t similarity index 100% rename from t/000_recipes/011_advanced_role_composition.t rename to t/000_recipes/roles/002_advanced_role_composition.t diff --git a/t/010_basics/001_basic_class_setup.t b/t/010_basics/001_basic_class_setup.t index 7647693..0520394 100644 --- a/t/010_basics/001_basic_class_setup.t +++ b/t/010_basics/001_basic_class_setup.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 23; +use Test::More tests => 22; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/010_basics/002_require_superclasses.t b/t/010_basics/002_require_superclasses.t index ced023f..28b52e0 100644 --- a/t/010_basics/002_require_superclasses.t +++ b/t/010_basics/002_require_superclasses.t @@ -5,11 +5,9 @@ use warnings; use lib 't/lib', 'lib'; -use Test::More tests => 6; +use Test::More tests => 5; + -BEGIN { - use_ok('Moose'); -} { package Bar; diff --git a/t/010_basics/003_super_and_override.t b/t/010_basics/003_super_and_override.t index e04eea5..54d5c2d 100644 --- a/t/010_basics/003_super_and_override.t +++ b/t/010_basics/003_super_and_override.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 17; +use Test::More tests => 16; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/010_basics/004_inner_and_augment.t b/t/010_basics/004_inner_and_augment.t index f129ef2..15f4248 100644 --- a/t/010_basics/004_inner_and_augment.t +++ b/t/010_basics/004_inner_and_augment.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 17; +use Test::More tests => 16; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/010_basics/005_override_augment_inner_super.t b/t/010_basics/005_override_augment_inner_super.t index 6732c60..2d4a330 100644 --- a/t/010_basics/005_override_augment_inner_super.t +++ b/t/010_basics/005_override_augment_inner_super.t @@ -3,11 +3,9 @@ use strict; use warnings; -use Test::More tests => 6; +use Test::More tests => 5; + -BEGIN { - use_ok('Moose'); -} { package Foo; diff --git a/t/010_basics/006_override_and_foreign_classes.t b/t/010_basics/006_override_and_foreign_classes.t index 812002e..26fbb44 100644 --- a/t/010_basics/006_override_and_foreign_classes.t +++ b/t/010_basics/006_override_and_foreign_classes.t @@ -3,11 +3,9 @@ use strict; use warnings; -use Test::More tests => 16; +use Test::More tests => 15; + -BEGIN { - use_ok('Moose'); -} =pod diff --git a/t/010_basics/008_wrapped_method_cxt_propagation.t b/t/010_basics/008_wrapped_method_cxt_propagation.t index 8e97669..226cf54 100644 --- a/t/010_basics/008_wrapped_method_cxt_propagation.t +++ b/t/010_basics/008_wrapped_method_cxt_propagation.t @@ -3,11 +3,9 @@ use strict; use warnings; -use Test::More tests => 8; +use Test::More tests => 7; + -BEGIN { - use_ok('Moose'); -} { package TouchyBase; diff --git a/t/010_basics/009_import_unimport.t b/t/010_basics/009_import_unimport.t index 9bc2c76..80e494b 100644 --- a/t/010_basics/009_import_unimport.t +++ b/t/010_basics/009_import_unimport.t @@ -3,11 +3,8 @@ use strict; use warnings; -use Test::More tests => 47; +use Test::More tests => 42; -BEGIN { - use_ok('Moose'); -} my @moose_exports = qw( extends with @@ -21,21 +18,19 @@ my @moose_exports = qw( { package Foo; -} -eval q{ - package Foo; - use Moose; -}; -ok(!$@, '... Moose succesfully exported into Foo'); + eval 'use Moose'; + die $@ if $@; +} can_ok('Foo', $_) for @moose_exports; -eval q{ +{ package Foo; - no Moose; -}; -ok(!$@, '... Moose succesfully un-exported from Foo'); + + eval 'no Moose'; + die $@ if $@; +} ok(!Foo->can($_), '... Foo can no longer do ' . $_) for @moose_exports; @@ -50,21 +45,19 @@ my @moose_type_constraint_exports = qw( { package Bar; -} -eval q{ - package Bar; - use Moose::Util::TypeConstraints; -}; -ok(!$@, '... Moose::Util::TypeConstraints succesfully exported into Bar'); + eval 'use Moose::Util::TypeConstraints'; + die $@ if $@; +} can_ok('Bar', $_) for @moose_type_constraint_exports; -eval q{ +{ package Bar; - no Moose::Util::TypeConstraints; -}; -ok(!$@, '... Moose::Util::TypeConstraints succesfully un-exported from Bar'); + + eval 'no Moose::Util::TypeConstraints'; + die $@ if $@; +} ok(!Bar->can($_), '... Bar can no longer do ' . $_) for @moose_type_constraint_exports; diff --git a/t/010_basics/010_method_modifier_with_regexp.t b/t/010_basics/010_method_modifier_with_regexp.t index c57030b..41f55cd 100644 --- a/t/010_basics/010_method_modifier_with_regexp.t +++ b/t/010_basics/010_method_modifier_with_regexp.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 10; +use Test::More tests => 9; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { diff --git a/t/010_basics/011_moose_respects_type_constraints.t b/t/010_basics/011_moose_respects_type_constraints.t index ead0d1a..a62f147 100644 --- a/t/010_basics/011_moose_respects_type_constraints.t +++ b/t/010_basics/011_moose_respects_type_constraints.t @@ -3,13 +3,10 @@ use strict; use warnings; -use Test::More tests => 9; +use Test::More tests => 7; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Util::TypeConstraints'); -} +use Moose::Util::TypeConstraints; =pod diff --git a/t/010_basics/012_rebless.t b/t/010_basics/012_rebless.t index fcc5dd6..c8ec23e 100644 --- a/t/010_basics/012_rebless.t +++ b/t/010_basics/012_rebless.t @@ -3,14 +3,11 @@ use strict; use warnings; -use Test::More tests => 13; +use Test::More tests => 11; use Test::Exception; use Scalar::Util 'blessed'; -BEGIN { - use_ok('Moose'); - use_ok("Moose::Util::TypeConstraints"); -} +use Moose::Util::TypeConstraints; subtype 'Positive' => as 'Num' diff --git a/t/010_basics/013_create.t b/t/010_basics/013_create.t index 8fbafe7..e83972d 100644 --- a/t/010_basics/013_create.t +++ b/t/010_basics/013_create.t @@ -3,15 +3,9 @@ use strict; use warnings; -use Test::More tests => 10; +use Test::More tests => 7; use Test::Exception; -BEGIN { - use_ok('Moose::Meta::Class'); - use_ok('Moose'); - use_ok('Moose::Role'); -} - { package Class; use Moose; diff --git a/t/010_basics/014_create_anon.t b/t/010_basics/014_create_anon.t index d989455..d324b4b 100644 --- a/t/010_basics/014_create_anon.t +++ b/t/010_basics/014_create_anon.t @@ -3,13 +3,9 @@ use strict; use warnings; -use Test::More tests => 11; +use Test::More tests => 8; -BEGIN { - use_ok('Moose::Meta::Class'); - use_ok('Moose'); - use_ok('Moose::Role'); -} +use Moose::Meta::Class; { package Class; diff --git a/t/010_basics/015_buildargs.t b/t/010_basics/015_buildargs.t new file mode 100644 index 0000000..bff4aeb --- /dev/null +++ b/t/010_basics/015_buildargs.t @@ -0,0 +1,43 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 14; + +{ + package Foo; + use Moose; + + has bar => ( is => "rw" ); + has baz => ( is => "rw" ); + + sub BUILDARGS { + my ( $self, @args ) = @_; + unshift @args, "bar" if @args % 2 == 1; + return {@args}; + } + + package Bar; + use Moose; + + extends qw(Foo); +} + +foreach my $class qw(Foo Bar) { + is( $class->new->bar, undef, "no args" ); + is( $class->new( bar => 42 )->bar, 42, "normal args" ); + is( $class->new( 37 )->bar, 37, "single arg" ); + { + my $o = $class->new(bar => 42, baz => 47); + is($o->bar, 42, '... got the right bar'); + is($o->baz, 47, '... got the right bar'); + } + { + my $o = $class->new(42, baz => 47); + is($o->bar, 42, '... got the right bar'); + is($o->baz, 47, '... got the right bar'); + } +} + + diff --git a/t/010_basics/016_load_into_main.t b/t/010_basics/016_load_into_main.t new file mode 100644 index 0000000..683ae4b --- /dev/null +++ b/t/010_basics/016_load_into_main.t @@ -0,0 +1,20 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; + +BEGIN { + eval "use Test::Output;"; + plan skip_all => "Test::Output is required for this test" if $@; + plan tests => 2; +} + +stderr_is( sub { package main; eval 'use Moose' }, + "Moose does not export its sugar to the 'main' package.\n", + 'Moose warns when loaded from the main package' ); + +stderr_is( sub { package main; eval 'use Moose::Role' }, + "Moose::Role does not export its sugar to the 'main' package.\n", + 'Moose::Role warns when loaded from the main package' ); diff --git a/t/020_attributes/001_attribute_reader_generation.t b/t/020_attributes/001_attribute_reader_generation.t index 7ad1447..d27dbfc 100644 --- a/t/020_attributes/001_attribute_reader_generation.t +++ b/t/020_attributes/001_attribute_reader_generation.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 14; +use Test::More tests => 13; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/020_attributes/002_attribute_writer_generation.t b/t/020_attributes/002_attribute_writer_generation.t index e803775..1b25f3c 100644 --- a/t/020_attributes/002_attribute_writer_generation.t +++ b/t/020_attributes/002_attribute_writer_generation.t @@ -3,14 +3,12 @@ use strict; use warnings; -use Test::More tests => 30; +use Test::More tests => 29; use Test::Exception; use Scalar::Util 'isweak'; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/020_attributes/003_attribute_accessor_generation.t b/t/020_attributes/003_attribute_accessor_generation.t index d8b3638..0355c2b 100644 --- a/t/020_attributes/003_attribute_accessor_generation.t +++ b/t/020_attributes/003_attribute_accessor_generation.t @@ -3,14 +3,12 @@ use strict; use warnings; -use Test::More tests => 58; +use Test::More tests => 57; use Test::Exception; use Scalar::Util 'isweak'; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/020_attributes/004_attribute_triggers.t b/t/020_attributes/004_attribute_triggers.t index b5cf34e..0695dc3 100644 --- a/t/020_attributes/004_attribute_triggers.t +++ b/t/020_attributes/004_attribute_triggers.t @@ -5,12 +5,10 @@ use warnings; use Scalar::Util 'isweak'; -use Test::More tests => 26; +use Test::More tests => 25; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/020_attributes/005_attribute_does.t b/t/020_attributes/005_attribute_does.t index 8161537..d05a808 100644 --- a/t/020_attributes/005_attribute_does.t +++ b/t/020_attributes/005_attribute_does.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 10; +use Test::More tests => 9; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo::Role; diff --git a/t/020_attributes/006_attribute_required.t b/t/020_attributes/006_attribute_required.t index 4f65021..0975765 100644 --- a/t/020_attributes/006_attribute_required.t +++ b/t/020_attributes/006_attribute_required.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 16; +use Test::More tests => 15; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/020_attributes/007_attribute_custom_metaclass.t b/t/020_attributes/007_attribute_custom_metaclass.t index a7b8bdb..eb74b0c 100644 --- a/t/020_attributes/007_attribute_custom_metaclass.t +++ b/t/020_attributes/007_attribute_custom_metaclass.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 17; +use Test::More tests => 16; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo::Meta::Attribute; diff --git a/t/020_attributes/008_attribute_type_unions.t b/t/020_attributes/008_attribute_type_unions.t index 05b94eb..95ce5f9 100644 --- a/t/020_attributes/008_attribute_type_unions.t +++ b/t/020_attributes/008_attribute_type_unions.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 19; +use Test::More tests => 18; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/020_attributes/009_attribute_inherited_slot_specs.t b/t/020_attributes/009_attribute_inherited_slot_specs.t index 7f3b859..7d84bfc 100644 --- a/t/020_attributes/009_attribute_inherited_slot_specs.t +++ b/t/020_attributes/009_attribute_inherited_slot_specs.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 83; +use Test::More tests => 82; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Thing; diff --git a/t/020_attributes/010_attribute_delegation.t b/t/020_attributes/010_attribute_delegation.t index 2e6e585..91bcf17 100644 --- a/t/020_attributes/010_attribute_delegation.t +++ b/t/020_attributes/010_attribute_delegation.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 85; +use Test::More tests => 84; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + # ------------------------------------------------------------------- # HASH handles diff --git a/t/020_attributes/011_more_attr_delegation.t b/t/020_attributes/011_more_attr_delegation.t index f331a05..66942a7 100644 --- a/t/020_attributes/011_more_attr_delegation.t +++ b/t/020_attributes/011_more_attr_delegation.t @@ -201,7 +201,7 @@ ok( !$p->can("child_b_method_2"), "but not ChildB's unspecified siblings" ); ok( !$p->can($_), "none of ChildD's methods ($_)" ) - for grep { /^child/ } map { $_->{name} } ChildD->meta->compute_all_applicable_methods(); + for grep { /^child/ } map { $_->name } ChildD->meta->get_all_methods(); can_ok( $p, "child_c_method_3_la" ); can_ok( $p, "child_c_method_4_la" ); diff --git a/t/020_attributes/012_misc_attribute_tests.t b/t/020_attributes/012_misc_attribute_tests.t index 24e3f70..52f6e14 100644 --- a/t/020_attributes/012_misc_attribute_tests.t +++ b/t/020_attributes/012_misc_attribute_tests.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 44; +use Test::More tests => 47; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { { @@ -253,3 +251,14 @@ BEGIN { } +{ + package OutOfClassTest; + + use Moose; +} + +lives_ok { OutOfClassTest::has('foo'); } 'create attr via direct sub call'; +lives_ok { OutOfClassTest->can('has')->('bar'); } 'create attr via can'; + +ok(OutOfClassTest->meta->get_attribute('foo'), 'attr created from sub call'); +ok(OutOfClassTest->meta->get_attribute('bar'), 'attr created from can'); diff --git a/t/020_attributes/013_attr_dereference_test.t b/t/020_attributes/013_attr_dereference_test.t index 903134b..11b75d7 100644 --- a/t/020_attributes/013_attr_dereference_test.t +++ b/t/020_attributes/013_attr_dereference_test.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 12; +use Test::More tests => 11; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Customer; diff --git a/t/020_attributes/014_misc_attribute_coerce_lazy.t b/t/020_attributes/014_misc_attribute_coerce_lazy.t index 37e354d..4fe4ee4 100644 --- a/t/020_attributes/014_misc_attribute_coerce_lazy.t +++ b/t/020_attributes/014_misc_attribute_coerce_lazy.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 3; +use Test::More tests => 2; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package HTTPHeader; diff --git a/t/020_attributes/015_attribute_traits.t b/t/020_attributes/015_attribute_traits.t index 0cb44fd..189d212 100644 --- a/t/020_attributes/015_attribute_traits.t +++ b/t/020_attributes/015_attribute_traits.t @@ -3,13 +3,11 @@ use strict; use warnings; -use Test::More tests => 13; +use Test::More tests => 12; use Test::Exception; use Test::Moose; -BEGIN { - use_ok('Moose'); -} + { package My::Attribute::Trait; diff --git a/t/020_attributes/016_attribute_traits_registered.t b/t/020_attributes/016_attribute_traits_registered.t index 01b9536..91dc88a 100644 --- a/t/020_attributes/016_attribute_traits_registered.t +++ b/t/020_attributes/016_attribute_traits_registered.t @@ -3,13 +3,11 @@ use strict; use warnings; -use Test::More tests => 24; +use Test::More tests => 23; use Test::Exception; use Test::Moose; -BEGIN { - use_ok('Moose'); -} + { package My::Attribute::Trait; diff --git a/t/020_attributes/017_attribute_traits_n_meta.t b/t/020_attributes/017_attribute_traits_n_meta.t index 6930dc1..c4dc206 100644 --- a/t/020_attributes/017_attribute_traits_n_meta.t +++ b/t/020_attributes/017_attribute_traits_n_meta.t @@ -3,13 +3,11 @@ use strict; use warnings; -use Test::More tests => 8; +use Test::More tests => 7; use Test::Exception; use Test::Moose; -BEGIN { - use_ok('Moose'); -} + { package My::Meta::Attribute::DefaultReadOnly; diff --git a/t/020_attributes/018_no_init_arg.t b/t/020_attributes/018_no_init_arg.t index ae714ec..f5ebf87 100644 --- a/t/020_attributes/018_no_init_arg.t +++ b/t/020_attributes/018_no_init_arg.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 5; +use Test::More tests => 4; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/020_attributes/019_attribute_lazy_initializer.t b/t/020_attributes/019_attribute_lazy_initializer.t index 2c6f9ac..6735822 100644 --- a/t/020_attributes/019_attribute_lazy_initializer.t +++ b/t/020_attributes/019_attribute_lazy_initializer.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 24; +use Test::More tests => 23; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/020_attributes/020_trigger_and_coerce.t b/t/020_attributes/020_trigger_and_coerce.t index 712cacf..5d102ba 100644 --- a/t/020_attributes/020_trigger_and_coerce.t +++ b/t/020_attributes/020_trigger_and_coerce.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 12; +use Test::More tests => 11; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Fake::DateTime; diff --git a/t/020_attributes/021_method_generation_rules.t b/t/020_attributes/021_method_generation_rules.t index db96f52..d0fbe0b 100644 --- a/t/020_attributes/021_method_generation_rules.t +++ b/t/020_attributes/021_method_generation_rules.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 18; +use Test::More tests => 17; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + =pod diff --git a/t/020_attributes/022_legal_options_for_inheritance.t b/t/020_attributes/022_legal_options_for_inheritance.t new file mode 100644 index 0000000..e30cc97 --- /dev/null +++ b/t/020_attributes/022_legal_options_for_inheritance.t @@ -0,0 +1,48 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Test::More tests => 2; + + + +{ + package Bar::Meta::Attribute; + use Moose; + + extends 'Moose::Meta::Attribute'; + + has 'my_legal_option' => ( + isa => 'CodeRef', + is => 'rw', + ); + + around legal_options_for_inheritance => sub { + return (shift->(@_), qw/my_legal_option/); + }; + + package Bar; + use Moose; + + has 'bar' => ( + metaclass => 'Bar::Meta::Attribute', + my_legal_option => sub { 'Bar' } + ); + + package Bar::B; + use Moose; + + extends 'Bar'; + + has '+bar' => ( + my_legal_option => sub { 'Bar::B' } + ); +} + +my $bar_attr = Bar::B->meta->get_attribute('bar'); +my ($legal_option) = grep { + $_ eq 'my_legal_option' +} $bar_attr->legal_options_for_inheritance; +is($legal_option, 'my_legal_option', + '... added my_legal_option as legal option for inheritance' ); +is($bar_attr->my_legal_option->(), 'Bar::B', '... overloaded my_legal_option'); diff --git a/t/030_roles/001_meta_role.t b/t/030_roles/001_meta_role.t index c13b336..f8b377b 100644 --- a/t/030_roles/001_meta_role.t +++ b/t/030_roles/001_meta_role.t @@ -3,13 +3,10 @@ use strict; use warnings; -use Test::More tests => 29; +use Test::More tests => 27; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Meta::Role'); -} +use Moose::Meta::Role; { package FooRole; diff --git a/t/030_roles/002_role.t b/t/030_roles/002_role.t index 3b99de3..b03027d 100644 --- a/t/030_roles/002_role.t +++ b/t/030_roles/002_role.t @@ -3,13 +3,9 @@ use strict; use warnings; -use Test::More tests => 37; +use Test::More tests => 36; use Test::Exception; -BEGIN { - use_ok('Moose::Role'); -} - =pod NOTE: diff --git a/t/030_roles/003_apply_role.t b/t/030_roles/003_apply_role.t index 4be0dfa..d105868 100644 --- a/t/030_roles/003_apply_role.t +++ b/t/030_roles/003_apply_role.t @@ -3,13 +3,9 @@ use strict; use warnings; -use Test::More tests => 87; +use Test::More tests => 86; use Test::Exception; -BEGIN { - use_ok('Moose::Role'); -} - { package FooRole; use Moose::Role; diff --git a/t/030_roles/004_role_composition_errors.t b/t/030_roles/004_role_composition_errors.t index a96244c..0325dc4 100644 --- a/t/030_roles/004_role_composition_errors.t +++ b/t/030_roles/004_role_composition_errors.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 11; +use Test::More tests => 10; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo::Role; diff --git a/t/030_roles/005_role_conflict_detection.t b/t/030_roles/005_role_conflict_detection.t index 0c4f9a8..09a664e 100644 --- a/t/030_roles/005_role_conflict_detection.t +++ b/t/030_roles/005_role_conflict_detection.t @@ -3,14 +3,9 @@ use strict; use warnings; -use Test::More tests => 89; # it's really 126 with kolibre's tests; +use Test::More tests => 87; # it's really 124 with kolibre's tests; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Role'); -} - =pod Mutually recursive roles. diff --git a/t/030_roles/006_role_exclusion.t b/t/030_roles/006_role_exclusion.t index 2f99164..578ba82 100644 --- a/t/030_roles/006_role_exclusion.t +++ b/t/030_roles/006_role_exclusion.t @@ -3,14 +3,9 @@ use strict; use warnings; -use Test::More tests => 24; +use Test::More tests => 22; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Role'); -} - =pod The idea and examples for this feature are taken diff --git a/t/030_roles/007_roles_and_req_method_edge_cases.t b/t/030_roles/007_roles_and_req_method_edge_cases.t index fa63489..9017f85 100644 --- a/t/030_roles/007_roles_and_req_method_edge_cases.t +++ b/t/030_roles/007_roles_and_req_method_edge_cases.t @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 17; +use Test::More tests => 15; use Test::Exception; =pod @@ -16,15 +16,6 @@ are actually related to class construction order and not any real functionality. - SL -=cut - -BEGIN { - use_ok('Moose'); - use_ok('Moose::Role'); -} - -=pod - Role which requires a method implemented in another role as an override (it does not remove the requirement) diff --git a/t/030_roles/008_role_conflict_edge_cases.t b/t/030_roles/008_role_conflict_edge_cases.t index 9d31847..12e4873 100644 --- a/t/030_roles/008_role_conflict_edge_cases.t +++ b/t/030_roles/008_role_conflict_edge_cases.t @@ -3,14 +3,9 @@ use strict; use warnings; -use Test::More tests => 34; +use Test::More tests => 32; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Role'); -} - =pod Check for repeated inheritence causing diff --git a/t/030_roles/009_more_role_edge_cases.t b/t/030_roles/009_more_role_edge_cases.t index 41f7a35..0fec31c 100644 --- a/t/030_roles/009_more_role_edge_cases.t +++ b/t/030_roles/009_more_role_edge_cases.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 75; +use Test::More tests => 74; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { # NOTE: diff --git a/t/030_roles/010_run_time_role_composition.t b/t/030_roles/010_run_time_role_composition.t index d4a5da9..1a86a18 100644 --- a/t/030_roles/010_run_time_role_composition.t +++ b/t/030_roles/010_run_time_role_composition.t @@ -3,13 +3,11 @@ use strict; use warnings; -use Test::More tests => 28; +use Test::More tests => 27; use Scalar::Util qw(blessed); -BEGIN { - use_ok('Moose'); -} + =pod diff --git a/t/030_roles/011_overriding.t b/t/030_roles/011_overriding.t index 1ebc3eb..1ee53e1 100644 --- a/t/030_roles/011_overriding.t +++ b/t/030_roles/011_overriding.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 40; +use Test::More tests => 39; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { # test no conflicts here diff --git a/t/030_roles/012_method_exclusion_in_composition.t b/t/030_roles/012_method_exclusion_in_composition.t index 30f77f4..b3128b3 100644 --- a/t/030_roles/012_method_exclusion_in_composition.t +++ b/t/030_roles/012_method_exclusion_in_composition.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 20; +use Test::More tests => 19; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package My::Role; diff --git a/t/030_roles/013_method_aliasing_in_composition.t b/t/030_roles/013_method_aliasing_in_composition.t index 0405e13..4eeeed6 100644 --- a/t/030_roles/013_method_aliasing_in_composition.t +++ b/t/030_roles/013_method_aliasing_in_composition.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 36; +use Test::More tests => 35; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package My::Role; diff --git a/t/030_roles/014_more_alias_and_exclude.t b/t/030_roles/014_more_alias_and_exclude.t index 3fce48d..346c636 100644 --- a/t/030_roles/014_more_alias_and_exclude.t +++ b/t/030_roles/014_more_alias_and_exclude.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 10; +use Test::More tests => 9; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/030_roles/015_runtime_roles_and_attrs.t b/t/030_roles/015_runtime_roles_and_attrs.t index 3cf2fb6..a2b3842 100644 --- a/t/030_roles/015_runtime_roles_and_attrs.t +++ b/t/030_roles/015_runtime_roles_and_attrs.t @@ -3,13 +3,11 @@ use strict; use warnings; -use Test::More tests => 12; +use Test::More tests => 11; use Test::Exception; use Scalar::Util 'blessed'; -BEGIN { - use_ok('Moose'); -} + { diff --git a/t/030_roles/016_runtime_roles_and_nonmoose.t b/t/030_roles/016_runtime_roles_and_nonmoose.t index 056aef7..ecf1af0 100644 --- a/t/030_roles/016_runtime_roles_and_nonmoose.t +++ b/t/030_roles/016_runtime_roles_and_nonmoose.t @@ -3,13 +3,11 @@ use strict; use warnings; -use Test::More tests => 8; +use Test::More tests => 7; use Test::Exception; use Scalar::Util 'blessed'; -BEGIN { - use_ok('Moose'); -} + { diff --git a/t/030_roles/017_extending_role_attrs.t b/t/030_roles/017_extending_role_attrs.t index 2cb8c11..95572a5 100644 --- a/t/030_roles/017_extending_role_attrs.t +++ b/t/030_roles/017_extending_role_attrs.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 28; +use Test::More tests => 27; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + =pod diff --git a/t/030_roles/018_runtime_roles_w_params.t b/t/030_roles/018_runtime_roles_w_params.t index 44941ee..d3eddae 100644 --- a/t/030_roles/018_runtime_roles_w_params.t +++ b/t/030_roles/018_runtime_roles_w_params.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 22; +use Test::More tests => 21; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/030_roles/019_build.t b/t/030_roles/019_build.t new file mode 100644 index 0000000..03b149a --- /dev/null +++ b/t/030_roles/019_build.t @@ -0,0 +1,74 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use Test::More tests => 6; + +# this test script ensures that my idiom of: +# role: sub BUILD, after BUILD +# continues to work to run code after object initialization, whether the class +# has a BUILD method or not + +my @CALLS; + +do { + package TestRole; + use Moose::Role; + + sub BUILD { push @CALLS, 'TestRole::BUILD' } + before BUILD => sub { push @CALLS, 'TestRole::BUILD:before' }; + after BUILD => sub { push @CALLS, 'TestRole::BUILD:after' }; +}; + +do { + package ClassWithBUILD; + use Moose; + with 'TestRole'; + + sub BUILD { push @CALLS, 'ClassWithBUILD::BUILD' } +}; + +do { + package ClassWithoutBUILD; + use Moose; + with 'TestRole'; +}; + +is_deeply([splice @CALLS], [], "no calls to BUILD yet"); + +ClassWithBUILD->new; + +is_deeply([splice @CALLS], [ + 'TestRole::BUILD:before', + 'ClassWithBUILD::BUILD', + 'TestRole::BUILD:after', +]); + +ClassWithoutBUILD->new; + +is_deeply([splice @CALLS], [ + 'TestRole::BUILD:before', + 'TestRole::BUILD', + 'TestRole::BUILD:after', +]); + +ClassWithBUILD->meta->make_immutable; +ClassWithoutBUILD->meta->make_immutable; + +is_deeply([splice @CALLS], [], "no calls to BUILD yet"); + +ClassWithBUILD->new; + +is_deeply([splice @CALLS], [ + 'TestRole::BUILD:before', + 'ClassWithBUILD::BUILD', + 'TestRole::BUILD:after', +]); + +ClassWithoutBUILD->new; + +is_deeply([splice @CALLS], [ + 'TestRole::BUILD:before', + 'TestRole::BUILD', + 'TestRole::BUILD:after', +]); + diff --git a/t/030_roles/020_role_composite.t b/t/030_roles/020_role_composite.t index ff70579..506968e 100644 --- a/t/030_roles/020_role_composite.t +++ b/t/030_roles/020_role_composite.t @@ -3,14 +3,11 @@ use strict; use warnings; -use Test::More tests => 17; +use Test::More tests => 14; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Meta::Role::Application::RoleSummation'); - use_ok('Moose::Meta::Role::Composite'); -} +use Moose::Meta::Role::Application::RoleSummation; +use Moose::Meta::Role::Composite; { package Role::Foo; diff --git a/t/030_roles/021_role_composite_exclusion.t b/t/030_roles/021_role_composite_exclusion.t index 4a1cad6..aa618f5 100644 --- a/t/030_roles/021_role_composite_exclusion.t +++ b/t/030_roles/021_role_composite_exclusion.t @@ -3,14 +3,11 @@ use strict; use warnings; -use Test::More tests => 15; +use Test::More tests => 12; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Meta::Role::Application::RoleSummation'); - use_ok('Moose::Meta::Role::Composite'); -} +use Moose::Meta::Role::Application::RoleSummation; +use Moose::Meta::Role::Composite; { package Role::Foo; diff --git a/t/030_roles/022_role_composition_req_methods.t b/t/030_roles/022_role_composition_req_methods.t index 0d04cbc..be56016 100644 --- a/t/030_roles/022_role_composition_req_methods.t +++ b/t/030_roles/022_role_composition_req_methods.t @@ -3,14 +3,11 @@ use strict; use warnings; -use Test::More tests => 19; +use Test::More tests => 16; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Meta::Role::Application::RoleSummation'); - use_ok('Moose::Meta::Role::Composite'); -} +use Moose::Meta::Role::Application::RoleSummation; +use Moose::Meta::Role::Composite; { package Role::Foo; diff --git a/t/030_roles/023_role_composition_attributes.t b/t/030_roles/023_role_composition_attributes.t index bc6da7e..b0e7ad2 100644 --- a/t/030_roles/023_role_composition_attributes.t +++ b/t/030_roles/023_role_composition_attributes.t @@ -3,14 +3,11 @@ use strict; use warnings; -use Test::More tests => 10; +use Test::More tests => 7; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Meta::Role::Application::RoleSummation'); - use_ok('Moose::Meta::Role::Composite'); -} +use Moose::Meta::Role::Application::RoleSummation; +use Moose::Meta::Role::Composite; { package Role::Foo; diff --git a/t/030_roles/024_role_composition_methods.t b/t/030_roles/024_role_composition_methods.t index a77a4dc..36c3bff 100644 --- a/t/030_roles/024_role_composition_methods.t +++ b/t/030_roles/024_role_composition_methods.t @@ -3,14 +3,11 @@ use strict; use warnings; -use Test::More tests => 22; +use Test::More tests => 19; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Meta::Role::Application::RoleSummation'); - use_ok('Moose::Meta::Role::Composite'); -} +use Moose::Meta::Role::Application::RoleSummation; +use Moose::Meta::Role::Composite; { package Role::Foo; diff --git a/t/030_roles/025_role_composition_override.t b/t/030_roles/025_role_composition_override.t index 9f38de3..33b61cd 100644 --- a/t/030_roles/025_role_composition_override.t +++ b/t/030_roles/025_role_composition_override.t @@ -3,14 +3,11 @@ use strict; use warnings; -use Test::More tests => 11; +use Test::More tests => 8; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Meta::Role::Application::RoleSummation'); - use_ok('Moose::Meta::Role::Composite'); -} +use Moose::Meta::Role::Application::RoleSummation; +use Moose::Meta::Role::Composite; { package Role::Foo; diff --git a/t/030_roles/026_role_composition_method_mods.t b/t/030_roles/026_role_composition_method_mods.t index ce94eda..d83ee4e 100644 --- a/t/030_roles/026_role_composition_method_mods.t +++ b/t/030_roles/026_role_composition_method_mods.t @@ -3,14 +3,11 @@ use strict; use warnings; -use Test::More tests => 10; +use Test::More tests => 7; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Meta::Role::Application::RoleSummation'); - use_ok('Moose::Meta::Role::Composite'); -} +use Moose::Meta::Role::Application::RoleSummation; +use Moose::Meta::Role::Composite; { package Role::Foo; diff --git a/t/030_roles/030_role_parameterized.t b/t/030_roles/030_role_parameterized.t index 75fbdc5..997520b 100644 --- a/t/030_roles/030_role_parameterized.t +++ b/t/030_roles/030_role_parameterized.t @@ -3,14 +3,9 @@ use strict; use warnings; -use Test::More tests => 1; +use Test::More skip_all => 'The feature this test exercises is not yet written'; use Test::Exception; -BEGIN { - use_ok('Moose'); -} - -=pod { package Scalar; @@ -40,6 +35,3 @@ is_deeply( sub eq { shift == shift } } - -=cut - diff --git a/t/030_roles/031_roles_applied_in_create.t b/t/030_roles/031_roles_applied_in_create.t new file mode 100644 index 0000000..8aed354 --- /dev/null +++ b/t/030_roles/031_roles_applied_in_create.t @@ -0,0 +1,27 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 1; +use Test::Exception; +use Moose::Meta::Class; +use Moose::Util; + +use lib 't/lib', 'lib'; + + +# Note that this test passed (pre svn #5543) if we inlined the role +# definitions in this file, as it was very timing sensitive. +lives_ok( + sub { + my $builder_meta = Moose::Meta::Class->create( + 'YATTA' => ( + superclass => 'Moose::Meta::Class', + roles => [qw( Role::Interface Role::Child )], + ) + ); + }, + 'Create a new class with several roles' +); + diff --git a/t/040_type_constraints/001_util_type_constraints.t b/t/040_type_constraints/001_util_type_constraints.t index 29d628a..20ceae6 100644 --- a/t/040_type_constraints/001_util_type_constraints.t +++ b/t/040_type_constraints/001_util_type_constraints.t @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 38; +use Test::More tests => 44; use Test::Exception; use Scalar::Util (); @@ -104,3 +104,17 @@ is($string->validate(5), lives_ok { Moose::Meta::Attribute->new('bob', isa => 'Spong') } 'meta-attr construction ok even when type constraint utils loaded first'; + +# Test type constraint predicate return values. + +foreach my $predicate (qw/equals is_subtype_of is_a_type_of/) { + ok( !defined $string->$predicate('DoesNotExist'), "$predicate predicate returns undef for non existant constraint"); +} + +# Test adding things which don't look like types to the registry throws an exception + +my $r = Moose::Util::TypeConstraints->get_type_constraint_registry; +throws_ok {$r->add_type_constraint()} qr/not a valid type constraint/, '->add_type_constraint(undef) throws'; +throws_ok {$r->add_type_constraint('foo')} qr/not a valid type constraint/, '->add_type_constraint("foo") throws'; +throws_ok {$r->add_type_constraint(bless {}, 'SomeClass')} qr/not a valid type constraint/, '->add_type_constraint(SomeClass->new) throws'; + diff --git a/t/040_type_constraints/005_util_type_coercion.t b/t/040_type_constraints/005_util_type_coercion.t index e631309..0e0b732 100644 --- a/t/040_type_constraints/005_util_type_coercion.t +++ b/t/040_type_constraints/005_util_type_coercion.t @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 14; +use Test::More tests => 24; use Test::Exception; BEGIN { @@ -38,39 +38,54 @@ ok(Header($header), '... this passed the type test'); ok(!Header([]), '... this did not pass the type test'); ok(!Header({}), '... this did not pass the type test'); -my $coercion = find_type_constraint('Header')->coercion; -isa_ok($coercion, 'Moose::Meta::TypeCoercion'); +my $anon_type = subtype Object => where { $_->isa('HTTPHeader') }; -{ - my $coerced = $coercion->coerce([ 1, 2, 3 ]); - isa_ok($coerced, 'HTTPHeader'); +lives_ok { + coerce $anon_type + => from ArrayRef + => via { HTTPHeader->new(array => $_[0]) } + => from HashRef + => via { HTTPHeader->new(hash => $_[0]) }; +} 'coercion of anonymous subtype succeeds'; - is_deeply( - $coerced->array(), - [ 1, 2, 3 ], - '... got the right array'); - is($coerced->hash(), undef, '... nothing assigned to the hash'); -} +foreach my $coercion ( + find_type_constraint('Header')->coercion, + $anon_type->coercion + ) { -{ - my $coerced = $coercion->coerce({ one => 1, two => 2, three => 3 }); - isa_ok($coerced, 'HTTPHeader'); + my $coercion = find_type_constraint('Header')->coercion; + isa_ok($coercion, 'Moose::Meta::TypeCoercion'); - is_deeply( - $coerced->hash(), - { one => 1, two => 2, three => 3 }, - '... got the right hash'); - is($coerced->array(), undef, '... nothing assigned to the array'); -} - -{ - my $scalar_ref = \(my $var); - my $coerced = $coercion->coerce($scalar_ref); - is($coerced, $scalar_ref, '... got back what we put in'); -} - -{ - my $coerced = $coercion->coerce("Foo"); - is($coerced, "Foo", '... got back what we put in'); + { + my $coerced = $coercion->coerce([ 1, 2, 3 ]); + isa_ok($coerced, 'HTTPHeader'); + + is_deeply( + $coerced->array(), + [ 1, 2, 3 ], + '... got the right array'); + is($coerced->hash(), undef, '... nothing assigned to the hash'); + } + + { + my $coerced = $coercion->coerce({ one => 1, two => 2, three => 3 }); + isa_ok($coerced, 'HTTPHeader'); + + is_deeply( + $coerced->hash(), + { one => 1, two => 2, three => 3 }, + '... got the right hash'); + is($coerced->array(), undef, '... nothing assigned to the array'); + } + + { + my $scalar_ref = \(my $var); + my $coerced = $coercion->coerce($scalar_ref); + is($coerced, $scalar_ref, '... got back what we put in'); + } + + { + my $coerced = $coercion->coerce("Foo"); + is($coerced, "Foo", '... got back what we put in'); + } } - diff --git a/t/040_type_constraints/006_util_type_reloading.t b/t/040_type_constraints/006_util_type_reloading.t index e0a253f..4cde153 100644 --- a/t/040_type_constraints/006_util_type_reloading.t +++ b/t/040_type_constraints/006_util_type_reloading.t @@ -5,12 +5,10 @@ use warnings; use lib 't/lib', 'lib'; -use Test::More tests => 5; +use Test::More tests => 4; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + $SIG{__WARN__} = sub { 0 }; diff --git a/t/040_type_constraints/007_util_more_type_coercion.t b/t/040_type_constraints/007_util_more_type_coercion.t index 6480222..2bcafc1 100644 --- a/t/040_type_constraints/007_util_more_type_coercion.t +++ b/t/040_type_constraints/007_util_more_type_coercion.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 26; +use Test::More tests => 25; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package HTTPHeader; diff --git a/t/040_type_constraints/009_union_types_and_coercions.t b/t/040_type_constraints/009_union_types_and_coercions.t index a7d33d9..69254f5 100644 --- a/t/040_type_constraints/009_union_types_and_coercions.t +++ b/t/040_type_constraints/009_union_types_and_coercions.t @@ -9,12 +9,10 @@ use Test::Exception; BEGIN { eval "use IO::String; use IO::File;"; plan skip_all => "IO::String and IO::File are required for this test" if $@; - plan tests => 29; + plan tests => 28; } -BEGIN { - use_ok('Moose'); -} + { package Email::Moose; diff --git a/t/040_type_constraints/010_misc_type_tests.t b/t/040_type_constraints/010_misc_type_tests.t index 8e7a1cf..779bcf3 100644 --- a/t/040_type_constraints/010_misc_type_tests.t +++ b/t/040_type_constraints/010_misc_type_tests.t @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 3; +use Test::More tests => 8; use Test::Exception; BEGIN { @@ -17,4 +17,33 @@ lives_ok { } '... create bare subtype fine'; my $numb3rs = find_type_constraint('Numb3rs'); -isa_ok($numb3rs, 'Moose::Meta::TypeConstraint'); \ No newline at end of file +isa_ok($numb3rs, 'Moose::Meta::TypeConstraint'); + +# subtype with unions + +{ + package Test::Moose::Meta::TypeConstraint::Union; + + use overload '""' => sub {'Broken|Test'}, fallback => 1; + use Moose; + + extends 'Moose::Meta::TypeConstraint'; +} + +my $dummy_instance = Test::Moose::Meta::TypeConstraint::Union->new; + +ok $dummy_instance => "Created Instance"; + +isa_ok $dummy_instance, + 'Test::Moose::Meta::TypeConstraint::Union' => 'isa correct type'; + +is "$dummy_instance", "Broken|Test" => + 'Got expected stringification result'; + +my $subtype1 = subtype 'New1' => as $dummy_instance; + +ok $subtype1 => 'made a subtype from our type object'; + +my $subtype2 = subtype 'New2' => as $subtype1; + +ok $subtype2 => 'made a subtype of our subtype'; diff --git a/t/040_type_constraints/021_maybe_type_constraint.t b/t/040_type_constraints/021_maybe_type_constraint.t index c4f62d7..06528d3 100644 --- a/t/040_type_constraints/021_maybe_type_constraint.t +++ b/t/040_type_constraints/021_maybe_type_constraint.t @@ -3,13 +3,10 @@ use strict; use warnings; -use Test::More tests => 19; +use Test::More tests => 17; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Util::TypeConstraints'); -} +use Moose::Util::TypeConstraints; my $type = Moose::Util::TypeConstraints::find_or_parse_type_constraint('Maybe[Int]'); isa_ok($type, 'Moose::Meta::TypeConstraint'); diff --git a/t/040_type_constraints/023_types_and_undef.t b/t/040_type_constraints/023_types_and_undef.t index f5eccd1..f20ee2e 100644 --- a/t/040_type_constraints/023_types_and_undef.t +++ b/t/040_type_constraints/023_types_and_undef.t @@ -3,13 +3,10 @@ use strict; use warnings; -use Test::More tests => 55; +use Test::More tests => 54; use Test::Exception; -BEGIN -{ - use_ok('Moose'); -} + { package Foo; diff --git a/t/040_type_constraints/025_type_coersion_on_lazy_attributes.t b/t/040_type_constraints/025_type_coersion_on_lazy_attributes.t new file mode 100644 index 0000000..ebe9d33 --- /dev/null +++ b/t/040_type_constraints/025_type_coersion_on_lazy_attributes.t @@ -0,0 +1,33 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 2; + +{ + package SomeClass; + use Moose; + use Moose::Util::TypeConstraints; + + subtype 'DigitSix' => as 'Num' + => where { /^6$/ }; + subtype 'TextSix' => as 'Str' + => where { /Six/i }; + coerce 'TextSix' + => from 'DigitSix' + => via { confess("Cannot live without 6 ($_)") unless /^6$/; 'Six' }; + + has foo => ( + is => 'ro', + isa => 'TextSix', + coerce => 1, + default => 6, + lazy => 1 + ); +} + +my $attr = SomeClass->meta->get_attribute('foo'); +is($attr->get_value(SomeClass->new()), 'Six'); +is(SomeClass->new()->foo, 'Six'); + diff --git a/t/050_metaclasses/001_custom_attr_meta_with_roles.t b/t/050_metaclasses/001_custom_attr_meta_with_roles.t index e22cf9b..ba18c69 100644 --- a/t/050_metaclasses/001_custom_attr_meta_with_roles.t +++ b/t/050_metaclasses/001_custom_attr_meta_with_roles.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 4; +use Test::More tests => 3; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package My::Custom::Meta::Attr; diff --git a/t/050_metaclasses/002_custom_attr_meta_as_role.t b/t/050_metaclasses/002_custom_attr_meta_as_role.t index e979ea9..6c0fbfe 100644 --- a/t/050_metaclasses/002_custom_attr_meta_as_role.t +++ b/t/050_metaclasses/002_custom_attr_meta_as_role.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 3; +use Test::More tests => 2; use Test::Exception; -BEGIN { - use_ok('Moose'); -}; +; lives_ok { package MooseX::Attribute::Test; diff --git a/t/050_metaclasses/003_moose_w_metaclass.t b/t/050_metaclasses/003_moose_w_metaclass.t index dce160b..55576ba 100644 --- a/t/050_metaclasses/003_moose_w_metaclass.t +++ b/t/050_metaclasses/003_moose_w_metaclass.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 5; +use Test::More tests => 4; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + =pod @@ -53,6 +51,6 @@ isa_ok(Foo->meta, 'Foo::Meta'); eval 'use Moose;'; ::ok($@, '... could not load moose without correct metaclass'); ::like($@, - qr/^You already have a \&meta function\, but it does not return a Moose\:\:Meta\:\:Class/, + qr/^Bar already has a metaclass, but it does not inherit Moose::Meta::Class/, '... got the right error too'); } diff --git a/t/050_metaclasses/004_moose_for_meta.t b/t/050_metaclasses/004_moose_for_meta.t index dc966cd..d355dbc 100644 --- a/t/050_metaclasses/004_moose_for_meta.t +++ b/t/050_metaclasses/004_moose_for_meta.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 17; +use Test::More tests => 16; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + =pod diff --git a/t/050_metaclasses/010_extending_and_embedding.t b/t/050_metaclasses/010_extending_and_embedding_back_compat.t similarity index 94% rename from t/050_metaclasses/010_extending_and_embedding.t rename to t/050_metaclasses/010_extending_and_embedding_back_compat.t index 3fce149..9771a73 100644 --- a/t/050_metaclasses/010_extending_and_embedding.t +++ b/t/050_metaclasses/010_extending_and_embedding_back_compat.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 8; +use Test::More tests => 7; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + BEGIN { package MyFramework::Base; diff --git a/t/050_metaclasses/011_init_meta.t b/t/050_metaclasses/011_init_meta.t index 0f83849..96290dd 100644 --- a/t/050_metaclasses/011_init_meta.t +++ b/t/050_metaclasses/011_init_meta.t @@ -3,11 +3,10 @@ use strict; use warnings; -use Test::More tests => 5; +use Test::More tests => 4; + +use Moose (); -BEGIN { - use_ok('Moose'); -} { package Foo; } diff --git a/t/050_metaclasses/012_moose_exporter.t b/t/050_metaclasses/012_moose_exporter.t new file mode 100644 index 0000000..612196d --- /dev/null +++ b/t/050_metaclasses/012_moose_exporter.t @@ -0,0 +1,241 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; +use Test::Exception; + +BEGIN { + unless ( eval 'use Test::Warn; 1' ) { + plan skip_all => 'These tests require Test::Warn'; + } + else { + plan tests => 40; + } +} + + +{ + package HasOwnImmutable; + + use Moose; + + no Moose; + + ::warning_is( sub { eval q[sub make_immutable { return 'foo' }] }, + '', + 'no warning when defining our own make_immutable sub' ); +} + +{ + is( HasOwnImmutable->make_immutable(), 'foo', + 'HasOwnImmutable->make_immutable does not get overwritten' ); +} + +{ + package MooseX::Empty; + + use Moose (); + Moose::Exporter->setup_import_methods( also => 'Moose' ); +} + +{ + package WantsMoose; + + MooseX::Empty->import(); + + sub foo { 1 } + + ::can_ok( 'WantsMoose', 'has' ); + ::can_ok( 'WantsMoose', 'with' ); + ::can_ok( 'WantsMoose', 'foo' ); + + MooseX::Empty->unimport(); +} + +{ + # Note: it's important that these methods be out of scope _now_, + # after unimport was called. We tried a + # namespace::clean(0.08)-based solution, but had to abandon it + # because it cleans the namespace _later_ (when the file scope + # ends). + ok( ! WantsMoose->can('has'), 'WantsMoose::has() has been cleaned' ); + ok( ! WantsMoose->can('with'), 'WantsMoose::with() has been cleaned' ); + can_ok( 'WantsMoose', 'foo' ); + + # This makes sure that Moose->init_meta() happens properly + isa_ok( WantsMoose->meta(), 'Moose::Meta::Class' ); + isa_ok( WantsMoose->new(), 'Moose::Object' ); + +} + +{ + package MooseX::Sugar; + + use Moose (); + + sub wrapped1 { + my $caller = shift; + return $caller . ' called wrapped1'; + } + + Moose::Exporter->setup_import_methods( + with_caller => ['wrapped1'], + also => 'Moose', + ); +} + +{ + package WantsSugar; + + MooseX::Sugar->import(); + + sub foo { 1 } + + ::can_ok( 'WantsSugar', 'has' ); + ::can_ok( 'WantsSugar', 'with' ); + ::can_ok( 'WantsSugar', 'wrapped1' ); + ::can_ok( 'WantsSugar', 'foo' ); + ::is( wrapped1(), 'WantsSugar called wrapped1', + 'wrapped1 identifies the caller correctly' ); + + MooseX::Sugar->unimport(); +} + +{ + ok( ! WantsSugar->can('has'), 'WantsSugar::has() has been cleaned' ); + ok( ! WantsSugar->can('with'), 'WantsSugar::with() has been cleaned' ); + ok( ! WantsSugar->can('wrapped1'), 'WantsSugar::wrapped1() has been cleaned' ); + can_ok( 'WantsSugar', 'foo' ); +} + +{ + package MooseX::MoreSugar; + + use Moose (); + + sub wrapped2 { + my $caller = shift; + return $caller . ' called wrapped2'; + } + + sub as_is1 { + return 'as_is1'; + } + + Moose::Exporter->setup_import_methods( + with_caller => ['wrapped2'], + as_is => ['as_is1'], + also => 'MooseX::Sugar', + ); +} + +{ + package WantsMoreSugar; + + MooseX::MoreSugar->import(); + + sub foo { 1 } + + ::can_ok( 'WantsMoreSugar', 'has' ); + ::can_ok( 'WantsMoreSugar', 'with' ); + ::can_ok( 'WantsMoreSugar', 'wrapped1' ); + ::can_ok( 'WantsMoreSugar', 'wrapped2' ); + ::can_ok( 'WantsMoreSugar', 'as_is1' ); + ::can_ok( 'WantsMoreSugar', 'foo' ); + ::is( wrapped1(), 'WantsMoreSugar called wrapped1', + 'wrapped1 identifies the caller correctly' ); + ::is( wrapped2(), 'WantsMoreSugar called wrapped2', + 'wrapped2 identifies the caller correctly' ); + ::is( as_is1(), 'as_is1', + 'as_is1 works as expected' ); + + MooseX::MoreSugar->unimport(); +} + +{ + ok( ! WantsMoreSugar->can('has'), 'WantsMoreSugar::has() has been cleaned' ); + ok( ! WantsMoreSugar->can('with'), 'WantsMoreSugar::with() has been cleaned' ); + ok( ! WantsMoreSugar->can('wrapped1'), 'WantsMoreSugar::wrapped1() has been cleaned' ); + ok( ! WantsMoreSugar->can('wrapped2'), 'WantsMoreSugar::wrapped2() has been cleaned' ); + ok( ! WantsMoreSugar->can('as_is1'), 'WantsMoreSugar::as_is1() has been cleaned' ); + can_ok( 'WantsMoreSugar', 'foo' ); +} + +{ + package My::Metaclass; + use Moose; + BEGIN { extends 'Moose::Meta::Class' } + + package My::Object; + use Moose; + BEGIN { extends 'Moose::Object' } + + package HasInitMeta; + + use Moose (); + + sub init_meta { + shift; + return Moose->init_meta( @_, + metaclass => 'My::Metaclass', + base_class => 'My::Object', + ); + } + + Moose::Exporter->setup_import_methods( also => 'Moose' ); +} + +{ + package NewMeta; + + HasInitMeta->import(); +} + +{ + isa_ok( NewMeta->meta(), 'My::Metaclass' ); + isa_ok( NewMeta->new(), 'My::Object' ); +} + +{ + package MooseX::CircularAlso; + + use Moose (); + + ::dies_ok( + sub { + Moose::Exporter->setup_import_methods( + also => [ 'Moose', 'MooseX::CircularAlso' ], + ); + }, + 'a circular reference in also dies with an error' + ); + + ::like( + $@, + qr/\QCircular reference in also parameter to MooseX::Exporter between MooseX::CircularAlso and MooseX::CircularAlso/, + 'got the expected error from circular reference in also' + ); +} + +{ + package MooseX::CircularAlso; + + use Moose (); + + ::dies_ok( + sub { + Moose::Exporter->setup_import_methods( + also => [ 'NoSuchThing' ], + ); + }, + 'a package which does not use Moose::Exporter in also dies with an error' + ); + + ::like( + $@, + qr/\QPackage in also (NoSuchThing) does not seem to use MooseX::Exporter/, + 'got the expected error from a reference in also to a package which does not use Moose::Exporter' + ); +} diff --git a/t/050_metaclasses/013_metaclass_traits.t b/t/050_metaclasses/013_metaclass_traits.t new file mode 100644 index 0000000..9a32641 --- /dev/null +++ b/t/050_metaclasses/013_metaclass_traits.t @@ -0,0 +1,193 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 28; +use Test::Exception; + +{ + package My::SimpleTrait; + + use Moose::Role; + + sub simple { return 5 } +} + +{ + package Foo; + + use Moose -traits => [ 'My::SimpleTrait' ]; +} + +can_ok( Foo->meta(), 'simple' ); +is( Foo->meta()->simple(), 5, + 'Foo->meta()->simple() returns expected value' ); + +{ + package Bar; + + use Moose -traits => 'My::SimpleTrait'; +} + +can_ok( Bar->meta(), 'simple' ); +is( Bar->meta()->simple(), 5, + 'Foo->meta()->simple() returns expected value' ); + +{ + package My::SimpleTrait2; + + use Moose::Role; + + # This needs to happen at compile time so it happens before we + # apply traits to Bar + BEGIN { + has 'attr' => + ( is => 'ro', + default => 'something', + ); + } + + sub simple { return 5 } +} + +{ + package Bar; + + use Moose -traits => [ 'My::SimpleTrait2' ]; +} + +can_ok( Bar->meta(), 'simple' ); +is( Bar->meta()->simple(), 5, + 'Bar->meta()->simple() returns expected value' ); +can_ok( Bar->meta(), 'attr' ); +is( Bar->meta()->attr(), 'something', + 'Bar->meta()->attr() returns expected value' ); + +{ + package My::SimpleTrait3; + + use Moose::Role; + + BEGIN { + has 'attr2' => + ( is => 'ro', + default => 'something', + ); + } + + sub simple2 { return 55 } +} + +{ + package Baz; + + use Moose -traits => [ 'My::SimpleTrait2', 'My::SimpleTrait3' ]; +} + +can_ok( Baz->meta(), 'simple' ); +is( Baz->meta()->simple(), 5, + 'Baz->meta()->simple() returns expected value' ); +can_ok( Baz->meta(), 'attr' ); +is( Baz->meta()->attr(), 'something', + 'Baz->meta()->attr() returns expected value' ); +can_ok( Baz->meta(), 'simple2' ); +is( Baz->meta()->simple2(), 55, + 'Baz->meta()->simple2() returns expected value' ); +can_ok( Baz->meta(), 'attr2' ); +is( Baz->meta()->attr2(), 'something', + 'Baz->meta()->attr2() returns expected value' ); + +{ + package My::Trait::AlwaysRO; + + use Moose::Role; + + around '_process_new_attribute', '_process_inherited_attribute' => + sub { + my $orig = shift; + my ( $self, $name, %args ) = @_; + + $args{is} = 'ro'; + + return $self->$orig( $name, %args ); + }; +} + +{ + package Quux; + + use Moose -traits => [ 'My::Trait::AlwaysRO' ]; + + has 'size' => + ( is => 'rw', + isa => 'Int', + ); +} + +ok( Quux->meta()->has_attribute('size'), + 'Quux has size attribute' ); +ok( ! Quux->meta()->get_attribute('size')->writer(), + 'size attribute does not have a writer' ); + +{ + package My::Class::Whatever; + + use Moose::Role; + + sub whatever { 42 } + + package Moose::Meta::Class::Custom::Trait::Whatever; + + sub register_implementation { + return 'My::Class::Whatever'; + } +} + +{ + package RanOutOfNames; + + use Moose -traits => [ 'Whatever' ]; +} + +ok( RanOutOfNames->meta()->meta()->has_method('whatever'), + 'RanOutOfNames->meta() has whatever method' ); + +{ + package Role::Foo; + + use Moose::Role -traits => [ 'My::SimpleTrait' ]; +} + +can_ok( Role::Foo->meta(), 'simple' ); +is( Role::Foo->meta()->simple(), 5, + 'Role::Foo->meta()->simple() returns expected value' ); + +{ + require Moose::Util::TypeConstraints; + dies_ok( sub { Moose::Util::TypeConstraints->import( -traits => 'My::SimpleTrait' ) }, + 'cannot provide -traits to an exporting module that does not init_meta' ); + like( $@, qr/does not have an init_meta/, + '... and error provides a useful explanation' ); +} + +SKIP: +{ + skip 'This will blow up until Moose::Meta::Class->_fix_metaclass_incompatibility understands roles', 5; +{ + package Foo::Subclass; + + use Moose -traits => [ 'My::SimpleTrait3' ]; + + extends 'Foo'; +} + +can_ok( Foo::Subclass->meta(), 'simple' ); +is( Foo::Subclass->meta()->simple(), 5, + 'Foo::Subclass->meta()->simple() returns expected value' ); +is( Foo::Subclass->meta()->simple2(), 55, + 'Foo::Subclass->meta()->simple2() returns expected value' ); +can_ok( Foo::Subclass->meta(), 'attr2' ); +is( Foo::Subclass->meta()->attr2(), 'something', + 'Foo::Subclass->meta()->attr2() returns expected value' ); +} diff --git a/t/050_metaclasses/014_goto_moose_import.t b/t/050_metaclasses/014_goto_moose_import.t new file mode 100644 index 0000000..41056a9 --- /dev/null +++ b/t/050_metaclasses/014_goto_moose_import.t @@ -0,0 +1,82 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 8; +use Test::Exception; + +# Some packages out in the wild cooperate with Moose by using goto +# &Moose::import. we want to make sure it still works. + +{ + package MooseAlike1; + + use strict; + use warnings; + + use Moose (); + + sub import { + goto &Moose::import; + } + + sub unimport { + goto &Moose::unimport; + } +} + +{ + package Foo; + + MooseAlike1->import(); + + ::lives_ok( sub { has( 'size' ) }, + 'has was exported via MooseAlike1' ); + + MooseAlike1->unimport(); +} + +ok( ! Foo->can('has'), + 'No has sub in Foo after MooseAlike1 is unimported' ); +ok( Foo->can('meta'), + 'Foo has a meta method' ); +isa_ok( Foo->meta(), 'Moose::Meta::Class' ); + + +{ + package MooseAlike2; + + use strict; + use warnings; + + use Moose (); + + my $import = \&Moose::import; + sub import { + goto $import; + } + + my $unimport = \&Moose::unimport; + sub unimport { + goto $unimport; + } +} + +{ + package Bar; + + MooseAlike2->import(); + + ::lives_ok( sub { has( 'size' ) }, + 'has was exported via MooseAlike2' ); + + MooseAlike2->unimport(); +} + + +ok( ! Bar->can('has'), + 'No has sub in Bar after MooseAlike2 is unimported' ); +ok( Bar->can('meta'), + 'Bar has a meta method' ); +isa_ok( Bar->meta(), 'Moose::Meta::Class' ); diff --git a/t/050_metaclasses/015_metarole.t b/t/050_metaclasses/015_metarole.t new file mode 100644 index 0000000..97209a5 --- /dev/null +++ b/t/050_metaclasses/015_metarole.t @@ -0,0 +1,383 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 59; + +use Moose::Util::MetaRole; + + +{ + package My::Meta::Class; + use Moose; + extends 'Moose::Meta::Class'; +} + +{ + package My::Meta::Attribute; + use Moose; + extends 'Moose::Meta::Attribute'; +} + +{ + package My::Meta::Method; + use Moose; + extends 'Moose::Meta::Method'; +} + +{ + package My::Meta::Instance; + use Moose; + extends 'Moose::Meta::Instance'; +} + +{ + package My::Meta::MethodConstructor; + use Moose; + extends 'Moose::Meta::Method::Constructor'; +} + +{ + package My::Meta::MethodDestructor; + use Moose; + extends 'Moose::Meta::Method::Destructor'; +} + +{ + package Role::Foo; + use Moose::Role; + has 'foo' => ( is => 'ro', default => 10 ); +} + +{ + package My::Class; + + use Moose; +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class', + metaclass_roles => ['Role::Foo'], + ); + + ok( My::Class->meta()->meta()->does_role('Role::Foo'), + 'apply Role::Foo to My::Class->meta()' ); + is( My::Class->meta()->foo(), 10, + '... and call foo() on that meta object' ); +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class', + attribute_metaclass_roles => ['Role::Foo'], + ); + + ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class->meta()'s attribute metaclass} ); + ok( My::Class->meta()->meta()->does_role('Role::Foo'), + '... My::Class->meta() still does Role::Foo' ); + + My::Class->meta()->add_attribute( 'size', is => 'ro' ); + is( My::Class->meta()->get_attribute('size')->foo(), 10, + '... call foo() on an attribute metaclass object' ); +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class', + method_metaclass_roles => ['Role::Foo'], + ); + + ok( My::Class->meta()->method_metaclass()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class->meta()'s method metaclass} ); + ok( My::Class->meta()->meta()->does_role('Role::Foo'), + '... My::Class->meta() still does Role::Foo' ); + ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'), + q{... My::Class->meta()'s attribute metaclass still does Role::Foo} ); + + My::Class->meta()->add_method( 'bar' => sub { 'bar' } ); + is( My::Class->meta()->get_method('bar')->foo(), 10, + '... call foo() on a method metaclass object' ); +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class', + instance_metaclass_roles => ['Role::Foo'], + ); + + ok( My::Class->meta()->instance_metaclass()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class->meta()'s instance metaclass} ); + ok( My::Class->meta()->meta()->does_role('Role::Foo'), + '... My::Class->meta() still does Role::Foo' ); + ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'), + q{... My::Class->meta()'s attribute metaclass still does Role::Foo} ); + ok( My::Class->meta()->method_metaclass()->meta()->does_role('Role::Foo'), + q{... My::Class->meta()'s method metaclass still does Role::Foo} ); + + is( My::Class->meta()->get_meta_instance()->foo(), 10, + '... call foo() on an instance metaclass object' ); +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class', + constructor_class_roles => ['Role::Foo'], + ); + + ok( My::Class->meta()->constructor_class()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class->meta()'s constructor class} ); + ok( My::Class->meta()->meta()->does_role('Role::Foo'), + '... My::Class->meta() still does Role::Foo' ); + ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'), + q{... My::Class->meta()'s attribute metaclass still does Role::Foo} ); + ok( My::Class->meta()->method_metaclass()->meta()->does_role('Role::Foo'), + q{... My::Class->meta()'s method metaclass still does Role::Foo} ); + ok( My::Class->meta()->instance_metaclass()->meta()->does_role('Role::Foo'), + q{... My::Class->meta()'s instance metaclass still does Role::Foo} ); + + # Actually instantiating the constructor class is too freaking hard! + ok( My::Class->meta()->constructor_class()->can('foo'), + '... constructor class has a foo method' ); +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class', + destructor_class_roles => ['Role::Foo'], + ); + + ok( My::Class->meta()->destructor_class()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class->meta()'s destructor class} ); + ok( My::Class->meta()->meta()->does_role('Role::Foo'), + '... My::Class->meta() still does Role::Foo' ); + ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'), + q{... My::Class->meta()'s attribute metaclass still does Role::Foo} ); + ok( My::Class->meta()->method_metaclass()->meta()->does_role('Role::Foo'), + q{... My::Class->meta()'s method metaclass still does Role::Foo} ); + ok( My::Class->meta()->instance_metaclass()->meta()->does_role('Role::Foo'), + q{... My::Class->meta()'s instance metaclass still does Role::Foo} ); + ok( My::Class->meta()->constructor_class()->meta()->does_role('Role::Foo'), + q{... My::Class->meta()'s constructor class still does Role::Foo} ); + + # same problem as the constructor class + ok( My::Class->meta()->destructor_class()->can('foo'), + '... destructor class has a foo method' ); +} + +{ + Moose::Util::MetaRole::apply_base_class_roles( + for_class => 'My::Class', + roles => ['Role::Foo'], + ); + + ok( My::Class->meta()->does_role('Role::Foo'), + 'apply Role::Foo to My::Class base class' ); + is( My::Class->new()->foo(), 10, + '... call foo() on a My::Class object' ); +} + +{ + package My::Class2; + + use Moose; +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class2', + metaclass_roles => ['Role::Foo'], + attribute_metaclass_roles => ['Role::Foo'], + method_metaclass_roles => ['Role::Foo'], + instance_metaclass_roles => ['Role::Foo'], + constructor_class_roles => ['Role::Foo'], + destructor_class_roles => ['Role::Foo'], + ); + + ok( My::Class2->meta()->meta()->does_role('Role::Foo'), + 'apply Role::Foo to My::Class2->meta()' ); + is( My::Class2->meta()->foo(), 10, + '... and call foo() on that meta object' ); + ok( My::Class2->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class2->meta()'s attribute metaclass} ); + My::Class2->meta()->add_attribute( 'size', is => 'ro' ); + + is( My::Class2->meta()->get_attribute('size')->foo(), 10, + '... call foo() on an attribute metaclass object' ); + + ok( My::Class2->meta()->method_metaclass()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class2->meta()'s method metaclass} ); + + My::Class2->meta()->add_method( 'bar' => sub { 'bar' } ); + is( My::Class2->meta()->get_method('bar')->foo(), 10, + '... call foo() on a method metaclass object' ); + + ok( My::Class2->meta()->instance_metaclass()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class2->meta()'s instance metaclass} ); + is( My::Class2->meta()->get_meta_instance()->foo(), 10, + '... call foo() on an instance metaclass object' ); + + ok( My::Class2->meta()->constructor_class()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class2->meta()'s constructor class} ); + ok( My::Class2->meta()->constructor_class()->can('foo'), + '... constructor class has a foo method' ); + + ok( My::Class2->meta()->destructor_class()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class2->meta()'s destructor class} ); + ok( My::Class2->meta()->destructor_class()->can('foo'), + '... destructor class has a foo method' ); +} + + +{ + package My::Meta; + + use Moose::Exporter; + Moose::Exporter->setup_import_methods( also => 'Moose' ); + + sub init_meta { + shift; + my %p = @_; + + Moose->init_meta( %p, metaclass => 'My::Meta::Class' ); + } +} + +{ + package My::Class3; + + My::Meta->import(); +} + + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class3', + metaclass_roles => ['Role::Foo'], + ); + + ok( My::Class3->meta()->meta()->does_role('Role::Foo'), + 'apply Role::Foo to My::Class3->meta()' ); + is( My::Class3->meta()->foo(), 10, + '... and call foo() on that meta object' ); + ok( ( grep { $_ eq 'My::Meta::Class' } My::Class3->meta()->meta()->superclasses() ), + 'apply_metaclass_roles() does not interfere with metaclass set via Moose->init_meta()' ); +} + +{ + package Role::Bar; + use Moose::Role; + has 'bar' => ( is => 'ro', default => 200 ); +} + +{ + package My::Class4; + use Moose; +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class4', + metaclass_roles => ['Role::Foo'], + ); + + ok( My::Class4->meta()->meta()->does_role('Role::Foo'), + 'apply Role::Foo to My::Class4->meta()' ); + + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class4', + metaclass_roles => ['Role::Bar'], + ); + + ok( My::Class4->meta()->meta()->does_role('Role::Bar'), + 'apply Role::Bar to My::Class4->meta()' ); + ok( My::Class4->meta()->meta()->does_role('Role::Foo'), + '... and My::Class4->meta() still does Role::Foo' ); +} + +{ + package My::Class5; + use Moose; + + extends 'My::Class'; +} + +{ + ok( My::Class5->meta()->meta()->does_role('Role::Foo'), + q{My::Class55->meta()'s does Role::Foo because it extends My::Class} ); + ok( My::Class5->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'), + q{My::Class5->meta()'s attribute metaclass also does Role::Foo} ); + ok( My::Class5->meta()->method_metaclass()->meta()->does_role('Role::Foo'), + q{My::Class5->meta()'s method metaclass also does Role::Foo} ); + ok( My::Class5->meta()->instance_metaclass()->meta()->does_role('Role::Foo'), + q{My::Class5->meta()'s instance metaclass also does Role::Foo} ); + ok( My::Class5->meta()->constructor_class()->meta()->does_role('Role::Foo'), + q{My::Class5->meta()'s constructor class also does Role::Foo} ); + ok( My::Class5->meta()->destructor_class()->meta()->does_role('Role::Foo'), + q{My::Class5->meta()'s destructor class also does Role::Foo} ); +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class5', + metaclass_roles => ['Role::Bar'], + ); + + ok( My::Class5->meta()->meta()->does_role('Role::Bar'), + q{apply Role::Bar My::Class5->meta()} ); + ok( My::Class5->meta()->meta()->does_role('Role::Foo'), + q{... and My::Class5->meta() still does Role::Foo} ); +} + +SKIP: +{ + skip + 'These tests will fail until Moose::Meta::Class->_fix_metaclass_incompatibility is much smarter.', + 2; + +{ + package My::Class6; + use Moose; + + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class6', + metaclass_roles => ['Role::Bar'], + ); + + extends 'My::Class'; +} + +{ + ok( My::Class6->meta()->meta()->does_role('Role::Bar'), + q{apply Role::Bar My::Class6->meta() before extends} ); + ok( My::Class6->meta()->meta()->does_role('Role::Foo'), + q{... and My::Class6->meta() does Role::Foo because it extends My::Class} ); +} +} + +# This is the hack needed to work around the +# _fix_metaclass_incompatibility problem. You must call extends() +# (which in turn calls _fix_metaclass_imcompatibility) _before_ you +# apply more extensions in the subclass. +{ + package My::Class7; + use Moose; + + # In real usage this would go in a BEGIN block so it happened + # before apply_metaclass_roles was called by an extension. + extends 'My::Class'; + + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class7', + metaclass_roles => ['Role::Bar'], + ); +} + +{ + ok( My::Class7->meta()->meta()->does_role('Role::Bar'), + q{apply Role::Bar My::Class7->meta() before extends} ); + ok( My::Class7->meta()->meta()->does_role('Role::Foo'), + q{... and My::Class7->meta() does Role::Foo because it extends My::Class} ); +} diff --git a/t/050_metaclasses/016_metarole_w_metaclass_pm.t b/t/050_metaclasses/016_metarole_w_metaclass_pm.t new file mode 100644 index 0000000..4db435e --- /dev/null +++ b/t/050_metaclasses/016_metarole_w_metaclass_pm.t @@ -0,0 +1,109 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 8; + +use Moose::Util::MetaRole; + +BEGIN +{ + package My::Meta::Class; + use Moose; + extends 'Moose::Meta::Class'; +} + +BEGIN +{ + package My::Meta::Attribute; + use Moose; + extends 'Moose::Meta::Attribute'; +} + +BEGIN +{ + package My::Meta::Method; + use Moose; + extends 'Moose::Meta::Method'; +} + +BEGIN +{ + package My::Meta::Instance; + use Moose; + extends 'Moose::Meta::Instance'; +} + +BEGIN +{ + package Role::Foo; + use Moose::Role; + has 'foo' => ( is => 'ro', default => 10 ); +} + +{ + package My::Class; + + use metaclass 'My::Meta::Class'; + use Moose; +} + +{ + package My::Class2; + + use metaclass 'My::Meta::Class' => ( + attribute_metaclass => 'My::Meta::Attribute', + method_metaclass => 'My::Meta::Method', + instance_metaclass => 'My::Meta::Instance', + ); + + use Moose; +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class', + metaclass_roles => ['Role::Foo'], + ); + + ok( My::Class->meta()->meta()->does_role('Role::Foo'), + 'apply Role::Foo to My::Class->meta()' ); + has_superclass( My::Class->meta(), 'My::Meta::Class', + 'apply_metaclass_roles works with metaclass.pm' ); +} + +{ + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => 'My::Class2', + attribute_metaclass_roles => ['Role::Foo'], + method_metaclass_roles => ['Role::Foo'], + instance_metaclass_roles => ['Role::Foo'], + ); + + ok( My::Class2->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class2->meta()'s attribute metaclass} ); + has_superclass( My::Class2->meta()->attribute_metaclass(), 'My::Meta::Attribute', + '... and this does not interfere with attribute metaclass set via metaclass.pm' ); + ok( My::Class2->meta()->method_metaclass()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class2->meta()'s method metaclass} ); + has_superclass( My::Class2->meta()->method_metaclass(), 'My::Meta::Method', + '... and this does not interfere with method metaclass set via metaclass.pm' ); + ok( My::Class2->meta()->instance_metaclass()->meta()->does_role('Role::Foo'), + q{apply Role::Foo to My::Class2->meta()'s instance metaclass} ); + has_superclass( My::Class2->meta()->instance_metaclass(), 'My::Meta::Instance', + '... and this does not interfere with instance metaclass set via metaclass.pm' ); +} + +# like isa_ok but works with a class name, not just refs +sub has_superclass { + my $thing = shift; + my $parent = shift; + my $desc = shift; + + my %supers = map { $_ => 1 } $thing->meta()->superclasses(); + + local $Test::Builder::Level = $Test::Builder::Level + 1; + + ok( $supers{$parent}, $desc ); +} diff --git a/t/060_compat/002_moose_respects_base.t b/t/060_compat/002_moose_respects_base.t index c967c22..e146561 100644 --- a/t/060_compat/002_moose_respects_base.t +++ b/t/060_compat/002_moose_respects_base.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 7; +use Test::More tests => 6; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + =pod diff --git a/t/060_compat/003_foreign_inheritence.t b/t/060_compat/003_foreign_inheritence.t index 8fcb324..323b97e 100644 --- a/t/060_compat/003_foreign_inheritence.t +++ b/t/060_compat/003_foreign_inheritence.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 7; +use Test::More tests => 6; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Elk; diff --git a/t/100_bugs/001_subtype_quote_bug.t b/t/100_bugs/001_subtype_quote_bug.t index f01d168..a97e2d1 100644 --- a/t/100_bugs/001_subtype_quote_bug.t +++ b/t/100_bugs/001_subtype_quote_bug.t @@ -28,4 +28,5 @@ be well from now on. { package Object::Test; } -use_ok('Moose'); +package Foo; +::use_ok('Moose'); diff --git a/t/100_bugs/002_subtype_conflict_bug.t b/t/100_bugs/002_subtype_conflict_bug.t index 095213c..1dabe6c 100644 --- a/t/100_bugs/002_subtype_conflict_bug.t +++ b/t/100_bugs/002_subtype_conflict_bug.t @@ -5,11 +5,9 @@ use warnings; use lib 't/lib', 'lib'; -use Test::More tests => 3; +use Test::More tests => 2; + -BEGIN { - use_ok('Moose'); -} use_ok('MyMooseA'); use_ok('MyMooseB'); \ No newline at end of file diff --git a/t/100_bugs/004_subclass_use_base_bug.t b/t/100_bugs/004_subclass_use_base_bug.t index 6052f83..689cc21 100644 --- a/t/100_bugs/004_subclass_use_base_bug.t +++ b/t/100_bugs/004_subclass_use_base_bug.t @@ -3,11 +3,9 @@ use strict; use warnings; -use Test::More tests => 3; +use Test::More tests => 2; + -BEGIN { - use_ok('Moose'); -} =pod diff --git a/t/100_bugs/005_inline_reader_bug.t b/t/100_bugs/005_inline_reader_bug.t index 7321764..2e67750 100644 --- a/t/100_bugs/005_inline_reader_bug.t +++ b/t/100_bugs/005_inline_reader_bug.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 2; +use Test::More tests => 1; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + =pod diff --git a/t/100_bugs/007_reader_precedence_bug.t b/t/100_bugs/007_reader_precedence_bug.t index b4c18cc..c36721f 100644 --- a/t/100_bugs/007_reader_precedence_bug.t +++ b/t/100_bugs/007_reader_precedence_bug.t @@ -2,7 +2,6 @@ use strict; use warnings; -use Moose; use Test::More tests => 3; diff --git a/t/100_bugs/008_new_w_undef.t b/t/100_bugs/008_new_w_undef.t index 58d7074..4001dc3 100644 --- a/t/100_bugs/008_new_w_undef.t +++ b/t/100_bugs/008_new_w_undef.t @@ -2,7 +2,6 @@ use strict; use warnings; -use Moose; use Test::More tests => 1; use Test::Exception; diff --git a/t/100_bugs/009_augment_recursion_bug.t b/t/100_bugs/009_augment_recursion_bug.t index df985e7..3e41104 100644 --- a/t/100_bugs/009_augment_recursion_bug.t +++ b/t/100_bugs/009_augment_recursion_bug.t @@ -3,11 +3,9 @@ use strict; use warnings; -use Test::More tests => 4; +use Test::More tests => 3; + -BEGIN { - use_ok('Moose'); -} { package Foo; diff --git a/t/100_bugs/010_immutable_n_default_x2.t b/t/100_bugs/010_immutable_n_default_x2.t index e5d3b19..5cc2fb0 100644 --- a/t/100_bugs/010_immutable_n_default_x2.t +++ b/t/100_bugs/010_immutable_n_default_x2.t @@ -3,11 +3,9 @@ use strict; use warnings; -use Test::More tests => 3; +use Test::More tests => 2; + -BEGIN { - use_ok('Moose'); -} { package Foo; diff --git a/t/100_bugs/011_DEMOLISH_eats_exceptions.t b/t/100_bugs/011_DEMOLISH_eats_exceptions.t index 10262f6..7574db7 100644 --- a/t/100_bugs/011_DEMOLISH_eats_exceptions.t +++ b/t/100_bugs/011_DEMOLISH_eats_exceptions.t @@ -4,13 +4,10 @@ use strict; use warnings; use FindBin; -use Test::More tests => 146; +use Test::More tests => 144; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Util::TypeConstraints'); -} +use Moose::Util::TypeConstraints; subtype 'FilePath' => as 'Str' diff --git a/t/100_bugs/012_DEMOLISH_eats_mini.t b/t/100_bugs/012_DEMOLISH_eats_mini.t index 130e2c0..5773a60 100644 --- a/t/100_bugs/012_DEMOLISH_eats_mini.t +++ b/t/100_bugs/012_DEMOLISH_eats_mini.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 5; +use Test::More tests => 4; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/100_bugs/016_inheriting_from_roles.t b/t/100_bugs/016_inheriting_from_roles.t new file mode 100644 index 0000000..2fa0357 --- /dev/null +++ b/t/100_bugs/016_inheriting_from_roles.t @@ -0,0 +1,23 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 1; +use Test::Exception; + + + +{ + package My::Role; + use Moose::Role; +} +{ + package My::Class; + use Moose; + + ::throws_ok { + extends 'My::Role'; + } qr/You cannot inherit from a Moose Role \(My\:\:Role\)/, + '... this croaks correctly'; +} diff --git a/t/100_bugs/017_type_constraint_messages.t b/t/100_bugs/017_type_constraint_messages.t new file mode 100644 index 0000000..6740e81 --- /dev/null +++ b/t/100_bugs/017_type_constraint_messages.t @@ -0,0 +1,68 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More no_plan => 1; +use Test::Exception; + + + +# RT #37569 + +{ + package MyObject; + use Moose; + + package Foo; + use Moose; + use Moose::Util::TypeConstraints; + + subtype 'MyArrayRef' + => as 'ArrayRef' + => where { defined $_->[0] } + => message { ref $_ ? "ref: ". ref $_ : 'scalar' } # stringy + ; + + subtype 'MyObjectType' + => as 'Object' + => where { $_->isa('MyObject') } + => message { + if ( $_->isa('SomeObject') ) { + return 'More detailed error message'; + } + elsif ( blessed $_ ) { + return 'Well it is an object'; + } + else { + return 'Doh!'; + } + } + ; + + type 'NewType' + => where { $_->isa('MyObject') } + => message { blessed $_ ? 'blessed' : 'scalar' } + ; + + has 'obj' => ( is => 'rw', isa => 'MyObjectType' ); + has 'ar' => ( is => 'rw', isa => 'MyArrayRef' ); + has 'nt' => ( is => 'rw', isa => 'NewType' ); +} + +my $foo = Foo->new; +my $obj = MyObject->new; + +throws_ok { + $foo->ar([]); +} qr/Attribute \(ar\) does not pass the type constraint because: ref: ARRAY/, '... got the right error message'; + +throws_ok { + $foo->obj($foo); # Doh! +} qr/Attribute \(obj\) does not pass the type constraint because: Well it is an object/, '... got the right error message'; + +throws_ok { + $foo->nt($foo); # scalar +} qr/Attribute \(nt\) does not pass the type constraint because: blessed/, '... got the right error message'; + + diff --git a/t/100_bugs/018_immutable_metaclass_does_role.t b/t/100_bugs/018_immutable_metaclass_does_role.t new file mode 100644 index 0000000..be4f016 --- /dev/null +++ b/t/100_bugs/018_immutable_metaclass_does_role.t @@ -0,0 +1,92 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 36; +use Test::Exception; + + + +BEGIN { + package MyRole; + use Moose::Role; + + requires 'foo'; + + package MyMetaclass; + use Moose qw(extends with); + extends 'Moose::Meta::Class'; + with 'MyRole'; + + sub foo { 'i am foo' } +} + +{ + package MyClass; + use metaclass ('MyMetaclass'); + use Moose; +} + +my $mc = MyMetaclass->initialize('MyClass'); +isa_ok($mc, 'MyMetaclass'); + +ok($mc->meta->does_role('MyRole'), '... the metaclass does the role'); + +is(MyClass->meta, $mc, '... these metas are the same thing'); +is(MyClass->meta->meta, $mc->meta, '... these meta-metas are the same thing'); + +my $a = MyClass->new; +ok( $a->meta->meta->does_role('MyRole'), 'metaclass does MyRole' ); +is( $a->meta->foo, 'i am foo', '... foo method returns expected value' ); +ok( MyClass->meta->meta->does_role('MyRole'), 'metaclass does MyRole' ); +is( MyClass->meta->foo, 'i am foo', '... foo method returns expected value' ); + +lives_ok { + MyClass->meta->make_immutable; +} '... make MyClass immutable okay'; + +is(MyClass->meta, $mc, '... these metas are still the same thing'); +is(MyClass->meta->meta, $mc->meta, '... these meta-metas are the same thing'); + +ok( $a->meta->meta->does_role('MyRole'), 'metaclass does MyRole' ); +is( $a->meta->foo, 'i am foo', '... foo method returns expected value' ); +ok( MyClass->meta->meta->does_role('MyRole'), 'metaclass does MyRole' ); +is( MyClass->meta->foo, 'i am foo', '... foo method returns expected value' ); + +lives_ok { + MyClass->meta->make_mutable; +} '... make MyClass mutable okay'; + +is(MyClass->meta, $mc, '... these metas are still the same thing'); +is(MyClass->meta->meta, $mc->meta, '... these meta-metas are the same thing'); + +ok( $a->meta->meta->does_role('MyRole'), 'metaclass does MyRole' ); +is( $a->meta->foo, 'i am foo', '... foo method returns expected value' ); +ok( MyClass->meta->meta->does_role('MyRole'), 'metaclass does MyRole' ); +is( MyClass->meta->foo, 'i am foo', '... foo method returns expected value' ); + +lives_ok { + MyMetaclass->meta->make_immutable; +} '... make MyClass immutable okay'; + +is(MyClass->meta, $mc, '... these metas are still the same thing'); +is(MyClass->meta->meta, $mc->meta, '... these meta-metas are the same thing'); + +ok( $a->meta->meta->does_role('MyRole'), 'metaclass does MyRole' ); +is( $a->meta->foo, 'i am foo', '... foo method returns expected value' ); +ok( MyClass->meta->meta->does_role('MyRole'), 'metaclass does MyRole' ); +is( MyClass->meta->foo, 'i am foo', '... foo method returns expected value' ); + +lives_ok { + MyClass->meta->make_immutable; +} '... make MyClass immutable okay'; + +is(MyClass->meta, $mc, '... these metas are still the same thing'); +is(MyClass->meta->meta, $mc->meta, '... these meta-metas are the same thing'); + +ok( $a->meta->meta->does_role('MyRole'), 'metaclass does MyRole' ); +is( $a->meta->foo, 'i am foo', '... foo method returns expected value' ); +ok( MyClass->meta->meta->does_role('MyRole'), 'metaclass does MyRole' ); +is( MyClass->meta->foo, 'i am foo', '... foo method returns expected value' ); + diff --git a/t/200_examples/001_example.t b/t/200_examples/001_example.t index 33f9058..473da12 100644 --- a/t/200_examples/001_example.t +++ b/t/200_examples/001_example.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 21; +use Test::More tests => 20; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + ## Roles diff --git a/t/200_examples/002_example_Moose_POOP.t b/t/200_examples/002_example_Moose_POOP.t index 0298123..a7e0153 100644 --- a/t/200_examples/002_example_Moose_POOP.t +++ b/t/200_examples/002_example_Moose_POOP.t @@ -10,7 +10,7 @@ BEGIN { plan skip_all => "DBM::Deep 1.0003 (or greater) is required for this test" if $@; eval "use DateTime::Format::MySQL;"; plan skip_all => "DateTime::Format::MySQL is required for this test" if $@; - plan tests => 89; + plan tests => 88; } use Test::Exception; @@ -24,9 +24,7 @@ END { unlink('newswriter.db') if -e 'newswriter.db'; } -BEGIN { - use_ok('Moose'); -} + =pod @@ -139,9 +137,10 @@ BEGIN { extends 'Moose::Meta::Class'; override 'construct_instance' => sub { - my ($class, %params) = @_; - return $class->get_meta_instance->find_instance($params{oid}) - if $params{oid}; + my $class = shift; + my $params = @_ == 1 ? $_[0] : {@_}; + return $class->get_meta_instance->find_instance($params->{oid}) + if $params->{oid}; super(); }; diff --git a/t/200_examples/003_example.t b/t/200_examples/003_example.t index 3c3e463..9e1c530 100644 --- a/t/200_examples/003_example.t +++ b/t/200_examples/003_example.t @@ -3,14 +3,9 @@ use strict; use warnings; -use Test::More tests => 32; +use Test::More tests => 30; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Role'); -} - sub U { my $f = shift; sub { $f->($f, @_) }; diff --git a/t/200_examples/004_example_w_DCS.t b/t/200_examples/004_example_w_DCS.t index e8d1d25..3dd0e6a 100644 --- a/t/200_examples/004_example_w_DCS.t +++ b/t/200_examples/004_example_w_DCS.t @@ -17,16 +17,11 @@ Pretty well if I do say so myself :) BEGIN { eval "use Declare::Constraints::Simple;"; plan skip_all => "Declare::Constraints::Simple is required for this test" if $@; - plan tests => 11; + plan tests => 9; } use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Util::TypeConstraints'); -} - { package Foo; use Moose; diff --git a/t/200_examples/005_example_w_TestDeep.t b/t/200_examples/005_example_w_TestDeep.t index 53daaa8..cd33fa9 100644 --- a/t/200_examples/005_example_w_TestDeep.t +++ b/t/200_examples/005_example_w_TestDeep.t @@ -18,16 +18,11 @@ but it is not completely horrid either. BEGIN { eval "use Test::Deep;"; plan skip_all => "Test::Deep is required for this test" if $@; - plan tests => 7; + plan tests => 5; } use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Util::TypeConstraints'); -} - { package Foo; use Moose; diff --git a/t/200_examples/008_record_set_iterator.t b/t/200_examples/008_record_set_iterator.t index 990f2a8..00eac6b 100644 --- a/t/200_examples/008_record_set_iterator.t +++ b/t/200_examples/008_record_set_iterator.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 9; +use Test::More tests => 8; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Record; diff --git a/t/300_immutable/001_immutable_moose.t b/t/300_immutable/001_immutable_moose.t index 026d516..772337b 100644 --- a/t/300_immutable/001_immutable_moose.t +++ b/t/300_immutable/001_immutable_moose.t @@ -3,52 +3,55 @@ use strict; use warnings; -use Test::More tests => 17; +use Test::More tests => 15; use Test::Exception; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Meta::Role'); -} +use Moose::Meta::Role; + { - package FooRole; - our $VERSION = '0.01'; - sub foo { 'FooRole::foo' } + package FooRole; + our $VERSION = '0.01'; + sub foo {'FooRole::foo'} } { - package Foo; - use Moose; - - #two checks because the inlined methods are different when - #there is a TC present. - has 'foos' => (is => 'ro', lazy_build => 1); - has 'bars' => (isa => 'Str', is => 'ro', lazy_build => 1); - has 'bazes' => (isa => 'Str', is => 'ro', builder => '_build_bazes'); - sub _build_foos { "many foos" } - sub _build_bars { "many bars" } - sub _build_bazes { "many bazes" } + package Foo; + use Moose; + + #two checks because the inlined methods are different when + #there is a TC present. + has 'foos' => ( is => 'ro', lazy_build => 1 ); + has 'bars' => ( isa => 'Str', is => 'ro', lazy_build => 1 ); + has 'bazes' => ( isa => 'Str', is => 'ro', builder => '_build_bazes' ); + sub _build_foos {"many foos"} + sub _build_bars {"many bars"} + sub _build_bazes {"many bazes"} } { - my $foo_role = Moose::Meta::Role->initialize('FooRole'); - my $meta = Foo->meta; - - lives_ok{ Foo->new } "lazy_build works"; - is(Foo->new->foos, 'many foos' , "correct value for 'foos' before inlining constructor"); - is(Foo->new->bars, 'many bars' , "correct value for 'bars' before inlining constructor"); - is(Foo->new->bazes, 'many bazes' , "correct value for 'bazes' before inlining constructor"); - lives_ok{ $meta->make_immutable } "Foo is imutable"; - lives_ok{ $meta->identifier } "->identifier on metaclass lives"; - dies_ok{ $meta->add_role($foo_role) } "Add Role is locked"; - lives_ok{ Foo->new } "Inlined constructor works with lazy_build"; - is(Foo->new->foos, 'many foos' , "correct value for 'foos' after inlining constructor"); - is(Foo->new->bars, 'many bars' , "correct value for 'bars' after inlining constructor"); - is(Foo->new->bazes, 'many bazes' , "correct value for 'bazes' after inlining constructor"); - lives_ok{ $meta->make_mutable } "Foo is mutable"; - lives_ok{ $meta->add_role($foo_role) } "Add Role is unlocked"; - + my $foo_role = Moose::Meta::Role->initialize('FooRole'); + my $meta = Foo->meta; + + lives_ok { Foo->new } "lazy_build works"; + is( Foo->new->foos, 'many foos', + "correct value for 'foos' before inlining constructor" ); + is( Foo->new->bars, 'many bars', + "correct value for 'bars' before inlining constructor" ); + is( Foo->new->bazes, 'many bazes', + "correct value for 'bazes' before inlining constructor" ); + lives_ok { $meta->make_immutable } "Foo is imutable"; + lives_ok { $meta->identifier } "->identifier on metaclass lives"; + dies_ok { $meta->add_role($foo_role) } "Add Role is locked"; + lives_ok { Foo->new } "Inlined constructor works with lazy_build"; + is( Foo->new->foos, 'many foos', + "correct value for 'foos' after inlining constructor" ); + is( Foo->new->bars, 'many bars', + "correct value for 'bars' after inlining constructor" ); + is( Foo->new->bazes, 'many bazes', + "correct value for 'bazes' after inlining constructor" ); + lives_ok { $meta->make_mutable } "Foo is mutable"; + lives_ok { $meta->add_role($foo_role) } "Add Role is unlocked"; } diff --git a/t/300_immutable/002_apply_roles_to_immutable.t b/t/300_immutable/002_apply_roles_to_immutable.t index c1ba765..e373311 100644 --- a/t/300_immutable/002_apply_roles_to_immutable.t +++ b/t/300_immutable/002_apply_roles_to_immutable.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 5; +use Test::More tests => 4; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package My::Role; diff --git a/t/300_immutable/003_immutable_meta_class.t b/t/300_immutable/003_immutable_meta_class.t index a4214e9..d8b1c75 100644 --- a/t/300_immutable/003_immutable_meta_class.t +++ b/t/300_immutable/003_immutable_meta_class.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 2; +use Test::More tests => 1; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package My::Meta; diff --git a/t/300_immutable/004_inlined_constructors_n_types.t b/t/300_immutable/004_inlined_constructors_n_types.t index 7830752..ec942e2 100644 --- a/t/300_immutable/004_inlined_constructors_n_types.t +++ b/t/300_immutable/004_inlined_constructors_n_types.t @@ -3,13 +3,9 @@ use strict; use warnings; -use Test::More tests => 4; +use Test::More tests => 10; use Test::Exception; -BEGIN { - use_ok('Moose'); -} - =pod This tests to make sure that the inlined constructor @@ -22,11 +18,18 @@ as with a Class::MOP::Attribute object. { package Foo; use Moose; + use Moose::Util::TypeConstraints; + + coerce 'Int' => from 'Str' => via { length $_ ? $_ : 69 }; has 'foo' => (is => 'rw', isa => 'Int'); has 'baz' => (is => 'rw', isa => 'Int'); has 'zot' => (is => 'rw', isa => 'Int', init_arg => undef); - + has 'moo' => (is => 'rw', isa => 'Int', coerce => 1, default => '', required => 1); + has 'boo' => (is => 'rw', isa => 'Int', coerce => 1, builder => '_build_boo', required => 1); + + sub _build_boo { '' } + Foo->meta->add_attribute( Class::MOP::Attribute->new( 'bar' => ( @@ -34,22 +37,27 @@ as with a Class::MOP::Attribute object. ) ) ); - - Foo->meta->make_immutable(debug => 0); } -lives_ok { - Foo->new(foo => 10, bar => "Hello World", baz => 10, zot => 4); -} '... this passes the constuctor correctly'; +for (1..2) { + my $is_immutable = Foo->meta->is_immutable; + my $mutable_string = $is_immutable ? 'immutable' : 'mutable'; + lives_ok { + my $f = Foo->new(foo => 10, bar => "Hello World", baz => 10, zot => 4); + is($f->moo, 69, "Type coersion works as expected on default ($mutable_string)"); + is($f->boo, 69, "Type coersion works as expected on builder ($mutable_string)"); + } "... this passes the constuctor correctly ($mutable_string)"; -lives_ok { - Foo->new(foo => 10, bar => "Hello World", baz => 10, zot => "not an int"); -} "... the constructor doesn't care about 'zot'"; + lives_ok { + Foo->new(foo => 10, bar => "Hello World", baz => 10, zot => "not an int"); + } "... the constructor doesn't care about 'zot' ($mutable_string)"; -dies_ok { - Foo->new(foo => "Hello World", bar => 100, baz => "Hello World"); -} '... this fails the constuctor correctly'; + dies_ok { + Foo->new(foo => "Hello World", bar => 100, baz => "Hello World"); + } "... this fails the constuctor correctly ($mutable_string)"; + Foo->meta->make_immutable(debug => 0) unless $is_immutable; +} diff --git a/t/300_immutable/005_multiple_demolish_inline.t b/t/300_immutable/005_multiple_demolish_inline.t index 4f543ce..53b31a0 100644 --- a/t/300_immutable/005_multiple_demolish_inline.t +++ b/t/300_immutable/005_multiple_demolish_inline.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 3; +use Test::More tests => 2; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package Foo; diff --git a/t/300_immutable/006_immutable_nonmoose_subclass.t b/t/300_immutable/006_immutable_nonmoose_subclass.t index c04a44b..c3787e4 100644 --- a/t/300_immutable/006_immutable_nonmoose_subclass.t +++ b/t/300_immutable/006_immutable_nonmoose_subclass.t @@ -3,15 +3,10 @@ use strict; use warnings; -use Test::More tests => 10; +use Test::More tests => 8; use Test::Exception; use Scalar::Util 'blessed'; -BEGIN { - use_ok('Moose'); - use_ok('Moose::Meta::Role'); -} - =pod This test it kind of odd, it tests diff --git a/t/300_immutable/007_immutable_trigger_from_constructor.t b/t/300_immutable/007_immutable_trigger_from_constructor.t index 6a42b73..cab557f 100644 --- a/t/300_immutable/007_immutable_trigger_from_constructor.t +++ b/t/300_immutable/007_immutable_trigger_from_constructor.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 4; +use Test::More tests => 3; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + { package AClass; diff --git a/t/300_immutable/008_immutable_constructor_error.t b/t/300_immutable/008_immutable_constructor_error.t index 1c235f4..62d6d3c 100644 --- a/t/300_immutable/008_immutable_constructor_error.t +++ b/t/300_immutable/008_immutable_constructor_error.t @@ -3,12 +3,10 @@ use strict; use warnings; -use Test::More tests => 3; +use Test::More tests => 2; use Test::Exception; -BEGIN { - use_ok('Moose'); -} + =pod diff --git a/t/300_immutable/009_buildargs.t b/t/300_immutable/009_buildargs.t new file mode 100644 index 0000000..6c1ca33 --- /dev/null +++ b/t/300_immutable/009_buildargs.t @@ -0,0 +1,47 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 14; + +{ + package Foo; + use Moose; + + has bar => ( is => "rw" ); + has baz => ( is => "rw" ); + + sub BUILDARGS { + my ( $self, @args ) = @_; + unshift @args, "bar" if @args % 2 == 1; + return {@args}; + } + + __PACKAGE__->meta->make_immutable; + + package Bar; + use Moose; + + extends qw(Foo); + + __PACKAGE__->meta->make_immutable; +} + +foreach my $class qw(Foo Bar) { + is( $class->new->bar, undef, "no args" ); + is( $class->new( bar => 42 )->bar, 42, "normal args" ); + is( $class->new( 37 )->bar, 37, "single arg" ); + { + my $o = $class->new(bar => 42, baz => 47); + is($o->bar, 42, '... got the right bar'); + is($o->baz, 47, '... got the right bar'); + } + { + my $o = $class->new(42, baz => 47); + is($o->bar, 42, '... got the right bar'); + is($o->baz, 47, '... got the right bar'); + } +} + + diff --git a/t/600_todo_tests/001_exception_reflects_failed_constraint.t b/t/600_todo_tests/001_exception_reflects_failed_constraint.t new file mode 100644 index 0000000..db7fe69 --- /dev/null +++ b/t/600_todo_tests/001_exception_reflects_failed_constraint.t @@ -0,0 +1,33 @@ +#!/usr/bin/perl + +# In the case where a child type constraint's parent constraint fails, +# the exception should reference the parent type constraint that actually +# failed instead of always referencing the child'd type constraint + +use strict; +use warnings; + +use Test::More tests => 4; +use Test::Exception; + +BEGIN { + use_ok('Moose::Util::TypeConstraints'); +} + +lives_ok { + subtype 'ParentConstraint' => as 'Str' => where {0}; +} 'specified parent type constraint'; + +my $tc; +lives_ok { + $tc = subtype 'ChildConstraint' => as 'ParentConstraint' => where {1}; +} 'specified child type constraint'; + +{ + my $errmsg = $tc->validate(); + + TODO: { + local $TODO = 'Not yet supported'; + ok($errmsg !~ /Validation failed for 'ChildConstraint'/, 'exception references failing parent constraint'); + }; +} diff --git a/t/600_todo_tests/002_various_role_shit.t b/t/600_todo_tests/002_various_role_shit.t new file mode 100644 index 0000000..5f73d10 --- /dev/null +++ b/t/600_todo_tests/002_various_role_shit.t @@ -0,0 +1,286 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More 'no_plan'; +use Test::Exception; + +sub req_or_has ($$) { + my ( $role, $method ) = @_; + local $Test::Builder::Level = $Test::Builder::Level + 1; + if ( $role ) { + ok( + $role->has_method($method) || $role->requires_method($method), + $role->name . " has or requires method $method" + ); + } else { + fail("role has or requires method $method"); + } +} + +{ + package Bar; + use Moose::Role; + + # this role eventually adds three methods, qw(foo bar xxy), but only one is + # known when it's still a role + + has foo => ( is => "rw" ); + + has gorch => ( reader => "bar" ); + + sub xxy { "BAAAD" } + + package Gorch; + use Moose::Role; + + # similarly this role gives attr and gorch_method + + has attr => ( is => "rw" ); + + sub gorch_method { "gorch method" } + + around dandy => sub { shift->(@_) . "bar" }; + + package Quxx; + use Moose; + + sub dandy { "foo" } + + # this object will be used in an attr of Foo to test that Foo can do the + # Gorch interface + + with qw(Gorch); + + package Dancer; + use Moose::Role; + + requires "twist"; + + package Dancer::Ballerina; + use Moose; + + with qw(Dancer); + + sub twist { } + + sub pirouette { } + + package Dancer::Robot; + use Moose::Role; + + # this doesn't fail but it produces a requires in the role + # the order doesn't matter + has twist => ( is => "rw" ); + ::lives_ok { with qw(Dancer) }; + + package Dancer::Something; + use Moose; + + # this fail even though the method already exists + + has twist => ( is => "rw" ); + + { + local our $TODO = "accessors don't satisfy role requires"; + ::lives_ok { with qw(Dancer) }; + } + + package Dancer::80s; + use Moose; + + # this should pass because ::Robot has the attribute to fill in the requires + # but due to the deferrence logic that doesn't actually work + { + local our $TODO = "attribute accessor in role doesn't satisfy role requires"; + ::lives_ok { with qw(Dancer::Robot) }; + } + + package Foo; + use Moose; + + with qw(Bar); + + has oink => ( + is => "rw", + handles => "Gorch", # should handles take the same arguments as 'with'? Meta::Role::Application::Delegation? + default => sub { Quxx->new }, + ); + + has dancer => ( + is => "rw", + does => "Dancer", + handles => "Dancer", + default => sub { Dancer::Ballerina->new }, + ); + + sub foo { 42 } + + sub bar { 33 } + + sub xxy { 7 } + + package Tree; + use Moose::Role; + + has bark => ( is => "rw" ); + + package Dog; + use Moose::Role; + + sub bark { warn "woof!" }; + + package EntPuppy; + use Moose; + + { + local our $TODO = "attrs and methods from a role should clash"; + ::dies_ok { with qw(Tree Dog) } + } +} + +# these fail because of the deferral logic winning over actual methods +# this might be tricky to fix due to the 'sub foo {}; has foo => ( )' hack +# we've been doing for a long while, though I doubt people relied on it for +# anything other than fulfilling 'requires' +{ + local $TODO = "attributes from role overwrite class methods"; + is( Foo->new->foo, 42, "attr did not zap overriding method" ); + is( Foo->new->bar, 33, "attr did not zap overriding method" ); +} +is( Foo->new->xxy, 7, "method did not zap overriding method" ); # duh + +# these pass, simple delegate +# mostly they are here to contrast the next blck +can_ok( Foo->new->oink, "dandy" ); +can_ok( Foo->new->oink, "attr" ); +can_ok( Foo->new->oink, "gorch_method" ); + +ok( Foo->new->oink->does("Gorch"), "Quxx does Gorch" ); + + +# these are broken because 'attr' is not technically part of the interface +can_ok( Foo->new, "gorch_method" ); +{ + local $TODO = "accessor methods from a role are omitted in handles role"; + can_ok( Foo->new, "attr" ); +} + +{ + local $TODO = "handles role doesn't add the role to the ->does of the delegate's parent class"; + ok( Foo->new->does("Gorch"), "Foo does Gorch" ); +} + + +# these work +can_ok( Foo->new->dancer, "pirouette" ); +can_ok( Foo->new->dancer, "twist" ); + +can_ok( Foo->new, "twist" ); +ok( !Foo->new->can("pirouette"), "can't pirouette, not part of the iface" ); + +{ + local $TODO = "handles role doesn't add the role to the ->does of the delegate's parent class"; + ok( Foo->new->does("Dancer") ); +} + + + + +my $gorch = Gorch->meta; + +isa_ok( $gorch, "Moose::Meta::Role" ); + +ok( $gorch->has_attribute("attr"), "has attribute 'attr'" ); + +{ + local $TODO = "role attribute isn't a meta attribute yet"; + isa_ok( $gorch->get_attribute("attr"), "Moose::Meta::Attribute" ); +} + +req_or_has($gorch, "gorch_method"); +ok( $gorch->has_method("gorch_method"), "has_method gorch_method" ); +ok( !$gorch->requires_method("gorch_method"), "requires gorch method" ); + +{ + local $TODO = "role method isn't a meta object yet"; + isa_ok( $gorch->get_method("gorch_method"), "Moose::Meta::Method" ); +} + +{ + local $TODO = "method modifier doesn't yet create a method requirement or meta object"; + req_or_has($gorch, "dandy" ); + + # this specific test is maybe not backwards compat, but in theory it *does* + # require that method to exist + ok( $gorch->requires_method("dandy"), "requires the dandy method for the modifier" ); +} + +{ + local $TODO = "attribute related methods are not yet known by the role"; + # we want this to be a part of the interface, somehow + req_or_has($gorch, "attr"); + ok( $gorch->has_method("attr"), "has_method attr" ); + isa_ok( $gorch->get_method("attr"), "Moose::Meta::Method" ); + isa_ok( $gorch->get_method("attr"), "Moose::Meta::Method::Accessor" ); +} + +my $robot = Dancer::Robot->meta; + +isa_ok( $robot, "Moose::Meta::Role" ); + +ok( $robot->has_attribute("twist"), "has attr 'twist'" ); + +{ + local $TODO = "role attribute isn't a meta attribute yet"; + isa_ok( $robot->get_attribute("twist"), "Moose::Meta::Attribute" ); +} + +{ + req_or_has($robot, "twist"); + + local $TODO = "attribute related methods are not yet known by the role"; + ok( $robot->has_method("twist"), "has twist method" ); + isa_ok( $robot->get_method("twist"), "Moose::Meta::Method" ); + isa_ok( $robot->get_method("twist"), "Moose::Meta::Method::Accessor" ); +} + +__END__ + +I think Attribute needs to be refactored in some way to better support roles. + +There are several possible ways to do this, all of them seem plausible to me. + +The first approach would be to change the attribute class to allow it to be +queried about the methods it would install. + +Then we instantiate the attribute in the role, and instead of deferring the +arguments, we just make an Cish method. + +Then we can interrogate the attr when adding it to the role, and generate stub +methods for all the methods it would produce. + +A second approach is kinda like the Immutable hack: wrap the attr in an +anonmyous class that disables part of its interface. + +A third method would be to create an Attribute::Partial object that would +provide a more role-ish behavior, and to do this independently of the actual +Attribute class. + +Something similar can be done for method modifiers, but I think that's even simpler. + + + +The benefits of doing this are: + +* Much better introspection of roles + +* More correctness in many cases (in my opinion anyway) + +* More roles are more usable as interface declarations, without having to split + them into two pieces (one for the interface with a bunch of requires(), and + another for the actual impl with the problematic attrs (and stub methods to + fix the accessors) and method modifiers (dunno if this can even work at all) + + diff --git a/t/600_todo_tests/003_immutable_n_around.t b/t/600_todo_tests/003_immutable_n_around.t new file mode 100644 index 0000000..c7b66f9 --- /dev/null +++ b/t/600_todo_tests/003_immutable_n_around.t @@ -0,0 +1,54 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More 'no_plan'; + +# if make_immutable is removed from the following code the tests pass + +{ + package Foo; + use Moose; + + has foo => ( is => "ro" ); + + package Bar; + use Moose; + + extends qw(Foo); + + around new => sub { + my $next = shift; + my ( $self, @args ) = @_; + $self->$next( foo => 42 ); + }; + + package Gorch; + use Moose; + + extends qw(Bar); + + package Zoink; + use Moose; + + extends qw(Gorch); + +} + +my @classes = qw(Foo Bar Gorch Zoink); + +tests: { + TODO: { + is( Foo->new->foo, undef, "base class (" . (Foo->meta->is_immutable ? "immutable" : "mutable") . ")" ); + is( Bar->new->foo, 42, "around new called on Bar->new (" . (Bar->meta->is_immutable ? "immutable" : "mutable") . ")" ); + local $TODO = 'these tests fail once Gorch is immutable' if Gorch->meta->is_immutable; + is( Gorch->new->foo, 42, "around new called on Gorch->new (" . (Gorch->meta->is_immutable ? "immutable" : "mutable") . ")" ); + is( Zoink->new->foo, 42, "around new called Zoink->new (" . (Zoink->meta->is_immutable ? "immutable" : "mutable") . ")" ); + } + + if ( @classes ) { + ( shift @classes )->meta->make_immutable; + redo tests; + } +} diff --git a/t/600_todo_tests/004_inlined_constructor_modified_new.t b/t/600_todo_tests/004_inlined_constructor_modified_new.t new file mode 100644 index 0000000..6679437 --- /dev/null +++ b/t/600_todo_tests/004_inlined_constructor_modified_new.t @@ -0,0 +1,48 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 6; + +my ($around_new); +{ + package Foo; + use Moose; + + around new => sub { my $o = shift; $around_new = 1; $o->(@_); }; + has 'foo' => (is => 'rw', isa => 'Int'); + + package Bar; + use Moose; + extends 'Foo'; + Bar->meta->make_immutable; +} + +my $orig_new = Foo->meta->find_method_by_name('new'); +isa_ok($orig_new, 'Class::MOP::Method::Wrapped'); +$orig_new = $orig_new->get_original_method; +isa_ok($orig_new, 'Moose::Meta::Method'); + +Foo->meta->make_immutable(debug => 0); +my $inlined_new = Foo->meta->find_method_by_name('new'); +isa_ok($inlined_new, 'Class::MOP::Method::Wrapped'); +$inlined_new = $inlined_new->get_original_method; + +TODO: +{ + local $TODO = 'but it isa Moose::Meta::Method instead'; + isa_ok($inlined_new, 'Moose::Meta::Method::Constructor'); +} + +Foo->new(foo => 100); +ok($around_new, 'around new called'); + +$around_new = 0; +Bar->new(foo => 100); + +TODO: +{ + local $TODO = 'but it is not called'; + ok($around_new, 'around new called'); +} diff --git a/t/600_todo_tests/005_moose_and_threads.t b/t/600_todo_tests/005_moose_and_threads.t new file mode 100644 index 0000000..112e7bd --- /dev/null +++ b/t/600_todo_tests/005_moose_and_threads.t @@ -0,0 +1,44 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More no_plan => 1; +use Test::Exception; + + + +=pod + +See this for some details: + +http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=476579 + +Here is the basic test case, it segfaults, so I am going +to leave it commented out. Basically it seems that there +is some bad interaction between the ??{} construct that +is used in the "parser" for type definitions and threading +so probably the fix would involve removing the ??{} usage +for something else. + +use threads; + +{ + package Foo; + use Moose; + has "bar" => (is => 'rw', isa => "Str | Num"); +} + +my $thr = threads->create(sub {}); +$thr->join(); + +=cut + +{ + local $TODO = 'This is just a stub for the test, see the POD'; + fail('Moose type constraints and threads dont get along'); +} + + + + diff --git a/t/lib/Role/Child.pm b/t/lib/Role/Child.pm new file mode 100644 index 0000000..bf5aa25 --- /dev/null +++ b/t/lib/Role/Child.pm @@ -0,0 +1,8 @@ +package Role::Child; +use Moose::Role; + +with 'Role::Parent' => { alias => { meth1 => 'aliased_meth1', } }; + +sub meth1 { } + +1; diff --git a/t/lib/Role/Interface.pm b/t/lib/Role/Interface.pm new file mode 100644 index 0000000..025cf40 --- /dev/null +++ b/t/lib/Role/Interface.pm @@ -0,0 +1,6 @@ +package Role::Interface; +use Moose::Role; + +requires "meth2"; + +1; diff --git a/t/lib/Role/Parent.pm b/t/lib/Role/Parent.pm new file mode 100644 index 0000000..0f49427 --- /dev/null +++ b/t/lib/Role/Parent.pm @@ -0,0 +1,7 @@ +package Role::Parent; +use Moose::Role; + +sub meth2 { } +sub meth1 { } + +1; diff --git a/t/pod_coverage.t b/t/pod_coverage.t index dcedee4..1f6b5ba 100644 --- a/t/pod_coverage.t +++ b/t/pod_coverage.t @@ -8,4 +8,30 @@ use Test::More; eval "use Test::Pod::Coverage 1.04"; plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@; -all_pod_coverage_ok( { trustme => [ qr/intialize_body/ ] } ); +# This is a stripped down version of all_pod_coverage_ok which lets us +# vary the trustme parameter per module. +my @modules = all_modules(); +plan tests => scalar @modules; + +my %trustme = ( + 'Moose' => ['make_immutable'], + 'Moose::Meta::Method::Constructor' => + [qw( initialize_body intialize_body)], + 'Moose::Meta::Method::Destructor' => ['initialize_body'], + 'Moose::Role' => [ + qw( after around augment before extends has inner make_immutable override super with ) + ], +); + +for my $module ( sort @modules ) { + my $trustme = []; + if ( $trustme{$module} ) { + my $methods = join '|', @{ $trustme{$module} }; + $trustme = [qr/$methods/]; + } + + pod_coverage_ok( + $module, { trustme => $trustme }, + "Pod coverage for $module" + ); +}