From: Dave Rolsky Date: Tue, 16 Jun 2009 16:33:32 +0000 (-0500) Subject: Finished slides & exercises for section 3on basic attributes. X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=8d1ce1d704a422c266cc8d3a5a50c98c2a25fc38;p=gitmo%2Fmoose-presentations.git Finished slides & exercises for section 3on basic attributes. Some tweaking of style and previous sections as well. --- diff --git a/moose-class/exercises/answers/01-classes/Employee.pm b/moose-class/exercises/answers/01-classes/Employee.pm index 6c301fc..3860493 100644 --- a/moose-class/exercises/answers/01-classes/Employee.pm +++ b/moose-class/exercises/answers/01-classes/Employee.pm @@ -4,14 +4,14 @@ use 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; diff --git a/moose-class/exercises/answers/02-roles/Employee.pm b/moose-class/exercises/answers/02-roles/Employee.pm index 6c301fc..3860493 100644 --- a/moose-class/exercises/answers/02-roles/Employee.pm +++ b/moose-class/exercises/answers/02-roles/Employee.pm @@ -4,14 +4,14 @@ use 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; diff --git a/moose-class/exercises/answers/02-roles/HasAccount.pm b/moose-class/exercises/answers/02-roles/HasAccount.pm index 4a4bd68..8287588 100644 --- a/moose-class/exercises/answers/02-roles/HasAccount.pm +++ b/moose-class/exercises/answers/02-roles/HasAccount.pm @@ -2,9 +2,7 @@ package HasAccount; use Moose::Role; -has balance => ( - is => 'rw', -); +has balance => ( is => 'rw' ); sub deposit { my $self = shift; diff --git a/moose-class/exercises/answers/03-basic-attributes/Employee.pm b/moose-class/exercises/answers/03-basic-attributes/Employee.pm new file mode 100644 index 0000000..11b8db4 --- /dev/null +++ b/moose-class/exercises/answers/03-basic-attributes/Employee.pm @@ -0,0 +1,35 @@ +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; diff --git a/moose-class/exercises/answers/03-basic-attributes/HasAccount.pm b/moose-class/exercises/answers/03-basic-attributes/HasAccount.pm new file mode 100644 index 0000000..76ea15e --- /dev/null +++ b/moose-class/exercises/answers/03-basic-attributes/HasAccount.pm @@ -0,0 +1,29 @@ +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; diff --git a/moose-class/exercises/answers/03-basic-attributes/Person.pm b/moose-class/exercises/answers/03-basic-attributes/Person.pm new file mode 100644 index 0000000..db69019 --- /dev/null +++ b/moose-class/exercises/answers/03-basic-attributes/Person.pm @@ -0,0 +1,33 @@ +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; diff --git a/moose-class/exercises/answers/03-basic-attributes/Printable.pm b/moose-class/exercises/answers/03-basic-attributes/Printable.pm new file mode 100644 index 0000000..cb9b58c --- /dev/null +++ b/moose-class/exercises/answers/03-basic-attributes/Printable.pm @@ -0,0 +1,9 @@ +package Printable; + +use Moose::Role; + +requires 'as_string'; + +no Moose::Role; + +1; diff --git a/moose-class/exercises/t/01-classes.t b/moose-class/exercises/t/01-classes.t index d4db456..e9747fb 100644 --- a/moose-class/exercises/t/01-classes.t +++ b/moose-class/exercises/t/01-classes.t @@ -18,7 +18,7 @@ # # An Employee has the following read-write attributes: # -# * position - read-write +# * title - read-write # * salary - read-write # * ssn - read-only # diff --git a/moose-class/exercises/t/03-basic-attributes.t b/moose-class/exercises/t/03-basic-attributes.t new file mode 100644 index 0000000..c0d3bad --- /dev/null +++ b/moose-class/exercises/t/03-basic-attributes.t @@ -0,0 +1,44 @@ +# 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(); diff --git a/moose-class/exercises/t/lib/MooseClass/Tests.pm b/moose-class/exercises/t/lib/MooseClass/Tests.pm index 7c4d338..5455a27 100644 --- a/moose-class/exercises/t/lib/MooseClass/Tests.pm +++ b/moose-class/exercises/t/lib/MooseClass/Tests.pm @@ -36,7 +36,7 @@ sub tests01 { 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' ); @@ -45,7 +45,7 @@ sub tests01 { } sub tests02 { - tests01( person_attr_count => 3 ); + tests01( person_attr_count => 3, @_ ); local $Test::Builder::Level = $Test::Builder::Level + 1; @@ -61,6 +61,50 @@ sub tests02 { 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; @@ -90,66 +134,56 @@ sub count_attrs { 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' ); @@ -180,23 +214,18 @@ sub person01 { 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 { @@ -206,10 +235,8 @@ 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); } @@ -218,34 +245,65 @@ sub employee02 { 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; diff --git a/moose-class/slides/index.html b/moose-class/slides/index.html index 59fbfd6..2ff609a 100644 --- a/moose-class/slides/index.html +++ b/moose-class/slides/index.html @@ -61,7 +61,7 @@ img#me05 {top: 43px;left: 36px;} @@ -814,7 +814,7 @@ sub last_name {

Typo?

- $self->{last_nane} + $self->{last_nane}
@@ -1090,7 +1090,7 @@ use Moose;

Exercises

-
$ cd exercises
+  
# cd exercises
 $ perl bin/prove -lv t/00-prereq.t
 
 Missing anything? Install it. (see tarballs/)
@@ -1575,12 +1575,494 @@ requires 'compare';
 

Exercises

-
$ cd exercises
+  
# cd exercises
 # perl bin/prove -lv t/02-roles.t
 
 Iterate til this passes all its tests
+
+

Part 3: Basic Attributes

+
+ +
+

Attributes Are Huge

+ +
    +
  • Moose's biggest feature
  • +
  • The target of many MooseX modules
  • +
+
+ +
+

Quick Review

+ +
    +
  • Declared with has
  • +
  • Read-only or read-write
  • +
+ +
package Shirt;
+use Moose;
+
+has 'color'     => ( is => 'ro' );
+has 'is_ripped' => ( is => 'rw' );
+
+ +
+

Required-ness

+ +
    +
  • Required means "must be passed to the constructor"
  • +
  • But can be undef
  • +
+
+ +
+

Required-ness

+ +
package Person;
+use Moose;
+
+has first_name => (
+    is       => 'ro',
+    required => 1,
+);
+
+Person->new( first_name => undef ); # ok
+Person->new(); # kaboom
+
+ +
+

Default and Builder

+ +
    +
  • Attributes can have defaults
  • +
  • Simple non-referecne scalars (number, string)
  • +
  • Subroutine reference
  • +
  • A builder method
  • +
+
+ +
+

Default

+ +
    +
  • Can be a non-reference scalar (including undef)
  • +
+ +
package Person;
+use Moose;
+
+has bank => (
+    is      => 'rw',
+    default => 'Spire FCU',
+);
+
+ +
+

Default

+ +
    +
  • Can be a subroutine reference
  • +
+ +
package Person;
+use Moose;
+
+has bank => (
+    is      => 'rw',
+    default =>
+        sub { Bank->new(
+                  name => 'Spire FCU' ) },
+);
+
+ +
+

Default as a Subroutine Reference

+ +
    +
  • Called as a method on the object
  • +
  • Called anew for each object
  • +
+
+ +
+

Why No Other Reference Types?

+ +
package Person;
+use Moose;
+
+has bank => (
+    is      => 'rw',
+    default => Bank->new(
+                   name => 'Spire FCU' ),
+);
+ +
    +
  • Now every person shares the same Bank object!
  • +
+
+ +
+

Defaulting to an Empty Reference

+ +
package Person;
+use Moose;
+
+has packages => (
+    is      => 'rw',
+    default => sub { [] },
+);
+
+ +
+

What if I Want to Share?

+ +
package Person;
+use Moose;
+
+my $highlander_bank =
+    Bank->new( name => 'Spire FCU' );
+
+has bank => (
+    is      => 'rw',
+    default => sub { $highlander_bank },
+);
+
+ +
+

Builder

+ +
    +
  • A method name which returns the default
  • +
+
+ +
+

Builder

+ +
package Person;
+use Moose;
+
+has bank => (
+    is      => 'rw',
+    builder => '_build_bank',
+);
+
+sub _build_bank {
+    my $self = shift;
+    return Bank->new( name => 'Spire FCU' );
+}
+
+ +
+

Default vs Builder

+ +
    +
  • Use default for simple scalars
  • +
  • Use default to return empty references
  • +
  • Use default for very trivial subroutine references
  • +
  • Use builder for everything else
  • +
+
+ +
+

Builder Bonuses

+ +
    +
  • Can be overridden and method modified, because it's called by name
  • +
  • Roles can require a builder
  • +
+
+ +
+

Role Requires Builder

+ +
package HasBank;
+use Moose::Role;
+
+requires '_build_bank';
+
+has bank => (
+    is      => 'ro',
+    builder => '_build_bank',
+);
+
+ +
+

Lazy, Good for Nothing Attributes

+ +
    +
  • Normally, defaults are generated during object construction
  • +
  • This can be expensive
  • +
  • We want to default to $self->size * 2, but attribute initialization order is unpredictable
  • +
  • Use lazy attributes!
  • +
+
+ +
+

The Power of Dynamic Defaults

+ +
package Person;
+use Moose;
+
+has shoe_size => (
+    is => 'ro',
+);
+
+ +
+

The Power of Dynamic Defaults

+ +
has shoes => (
+    is      => 'ro',
+    lazy    => 1,
+    builder => '_build_shoes',
+);
+
+sub _build_shoes {
+    my $self = shift;
+
+    return Shoes->new(
+        size => $_[0]->shoe_size );
+}
+
+ +
+

Lazy is Good

+ +
    +
  • Lazy defaults are executed when the attribute is read
  • +
  • Can see other object attributes
  • +
  • Still need to watch out for circular laziness
  • +
+
+ +
+

Clearer and Predicate

+ +
    +
  • Attributes can have a value, including undef, or not
  • +
  • Can clear the value with a clearer method
  • +
  • Can check for the existence of a value with a predicate method
  • +
  • By default, these methods are not created
  • +
+
+ +
+

Clearer and Predicate

+ +
package Person;
+use Moose;
+
+has account => (
+    is        => 'ro',
+    lazy      => 1,
+    builder   => '_build_account',
+    clearer   => '_clear_account',
+    predicate => 'has_account',
+);
+
+ +
+

Clearer and Lazy Defaults

+ +
    +
  • Lazy defaults are good for computed attributes
  • +
  • Clear the attribute when the source data changes
  • +
  • Recalculated at next access
  • +
+
+ +
+

Renaming constructor arguments

+ +
    +
  • By default, constructor names = attribute names
  • +
  • Use init_arg to change this
  • +
  • Set init_arg => undef to make it unconstructable
  • +
+
+ +
+

Some init_arg examples

+ +
package Person;
+use Moose;
+
+has shoe_size => (
+    is       => 'ro',
+    init_arg => 'foot_size',
+);
+
+Person->new( shoe_size => 13 );
+
+my $person =
+    Person->new( foot_size => 13 );
+print $person->shoe_size;
+
+ +
+

Some init_arg examples

+ +
package Person;
+use Moose;
+
+has shoes => (
+    is       => 'ro',
+    init_arg => undef,
+);
+
+Person->new( shoes => Shoes->new );
+
+ +
+

Why Set init_arg => undef?

+ +
    +
  • Use this with a lazy default for attributes-as-cache
  • +
  • Compute the value as needed
  • +
  • Ensure that it is always generated correctly (not set by constructor)
  • +
  • Use triggers or method modifiers (coming soon) to clear the value
  • +
+
+ +
+

Attribute Inheritance

+ +
    +
  • By default, subclasses inherit attribute as-is
  • +
  • Can change some attribute parameters in subclasses +
      +
    • default
    • +
    • builder
    • +
    • required
    • +
    • lazy
    • +
    • others we've not yet covered
    • +
    +
  • +
+
+ +
+

Attribute Inheritance Example

+ +
package Employee;
+use Moose;
+
+extends 'Person';
+
+has '+first_name' => (
+    default => 'Joe',
+);
+
+ +
+

Attribute Inheritance Warning

+ +
    +
  • An attribute is a contract about a class's API
  • +
  • Don't break that contract in a subclass
  • +
  • Especially important in the context of types
  • +
+
+ +
+

Changing Accessor Names

+ +
package Person;
+use Moose;
+
+has first_name => (
+    reader => 'first_name',
+    writer => 'first_name',
+);
+ +
    +
  • The long-hand version of is => 'rw'
  • +
+
+ +
+

Changing Accessor Names

+ +
package Person;
+use Moose;
+
+has first_name => (
+    reader => 'first_name',
+    writer => undef,
+);
+ +
    +
  • The long-hand version of is => 'ro'
  • +
+
+ + +
+

Changing Accessor Names

+ +
package Person;
+use Moose;
+
+has first_name => (
+    reader => 'get_first_name',
+    writer => 'set_first_name,
+);
+
+ +
+

Changing Accessor Names

+ +
package Person;
+use Moose;
+
+has first_name => (
+    is     => 'rw',
+    writer => '_first_name',
+);
+ +
    +
  • Can also mix-and-match
  • +
+
+ +
+

ETOOMUCHTYPING

+ +
    +
  • MooseX::FollowPBP
    get_foo and set_foo
  • +
  • MooseX::SemiAffordanceAccessor
    foo and set_foo
  • +
+
+ +
+

ETOOMUCHTYPING

+ +
package Person;
+use Moose;
+use MooseX::SemiAffordanceAccessor;
+
+has first_name => (
+    is => 'rw',
+);
+ +
    +
  • Creates first_name and set_first_name
  • +
+
+ +
+

Exercises

+ +
# cd exercises
+# perl bin/prove -lv t/03-basic-attributes.t
+
+Iterate til this passes all its tests
+
+
@@ -1594,3 +2076,4 @@ This work is licensed under a Creative Commons Attribution-Share Alike http://creativecommons.org/licenses/by-sa/3.0/us/ for details. --> + diff --git a/moose-class/slides/outline b/moose-class/slides/outline index 379453b..338e312 100644 --- a/moose-class/slides/outline +++ b/moose-class/slides/outline @@ -44,7 +44,11 @@ * required * default & builder * lazy +* predicate, clearer * init_arg +* reader & writer +* attribute inheritance +* MX attribute naming modules == Exercises @@ -52,6 +56,7 @@ ** 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 @@ -73,13 +78,6 @@ == Exercises -= Advanced attributes - -* delegation -* metaclass & traits - -== Exercises - = Types * built-in types @@ -93,6 +91,15 @@ == Exercises += Advanced attributes + +* weak_ref +* triggers +* delegation +* metaclass & traits + +== Exercises + = Introspection == Exercises diff --git a/moose-class/slides/ui/custom.css b/moose-class/slides/ui/custom.css index 602d2a5..29fd936 100644 --- a/moose-class/slides/ui/custom.css +++ b/moose-class/slides/ui/custom.css @@ -58,8 +58,9 @@ img.for-slide { line-height: 110%; } +.slide .current, .highlight { - color: #B02; + color: #03c; } .slide pre.small { @@ -71,10 +72,11 @@ img.for-slide { } .match-moose { - color: #B02; + color: #03c; } + .match-unsweet { - color: #B02; + color: #03c; font-weight: bold; }