package Elastic::Manual::Attributes;

# ABSTRACT: Fine-tuning how your attributes are indexed



__END__
=pod

=head1 NAME

Elastic::Manual::Attributes - Fine-tuning how your attributes are indexed

=head1 VERSION

version 0.20

=head1 SYNOPSIS

    package MyApp::User;

    use Elastic::Doc;

    has 'email' => (
        isa       => 'Str',
        is        => 'rw',
        analyzer  => 'english',
        multi => {
            untouched => { index    => 'not_analyzed' },
            ngrams    => { analyzer => 'edge_ngrams' }
        }
    );

    no Elastic::Doc;

=head1 INTRODUCTION

Elastic::Model uses Moose's type constraints to figure out how each of the
attributes in your classes should be indexed. This is a good start, but
it won't be long before you want to fine tune the indexing process.

For instance, if you have attribute C<title> which C<< isa => 'Str' >>, then
L<Elastic::Model::TypeMap::Moose> will
L<map|Elastic::Manual::Terminology/Mapping> the attribute as a C<string>,
whose value will be L<analyzed|Elastic::Manual::Terminology/Analysis> by the
L<standard analyzer|http://www.elasticsearch.org/guide/reference/index-modules/analysis/standard-analyzer.html>.

But perhaps you want the words in your C<title> to be stemmed
(eg "fox" will match "fox", "foxes" or "foxy"), or perhaps your title is in
Spanish and you want Spanish stemming rules to apply.
Or perhaps you want to use as-you-type auto-completion on the C<title> attribute.

This fine-tuning is easy to do, using the attribute keywords that are added
to your attributes when you include the line C< use Elastic::Doc; > in your
class.  These keywords come from L<Elastic::Model::Trait::Field>.

=head1 TYPES

Each attribute/field must have one of the following L<types|/type>:

=over

=item *

L<string|/STRING FIELDS>

=item *

L<integer, long, float, double, short, byte|/NUMERIC FIELDS>

=item *

L<date|/DATE FIELDS>

=item *

L<boolean|/BOOLEAN FIELDS>

=item *

L<binary|/BINARY FIELDS>

=item *

L<ip|/IP FIELDS> (IPv4 addresses)

=item *

L<geo_point|/GEO_POINT FIELDS>

=item *

L<object|/OBJECT FIELDS>

=item *

L<nested|/NESTED FIELDS> (a specialized form of C<object>)

=item *

L<multi_field|/multi> (index the same field in different ways)

=back

=head2 type

The C<type> keyword allows you to override the default C<type> that is
generated by the L<typemap|Elastic::Model::TypeMap::Default>. For instance:

    has 'epoch_milliseconds' => (
        isa     => 'Int',
        type    => 'date'
    );

Although the type constraint C<Int> would normally generate type C<long>,
we have, overridden the C<type> to be indexed as a C<date> field.

=head2 Arrays

A notable omission from the above list is C<array>.  That's because a field of
any type can store and index multiple values.  For example:

    has 'author'  => ( isa => 'Str'           );
    has 'authors' => ( isa => 'ArrayRef[Str]' );

Both of these fields would be mapped as C<< { type => 'string' } >>.

B<Note:> the values contained in an array must be of a single type in order to
be indexable. You can't mix types in an array.

Also see L</NESTED FIELDS> for a more detailed explanation, specifically about
how to deal with arrays of objects.

=head2 Unknown types

If the L<typemap|Elastic::Model::TypeMap::Default> does not know how to
deal with the type constraint on an attribute, and you haven't specified
a L</type>, then the field is mapped as:

    {
        type    => 'object',
        enabled => 0
    }

This means that the value in the field will be stored in ElasticSearch, but
will not be queryable.  Also, no attempt is made to inflate or deflate the
value - it is just passed through unchanged.  If it is a value that
L<JSON::XS> cannot handle natively, (eg a blessed ref), then Elastic::Model
will be unable to deal with it automatically.

See L</CUSTOM MAPPING, INFLATION AND DEFLATION> for details of how to
handle this situation.

=head1 GENERAL KEYWORDS

There are a number of keywords that apply to (almost) all of the field types
listed below:

