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

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;
}

Related

DBIx::something that can restore session variables on reconnection?

Ordinary DBI::db handler will lost all database session settings that was made using $dbh->do('SET variable_name=value').
Is there any DBIx::* class/package or so that provides method like "set_session" to set session variables and can restore this variables after detection of connection lost (connection timeout in 90% of real cases) ?
It may looks like this:
# inside the user code:
$dbh->set(variable => 'string', yet_another_variable => 42)
# inside the DBIx::* package:
sub reconnect {
# ...
while (my ($var, $val) = each %{$self->saved_vars}) {
$self->dbh->do("SET $var=?", {}, $val)
}
# ...
}
DBI supports something called Callbacks. I can't link to this bit of the doc as the section is quite long, so here it is verbatim.
A more common application for callbacks is setting connection state
only when a new connection is made (by connect() or connect_cached()).
Adding a callback to the connected method (when using connect) or via
connect_cached.connected (when useing connect_cached()>) makes this
easy. The connected() method is a no-op by default (unless you
subclass the DBI and change it). The DBI calls it to indicate that a
new connection has been made and the connection attributes have all
been set. You can give it a bit of added functionality by applying a
callback to it. For example, to make sure that MySQL understands your
application's ANSI-compliant SQL, set it up like so:
my $dbh = DBI->connect($dsn, $username, $auth, {
Callbacks => {
connected => sub {
shift->do(q{
SET SESSION sql_mode='ansi,strict_trans_tables,no_auto_value_on_zero';
});
return;
},
}
});
This is your exact use-case I believe. Do this instead of running your own code after you've connected.

Perl Mojolicious Model

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

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.

Managing session state with html::mason

I'm using HTML::Mason with Apache2 mod_perl2 for a project and am unsure what's a good way to manage session state easily.
Please don't say use Catalyst. I normally do, but not on this occasion.
After struggling with this for a long time I finally have a working solution:
This is using mysql to store session data so that no matter which front-end server you hit, you will get the same session data.
You will need a db with a table called sessions made with this:
CREATE TABLE sessions (id char(32), length int, a_session text);
This is in MySession.pm in my INC path.
package MySession;
use DBI();
use Apache::Session::MySQL;
use Apache2::Cookie;
sub start_session($){
my ($r) = #_;
my $cookie_name = 'mysite-session';
my $cookie_domain = '.mysite.com';
my $dsn = "DBI:mysql:database=db;host=host.com";
my $dbuser = 'admin';
my $dbpass = 'password';
my $dbh = DBI->connect($dsn, $dbuser, $dbpass, {'RaiseError' => 1});
my $session_cookie = Apache2::Cookie->fetch($r)->{$cookie_name};
my %cookie_hash;
if(defined($session_cookie)){
%cookie_hash = $session_cookie->value();
}
tie my %session, 'Apache::Session::MySQL', $cookie_hash{SessionID}, {
Handle => $dbh,
LockHandle => $dbh
};
my $cookie = Apache2::Cookie->new($r,
-name => $cookie_name,
-domain => $cookie_domain,
-value => {SessionID => $session{_session_id}}
);
$cookie->bake($r);
return \%session;
}
1;
Then on any page you wish to use/modify session data:
% use MySession;
% my $session = MySession::start_session($r);
% $session->{variable} = "Wow, I have a cookie";
Looks like I've found the answer in
MasonX::Request::WithApacheSession