Perl Mojolicious Model - perl

I am creating a
Mojolicious
application but I can't understand the documentation for creating a model.
Could someone provide an example of how to create a model, run a raw SQL query and get the results in a controller to pass to the view?
I am thinking of something like this:
Model
package LS::Model::Dt;
use Mojo::Base;
use DBI;
# Here is what I don't understand
# Do I need to create a subroutine that connects to the database like this?
sub connect_db {
my $user = 'user_sql';
my $pass = 'pass_sql';
my $connection = "dbi:Sybase:server=db.sql-srv.com;database=Adventure";
my $dbh = DBI->connect($connection, $user, $pass) or die 'Cannot connect';
}
sub queries{
my $query_selectall = "select * from foo";
my $all_query = $dbh->selectall_arrayref($query_selectall, {Slice => {}});
}
Controller
package LS::Controller::Home;
use Mojo::Base 'Mojolicious::Controller';
use LS::Model::Dt
sub home {
my $self = shift;
# Somehow get the query results here
my $query_res = #somehow get the query results here
$self->render(res=>$query_res);
}
1;

Later edit: I have managed somehow via this tutorial:
http://oliverguenther.de/2014/04/applications-with-mojolicious-part-four-database-schemas-with-dbixclass/
Github code for when you are now sure where the author is creating a file is available here:
https://github.com/oliverguenther/Moblo
If you encounter problems also check this:
Mojolicious Deploying database schema

Related

How can I 'refresh' a 'helper' function in Mojolicious?

Sometimes I need to refresh the "helper" functions.
An example:
if the the database connection function is in a helper function, and the database access changed, and I store this access data in a sqlite database, and I overwrite this data (in the sqlite database), how can I refresh in this situation the helper function?
I think you need to query the sqlite database for changes every time you connect or reuse the handle to your main database. If you notice a change in sqlite, you need to disconnect from the main, pull new credentials and reconnect to the main using new credentials.
$app->helper(
db => sub {
my $self = shift;
my $credentials_changed = your_function_to_check_this(...);
if($credentials_changed){
$dbh->disconnect if $dbh;
return db_connect(
$credentials->{db_host}, $credentials->{db_user},
$credentials->{db_database}, $credentials->{db_pass}
);
}
);
sub db_connect {
my ($db_host, $db_user, $db_database, $db_pass) = #_;
my $dbh = 0;
$dbh = DBI->connect("DBI:mysql:database=$db_database;host=$db_host", "$db_user", "$db_pass", {'RaiseError' => 1});
$dbh->{mysql_auto_reconnect} = 1;
return $dbh;
}

How to deploy a test database for DBIx Class

I have Mojolicious app with a test suite using Test::Class::Moose. Using DBIx::Class to interact with my database, is there a way to setup an in-memory database that I can add fixture data to?
I'd like to use an in memory database because it'll mean that the application will have less setup configuration. I do know how to setup an actual SQLite database for testing but managing that for testing along with a mySQL database for production doesn't sound like easy management (eg "oh no, forgot to rebuild the testing database").
Loading data from fixtures seems ideal, that way you have more control over what is actually in the database. Example, you find a bug with a row that contains certain data, add a row like that to your fixture file and test until successful.
Now how to actually set up an in-memory database using DBIx? :)
Right now this is how I'm building the schema for my Test::Class::Moose suite:
has cats => (
is => 'ro',
isa => 'Test::Mojo::Cats',
default => sub { return Test::Mojo::Cats->new(); }
);
has schema => (
is => 'ro',
lazy => 1,
builder => '_build_schema_and_populate',
);
sub _build_schema_and_populate {
my $test = shift;
my $config = $test->cats->app->config();
my $schema = Cat::Database::Schema->connect(
$config->{db_dsn},
$config->{db_user},
$config->{db_pass},
{
HandleError => DBIx::Error->HandleError,
unsafe => 1
}
);
require DBIx::Class::DeploymentHandler;
my $dh = DBIx::Class::DeploymentHandler->new({
schema => $schema,
sql_translator_args => { add_drop_table => 0 },
schema_version => 3,
});
$dh->prepare_install;
$dh->install;
my $json = read_file $config->{fixture_file};
my $fixtures = JSON::decode_json($json);
$schema->resultset($_)->populate($fixtures->{$_}) for keys %{$fixtures};
return $schema;
}
Where my config specifies dbi:SQLite:dbname=:memory: as the database dsn.
When running the test suite, the tables don't seem to be loaded, as I get errors stating the table does not exist, eg Can't locate object method "id" via package "no such table: cats"
Is there some extra setup that I'm not doing when wanting to deploy to an in-memory database?
Thanks
PS:
Doing the following works in a single script, I don't know if I'm doing something that Test::Class::Moose or Mojo doesn't like with the above
#!/usr/bin/perl
use Cats::Database::Schema;
use File::Slurp;
use JSON;
use Data::Dumper;
my $schema = Cats::Database::Schema->connect(
'dbi:SQLite:dbname=:memory:', '', ''
);
my $json = read_file('../t/fixtures.json');
my $fixtures = JSON::decode_json($json);
$schema->deploy();
$schema->resultset($_)->populate($fixtures->{$_}) for keys %{$fixtures};
# returns fixture data fine
# warn Dumper($schema->resultset('User')->search({}));
I believe I figured it out
The way I use the DBIx schema in the app is to instantiate it within a base controller which all sub controllers inherit. No matter how I built and populated the in memory database in the Test::Class::Moose object, it would not be using the instance specified there, instead it would be using the one specified in the base controller.
the solution was to move the schema construction up one level (from controller to the app root) as an attribute, allowing me to override it in Test Mojo to use the in memory db.