=head2 exclude

    has 'cache_key' => (
        is      => 'ro',
        exclude => 1,
        builder => '_generate_cache_key',
        lazy    => 1,
    );

If C<exclude> is true then the attribute will not be stored in ElasticSearch.
This is only useful for generated or temporary attributes. If you want
to store the attribute but make it not searchable, then you should use
the L</"index"> keyword instead.

=head2 index

    has 'tag' => (
        is      => 'rw',
        isa     => 'Str',
        index   => 'not_analyzed'
    );

The C<index> keyword controls how ElasticSearch will index your attribute.  It
accepts 3 values:

=over

=item *

C<no>: This attribute will not be indexed, and will thus not be searchable.

=item *

C<not_analyzed>: This attribute will be indexed using exactly the value
that you pass in, eg C<FoO> will be stored (and searchable) as C<FoO>.
Also see L<Elastic::Model::TypeMap::ES/Keyword>.

=item *

C<analyzed>:    This attribute will be
L<analyzed|Elastic::Manual::Terminology/Analysis>. In other
words, the text value will be passed through the specified (or default)
L</"analyzer"> before being indexed. The analyzer will tokenize and pre-process
the text to produce L<terms|Elastic::Manual::Terminology/Term>.
For example C<FoO BAR> would (depending on the analyzer) be stored as the
terms C<foo> and C<bar>. This is the default for string fields (except for enums).

=back

=head2 include_in_all

    has 'secret' => (
        is              => 'ro',
        isa             => 'Str',
        include_in_all  => 0
    );

By default, all attributes (except those with C<< index => 'no' >>) are also
indexed in the special C<_all> field. This is intended to make it easy
to search for documents that contain a value in any field.  If you would
like to exclude a particular attribute from the C<_all> field, then specify
C<< { include_in_all => 0 } >>.

B<Note:>

=over

=item *

The C<_all> field has its own L</"analyzer"> - so the tokens that
are stored in the C<_all> field may be different from the tokens stored in
the attribute itself.

=item *

When C<include_in_all> is set on a field of type C<object>, its
value will propagate down to all attributes within the object.

=item *

You can disable the C<_all> field completely using L<Elastic::Doc/has_mapping>:

    package MyApp::User;

    use Elastic::Doc;

    has_mapping {
        _all    => { enabled => 0 }
    };

=back

=head2 boost

    has 'title' => (
        is      => 'rw',
        isa     => 'Str',
        boost   => 2
    );

A C<boost> makes a value "more relevant".  For instance, the words in the
C<title> field of a blog post are probably a better indicator of the topic
than the words in the C<content> field. You can boost a field at
search time and at index time. The benefit of boosting at search time, is that
your C<boost> is not fixed.  The benefit of boosting at C<index> time is
that the C<boost> value is carried over to the C<_all> field.

Also see L</"omit_norms">.

=head2 multi

    has 'name' => (
        is      => 'ro',
        isa     => 'Str',
        multi   => {
            sorting     => { index    => 'not_analyzed' },
            partial     => { analyzer => 'ngrams'       }
        }
    );

It is a common requirement to be able to use a single field in different
ways. For instance, with the C<name> field example above, we may want to:

=over

=item *

Do a full text search for C<Joe Bloggs>

=item *

Do a partial match on all names beginning with C<Blo>

=item *

Sort results alphabetically by name.

=back

A single field definition is insufficient in this case:  The standard analyzer
won't allow partial matching, and because it generates multiple terms/tokens,
it can't be used for sorting. (You can only sort on a single value).

This is where L<multi_fields|http://www.elasticsearch.org/guide/reference/mapping/multi-field-type.html>
are useful.  The same value can be indexed and queried in multiple ways. When
you specify a C<multi> mapping, each "sub-field" inherits the C<type> of the
main field, but you can override this.
The "sub-fields" can be referred to as eg C<name.partial> or C<name.sorting>
and the "main field" C<name> can also be referred to as C<name.name>.

Another benefit of multi-fields is that they can be added without reindexing
all of your data.

=head2 index_name

    has 'foo' => (
        is          => 'rw',
        isa         => 'Str',
        index_name  => 'bar'
    );

