Hi all users of Elastic::Model
Elasticsearch 2.0.0 is out, and Elastic::Model doesn't support it. In fact, Elastic::Model doesn't support a number of things from Elasticsearch 1.x either. I apologise for neglecting this module.
My feeling is that Elastic::Model tries to do way too much. Like many frameworks, it ties you into doing things in a particular way, which may or may not make sense for your use case. Most people who use Elastic::Model seem to use a subset of the functionality, and then talk to Elasticsearch directly the rest of the time.
I don't think it makes sense to just update the code for 2.x, it needs a complete rethink.
Please could you add comments to this issue explaining what bits you find useful, what bits you never use, and what bits you find annoying. Perhaps the code can be split out into smaller more useful chunks.
You need a recent version of Java installed, then download the current stable release of Elasticsearch from http://www.Elasticsearch.org/download/. For instance:
curl -L -O https://github.com/downloads/elasticsearch/elasticsearch/elasticsearch-1.6.0.tar.gz
tar -xzf elasticsearch-1.7.3.tar.gz
Note: This version of Elastic::Model is intended for Elasticsearch 1.x . However, it can be used with Elasticsearch 0.90.x in "compatibility mode". See Elastic::Manual::Delta for instructions.
Use your favourite CPAN installer:
cpanm Elastic::Model
See "TEST SUITE" in Elastic::Manual for how to run the full test suite against a local Elasticsearch cluster.
cd elasticsearch-1.6.0/
./bin/elasticsearch # -d starts the server in the background
You now have a running Elasticsearch cluster with one node. You can test that it is running with:
curl http://localhost:9200/?pretty=true
See http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/setup.html for more information about installing and configuring Elasticsearch.
First set up a simple model. The model handles the relationship between your classes and Elasticsearch.
package MyApp;
use Elastic::Model;
has_namespace 'myapp' => {
user => 'MyApp::User'
};
no Elastic::Model;
1;
Your model must define at least one namespace, which tells Elastic::Model which type (like a table in a DB) should be handled by which of your classes. So the above declaration says:
"For all indices which belong to namespace myapp
, objects of class MyApp::User
will be stored under the type user
in Elasticsearch."
package MyApp::User;
use Elastic::Doc;
use DateTime();
has 'name' => (
is => 'rw',
isa => 'Str',
);
has 'email' => (
is => 'rw',
isa => 'Str',
);
has 'created' => (
is => 'ro',
isa => 'DateTime',
default => sub { DateTime->now }
);
no Elastic::Doc;
1;
This simple Moose class only changes "use Moose;
" to "use Elastic::Doc;
". At the moment we're not configuring anything else. Thanks to Moose's introspection, we have enough information to setup everything we need.
#!/bin/env perl
use strict;
use warnings;
use MyApp();
my $model = MyApp->new();
This creates an instance of your model, with a connection to a local Elasticsearch cluster. The last line is the equivalent of:
use Search::Elasticsearch();
my $es = Search::Elasticsearch->new(
client => '1_0::Direct',
nodes => 'localhost:9200'
);
my $model = MyApp->new( es => $es );
Before we get started, we need to create an index (like a database in a relational DB) in Elasticsearch.
$model->namespace('myapp')->index->create();
This has created index myapp
, which contains type user
(where a type is like a table in a database). It has also configured the type's mapping (which is like the schema or column definition).
Our index is now ready to use.
Before we can save or retrieve objects/documents from Elasticsearch, we need a domain
:
A domain is like a database handle. It allows us to talk to a particular index or alias. (An alias is like a shortcut which points at one or more indices.)
$domain = $model->domain('myapp');
See Elastic::Manual::Scaling for more about how to use aliases.
Normally, you would create an object with:
my $user = MyApp::User->new(...)
but to use all of the magic of Elastic::Model, you must create your object via the $domain
object:
my $user = $domain->new_doc(
user => { # $type => \%args_to_new
id => 1, # auto-generated if not provided
name => 'Clinton',
email => 'clint@domain.com',
}
);
$user->save; # save to Elasticsearch
Now, we can retrieve the user object from Elasticsearch, using the type
and id
:
$user = $domain->get( user => 1 );
say $user->name;
# Clinton
$user->email( 'clinton@domain.com' );
Elastic::Model keeps track of what attributes have been changed, plus their original value:
say $user->has_changed;
# 1
say $user->has_changed('email');
# 1
dump $user->old_values;
# { email => 'clint@domain.com' };
The UID (unique ID) of the object tracks (amongst other things) the current version number. Elasticsearch uses this version number to avoid overwriting changes that have been made by another process (see Optimistic Currency Control).
say $user->uid->version;
# 1
The version number is incremented each time a changed object is saved.
$user->save;
say $user->uid->version;
# 2
say $user->has_changed;
# 0
By default, everything in Elasticsearch is indexed and searchable. You can search across one index or many indices, one type or many types.
In order to query the objects stored in Elasticsearch, you need a view. Views are reusable, so you might create views like $recent_users
, $approved_comments
etc.
You can create a view
from your $domain
object, in which case the view will be limited to just that domain:
$view = $domain->view; # limited to index 'myapp';
To create a view which queries multiple domains, you could do:
$view = $model->view->domain(['index_1', 'alias_2']);
Or to query all domains known to your model:
$view = $model->view;
When setting an attribute on a view, a cloned instance of the old view is returned, meaning that you can use one view to derive another:
$all = $domain->view; # all types in $domain
$users = $all->type('user'); # type 'user' in index $domain
$clint = $users->queryb({name => 'clinton'}); # users whose name is 'clinton'
Queries can be specified using the standard Elasticsearch query DSL or with the more Perlish more compact ElasticSearch::SearchBuilder syntax.
Standard query DSL:
$search = $view->query( { text => { name => 'clinton' }})
->filter({ range => { created => { gt => '2012-01-01' }}});
SearchBuilder syntax:
$search = $view->queryb( { name => 'clinton' })
->filterb( { created => { gt => '2012-01-01' }});
Once you have defined your view, you call a search method (eg search()) which performs the search and returns a Results object.
my $results = $search->search;
say "Total results found: ".$results->total;
while (my $doc = $results->next_doc) {
say $doc->name
}
Views can also be used to return highlighted results, and aggregations, which provide aggregated results, much like GROUP-BY functions in SQL, for instance, the most popular terms, or the number of posting per day.