mojolicious helper storing an elasticsearch connection

i'm experimenting with elasticsearch within mojolicious.
I'm reasonably new at both.
I wanted to create a helper to store the ES connection and I was hoping to pass the helper configuration relating to ES (for example the node info, trace_on file etc).
If I write the following very simple helper, it works;
has elasticsearch => sub {
return Search::Elasticsearch->new( nodes => '192.168.56.21:9200', trace_to => ['File','/tmp/elasticsearch.log'] );
};
and then in startup
$self->helper(es => sub { $self->app->elasticsearch() });
however if I try to extend that to take config - like the following -
it fails. I get an error "cannot find index on package" when the application calls $self->es->index
has elasticsearch => sub {
my $config = shift;
my $params->{nodes} = '192.168.56.21:' . $config->{port};
$params->{trace_to} = $config->{trace_to} if $config->{trace_to};
my $es = Search::Elasticsearch->new( $params );
return $es;
};
and in startup
$self->helper(es => sub { $self->app->elasticsearch($self->config->{es}) });
I assume I'm simply misunderstanding helpers or config or both - can someone enlighten me?
Just fyi, in a separate controller file I use the helper as follows;
$self->es->index(
index => $self->_create_index_name($index),
type => 'crawl_data',
id => $esid,
body => {
content => encode_json $data,
}
);
that works fine if I create the helper using the simple (1st) form above.
I hope this is sufficient info? please let me know if anything else is required?
First of all, has and helper are not the same. has is a lazily built instance attribute. The only argument to an attribute constructor is the instance. For an app, it would look like:
package MyApp;
has elasticsearch => sub {
my $app = shift;
Search::ElasticSearch->new($app->config->{es});
};
sub startup {
my $app = shift;
...
}
This instance is then persistent for the life of the application after first use. I'm not sure if S::ES has any reconnect-on-drop logic, so you might need to think about it a permanent object is really what you want.
In contrast a helper is just a method, available to the app, all controllers and all templates (in the latter case, as a function). The first argument to a helper is a controller instance, whether the current one or a new one, depending on context. Therefore you need to build your helper like:
has (elasticsearch => sub {
my ($c, $config) = #_;
$config ||= $c->app->config->{es};
Search::ElasticSearch->new($config);
});
This mechanism will build the instance on demand and can accept pass-in arguments, perhaps for optional configuration override as I have shown in that example.
I hope this answers your questions.

Route to static file in Mojo

