Search code examples
perlelasticsearchmojolicious

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?


Solution

  • 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.