File Coverage

blib/lib/Stepford/Role/Step/FileGenerator/Atomic.pm
Criterion Covered Total %
statement 28 28 100.0
branch n/a
condition n/a
subroutine 10 10 100.0
pod 1 1 100.0
total 39 39 100.0


line stmt bran cond sub pod time code
1             package Stepford::Role::Step::FileGenerator::Atomic;
2              
3 3     3   6817 use strict;
  3         9  
  3         90  
4 3     3   16 use warnings;
  3         6  
  3         81  
5 3     3   16 use namespace::autoclean;
  3         6  
  3         24  
6              
7             our $VERSION = '0.006001';
8              
9 3     3   293 use Carp qw( croak );
  3         6  
  3         162  
10 3     3   19 use Path::Class qw( file );
  3         6  
  3         126  
11 3     3   1332 use Scope::Guard qw( guard );
  3         1244  
  3         173  
12 3     3   25 use Stepford::Types qw( File );
  3         6  
  3         52  
13              
14 3     3   4951 use Moose::Role;
  3         7  
  3         33  
15              
16             with 'Stepford::Role::Step::FileGenerator';
17              
18             has pre_commit_file => (
19             is => 'ro',
20             isa => File,
21             lazy => 1,
22             builder => '_build_pre_commit_file',
23             );
24              
25       9 1   sub BUILD { }
26             before BUILD => sub {
27             my $self = shift;
28              
29             my @production_names = sort map { $_->name } $self->productions;
30              
31             croak 'The '
32             . ( ref $self )
33             . ' class consumed the Stepford::Role::Step::FileGenerator::Atomic'
34             . " role but contains more than one production: @production_names"
35             if @production_names > 1;
36              
37             return;
38             };
39              
40             sub _build_pre_commit_file {
41 8     8   30 my $self = shift;
42              
43 8         55 my $final_file = ( $self->productions )[0];
44 8         890 my $reader = $final_file->get_read_method;
45              
46 8         357 return file( $self->$reader . '.tmp' );
47             }
48              
49             around run => sub {
50             my $orig = shift;
51             my $self = shift;
52              
53             my $pre_commit = $self->pre_commit_file;
54             my $guard = guard { $pre_commit->remove if -f $pre_commit };
55              
56             $self->$orig(@_);
57              
58             my $read_method = ( $self->productions )[0]->get_read_method;
59             my $post_commit = $self->$read_method;
60              
61             # The step's run method may decide to simply not do anything if the
62             # post-commit file already exists, and that's ok.
63             return if -f $post_commit && !-f $pre_commit;
64              
65             croak 'The '
66             . ( ref $self )
67             . ' class consumed the Stepford::Role::Step::FileGenerator::Atomic'
68             . ' role but run produced no pre-commit production file at:'
69             . " $pre_commit"
70             unless -f $pre_commit;
71              
72             $self->logger->debug("Renaming $pre_commit to $post_commit");
73             rename( $pre_commit, $post_commit )
74             or croak "Failed renaming $pre_commit -> $post_commit: $!";
75             };
76              
77             1;
78              
79             # ABSTRACT: A role for steps that generate a file atomically
80              
81             __END__
82              
83             =pod
84              
85             =encoding UTF-8
86              
87             =head1 NAME
88              
89             Stepford::Role::Step::FileGenerator::Atomic - A role for steps that generate a file atomically
90              
91             =head1 VERSION
92              
93             version 0.006001
94              
95             =head1 DESCRIPTION
96              
97             This role consumes the L<Stepford::Role::Step::FileGenerator> role. It allows
98             only one file production, but makes sure it is written atomically - the file
99             will not exist if the step aborts. The file will only be committed to its
100             final destination when C<run> completes successfully.
101              
102             Instead of manipulating the file production directly, you work with the file
103             given by C<< $step->pre_commit_file >>. This role will make sure it gets
104             committed after C<run>.
105              
106             =head1 METHODS
107              
108             This role provides the following methods:
109              
110             =head2 $step->BUILD
111              
112             This method adds a wrapper to the BUILD method which ensures that there is
113             only one production.
114              
115             =head2 $step->pre_commit_file
116              
117             This returns a temporary file in a temporary directory that you can manipulate
118             inside C<run>. It will be removed if the step fails, or renamed to the final
119             file production if the step succeeds.
120              
121             =head1 CAVEATS
122              
123             When running steps in parallel, it is important to ensure that you do not call
124             the C<< $step->pre_commit_file >> method outside of the C<< $step->run >>
125             method. If you call this at object creation time, this can cause the tempdir
126             containing the C< pre_commit_file > file to be created and destroyed before
127             the run method ever gets a chance to run.
128              
129             =head1 SUPPORT
130              
131             Bugs may be submitted through L<https://github.com/maxmind/Stepford/issues>.
132              
133             =head1 AUTHOR
134              
135             Dave Rolsky <drolsky@maxmind.com>
136              
137             =head1 COPYRIGHT AND LICENSE
138              
139             This software is copyright (c) 2014 - 2023 by MaxMind, Inc.
140              
141             This is free software; you can redistribute it and/or modify it under
142             the same terms as the Perl 5 programming language system itself.
143              
144             =cut