Trimbach has asked for the wisdom of the Perl Monks concerning the following question:

I have (at long last) hunkered down on my first OO project. I've read Damian's book, (even attended his class at O'Reilley's U. of Perl) and I think I have a reasonable good grasp of the basics. My project was humming along well until I hit a pretty weird snag, and I don't know what to do.

The project I'm working on generates an HTML-formatted monthly calendar (and before anyone suggests HTML::CalendarMonth on CPAN I've already looked at it... it's WAY more heavy weight than I need. My module is an order of magnitude smaller.) I wanted to give the user the ability to pass HTML formatting to individual table cells (make the cell for February 6th red, for example) so my first attempt was this:

sub format_cell { my ($self, $format, @days) = @_; # @days lets you apply formatting to more # than one date at a time. foreach (@days) { $self->{'cell_format'}[$_] = $format; } }
...where $format is a reference to a hash. (This way I get to pass HTML formatting directly to the HTML output functions in CGI.pm.) This works all fine and dandy, but since I'm doing straight assignment whatever formatting that's passed in completely replaces whatever formatting was there, so that if the formatting originally was {align => "left"} it gets clobbered when you pass {bgcolor => "red"}.

The answer is just to assign only to the keys I need instead of clobbering the whole hash, so here's try #2:

sub format_cell2 { my ($self, $format, @days) = @_; foreach (@days) { foreach my $key (keys %$format) { ${$self->{'cell_format'}[$_]}{$key} = + ${$format}{$key}; } } }
Same as before, only now I'm just referencing the hash keys passed, and only the ones passed change values in my object. I thought certainly this would work.

The problem is that whenever this method is called on a single object it changes ALL the values on ALL the days, and, as a particularly weird bonus, it changes it on EVERY object created with the module. (One object is created for each month... I have a program creating, say, 6 months at a time.)

Here's an actual example of how I call the method:

$html_months{$key} -> format_cell2({bgcolor => "red"}, 15)
...where $html_months{$key} contains an object for some month. The idea is that the cell for the 15th of that month should now be red, but in reality, ALL cells become red, on ALL objects in %html_months. This happens even if the method is called prior to the creation of the other objects.

I have a theory that I have somehow called a Class method, which would explain the universality of my problem, but in my debugging I've verified that it's only being called against a single object, and only one time.

Sigh... sorry for this being so long, but I really have no idea what to do. Any help from any OO Monks out there would be appreciated.

Gary Blackburn
Trained Killer

Replies are listed 'Best First'.
Re: Strange method behavior
by chromatic (Archbishop) on Dec 17, 2000 at 07:37 UTC
    I think we would need to see your constructor and the way you build new objects to do a lot more debugging. In the absence of that information, here's where I'd focus my debugging:
    foreach my $day (@days) { my $day_data = $self->{cell_format}[$day]; print STDERR "Changing hash $day_data for day $day!\n"; @$day_data{keys %$format} = values %$format; }
    That way, you can compare the reference address for multiple accesses. (My bet is on something tricky in the constructor, though.)
      Well, I took your advice and spent some additional time with the constructor to see what was going on. I was using a hash with default values for cell_format that populated every date during construction. Problem was, I accidentally populated every date with a reference to the default hash (and the same reference at that.) For some reason format_cell2 was changing the value of my default hash which instantly changed all the places where that hash was referenced. Bad Trimbach. Bad.

      It's clear that's what happened, although I don't grok why that hash was getting changed at all, but hey, I fixed it and now everything works. Thanks Chromatic (and everyone else) for sparking the idea that led me to the solution... and I promise to play nicer with references in the future.

      Gary Blackburn
      Trained Killer

        The original hash was getting changed because all your references pointed to that hash. The thing to remember is that when you derefence a reference, you get the original data structure back, not a copy of the structure. To get a copy, you need to make the copy when you create the reference.
        my %hash = (snark => 'snark'); # hash my $ref = \%hash; # ref to hash surprise($ref); # pass ref to hash sub surprise { my $ref = shift; # copy ref to hash $ref->{snark} = 'boojum'; # change ref'ed hash } print "snark => $hash{snark}\n"; # print original hash __END__ snark => boojum
Re: Strange method behavior
by repson (Chaplain) on Dec 17, 2000 at 07:11 UTC
    Is this the code you are actually using or is it a stripped version?
    One things you could test doing is having a sub like this:
    sub format_cell3 { my ($self,$format) = @_; $self->{cell_format}[0]{bgcolor} = $format->{bgcolor}; }
    If this simplified case still doesn't work then something is definately wrong.
    In your assignment you should only need this initital -> and from there on you simply have a HoAoH to deal with.
      My code samples were complete... just cut 'n pasted directly from my source. I just tried naming the key directly instead of relying on keys %$format and got the same, "change-all-objects-everywhere" behavior.

      I'll agree with the "something is definitely wrong" part... I've isolated everything down to the subs I referenced, because I've tested all the values of $self->{cell_format} in the subs immediately before assignment, and then immediately after and the assignment is definitely the culprit.

      Gary Blackburn
      Trained Killer

        How are you building your object? Are you creating an object from scratch or passing the same one back? It sounds almost like each instance of your object is pointing to the same thing...
Re: Strange method behavior
by Fastolfe (Vicar) on Dec 17, 2000 at 06:56 UTC
    I'm not sure what version of HTML::CalendarMonth you looked at, but the one on my system will let you do all sorts of formatting on individual cells, cell columns, whatever. I know this doesn't really answer your problem (because it'll involve me spending more time than I currently have at the moment), but I thought I would mention this in case you wanted to re-investigate HTML::CalendarMonth.
      My main problem is that there's lots and lots (and lots) of dependencies involved with HTML::CalendarMonth, including HTML::Tree, HTML::Tagset, HTML Element Extended just to name a few. One of the module dependencies is Tie::Array, which doesn't even run on Perl 5.004 (the version my ISP has installed) so, even if I really wanted to import somewhere around a dozen modules it still wouldn't work unless I convinced my ISP to upgrade. Besides, I can do all I need to do in fewer than 150 lines of code... and when I'm done I'll have a very lightweight module that will be very useful on other projects.

      Gary Blackburn
      Trained Killer