ElasticSearch uses dot-notation to refer to nested hashes. For instance, with
this data structure:

    {
        foo => {
            bar => {
                baz => 'xxx'
            }
        }
    }

... you could refer to the C<baz> value as C<baz> or as C<foo.bar.baz>.

Sometimes, you may want to specify a different name for a field.  For instance:

    {
        street => {
            name    => 'Oxford Street',
            number  => 1
        },
        town => {
            name    => 'London'
        }
    }

You can use the C<index_name> to distinguish C<town_name> from C<street_name>.

=head2 unique_key

    has 'email' => (
        is          => 'ro',
        isa         => 'Str',
        unique_key  => 'myapp_email'
    );

The L</unique_key> keyword allows you to ensure that an attribute's value
is unique, eg to ensure that only one user account has a particular email
address.  See L<Elastic::Manual::Attributes::Unique> for a fuller explanation.

=head2 store

    has 'big_field' => (
        is          => 'ro',
        isa         => 'Str',
        store       => 'yes'
    );

Individual fields can be stored (ie have their original value stored on disk).
This is not the same as whether the value is indexed or not (see L</"index">).
It just means that this individual value can be retrieved separately from the
others. C<stored> defaults to C<'no'> but can be set to C<'yes'>.

You almost never need this.  The C<_source> field (which is stored by default)
contains the hashref representing your whole object, and is returned by
default when you get or search for a document.  This means a single disk seek
to load theC<_source> field, rather than a disk seek (think 5ms) for
every stored field! It is much more efficient.

=head2 null_value

    has 'foo' => (
        is          => 'rw',
        isa         => 'Str',
        null_value  => 'none'
    );

If the attribute's value is C<undef> then the C<null_value> will be indexed
instead. This option is included for completeness, but isn't very useful.
Rather just leave the value as C<undef> and use the
L<exists|http://www.elasticsearch.org/guide/reference/query-dsl/exists-filter.html>
and L<missing|http://www.elasticsearch.org/guide/reference/query-dsl/missing-filter.html>
filters when you need to consider C<undef> values.

=head1 STRING FIELDS

String fields fall into two broad categories:

=over

=item *

L<Text|Elastic::Manual::Terminology/Text>, like this paragraph, which
needs to be searchable via flexible
L<full text queries|Elastic::Manual::Terminology/Full Text Query>

=item *

L<Terms|Elastic::Manual::Terminology/Term>, for instance tags, postcodes, enums,
which should be stored and queried exactly (eg C<Perl> is different from C<PERL>)

=back

The simplest way to distinguish between text and terms is to set the
L</index> keyword to C<analyzed> (the default) for text or to
C<not_analyzed> for terms. See L<Elastic::Manual::Analysis> for a more
detailed discussion.

The keywords below apply only to fields of L</type> C<string>. You can also
use L</index>, L</include_in_all>, L</boost>, L</multi>, L</index_name>,
L</unique_key>, L</store> and L</null_value>.

=head2 analyzer

    has 'email' => (
        is          => 'ro',
        isa         => 'Str',
        analyzer    => 'my_email_analyzer'
    );

Specify which analyzer (built-in or custom) to use at index time and at search
time. This is the equivalent of setting L</"index_analyzer"> and
L</"search_analyzer"> to the same value.

Also see L<Elastic::Manual::Analysis> for an explanation.

=head2 index_analyzer

    has 'email' => (
        is              => 'ro',
        isa             => 'Str',
        index_analyzer  => 'my_email_analyzer'
    );

Sets the L</"analyzer"> to use at index time only.

=head2 search_analyzer

    has 'email' => (
        is              => 'ro',
        isa             => 'Str',
        search_analyzer => 'my_email_analyzer'
    );

Sets the L</"analyzer"> to use at search time only.

=head2 search_quote_analyzer

    has 'email' => (
        is                    => 'ro',
        isa                   => 'Str',
        search_analyzer       => 'my_email_analyzer',
        search_quote_analyzer => 'my_quoted_email_analyzer'
    );

