Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation

Re^2: Why won't a hash in a hash work as a hash reference to create an object?

by Lady_Aleena (Deacon)
on Apr 09, 2012 at 02:33 UTC ( #964065=note: print w/replies, xml ) Need Help??

in reply to Re: Why won't a hash in a hash work as a hash reference to create an object?
in thread Why won't a hash in a hash work as a hash reference to create an object?

GrandFather, I am going to try to explain what I see, but I will probably have some of it wrong, so please correct me where I'm in error.

  1. It looks like new under package OptionsBase; is just setting the hash keys for the hash that is being created. I'm not exactly sure what headingsLU is doing.
  2. It looks like load under package OptionsBase; is getting the file and loading it into the hash.
  3. It looks like save under package OptionsBase; is saving the hash back to the file. (I normally just save without a subroutine, just open and print.)
  4. I can't tell what setOptions and getOptions are doing. I see an array called @badOptions, but I don't know where the bad options are coming from.
  5. I take it that I can split this up into two different modules at package TwitterOptions; since my original get_hash is its own beast separate from the Twitter code or would that completely mess up your ISA stuff?</c>
  6. I'm not exactly sure why you wrote something for modifying the hash, since that will only happen when another script is run which will modify the data file. (Also, followers is a number not a list. There is %followers that has the headings [qw(id screen_name greet)])

The get_hash subroutine is one that I use everywhere in my work to create a hash from my data files. (movies, role playing, miscellany, etc)

my %movies = get_hash( file => data_file('Movies','movies.txt'), headings => [qw(title alt_title series franchise start_year end_year + media format wikipedia allmovie imdb tvcom flixster genre theme base +d_on company)], ); my %series = get_hash( file => data_file('Movies','series.txt'), headings => [qw(title wikipedia allmovie programs)], ); my %gems = get_hash( file => data_file, headings => [qw(gem image_mineral image_gemstone gemstone_library ge +mstone_gbg tradeshop)], );

I think I'll just give you the bigger picture so you can see what I did and where I did it.

Base::Data is where get_hash and even data_file are.

package Base::Data; use strict; use warnings FATAL => qw( all ); use base 'Exporter'; our @EXPORT_OK = qw(data_file data_directory get_hash alpha_array alph +a_hash); use File::Basename; use File::Spec; use List::Util qw(first); use Base::Roots qw(get_root); sub data_file { my ($directory,$filename) = @_; my $file_name = basename($0); my $root_path = get_root('path'); my $root_data = get_root('data'); my $relative_path = File::Spec->abs2rel($file_name,$root_path); $relative_path =~ s/\.\w+$//; my $data; if ($directory && $filename) { $data = "$root_data/$directory/$filename"; } else { $data = first {-e $_} map("$root_data/$relative_path.$_",qw(csv tx +t)); } if (!defined $data) { die "No file associated with $relative_path."; } return $data; } sub data_directory { my ($dir) = @_; $dir =~ s/ /_/g; return get_root('data')."/$dir/"; } # Written with rindolf in #perlcafe on freenode; golfed with the help +of [GrandFather] of PerlMonks. # Changed to accept named parameters to make it prettier to use. # The parameters are file, headings, and a very optional sort. sub get_hash { my %opt = @_; open(my $fh, '<', $opt{file}) or die("can't open $opt{file} $!"); my $line_number = 0; my %hash; while (my $line = <$fh>) { ++$line_number; chomp $line; my @values = split(/\|/,$line); my $n = 0; $hash{$values[0]}{sort_number} = $line_number if $opt{sort}; for my $heading (@{$opt{headings}}) { $hash{$values[0]}{$heading} = defined($values[$n]) ? $values[$n] + : ''; ++$n; } } return %hash; } sub first_alpha { my $alpha = shift; $alpha = ucfirst($alpha) if $alpha =~ /^\l./; $alpha =~ s/\s*\b(A|a|An|an|The|the)(_|\s)//xi; if ($alpha =~ /^\d/) { $alpha = "#"; # $alpha =~ s/^([\d\.,]+).*/$1/; # $alpha =~ s/(\d),(\d)/$1$2/; } else { $alpha =~ s/^(.)(\w|\W)+/$1/; } return $alpha; } sub alpha_array { my ($org_list) = @_; my %alpha_hash; for my $org_value (@{$org_list}) { my $alpha = first_alpha($org_value); push @{$alpha_hash{$alpha}}, $org_value; } return %alpha_hash; } sub alpha_hash { my ($org_list) = @_; my %alpha_hash; for my $org_value (keys %{$org_list}) { my $alpha = first_alpha($org_value); $alpha_hash{$alpha}{$org_value} = $org_list->{$org_value}; } return %alpha_hash; } 1;

Since I use get_root a lot in Base::Data, I'll include Base::Roots. That was where I was first trying out objects and couldn't figure it out there.

package Base::Roots; use strict; use warnings FATAL => qw( all ); use base 'Exporter'; our @EXPORT = qw(get_root); my $server = $ENV{SERVER_NAME} ? $ENV{SERVER_NAME} : 'localhost'; my %hosts = ( 'localhost' => { path => q(C:/Documents and Settings/<ME>/My Documents/fantasy), link => q(http://localhost), user => q(<ME>), name => q(<ME>'s Domain), mail => q(<ME>@localhost), }, '' => { path => q(/ftp/pub/www/fantasy), link => q(, user => q(Fantasy), name => q(Fantasy's Realm), mail => q(, }, '' => { path => q(/www/fantasy/public_html), link => q(, user => q(Fantasy), name => q(Fantasy's Realm), mail => q(, } ); my $root_path = $hosts{$server}{path}; for my $host (keys %hosts) { $hosts{$host}{data} = "$root_path/files/data"; for my $key qw(audio css images) { $hosts{$host}{$key} = $hosts{$host}{link}."/files/$key"; } } sub get_root { my ($host_key) = @_; return $hosts{$server}{$host_key}; } 1;

So, if the top half of what you did could be put in a module on its own, maybe called Base::FileData or something, I would be happy to see how to split this up. You did a lot of work here and added complexities I have yet to understand, but I'll be looking over this over the next few days trying to figure it out.

Have a cookie and a very nice day!
Lady Aleena

Replies are listed 'Best First'.
Re^3: Why won't a hash in a hash work as a hash reference to create an object?
by GrandFather (Sage) on Apr 19, 2012 at 03:14 UTC
    1. new is a constructor. It creates an object which may include doing a little house keeping to get the object into good initial shape. In this case it is generating a lookup table of the allowed column headings for internal use by other members of the class (setOptions and getOptions for example).
    2. load as you surmise is loading the hash. It is also providing empty strings for any missing columns and is providing a sort number based on the file line.
    3. "simply open and print" don't work for a hash. save ensures that only heading colums are output to the file and that they are output in the correct order correctly formatted. Actually both save and load could be beefed up to work with quoted strings and embedded newlines etc, but if you want that you'd have them use Text::CSV to do the heavy lifting. And guess what? All classes derived from OptionsBase then get the new magic at no added cost!
    4. Look at the line where @badOptions is set and figure it out now that you have had a sleep. setOptions and getOptions provide checked access to options. In particular they check that the options that you are getting or setting are ones that are allowed for the options class. That allows you to catch bad option stuff early make it easier to catch programming errors.
    5. ISA doesn't care where the base class comes from so long as it has been seen by the time methods are called on an object that needs to access the base class definition so it doesn't matter where OptionsBase lives. It would be usual for TwitterOptions to live in its own module and for that module to use OptionsBase in the module.
    6. I'm not greatly interested in addressing your specific implementation details in the example I gave. Those are for you to sort out. However it makes sense to have a single cohesive class to handle reading and writing a set of options to persistent storage so I provided a load and save.

    The point of the example is exactly that you have a piece of common code (get_hash) that is used all over the place but presents a very raw and unsafe "interface". By wrapping that up in an object you can easily tailor it to different contexts (different lists if headings for example) and gain a cleaner way of managing your options with better sanity checking of usage. It also makes it easy in the future to change how you persist the options. If for example you were to realise that databases were wonderful (unlikely I know ;) ) and wanted to switch all your option files over to database tables you can facilitate that in one place by simply changing OptionsBase.

    True laziness is hard work

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://964065]
and the shadows deepen...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (4)
As of 2017-03-30 08:55 GMT
Find Nodes?
    Voting Booth?
    Should Pluto Get Its Planethood Back?

    Results (355 votes). Check out past polls.