Some tweaking of style and previous sections as well.
extends 'Person';
-has position => ( is => 'rw' );
-has salary => ( is => 'rw' );
-has ssn => ( is => 'ro' );
+has title => ( is => 'rw' );
+has salary => ( is => 'rw' );
+has ssn => ( is => 'ro' );
override full_name => sub {
my $self = shift;
- return super() . q[ (] . $self->position . q[)];
+ return super() . q[ (] . $self->title . q[)];
};
no Moose;
extends 'Person';
-has position => ( is => 'rw' );
-has salary => ( is => 'rw' );
-has ssn => ( is => 'ro' );
+has title => ( is => 'rw' );
+has salary => ( is => 'rw' );
+has ssn => ( is => 'ro' );
override full_name => sub {
my $self = shift;
- return super() . q[ (] . $self->position . q[)];
+ return super() . q[ (] . $self->title . q[)];
};
no Moose;
use Moose::Role;
-has balance => (
- is => 'rw',
-);
+has balance => ( is => 'rw' );
sub deposit {
my $self = shift;
--- /dev/null
+package Employee;
+
+use Moose;
+
+extends 'Person';
+
+has '+title' => (
+ default => 'Worker',
+);
+
+has salary_level => (
+ is => 'rw',
+ default => 1,
+);
+
+has salary => (
+ is => 'ro',
+ lazy => 1,
+ builder => '_build_salary',
+ init_arg => undef,
+);
+
+has ssn => ( is => 'ro' );
+
+sub _build_salary {
+ my $self = shift;
+
+ return $self->salary_level * 10000;
+}
+
+no Moose;
+
+__PACKAGE__->meta->make_immutable;
+
+1;
--- /dev/null
+package HasAccount;
+
+use Moose::Role;
+
+has balance => (
+ is => 'rw',
+ default => 100,
+);
+
+sub deposit {
+ my $self = shift;
+ my $amount = shift;
+
+ $self->balance( $self->balance + $amount );
+}
+
+sub withdraw {
+ my $self = shift;
+ my $amount = shift;
+
+ die "Balance cannot be negative"
+ if $self->balance < $amount;
+
+ $self->balance( $self->balance - $amount );
+}
+
+no Moose::Role;
+
+1;
--- /dev/null
+package Person;
+
+use Moose;
+
+with 'Printable', 'HasAccount';
+
+has title => (
+ is => 'rw',
+ predicate => 'has_title',
+ clearer => 'clear_title',
+);
+
+has first_name => ( is => 'rw' );
+
+has last_name => ( is => 'rw' );
+
+sub full_name {
+ my $self = shift;
+
+ my $title = join q{ }, $self->first_name, $self->last_name;
+ $title .= q[ (] . $self->title . q[)]
+ if $self->has_title;
+
+ return $title;
+}
+
+sub as_string { $_[0]->full_name }
+
+no Moose;
+
+__PACKAGE__->meta->make_immutable;
+
+1;
--- /dev/null
+package Printable;
+
+use Moose::Role;
+
+requires 'as_string';
+
+no Moose::Role;
+
+1;
#
# An Employee has the following read-write attributes:
#
-# * position - read-write
+# * title - read-write
# * salary - read-write
# * ssn - read-only
#
--- /dev/null
+# Your tasks ...
+#
+# Go back to your Person class and make the first_name and last_name
+# attributes required.
+#
+# Move the title attribute from the Employee class to the Person
+# class. Adjust full_name in the Person class so it includes the
+# title, which is optional.
+#
+# Add a predicate (has_title) and clearer (clear_title) to the title
+# attribute as well.
+#
+# If a person has no title, the full_name method should simply return
+# the first and last name. Use the title's predicate method in the new
+# full_name method.
+#
+# Go back to the Employee class
+#
+# Make the title attribute default to the string 'Worker' for the
+# Employee class. You can now inherit full_name from the Person class
+# rather than re-implementing it.
+#
+# Add a read-write salary_level attribute. This will be a number from
+# 1-10 (but we will deal with enforcing this later). This attribute
+# should default to 1.
+#
+# Make the salary attribute read-only. Also make it lazy. The default
+# should be calculated as salary_level * 10000. Use a builder method
+# to set the default. Name the builder "_build_salary". This attribute
+# should not be settable via the constructor.
+#
+# Go back to the HasAccount role and make the balance default to 100.
+
+use strict;
+use warnings;
+
+use lib 't/lib';
+
+use MooseClass::Tests;
+
+use Person;
+use Employee;
+
+MooseClass::Tests::tests03();
count_attrs( 'Employee', $p{employee_attr_count} );
- has_rw_attr( 'Employee', $_ ) for qw( position salary );
+ has_rw_attr( 'Employee', $_ ) for qw( title salary );
has_ro_attr( 'Employee', 'ssn' );
has_overridden_method( 'Employee', 'full_name' );
}
sub tests02 {
- tests01( person_attr_count => 3 );
+ tests01( person_attr_count => 3, @_ );
local $Test::Builder::Level = $Test::Builder::Level + 1;
employee02();
}
+sub tests03 {
+ {
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ has_meta('Person');
+ has_meta('Employee');
+
+ has_rw_attr( 'Person', 'title' );
+
+ has_rw_attr( 'Employee', 'title' );
+ has_rw_attr( 'Employee', 'salary_level' );
+ has_ro_attr( 'Employee', 'salary' );
+
+ has_method( 'Employee', '_build_salary' );
+ }
+
+ ok( ! Employee->meta->has_method('full_name'),
+ 'Employee no longer implements a full_name method' );
+
+ my $person_title_attr = Person->meta->get_attribute('title');
+ ok( !$person_title_attr->is_required, 'title is not required in Person' );
+ is( $person_title_attr->predicate, 'has_title',
+ 'Person title attr has a has_title predicate' );
+ is( $person_title_attr->clearer, 'clear_title',
+ 'Person title attr has a clear_title clearer' );
+
+ my $balance_attr = Person->meta->get_attribute('balance');
+ is( $balance_attr->default, 100, 'balance defaults to 100' );
+
+ my $employee_title_attr = Employee->meta->get_attribute('title');
+ is( $employee_title_attr->default, 'Worker',
+ 'title defaults to Worker in Employee' );
+
+ my $salary_level_attr = Employee->meta->get_attribute('salary_level');
+ is( $salary_level_attr->default, 1, 'salary_level defaults to 1' );
+
+ my $salary_attr = Employee->meta->get_attribute('salary');
+ ok( !$salary_attr->init_arg, 'no init_arg for salary attribute' );
+ ok( $salary_attr->has_builder, 'salary attr has a builder' );
+
+ person03();
+ employee03();
+}
+
sub has_meta {
my $class = shift;
my $count = shift;
my $noun = PL_N( 'attribute', $count );
- is(
- scalar $class->meta->get_attribute_list, $count,
- "$class defines $count $noun"
- );
+ is( scalar $class->meta->get_attribute_list, $count,
+ "$class defines $count $noun" );
}
sub has_rw_attr {
my $class = shift;
my $name = shift;
- my $article = A($name);
+ my $articled = A($name);
ok( $class->meta->has_attribute($name),
- "$class has $article $name attribute" );
+ "$class has $articled attribute" );
my $attr = $class->meta->get_attribute($name);
- is(
- $attr->get_read_method, $name,
- "$name attribute has a reader accessor - $name()"
- );
- is(
- $attr->get_write_method, $name,
- "$name attribute has a writer accessor - $name()"
- );
+ is( $attr->get_read_method, $name,
+ "$name attribute has a reader accessor - $name()" );
+ is( $attr->get_write_method, $name,
+ "$name attribute has a writer accessor - $name()" );
}
sub has_ro_attr {
my $class = shift;
my $name = shift;
- my $article = A($name);
+ my $articled = A($name);
ok( $class->meta->has_attribute($name),
- "$class has $article $name attribute" );
+ "$class has $articled attribute" );
my $attr = $class->meta->get_attribute($name);
- is(
- $attr->get_read_method, $name,
- "$name attribute has a reader accessor - $name()"
- );
- is(
- $attr->get_write_method, undef,
- "$name attribute does not have a writer"
- );
+ is( $attr->get_read_method, $name,
+ "$name attribute has a reader accessor - $name()" );
+ is( $attr->get_write_method, undef,
+ "$name attribute does not have a writer" );
}
sub has_method {
my $class = shift;
my $name = shift;
- my $article = A($name);
- ok( $class->meta->has_method($name), "$class has $article $name method" );
+ my $articled = A($name);
+ ok( $class->meta->has_method($name), "$class has $articled method" );
}
sub has_overridden_method {
my $class = shift;
my $name = shift;
- my $article = A($name);
- ok( $class->meta->has_method($name), "$class has $article $name method" );
+ my $articled = A($name);
+ ok( $class->meta->has_method($name), "$class has $articled method" );
my $meth = $class->meta->get_method($name);
isa_ok( $meth, 'Moose::Meta::Method::Overridden' );
last_name => 'Baggins',
);
- is(
- $person->full_name, 'Bilbo Baggins',
- 'full_name() is correctly implemented'
- );
+ is( $person->full_name, 'Bilbo Baggins',
+ 'full_name() is correctly implemented' );
}
sub employee01 {
my $employee = Employee->new(
first_name => 'Amanda',
last_name => 'Palmer',
- position => 'Singer',
+ title => 'Singer',
);
- is(
- $employee->full_name, 'Amanda Palmer (Singer)',
- 'full_name() is properly overriden in Employee'
- );
+ is( $employee->full_name, 'Amanda Palmer (Singer)', 'full_name() is properly overriden in Employee' );
}
sub person02 {
balance => 0,
);
- is(
- $person->as_string, 'Bilbo Baggins',
- 'as-string() is correctly implemented'
- );
+ is( $person->as_string, 'Bilbo Baggins',
+ 'as_string() is correctly implemented' );
account_tests($person);
}
my $employee = Employee->new(
first_name => 'Amanda',
last_name => 'Palmer',
- position => 'Singer',
+ title => 'Singer',
balance => 0,
);
- is(
- $employee->as_string, 'Amanda Palmer (Singer)',
- 'as_string() uses overridden full_name method in Employee'
- );
+ is( $employee->as_string, 'Amanda Palmer (Singer)',
+ 'as_string() uses overridden full_name method in Employee' );
account_tests($employee);
}
+sub person03 {
+ my $person = Person->new(
+ first_name => 'Bilbo',
+ last_name => 'Baggins',
+ );
+
+ is( $person->full_name, 'Bilbo Baggins',
+ 'full_name() is correctly implemented for a Person without a title' );
+ ok( !$person->has_title,
+ 'Person has_title predicate is working correctly' );
+
+ $person->title('Ringbearer');
+ ok( $person->has_title, 'Person has_title predicate is working correctly' );
+ is( $person->full_name, 'Bilbo Baggins (Ringbearer)',
+ 'full_name() is correctly implemented for a Person with a title' );
+
+ $person->clear_title;
+ ok( !$person->has_title, 'Person clear_title method cleared the title' );
+
+ account_tests( $person, 100 );
+}
+
+sub employee03 {
+ my $employee = Employee->new(
+ first_name => 'Jimmy',
+ last_name => 'Foo',
+ salary_level => 3,
+ salary => 42,
+ );
+
+ is( $employee->salary, 30000,
+ 'salary is calculated from salary_level, and salary passed to constructor is ignored' );
+}
+
sub account_tests {
local $Test::Builder::Level = $Test::Builder::Level + 1;
my $person = shift;
+ my $base_amount = shift || 0;
$person->deposit(50);
- eval { $person->withdraw(75) };
- like(
- $@, qr/\QBalance cannot be negative/,
- 'cannot withdraw more than is in our balance'
- );
+ eval { $person->withdraw( 75 + $base_amount ) };
+ like( $@, qr/\QBalance cannot be negative/,
+ 'cannot withdraw more than is in our balance' );
- $person->withdraw(23);
+ $person->withdraw( 23 );
- is( $person->balance, 27,
- 'balance is 25 after deposit of 50 and withdrawal of 23' );
+ is( $person->balance, 27 + $base_amount,
+ 'balance is 27 (+ starting balance) after deposit of 50 and withdrawal of 23' );
}
1;
<ul>
<li><strong>Declarative</strong> OO sugar</li>
<li>Introspectable</li>
- <li>Extensible</li>
+ <li>Extensible (MooseX::* on CPAN)</li>
</ul>
</div>
<div class="slide">
<h1>Typo?</h1>
- <code>$self->{last_na<span class="highlight">n</span>e}</code>
+ <code>$self->{last_na<span class="wrong">n</span>e}</code>
</div>
<div class="slide">
<div class="slide">
<h1>Exercises</h1>
- <pre>$ cd exercises
+ <pre># cd exercises
$ perl bin/prove -lv t/00-prereq.t
Missing anything? Install it. (see tarballs/)
<div class="slide">
<h1>Exercises</h1>
- <pre>$ cd exercises
+ <pre># cd exercises
# perl bin/prove -lv t/02-roles.t
Iterate til this passes all its tests</pre>
</div>
+<div class="slide fake-slide0">
+ <h1>Part 3: Basic Attributes</h1>
+</div>
+
+<div class="slide">
+ <h1>Attributes Are Huge</h1>
+
+ <ul>
+ <li>Moose's biggest feature</li>
+ <li>The target of <em>many</em> MooseX modules</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Quick Review</h1>
+
+ <ul>
+ <li>Declared with <code>has</code></li>
+ <li>Read-only or read-write</li>
+ </ul>
+
+ <pre><code>package Shirt;
+use Moose;
+
+has 'color' => ( is => 'ro' );
+has 'is_ripped' => ( is => 'rw' );</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Required-ness</h1>
+
+ <ul>
+ <li>Required means "must be passed to the constructor"</li>
+ <li>But can be <code>undef</code></li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Required-ness</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has first_name => (
+ is => 'ro',
+ <span class="current">required => 1,</span>
+);
+
+<span class="incremental">Person->new( first_name => undef ); # ok
+Person->new(); # kaboom</span></code></pre>
+</div>
+
+<div class="slide">
+ <h1>Default and Builder</h1>
+
+ <ul>
+ <li>Attributes can have defaults</li>
+ <li>Simple non-referecne scalars (number, string)</li>
+ <li>Subroutine reference</li>
+ <li>A builder method</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Default</h1>
+
+ <ul>
+ <li>Can be a non-reference scalar (including <code>undef</code>)</li>
+ </ul>
+
+ <pre><code>package Person;
+use Moose;
+
+has bank => (
+ is => 'rw',
+ default => 'Spire FCU',
+);</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Default</h1>
+
+ <ul>
+ <li>Can be a subroutine reference</li>
+ </ul>
+
+ <pre><code>package Person;
+use Moose;
+
+has bank => (
+ is => 'rw',
+ default =>
+ sub { Bank->new(
+ name => 'Spire FCU' ) },
+);</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Default as a Subroutine Reference</h1>
+
+ <ul>
+ <li>Called as a method on the object</li>
+ <li>Called anew for each object</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Why No Other Reference Types?</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has bank => (
+ is => 'rw',
+ <span class="wrong">default => Bank->new(
+ name => 'Spire FCU' ),</span>
+);</code></pre>
+
+ <ul>
+ <li>Now <strong>every</strong> person shares the <strong>same</strong> Bank object!</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Defaulting to an Empty Reference</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has packages => (
+ is => 'rw',
+ default => <span class="highlight">sub { [] }</span>,
+);</code></pre>
+</div>
+
+<div class="slide">
+ <h1>What if I Want to Share?</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+my $highlander_bank =
+ Bank->new( name => 'Spire FCU' );
+
+has bank => (
+ is => 'rw',
+ default => sub { $highlander_bank },
+);</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Builder</h1>
+
+ <ul>
+ <li>A method <em>name</em> which returns the default</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Builder</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has bank => (
+ is => 'rw',
+ builder => '_build_bank',
+);
+
+sub _build_bank {
+ my $self = shift;
+ return Bank->new( name => 'Spire FCU' );
+}</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Default vs Builder</h1>
+
+ <ul>
+ <li>Use default for simple scalars</li>
+ <li>Use default to return empty references</li>
+ <li>Use default for <em>very</em> trivial subroutine references</li>
+ <li>Use builder for everything else</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Builder Bonuses</h1>
+
+ <ul>
+ <li>Can be overridden and method modified, because it's called by <em>name</em></li>
+ <li>Roles can require a builder</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Role Requires Builder</h1>
+
+ <pre><code>package HasBank;
+use Moose::Role;
+
+requires '_build_bank';
+
+has bank => (
+ is => 'ro',
+ builder => '_build_bank',
+);</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Lazy, Good for Nothing Attributes</h1>
+
+ <ul>
+ <li>Normally, defaults are generated during object construction</li>
+ <li>This can be expensive</li>
+ <li>We want to default to <code>$self->size * 2</code>, but attribute initialization order is unpredictable</li>
+ <li>Use lazy attributes!</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>The Power of Dynamic Defaults</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has shoe_size => (
+ is => 'ro',
+);</code></pre>
+</div>
+
+<div class="slide">
+ <h1>The Power of Dynamic Defaults</h1>
+
+ <pre><code>has shoes => (
+ is => 'ro',
+ <span class="highlight">lazy => 1,</span>
+ builder => '_build_shoes',
+);
+
+sub _build_shoes {
+ my $self = shift;
+
+ return Shoes->new(
+ size => <span class="highlight">$_[0]->shoe_size</span> );
+}</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Lazy is Good</h1>
+
+ <ul>
+ <li>Lazy defaults are executed when the attribute is read</li>
+ <li>Can see other object attributes</li>
+ <li>Still need to watch out for circular laziness</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Clearer and Predicate</h1>
+
+ <ul>
+ <li>Attributes can have a value, including <code>undef</code>, or not</li>
+ <li>Can clear the value with a clearer method</li>
+ <li>Can check for the existence of a value with a predicate method</li>
+ <li>By default, these methods are not created</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Clearer and Predicate</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has account => (
+ is => 'ro',
+ lazy => 1,
+ builder => '_build_account',
+ <span class="highlight">clearer => '_clear_account',
+ predicate => 'has_account',</span>
+);</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Clearer and Lazy Defaults</h1>
+
+ <ul>
+ <li>Lazy defaults are good for computed attributes</li>
+ <li>Clear the attribute when the source data changes</li>
+ <li>Recalculated at next access</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Renaming constructor arguments</h1>
+
+ <ul>
+ <li>By default, constructor names = attribute names</li>
+ <li>Use <code>init_arg</code> to change this</li>
+ <li>Set <code>init_arg => undef</code> to make it unconstructable</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Some <code>init_arg</code> examples</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has shoe_size => (
+ is => 'ro',
+ <span class="highlight">init_arg => 'foot_size',</span>
+);
+
+Person->new( <span class="wrong">shoe_size => 13</span> );
+
+my $person =
+ Person->new( <span class="right">foot_size => 13</span> );
+print $person->shoe_size;</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Some <code>init_arg</code> examples</h1>
+
+<pre><code>package Person;
+use Moose;
+
+has shoes => (
+ is => 'ro',
+ <span class="highlight">init_arg => undef,</span>
+);
+
+Person->new( <span class="wrong">shoes => Shoes->new</span> );</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Why Set <code>init_arg => undef</code>?</h1>
+
+ <ul>
+ <li>Use this with a lazy default for attributes-as-cache</li>
+ <li>Compute the value as needed</li>
+ <li>Ensure that it is always generated correctly (not set by constructor)</li>
+ <li>Use triggers or method modifiers (coming soon) to clear the value</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Attribute Inheritance</h1>
+
+ <ul>
+ <li>By default, subclasses inherit attribute as-is</li>
+ <li>Can change some attribute parameters in subclasses
+ <ul>
+ <li>default</li>
+ <li>builder</li>
+ <li>required</li>
+ <li>lazy</li>
+ <li>others we've not yet covered</li>
+ </ul>
+ </li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Attribute Inheritance Example</h1>
+
+ <pre><code>package Employee;
+use Moose;
+
+extends 'Person';
+
+has '<span class="highlight">+first_name</span>' => (
+ default => 'Joe',
+);</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Attribute Inheritance Warning</h1>
+
+ <ul>
+ <li>An attribute is a contract about a class's API</li>
+ <li>Don't break that contract in a subclass</li>
+ <li>Especially important in the context of types</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Changing Accessor Names</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has first_name => (
+ <span class="highlight">reader</span> => 'first_name',
+ <span class="highlight">writer</span> => 'first_name',
+);</code></pre>
+
+ <ul>
+ <li>The long-hand version of <code>is => 'rw'</code></li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Changing Accessor Names</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has first_name => (
+ <span class="highlight">reader</span> => 'first_name',
+ <span class="highlight">writer</span> => undef,
+);</code></pre>
+
+ <ul>
+ <li>The long-hand version of <code>is => 'ro'</code></li>
+ </ul>
+</div>
+
+
+<div class="slide">
+ <h1>Changing Accessor Names</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has first_name => (
+ <span class="highlight">reader</span> => 'get_first_name',
+ <span class="highlight">writer</span> => 'set_first_name,
+);</code></pre>
+</div>
+
+<div class="slide">
+ <h1>Changing Accessor Names</h1>
+
+ <pre><code>package Person;
+use Moose;
+
+has first_name => (
+ <span class="highlight">is</span> => 'rw',
+ <span class="highlight">writer</span> => '_first_name',
+);</code></pre>
+
+ <ul>
+ <li>Can also mix-and-match</li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>ETOOMUCHTYPING</h1>
+
+ <ul>
+ <li><code>MooseX::FollowPBP</code><br /><code>get_foo</code> and <code>set_foo</code></li>
+ <li><code>MooseX::SemiAffordanceAccessor</code><br /><code>foo</code> and <code>set_foo</code></li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>ETOOMUCHTYPING</h1>
+
+ <pre><code>package Person;
+use Moose;
+<span class="highlight">use MooseX::SemiAffordanceAccessor;</span>
+
+has first_name => (
+ is => 'rw',
+);</code></pre>
+
+ <ul>
+ <li>Creates <code>first_name</code> and <code>set_first_name</code></li>
+ </ul>
+</div>
+
+<div class="slide">
+ <h1>Exercises</h1>
+
+ <pre># cd exercises
+# perl bin/prove -lv t/03-basic-attributes.t
+
+Iterate til this passes all its tests</pre>
+</div>
+
</div>
</body>
</html>
http://creativecommons.org/licenses/by-sa/3.0/us/ for details.
-->
+
* required
* default & builder
* lazy
+* predicate, clearer
* init_arg
+* reader & writer
+* attribute inheritance
+* MX attribute naming modules
== Exercises
** make first & last name required
* Go back to Employee
** make title default to "Worker"
+** add predicate & clearer for title
** add salary_level attribute, number from 1-10
** salary, lazy default of salary_level * 1,000, init_arg is undef
== Exercises
-= Advanced attributes
-
-* delegation
-* metaclass & traits
-
-== Exercises
-
= Types
* built-in types
== Exercises
+= Advanced attributes
+
+* weak_ref
+* triggers
+* delegation
+* metaclass & traits
+
+== Exercises
+
= Introspection
== Exercises
line-height: 110%;
}
+.slide .current,
.highlight {
- color: #B02;
+ color: #03c;
}
.slide pre.small {
}
.match-moose {
- color: #B02;
+ color: #03c;
}
+
.match-unsweet {
- color: #B02;
+ color: #03c;
font-weight: bold;
}