Sets the L</"analyzer"> to use in a
L<Query-String query|http://www.elasticsearch.org/guide/reference/query-dsl/query-string-query.html>
or  L<Field query|http://www.elasticsearch.org/guide/reference/query-dsl/field-query.html>
when the search phrase includes quotes (C<"">). If not set, then it falls back
to the L</search_analyzer> or the L</analyzer>.

=head2 omit_norms

    has 'status' => (
        is          => 'ro',
        isa         => 'Str',
        analyzer    => 'keyword',
        omit_norms  => 1
    );

Norms allow for index time L</"boost"> and for field length normalization
(shorter fields score higher).  This may not always be what you want. For
instance, a C<status> field may contain a single value that is never used
for relevance scoring, just for filtering (eg all docs where C<status> is
C<"active">). Or, if the values in a field are short (eg name, email) then
the field length normalization may skew the results incorrectly.

You can turn off norms with C<< { omit_norms => 0 } >>.

See L<http://www.lucidimagination.com/content/scaling-lucene-and-solr#d0e71>
for more about C<omit_norms>.

=head2 omit_term_freq_and_positions

    has 'status' => (
        is                              => 'ro',
        isa                             => 'Str',
        analyzer                        => 'keyword',
        omit_term_freq_and_positions    => 1
    )

ElasticSearch normally stores the frequency and position of each term in
analyzed text. If you don't need this information, then you can turn it off,
and save space.

See L<http://www.lucidimagination.com/content/scaling-lucene-and-solr#d0e63>
for more about C<omit_term_freq_and_positions>.

=head2 term_vector

    has 'text' => (
        is          => 'ro',
        isa         => 'Str',
        store       => 'yes',
        term_vector => 'with_positions_offsets',
    );

The full functionality of term vectors is not exposed via ElasticSearch, so
the only real value for now is for
L<highlighting snippets|/Fast snippet highlighting>. Allowed values are:
C<no> (the default), C<yes>, C<with_offsets>,
C<with_positions> and C<with_positions_offsets>.

=head3 Fast snippet highlighting

There are two highlighters available for
L<highlighting matching snippets|http://www.elasticsearch.org/guide/reference/api/search/highlighting.html>
in text fields: the C<highlighter>, which can be used on any analyzed text
field without any preparation,  and the C<fast-vector-highlighter> which is
faster (better for large text fields which require frequent highlighting),
but uses more disk space, and the field needs to be setup correctly before use:

    has 'big_field' => (
        is          => 'ro',
        isa         => 'Str',
        term_vector => 'with_positions_offsets'
    );

=head1 NUMERIC FIELDS

Numeric fields can have any of the following types:

=over

=item byte

Range: -128 to 127

=item short

Range: -32,768 to 32,767

=item integer

Range: -2,147,483,648 to 2,147,483,647

=item long

Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

=item float

Single-precision 32-bit IEEE 754 floating point

=item double

Double-precision 64-bit IEEE 754 floating point

=back

(See L<http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3>
for more on single or double value ranges.)

The following keyword applies only to numeric fields.

You can also use L</index>, L</include_in_all>, L</boost>, L</multi>,
L</index_name>, L</unique_key>, L</store> and L</null_value>.

=head2 precision_step (numeric)

    has 'count' => (
        isa             => 'Int',
        precision_step  => 2,
    );

The C<precision_step> determines the number of terms generated for each
value (defaults to 4). The more terms, the faster the lookup, but the more
memory used.

=head1 DATE FIELDS

Dates in ElasticSearch are stored internally as C<long> values containing
B<milli>seconds since the epoch. The maximum date range is 292269055 BC to
292278994 AD.

The following keywords apply only to fields of L</"type"> C<date>.
You can also use L</index>, L</include_in_all>, L</boost>, L</multi>,
L</index_name>, L</unique_key>, L</store> and L</null_value>.

=head2 format

    has 'year_week' => (
        isa     => 'Str',
        type    => 'date',
        format  => 'basic_week_date'
    );

Date fields by default can parse (1) milliseconds since epoch
(2) yyyy/MM/dd HH:mm:ss Z or (3) yyyy/MM/dd Z.

