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

"If I create a key,value pair in a hash(ref) which is stored in an array(ref) which is one of the methods on the object, will I need to rebuild the array and reset the property in order to effect the change?"

I think the code gives a more specific example of what I am asking.


Accessor building:

# -------------------------------------------------------------------- +---------- # OBJECT CREATION # -------------------------------------------------------------------- +---------- sub _create_accessors { my %OrderObjectFields = @_; foreach my $field (keys %OrderObjectFields) { my $default = $OrderObjectFields{ $field }; my $code = ' sub '.$field.' { my $value = (scalar @_>1? $_[0]->{"'.$field.'"} = $_[1 +] : $_[0]->{"'.$field.'"} + ); $value ="'.$default.'" unless defined $value; $value; }'; eval $code; die $@ if( $@ ); } } # -------------------------------------------------------------------- +---------- BEGIN { _create_accessors( 'order_no' => 0 , 'items_in_order' => '', 'supplier_list' => '', ); } # -------------------------------------------------------------------- +---------- # STANDARD OBJECT METHODS # -------------------------------------------------------------------- +---------- sub get { my( $self, $attribute ) = @_; return $self->{$attribute}; } # -------------------------------------------------------------------- +---------- sub set { my( $self, $attribute, $value ) = @_; $self->{$attribute} = $value; }
Which I am happy with (but no doubt someone will prove TIMTOWODI! ;P)

This is the code snippet which my question is based on:

sub set_stock { my( $self, $supplier, $line, $stockLevel ) = @_; my $supplierList = (); my $supplierNumber = $supplier->supplier_id; # $supplierList will be a reference to an array of references to h +ashes. unless( $supplierList = $self->get('supplier_list') ) { die( "Cannot set stock level when supplier list is corrupt: $s +upplierList" ); } foreach my $supp (@$supplierList) { next unless( $supp->{supplier_id} == $supplierNumber ); my $reference = $line->reference; # Here is the update. $supp->{$reference} = $stockLevel; } }
Having reached this point in the code, I became unsure whether I would have to splice @$supplerList or in some other way rebuild it, and then reassign it with $self->set

I just feel that it is starting to look messy and smacks of just not being right. I'd love some guidance on approaching this problem.

The more I preview this, the more I feel I am missing something obvious .. *goes back to reading the panther book some more*

--
Graq

Replies are listed 'Best First'.
(Ovid) Re: Storing Info with Accessors
by Ovid (Cardinal) on Aug 01, 2001 at 20:18 UTC

    graq asked:

    If I create a key,value pair in a hash(ref) which is stored in an array(ref) which is one of the methods on the object, will I need to rebuild the array and reset the property in order to effect the change?

    Without actually running your code, everything appears to be correct. As for your question, it appears to be a bit vague. What do you mean 'create a key/value' pair? Are you referring to one of the pairs creating in the BEGIN block? Are you referring to updating a key/value pair for a particular supplier or or adding a new key/value pair on the fly? None of these should be particularly difficult.

    In reading through your code, I'm not sure why you have the arrayref of hashrefs. It appears to me that this could be slow if you have many suppliers. The data structure that I am seeing looks like this (just the structure, I don't know the keys):

    [ { 'supplier_id' => 17, 'item_foo' => 3, 'item_bar' => 7 }, { 'supplier_id' => 28, 'item_foo' => 5, 'item_qux' => 202 } ]

    Unless the array index actually conveys useful information, I would probably turn this into a hashref of hashrefs with the primary hashs keys being the supplier_id:

    { '28' => { 'item_foo' => 5, 'item_qux' => 202 }, '17' => { 'item_foo' => 3, 'item_bar' => 7 } };

    If this works for you, your &set_stock sub becomes simpler and faster:

    sub set_stock { my( $self, $supplier, $line, $stockLevel ) = @_; my $supplierList = (); my $supplierNumber = $supplier->supplier_id; # $supplierList will be a reference to a hash of references to has +hes. unless( $supplierList = $self->get('supplier_list') ) { die( "Cannot set stock level when supplier list is corrupt: $s +upplierList" ); } my $reference = $line->reference; $supp->{ $supplierNumber }{ $reference } = $stockLevel; }

    Now that I've gotten thoroughly off-topic, what was your question again? :) If you can provide more detail of what problem you're trying to solve...

    Cheers,
    Ovid

    Vote for paco!

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

      What do you mean 'create a key/value' pair?

      I am refering to the hash for the new item=>stockLevel for a supplier.

      I would probably turn this into a hashref of hashrefs with the primary hashs keys being the supplier_id

      I think that is a good idea too, thanks :)

      Now that I've gotten thoroughly off-topic, what was your question again?

      By no means! You have hit the nail on the head.

      If you can provide more detail of what problem you're trying to solve...

      Hmm :)
      The object is an order. Each order has a set of suppliers that can supply at least one of the items in the order. Each supplier should have a complete list of how many of each of the items in the order they have in stock.

      Statement: Using the data structure and sub in your reply (assuming the syntax is correct), calling &set_stock will effect a permament change in the object properties without the need for calling the &set function.

      My question (pretty much) was "Is the above statement correct?"

      --
      Graq

