Cleaner way of declaring many classes in perl - perl

I´m working in a code which follows some steps, and each of this steps is done in one class. Right now my code looks like this:
use step_1_class;
use step_2_class;
use step_3_class;
use step_4_class;
use step_5_class;
use step_6_class;
use step_7_class;
...
use step_n_class;
my $o_step_1 = step_1_class->new(#args1);
my $o_step_2 = step_2_class->new(#args2);
my $o_step_3 = step_3_class->new(#args3);
my $o_step_4 = step_4_class->new(#args4,$args_4_1);
my $o_step_5 = step_5_class->new(#args5);
my $o_step_6 = step_6_class->new(#args6,$args_6_1);
my $o_step_7 = step_7_class->new(#args7);
...
my $o_step_n = step_n_class->new(#argsn);
Is there a cleaner way of declaring this somewhat similar classes wihtout using hundreds of lines?

Your use classes as written are equivalent to
BEGIN {
require step_1_class;
step_1_class->import() if step_1_class->can('import');
require step_2_class;
step_2_class->import() if step_2_class->can('import');
...
}
This can be rewritten as
BEGIN {
foreach my $i ( 1 .. $max_class ) {
eval "require step_${i}_class";
"step_${i}_class"->import() if "step_${i}_class"->can('import');
}
}
The new statements are a little more complex as you have separate variables and differing parameters, however this can be worked around by storing all the objects in an array and also preprocessing the parameters like so
my #steps;
my #parameters = ( undef, \#args1, \#args2, \#args3, [ #args4, $args_4_1], ...);
for ($i = 1; $i <= $max_class; $i++) {
push #steps, "step_${i}_class"->new(#{$parameters[$i]});
}

You can generate the use clauses in a Makefile. Generating the construction of the object will be more tricky as the arguments aren't uniform - but you can e.g. save the exceptions to a hash. This will make deployment more complex and searching the code tricky.
It might be wiser to rename each step by its purpose, and group the steps together logically to form a hierarchy instead of a plain sequence of steps.

Related

Is there a "function-like-thing" that can act as a template?

I have a code like this that is repeated multiple times in each of my conditional statements/cases. i have 3 conditions...for now, and everything works perfectly, but im mulling reformatting the script for easier reading.
One of the ways ive thought is to make a function, but the problem is that, i have a while loop that is intended for a specific scenario in each conditional statement that dequeues from a Queue containing some column names from a file.
so based on the code below that i want to put in some sort of template, i cant think of how this could work because as you can see, $tb stands for $table, which is what im opening prior to the conditional statements in my code.
if i were to include everything regarding the server connection and table in a function, that means when i pass the "function" containing the code to the while loops, it will be creating/instantiating the table every iteration, which wont make sense and wont work anyways.
so i am thinking of using something like annotations, something like a template which wont expect to return anything or need reasonable arguments like a function otherwise would. The question is, does something like that exist?
This is the code that is the same across all my while loops that i would like to "store" somewhere and just pass it to them:
$dqHeader = $csvFileHeadersQueue.Dequeue()
$column = New-Object Microsoft.SqlServer.Management.Smo.Column($tb, $dqHeader, $DataType1)
if ($dqHeader -in $PrimaryKeys)
{
# We require a primary key.
$column.Nullable = $false
#$column.Identity = $true #not needed with VarChar
#$column.IdentitySeed = 1 #not needed with VarChar
$tb.Columns.Add($column)
$primaryKey = New-Object Microsoft.SqlServer.Management.Smo.Index($tb, "PK_$csvFileBaseName")
$primaryKey.IndexType = [Microsoft.SqlServer.Management.Smo.IndexType]::ClusteredIndex
$primaryKey.IndexKeyType = [Microsoft.SqlServer.Management.Smo.IndexKeyType]::DriPrimaryKey #Referential Integrity to prevent data inconsistency. Changes in primary keys must be updated in foreign keys.
$primaryKey.IndexedColumns.Add((New-Object Microsoft.SqlServer.Management.Smo.IndexedColumn($primaryKey, $dqHeader)))
$tb.Indexes.Add($primaryKey)
}
else
{
$tb.Columns.Add($column)
}
think of it like a puzzle piece that would fit right in when requested to do so in the while loops to complete that "puzzle"
As per comment:
you can share a (hardcoded) [ScriptBlock] ($template = {code in post goes here}) with a While loop (or function) and invoke it with e.g. Invoke-Command $template or the call operator: &$template. Dynamically modifying an expression and using commands like Invoke-Expression or [ScriptBlock]::Create() is not a good idea due to risk of malicious code injections (see: #1454).
You might even add parameters to your shared [ScriptBlock], like:
$Template = {
[CmdletBinding()]Param ($DataType)
$column = New-Object Microsoft.SqlServer.Management.Smo.Column($tb, $dqHeader, $DataType)
...
}
ForEach ($MyDataType in #('MyDataType')) {
Invoke-Command $Template -ArgumentList $MyDataType
}
But the counter-question remains: Why not just creating a "helper" function?:
Function template($DataType) {
$column = New-Object Microsoft.SqlServer.Management.Smo.Column($tb, $dqHeader, $DataType)
...
}
ForEach ($MyDataType in #('MyDataType')) {
template $MyDataType
}

How do you only import `for` from `Perl6::Controls`?

Test case:
use 5.026;
use Perl6::Controls qw(for);
for (1..10) -> $n {
say $n;
}
loop {};
Expect:
Can't call method "loop" without a package or object reference
Got:
infinite loop
use Perl6::Controls qw(for);
BEGIN {
delete $^H{'Keyword::Simple/keywords'}{"loop"};
}
...
which I stumbled into running your script through B::Deparse.
To pick and choose the keywords you want to keep, you could say
use Perl6::Controls;
BEGIN {
my #keep = ...; # e.g. #keep = qw(for);
my %keywords;
#keywords{#keep} = #{$^H{'Keyword::Simple/keywords'}}{#keep};
$^H{'Keyword::Simple/keywords'} = \%keywords;
}
You can't. Looking at the source-code for Perl6::Controls it uses it's own import method to define all the new keywords using Keyword::Declare. It ignores any parameters passed on the use line.

Convert a DBIx::Class::Result into a hash

Using DBIx::Class, I found a solution to my issue, thankfully. But I'm sure there has to be a nicer way.
my $record = $schema->resultset("food")->create({name=>"bacon"});
How would I turn this record into a simple hashref instead of having to make this call right after.
my record = $schema->resultset("food")->search({name=>"bacon"})->hashref_array();
Ideally I want to be able to write a code snippet as simple as
{record=> $record}
instead of
{record => {name => $record->name, $record->food_id, ...}}
This would drive me insane with a table that has alot more columns.
I assume you're talking about DBIx::Class?
my $record = $schema->resultset("food")->create({name=>"bacon"});
my %record_columns = $record->get_columns;
# or, to get a HashRef directly
my $cols = { $record->get_columns };
# or, as you've asked for
my $foo = { record => { $record->get_columns } };
What you're looking for is included in DBIx::Class as DBIx::Class::ResultClass::HashRefInflator.

how to copy(insert) hash reference to another hash reference in perl?

recently started doing perl. I'm reading old code and trying to rewrite some stuff. I've got a question here about hash references.
#declar anon hash ref that will be returned
my $store_hash = {};
foreach my $item (#list)
{
#this will iterate based on list
my $ret_hash = ops_getval($item, $user)
#do some magic here to map $ret_hash into $store_hash
}
ops_getval is a function that returns a type of ref hash. I want to insert those values into $store_hash. How should I approach this? Can I directly do
$store_hash = ops_getval($var1,$var2)
Much appreciated!
I think the standard way to do this is:
#$store_hash{ keys %$ret_hash } = values %$ret_hash;
This merges all of the hashes returned by all of the calls to ops_getval into $store_hash.
An alternate approach that might be clearer to the eye, possibly at the cost of a lot of redundant data copying:
%$store_hash = (%$store_hash, %$ret_hash);
You would do something like:
$store_hash->{$item} = $ret_hash
In general:
$hashref->{$key} = $value
See here for more: http://perldoc.perl.org/perlref.html#Using-References
To be clear, you can use a loop and get this done.
foreach ( keys%{ $ret_hash } ){
$store_hash->{ $_ } = $ret_hash->{ $_ } ;
}

How do I merge multiple FLV files using FLV::Splice module?

If I have an array that contains multiple FLV files like so:
my #src_files = qw (1.flv 2.flv 3.flv 4.flv 5.flv 6.flv 7.flv);
I can call flvbind.exe as an external commandline program to do the merging like so:
my $args = join(' ',#src_files);
my $dest_file = 'merged.flv';
system "flvbind $dest_file $args";
But the usage example for FLV::Splice is this:
use FLV::Splic;
my $converter = FLV::Splice->new();
$converter->add_input('first.flv');
$converter->add_input('second.flv');
$converter->save('output.flv');
I seem to be unable to figure out how to do the same thing like the flvbind does.
Do I have to verbosely add every param like so:
use FLV::Splice;
my $dest_file = 'merged.flv';
my $converter = FLV::Splice->new();
$converter->add_input('1.flv');
$converter->add_input('2.flv');
$converter->add_input('3.flv');
$converter->add_input('4.flv');
$converter->add_input('5.flv');
$converter->add_input('6.flv');
$converter->add_input('7.flv');
$converter->save("$dest_file");
This does not look like the correct way. I mean do I have to verbosely add every param? Is there a way to simplify the repeated use of the add_input method? Any pointers? Thanks like always :)
UPDATE:
It turns out that this is a silly question. Thanks, #Eric for giving me the correct answer. I thought of using a for loop to reduce the repeated use of the add_input method but somehow I thought it would not work and I thought I was stuck. Well, I'll be reminding myself not to easily bother other people next time.
It's fairly easy to reduce the repetition:
$converter->add_input($_) for #src_files;
Or wrap the whole thing in a subroutine:
sub flv_splice {
my $dest_file = shift;
my $converter = FLV::Splice->new();
$converter->add_input($_) for #_;
$converter->save($dest_file);
}
flv_splice 'merged.flv', #src_files;