If you would like to specify a different format, you can use one of the
L<built-in formats|http://www.elasticsearch.org/guide/reference/mapping/date-format.html>
or a L<custom format|http://joda-time.sourceforge.net/api-release/org/joda/time/format/DateTimeFormat.html>.

=head2 precision_step (date)

    has 'count' => (
        isa             => 'Int',
        precision_step  => 2,
    );

The C<precision_step> determines the number of terms generated for each
value (defaults to 4). The more terms, the faster the lookup, but the more
memory used.

=head1 BOOLEAN FIELDS

Boolean fields accept C<undef>, C<0>, C<""> and C<"false"> as C<false> values,
and any other value as C<true>.

There are no C<boolean>-specific keywords, but the general keywords apply:
L</index>, L</include_in_all>, L</boost>, L</multi>,
L</index_name>, L</store> and L</null_value>.

B<Note>: By default, ElasticSearch treats C<undef> as NEITHER C<true> NOR
C<false>, but as C<null> (ie missing). To work around this, we automatically
set L</null_value> to C<0> to make C<boolean> fields more Perlish. If you
would like to revert to the default behaviour, set L</null_value> to C<undef>.

=head1 BINARY FIELDS

Binary data can be stored in ElasticSearch in Base64 encoding. The easiest
way to do this is to use L<Elastic::Model::Types/Binary>, which will handle
the Base64 conversion for you:

    use Elastic::Model::Types qw(Binary);

    has 'binary_field' => (
        is      => 'ro',
        isa     => Binary
    );

The field is always not L<indexed|/index>.

There are no C<binary>-specific keywords, but you can use:
L</store> (defaults to C<yes>)and L</index_name>.

=head1 IP FIELDS

Fields of type C<ip> can be used to index IPv4 addresses in numeric form.

The following keyword applies only to fields of L</type> C<ip>.
You can also use L</index>, L</include_in_all>, L</boost>, L</multi>,
L</index_name>, L</unique_key>, L</store> and L</null_value>.

=head2 precision_step (ip)

    has 'address' => (
        isa             => 'Str',
        type            => 'ip'
        precision_step  => 2,
    );

The C<precision_step> determines the number of terms generated for each
value (defaults to 4). The more terms, the faster the lookup, but the more
memory used.

=head1 GEO_POINT FIELDS

C<geo_point> fields are used to index latitude/longitude points.  The easiest
way to use them is to use L<Elastic::Model::Types/GeoPoint>:

    use Elastic::Model::Types qw(GeoPoint);

    has 'point' => (
        is      => 'ro',
        isa     => GeoPoint,
        coerce  => 1
    );

See L<http://www.elasticsearch.org/guide/reference/mapping/geo-point-type.html>
for more information about geo_point fields.

The following keywords apply only to fields of L</"type"> C<geo_point>.
You can also use L</multi>, L</index_name> and L</store>.

=head2 lat_lon

    has 'point' => (
        is      => 'ro',
        isa     => GeoPoint,
        lat_lon => 1
    );

By default, a geo-point is indexed as a lat-lon combination.  To index the
C<lat> and C<lon> fields as numeric fields as well, which is considered
good practice, as both the L<geo distance|http://www.elasticsearch.org/guide/reference/query-dsl/geo-distance-filter.html>
and L<bounding box|http://www.elasticsearch.org/guide/reference/query-dsl/geo-bounding-box-filter.html>
filters can either be executed using in memory checks, or using the indexed
lat lon values.  B<Note:> indexed lat lon only makes sense when there is a
single geo point value for the field, and not multiple values.

=head2 geohash

    has 'point' => (
        is      => 'ro',
        isa     => GeoPoint,
        geohash => 1,
    );

Set C<geohash> to true to index the L<geohash|http://en.wikipedia.org/wiki/Geohash>
value as well.

This value is queryable via (eg) the C<point.geohash> field.

=head2 geohash_precision

    has 'point' => (
        is                  => 'ro',
        isa                 => GeoPoint,
        geohash             => 1,
        geohash_precision   => 8,
    );

The C<geohash_precision> determines how accurate the geohash will be -
defaults to 12.

=head1 OBJECT FIELDS