I have small app based on mojolicious. And I have index.html in public dir. I want to have route to this file when user asks for '/'.
I wrote two solution, but I don't like them.
First solution - add simple controller.
sub stratup {
//...
$r->get('/')->to('general#index_html');
//...
}
package MyPackage::General;
use Mojo::Base 'Mojolicious::Controller';
use strict;
use warnings;
sub index_html {
my $self = shift;
$self->render_static('index.html');
return;
}
1;
Second solution - add hook
sub startup {
my $self = shift;
$self->hook(before_dispatch => sub {
my $self = shift;
if ($self->req->url eq '/') {
$self->req->url( Mojo::URL->new('/index.html') );
}
});
What I want:
$r->get('/')->to('/index.html');
or something like that.
P.S. I know, than usualy nginx/apache do it, but I use morbo to run code.
You want:
$r->get('...')->to(cb => sub {
my $c = shift;
$c->reply->static('index.html')
});
(As long as you're after Mojolicous 5.45 2014-09-26)
By far the simplest way is
get "/" => "index";
I'll dig this up from the graveyard, why not.
I found myself similarly trying to serve a static html file in a docker container that I had using to serve both a Mojolicious REST API and a Vue.js front end. After searching around and piecing sporadic information together, this is what seems to work for me.
** disclaimer: I have not fully tested this with Vue routing and other aspects as yet.
My directory structure:
/app
/app/script
/app/modules/ui
/app/modules/ui/dist
From the command line the app directory, using morbo to test:
morbo script/ui.pl
ui.pl script
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
use Mojo::File qw(curfile);
use v5.25;
my $app = app;
my $static = $app->static;
push #{$static->paths}, curfile->dirname->sibling('modules/ui/dist')->to_string;
any '/' => sub {
my $c = shift;
my $content = $static->file("/index.html")->slurp;
$c->render(text => $content);
};
$app->start;
Using a combo of information from https://metacpan.org/pod/Mojolicious::Static and basic routing information at https://docs.mojolicious.org/Mojolicious/Lite, I could get the vue.js index page to render as expected.
** UPDATED A DAY LATER **
As it turns out, there is an easier way, though not clearly documented. If you place the static files inside your public folder, you can use the default helpers included with Mojolicious to render the files. The documentation refers to it here, https://docs.mojolicious.org/Mojolicious/Guides/Rendering#Serving-static-files, but it's not very clear on how to make it happen.
I tooled around some, but it took browsing the code of Controller.pm of for Mojolicious to sort it out. This section of the POD led me to determine how to get the reply object:
=head2 helpers
my $helpers = $c->helpers;
Return a proxy object containing the current controller object and on which helpers provided by /app can be called. This includes all helpers from Mojolicious::Plugin::DefaultHelpers and Mojolicious::Plugin::TagHelpers.
# Make sure to use the "title" helper and not the controller method
$c->helpers->title('Welcome!');
# Use a nested helper instead of the "reply" controller method
$c->helpers->reply->not_found;
Based on this, I can drop my files into the public folder:
/app/public/index.html
Then modify my controller to match:
# https://docs.mojolicious.org/Mojolicious/Guides/Rendering#Serving-static-files
any '/' => sub {
my $c = shift;
$c->helpers->reply->static('index.html');
};

Perl Net::SNMP returns noSuchName when snmpwalk works

I am new to perl, but I am trying to write a plug-in for nagios. I have a simple get request that fails, but if I try the same request with snmpwalk it works.
My code is:
#!/usr/bin/perl -w
use strict;
use Net::SNMP;
my $host = '10.10.10.203';
my $community = 'myComm';
my $session;
my $error;
my $response = undef;
($session, $error) = Net::SNMP->session(
-hostname => $host,
-version => 2,
-community =>$community,
-port => 161,
-timeout => 20
);
my $uptimeOID = '1.3.6.1.2.1.1.3.0';
my $myOID = '1.3.6.1.4.1.7933';
if( !defined( $response = $session->get_request($myOID)))
{
if( $session->error_status == 2)
{
my $sessionError = $session->error;
print ("($sessionError) OID not supported ($myOID).\n");
}
}
else
{
print ("$response");
}
If I run this script it will fail saying noSuchName, but if a run:
snmpwalk -v 2c -c myComm 10.10.10.203 1.3.6.1.4.1.7933
I get the response I want. Does anybody know why this wont work?
If I check the uptime OID with this script it will work the way it should.
You've already identified that via the command-line you're doing a "walk" rather than a "get". If there's a specific value you want to "get" in your script, put in the full OID identifying the target.
There's something in a table record that you probably want to get at (and it seems like everything in FASTTRAKIDERAID-MIB is in fact tabular), so a simple get isn't enough. Look at the snmpwalk.pl script that comes with Net::SNMP or see if SNMP::Util can easily provide the functionality you're looking for.
Use get_next_request, not get_request. It will return the first valid oid after the one you pass it.
I found my problem. When I use snmpwalk, it will grab the whole tree and return a value. The perl module will not. It doesn't traverse the tree to the end even thought there is only one thing below it, it just says no.