Re: Storing Info with Accessors
by DBX (Pilgrim) on Aug 01, 2001 at 20:48 UTC
    It seems to me you could avoid a lot of this by writing an Autoload method that handles your accessor problems. A standard one (lifted from Damian Conway's Awesome "Object Oriented Perl") looks like:
    sub AUTOLOAD { my ($self,$newval) = @_; if ($AUTOLOAD =~ /.*::get_(\w+)/){ return $self->{$1}; }elsif($AUTOLOAD =~ /.*::set_(\w+)/){ $self->{$1} = $newval; }else{ croak "$AUTOLOAD is not a method"; } }
    You could then just call something like my $value = $object->get_items_in_order(); and avoid a begin block and the overhead of eval'ing altogether. With some minor modifications, the Autoload could deal with your array reference problems I think (unless I misunderstood).
      Interesting. That looks extremely useful and much better than my original code (and got a significant ++ing)

      Do you know of any differences between the two (my original and your suggestion) in behaviour, performance, problems, restritions? Any conditions under which my original code is 'better' than your suggestion?

      Or is your suggestion exactly what it looks like - much better all round :) ?

      --
      Graq

Re: Storing Info with Accessors
by bikeNomad (Priest) on Aug 01, 2001 at 20:21 UTC
    If your object is maintaining a reference to the supplierList, you can just splice it if you want to. The reference remains an intact reference to the same array. But actually, in your code, I don't see why you need to rebuild anything, since you've only modified a hash that is referred to by an array that is referred to by self. You haven't changed the object structure in any way.

    As an aside, you've chosen a rather roundabout way to make accessors with that string eval. Why not just use a closure:

    sub _create_accessors { my %orderObjectFields = @_; foreach my $field ( keys %orderObjectFields ) { my $default = $orderObjectFields{$field}; no strict 'refs'; *{ __PACKAGE__ . "::$field" } = sub { my $value = ( scalar @_ > 1 ? $_[0]->{$field} = $_[1] : $_[0]->{$fie +ld} ); $value = $default unless defined $value; $value; }; } }
      As an aside, you've chosen a rather roundabout way to make accessors with that string eval. Why not just use a closure:

      I am, in a few months, going to be looking at various parts of the project and sprucing up the code. But I am wary of no strict 'refs'. I really do prefer to avoid using such methods. Your suggestion has given me ideas though, thanks :)

      --
      Graq

        Since no strict 'refs' only affects the block it's in, it won't affect the rest of your program (past the next curly brace, anyway). I make a practice of putting it in the innermost scope possible. You can do the following instead if you're paranoid. Note that the no strict 'refs' only affects the one line with the assignment. Why would you hesitate to use that? I hope you're not absorbing the 'always use strict everywhere' dogma...

        foreach my $field ( keys %orderObjectFields ) { my $default = $orderObjectFields{$field}; my $closure = sub { my $value = ( scalar @_ > 1 ? $_[0]->{$field} = $_[1] : $_[0]->{$fie +ld} ); $value = $default unless defined $value; $value; }; no strict 'refs'; # only affects next statement: *{ __PACKAGE__ . "::$field" } = $closure; }