Hashrefs (and objects which have been serialised to hashrefs) are considered
to be "objects", as in JSON objects. Your doc class is serialized to a
JSON object/hash, which is known as the L<root_object|http://www.elasticsearch.org/guide/reference/mapping/root-object-type.html>.
The mapping for the root object can be configured with L<Elastic::Doc/"has_mapping">.

Your doc class may have attributes which are hash-refs, or objects, which
may themselves contain hash-refs or objects. Multi-level data structures are
allowed.

The following keywords apply only to fields of L</"type"> C<object> and
C<nested>. You can also use L</include_in_all>, L</include_attrs>,
L</exclude_attrs>.

The mapping for these structures should be automatically generated, but
these keywords give you some extra control:

=head2 enabled

    has 'foo' => (
        is      => 'ro',
        isa     => 'HashRef',
        enabled => 0
    );

Setting C<enabled> to false disables the indexing of any value in the object.
Defaults to C<false>.

=head2 dynamic

    has 'foo' => (
        is      => 'ro',
        isa     => 'HashRef',
        dynamic => 1
    );

ElasticSearch defaults to trying to detect field types dynamically, but
this can lead to mistakes, eg is C<"123"> a C<string> or a C<long>?
Elastic::Model turns off this dynamic detection, and instead uses Moose's
type constraints to determine what type each field should have.

If you know what you're doing, you can set C<dynamic> to  C<1> (auto-detect
new field types), C<0> (ignore new fields) or C<'strict'> (throw an error
if an unknown field is included).

=head2 path

The C<path> keyword controls how the attribute names of an object are flattened
into field names in Elasticsearch. It can be set to C<full> (the default) or
C<just_path>.

For instance, given the following example:

    package MyApp::Types;

    use MooseX::Types -declare 'FullName';

    use MooseX::Types::Moose qw(Str);
    use MooseX::Types::Structured qw(Dict);

    subtype Fullname,
    as Dict[
        first   => Str,
        last    => Str
    ];


    package MyApp::Couple;

    use Moose;
    use MyApp::Types qw(FullName);

    has 'husband' => (
        is      => 'ro',
        isa     => FullName,
    );

    has 'wife' => (
        is      => 'ro',
        isa     => FullName,
    );

A C<MyApp::Couple> object may look like this:

    {
        husband => { first => 'John', last => 'Smith' },
        wife    => { first => 'Mary', last => 'Smith' }
    }

By default, this data would be flattened and stored as follows:

    FIELD NAME        |  VALUES
    ------------------|---------
    husband.first     |  john
    husband.last      |  smith
    wife.first        |  mary
    wife.last         |  smith

These field names, or "paths", can also have the C<type> name prepended,
so C<husband.first> could also be referred to as C<couple.husband.first>.

The C<path> keyword can be used to control the construction of this path.  For
instance, if C<MyApp::Couple> was defined as:

    package MyApp::Couple;

    use Moose;
    use MyApp::Types qw(FullName);

    has 'husband' => (
        is      => 'ro',
        isa     => FullName,
        path    => 'just_name'
    );

    has 'wife' => (
        is      => 'ro',
        isa     => FullName,
        path    => 'just_name'
    );

then the values would be indexed as:

    FIELD NAME        |  VALUES
    ------------------|---------------
    first             |  john, mary
    last              |  smith, smith

... also accessible as C<couple.first> and C<couple.last>.

The C<path> keyword can also be combined with the L</"index_name"> keyword.

=head1 NESTED FIELDS

C<nested> fields are a sub-class of C<object> fields, that are useful when
your attribute can contain multiple values.

First an explanation.  Consider this data structure:

    {
        person  => [
            { first => 'John', last => 'Smith' },
            { first => 'Mary', last => 'Smith' },
            { first => 'Mary', last => 'Jones' }
        ]
    }

If the C<person> field is of type C<object>, then the above data structure is
flattened into something more like this:

    {
        'person.first' => ['John','Mary','Mary'],
        'person.last'  => ['Smith','Smith','Jones']
    }

With this structure it is impossible to run queries that depend on matching
on attributes of a SINGLE C<person> object.  For instance, a query asking
for docs that have a C<person> who has C<(first == John and last == Jones)>
would incorrectly match this document.

