File Coverage

blib/lib/Class/Tiny/Immutable.pm
Criterion Covered Total %
statement 38 39 97.4
branch 8 10 80.0
condition 2 3 66.6
subroutine 10 10 100.0
pod 1 3 33.3
total 59 65 90.7


line stmt bran cond sub pod time code
1 1     1   493 use strict;
  1         7  
  1         28  
2 1     1   6 use warnings;
  1         1  
  1         46  
3              
4             package Class::Tiny::Immutable;
5              
6 1     1   6 use Carp ();
  1         2  
  1         22  
7 1     1   533 use Class::Tiny ();
  1         2958  
  1         81  
8             our @ISA = 'Class::Tiny';
9              
10             our $VERSION = '0.001';
11              
12             my %REQUIRED_ATTRIBUTES;
13              
14             sub prepare_class {
15 1     1 0 19 my ( $class, $pkg ) = @_;
16 1     1   8 no strict 'refs';
  1         3  
  1         488  
17 1 50       2 @{"${pkg}::ISA"} = "Class::Tiny::Immutable::Object" unless @{"${pkg}::ISA"};
  1         17  
  1         7  
18             }
19              
20             sub create_attributes {
21 1     1 0 8 my ( $class, $pkg, @spec ) = @_;
22 1         5 $class->SUPER::create_attributes( $pkg, @spec );
23 1         159 $REQUIRED_ATTRIBUTES{$pkg}{$_} = 1 for grep { ref $_ ne 'HASH' } @spec;
  3         74  
24             }
25              
26             sub __gen_sub_body {
27 4     4   409 my ($self, $name, $has_default, $default_type) = @_;
28            
29 4 50 66     20 if ($has_default && $default_type eq 'CODE') {
    100          
30 0         0 return <<"HERE";
31             sub $name {
32             return (
33             ( \@_ == 1 )
34             ? ( exists \$_[0]{$name} ? \$_[0]{$name} : ( \$_[0]{$name} = \$default->( \$_[0] ) ) )
35             : Carp::croak( "$name is a read-only accessor" )
36             );
37             }
38             HERE
39             }
40             elsif ($has_default) {
41 1         7 return <<"HERE";
42             sub $name {
43             return (
44             ( \@_ == 1 )
45             ? ( exists \$_[0]{$name} ? \$_[0]{$name} : ( \$_[0]{$name} = \$default ) )
46             : Carp::croak( "$name is a read-only accessor" )
47             );
48             }
49             HERE
50             }
51             else {
52 3         14 return <<"HERE";
53             sub $name {
54             return \@_ == 1 ? \$_[0]{$name} : Carp::croak( "$name is a read-only accessor" );
55             }
56             HERE
57             }
58             }
59              
60             sub get_all_required_attributes_for {
61 5     5 1 4894 my ( $class, $pkg ) = @_;
62             # attributes are stored per package, so we need to walk the mro ourselves
63             # rely on Class::Tiny to have loaded the appropriate mro
64             my %attr =
65 10         23 map { $_ => undef }
66 5 100       11 map { keys %{ $REQUIRED_ATTRIBUTES{$_} || {} } } @{ mro::get_linear_isa($pkg) };
  16         22  
  16         73  
  5         33  
67 5         23 return keys %attr;
68             }
69              
70             package Class::Tiny::Immutable::Object;
71              
72             our @ISA = 'Class::Tiny::Object';
73              
74             our $VERSION = '0.001';
75              
76             sub BUILD {
77 3     3   1218 my ( $self, $args ) = @_;
78 3         12 my @missing = grep { !exists $args->{$_} }
  6         16  
79             Class::Tiny::Immutable->get_all_required_attributes_for( ref $self );
80 3 100       384 Carp::croak( 'Missing required attributes: ' . join( ', ', sort @missing ) ) if @missing;
81             }
82              
83             1;
84              
85             =head1 NAME
86              
87             Class::Tiny::Immutable - Minimalist class construction, with read-only
88             attributes
89              
90             =head1 SYNOPSIS
91              
92             In I:
93              
94             package Person;
95            
96             use Class::Tiny::Immutable qw( name );
97            
98             1;
99              
100             In I:
101              
102             package Employee;
103             use parent 'Person';
104            
105             use Class::Tiny::Immutable qw( ssn ), {
106             timestamp => sub { time } # lazy attribute with default
107             };
108            
109             1;
110              
111             In I:
112              
113             use Employee;
114            
115             my $obj = Employee->new; # dies, name and ssn attributes are required
116             my $obj = Employee->new( name => "Larry", ssn => "111-22-3333" );
117            
118             my $name = $obj->name;
119             my $timestamp = $obj->timestamp;
120            
121             # no attributes can be set
122             $obj->ssn("222-33-4444"); # dies
123             $obj->timestamp(time); # dies
124              
125             =head1 DESCRIPTION
126              
127             L is a wrapper around L which makes the
128             generated attributes read-only, and required to be set in the object
129             constructor if they do not have a lazy default defined. In other words,
130             attributes are either "lazy" or "required".
131              
132             =head1 METHODS
133              
134             In addition to methods inherited from L, Class::Tiny::Immutable
135             defines the following additional introspection method:
136              
137             =head2 get_all_required_attributes_for
138              
139             my @required = Class::Tiny::Immutable->get_all_required_attributes_for($class);
140              
141             Returns an unsorted list of required attributes known to Class::Tiny::Immutable
142             for a class and its superclasses.
143              
144             =head1 BUGS
145              
146             Report any issues on the public bugtracker.
147              
148             =head1 AUTHOR
149              
150             Dan Book
151              
152             =head1 COPYRIGHT AND LICENSE
153              
154             This software is Copyright (c) 2017 by Dan Book.
155              
156             This is free software, licensed under:
157              
158             The Artistic License 2.0 (GPL Compatible)
159              
160             =head1 SEE ALSO
161              
162             L, L