Nested objects are the solution to this problem.  When an attribute is
marked as L</"type"> C<nested>, then ElasticSearch creates each object
as a separate-but-related hidden document. These nested objects can be queried with the
L<nested query|http://www.elasticsearch.org/guide/reference/query-dsl/nested-query.html>
and the L<nested filter|http://www.elasticsearch.org/guide/reference/query-dsl/nested-filter.html>.

The following keywords apply only to fields of L</"type"> C<nested>.
You can also use L</include_in_all>, L</path> L</dynamic>, L</include_attrs>
and L</exclude_attrs>.

=head2 include_in_parent

    has 'person' => (
        is                  => 'ro',
        isa                 => ArrayRef[Person],
        type                => 'nested',
        include_in_parent   => 1,
    );

If you would also like the data from the nested objects to be indexed in
their containing object (as in the first data structure above), then
set C<include_in_parent> to true.

=head2 include_in_root

    has 'person' => (
        is                  => 'ro',
        isa                 => ArrayRef[Person],
        type                => 'nested',
        include_in_root     => 1,
    );

Objects can be nested inside objects which are nested inside objects etc. The
C<include_in_root> keyword does the same as the L</"include_in_parent"> keyword,
but refers to the top-most document, rather than the direct parent.

=head1 ELASTIC::DOC FIELDS

You can have attributes in one class that refer to another L<Elastic::Doc>
class.  For instance, a C<MyApp::Post> object could have, as an attribute,
the C<MyApp::User> object to whom the post belongs.

You may want to store just the L<Elastic::Model::UID> of the C<user> object,
or you may want to include the user's name and email address, so that you
can search for posts by a user named "Joe Bloggs". You can't do joins in
a NoSQL database, so you need to denormalize your data.

By default, all attributes in an object are included.  You can change the
list with L</include_attrs> and L</exclude_attrs>. You can use these with
Moose classes too, but any attributes that are excluded, won't be stored
and it won't be possible to retrieve them.

=head2 include_attrs

    has 'user' => (
        is            => 'ro',
        isa           => 'MyApp::User',
        include_attrs => ['name','email']
    );

The above declaration will index the C<user> object's UID, plus the C<name>
and C<email> attributes.  If C<include_attrs> is not specified, then all
the attributes from the C<user> object will be indexed. If C<include_attrs>
is set to an empty array ref C<[]> then no attributes other than the UID
will be indexed.

=head2 exclude_attrs

    has 'user' => (
        is            => 'ro',
        isa           => 'MyApp::User',
        exclude_attrs => ['secret']
    );

The above declaration will index all the C<user> attributes, except for the
attribute C<secret>.

=head1 ATTACHMENT KEYWORDS

TODO

=head1 CUSTOM MAPPING, INFLATION AND DEFLATION

The preferred way to specify the mapping and how to deflate and inflate
an attribute is by specifying an C<isa> type constraint and adding a
L<typemap entry|Elastic::Model::TypeMap::Default>.

However, you can provide custom values with the following:

=head2 mapping

    has 'foo' => (
        is      => 'ro',
        type    => 'string',
        mapping => { index => 'no' },
    );

You can specify a custom C<mapping> directly in the attribute, which will be
used instead of the typemap entry that would be generated.  Any other
keywords that you specify (eg L</"type">) will be added to your C<mapping>.

=head2 deflator

    has 'foo' => (
        is       => 'ro',
        type     => 'string',
        deflator => sub { my $val = shift; return my_deflator($val) },
    );

You can specify a custom C<deflator> directly in the attribute. It should
return C<undef>, a string, or an unblessed data structure that can be converted
to JSON.

=head2 inflator

    has 'foo' => (
        is       => 'ro',
        type     => 'string',
        inflator => sub { my $val = shift; return my_inflator($val) },
    );

You can specify a custom C<inflator> directly in the attribute. It should
be able to reinflate the original value from the plain data structure that
is stored in ElasticSearch.

=head1 AUTHOR

Clinton Gormley <drtech@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Clinton Gormley.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

