README.md

# ESx

[![Build Status](http://img.shields.io/travis/ikeikeikeike/esx.svg?style=flat-square)](http://travis-ci.org/ikeikeikeike/esx)
[![Ebert](https://ebertapp.io/github/ikeikeikeike/esx.svg)](https://ebertapp.io/github/ikeikeikeike/esx)
[![Hex version](https://img.shields.io/hexpm/v/esx.svg "Hex version")](https://hex.pm/packages/esx)
[![Inline docs](https://inch-ci.org/github/ikeikeikeike/esx.svg)](http://inch-ci.org/github/ikeikeikeike/esx)
[![Lisence](https://img.shields.io/hexpm/l/ltsv.svg)](https://github.com/ikeikeikeike/esx/blob/master/LICENSE)

A client for the Elasticsearch with Ecto, written in Elixir

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:

1. Add `esx` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [{:esx, "~> x.x.x"}]
end
```

2. Ensure `esx` is started before your application:

```elixir
def application do
  [applications: [:esx]]
end
```

hexdocs: https://hexdocs.pm/esx


## Configuration

```elixir
config :esx, ESx.Model,
  url: "http://example.com:9200",
  repo: MyApp.Repo  # Optional, which defines Ecto for connecting database.
```

#### Multiple configuration

###### This is configuration that if you've have multiple Elasticsearch's Endpoint which's another one.

First, that configuration is defined with `ESx.Model.Base` into your project. It's like Ecto's Repo.

```elixir
defmodule MyApp.ESx do
  use ESx.Model.Base, app: :my_app
end
```

And so that there's `MyApp.ESx` configuration for Mix.config below.

```elixir
config :my_app, MyApp.ESx,
  scheme: "http",
  host: "example.com",
  port: 9200
```

#### Definition for all of configuration.

```elixir
config :esx, ESx.Model,
  repo: MyApp.Repo,                        # Optional, which defines Ecto for connecting database.
  protocol: "http",                        # or: scheme: "http"
  user: "yourname", password: "yourpass",  # or: userinfo: "yourname:yourpass"
  host: "127.0.0.1",
  port: 9200,
  path: "path-to-endpoint"
```

## Definition for Analysis.

#### DSL
```elixir
defmodule MyApp.Blog do
  use ESx.Schema

  index_name    "blog"  # Optional
  document_type "blog"  # Optional

  mapping _all: [enabled: false], _ttl: [enabled: true, default: "180d"] do
    indexes :title, type: "string"
    indexes :content, type: "string"
    indexes :publish, type: "boolean"
  end

  settings number_of_shards: 10, number_of_replicas: 2 do
    analysis do
      filter :ja_posfilter,
        type: "kuromoji_neologd_part_of_speech",
        stoptags: ["助詞-格助詞-一般", "助詞-終助詞"]
      tokenizer :ja_tokenizer,
        type: "kuromoji_neologd_tokenizer"
      analyzer :default,
        type: "custom", tokenizer: "ja_tokenizer",
        filter: ["kuromoji_neologd_baseform", "ja_posfilter", "cjk_width"]
    end
  end

end

```
#### Setting by keywords lists

```elixir
defmodule Something.Schema do
  use ESx.Schema

  mapping [
    _ttl: [
      enabled: true,
      default: "180d"
    ],
    _all: [
      enabled: false
    ],
    properties: [
      title: [
        type: "string",
        analyzer: "ja_analyzer"
      ],
      publish: [
        type: "boolean"
      ],
      content: [
        type: "string",
        analyzer: "ja_analyzer"
      ]
    ]
  ]

  settings [
    number_of_shards:   1,
    number_of_replicas: 0,
    analysis: [
      analyzer: [
        ja_analyzer: [
          type:      "custom",
          tokenizer: "kuromoji_neologd_tokenizer",
          filter:    ["kuromoji_neologd_baseform", "cjk_width"],
        ]
      ]
    ]
  ]

end
```

## Definition for updating record via such as a Model.

```elixir
defmodule MyApp.Blog do
  use ESx.Schema

  defstruct [:id, :title, :content, :publish]

  mapping do
    indexes :title, type: "string"
    indexes :content, type: "string"
    indexes :publish, type: "boolean"
  end
end
```

#### With Ecto's Model

```elixir
defmodule MyApp.Blog do
  use MyApp.Web, :model
  use ESx.Schema

  schema "blogs" do
    field :title, :string
    field :content, :string
    field :publish, :boolean

    timestamps
  end

  mapping do
    indexes :title, type: "string"
    indexes :content, type: "string"
    indexes :publish, type: "boolean"
  end
```

###### Indexing Data

The data's elements which sends to Elasticsearch is able to customize that will make it, this way is the same as Ecto.

```elixir
defmodule MyApp.Blog do
  @derive {Poison.Encoder, only: [:title, :publish]}
  schema "blogs" do
    field :title, :string
    field :content, :string
    field :publish, :boolean

    timestamps
  end
end
```

When Ecto's Schema and ESx's mapping have defferent fields or for customization more, defining function `as_indexed_json` will make it in order to send relational data to Elasticsearch, too. Commonly it called via `ESx.Model.index_document`, `ESx.Model.update_document`.

```elixir
defmodule MyApp.Blog do
  def as_indexed_json(struct, opts) do
    all_of_defined_data = super struct, opts
    ...
    ...

    some_of_custmized_data
  end
end
```

By default will send all of defined mapping's fields to Elasticsearch.

###### API Docs

- https://hexdocs.pm/esx/ESx.Schema.html

## Transport

`ESx.Transport` and `ESx.Model` will connect to multipe elasticsearch automatically if builded cluster systems on your environment.
```elixir
iex(1)> ESx.Transport.conn  # Sniffing cluster system and choose random Elasticsearch connection

01:10:26.694 [debug] curl -X GET 'http://127.0.0.1:9200/_nodes/http'  # Run sniffing

%ESx.Transport.Connection{client: HTTPoison, dead: false, # chose one of cluster connection.
 dead_since: 1492099826, failures: 0,
 pidname: :"RWxpeGlyLkVTeC5UcmFuc3BvcnQuQ29ubmVjdGlvbl9odHRwOi8vMTI3LjAuMC4xOjkyMDI=",
 resurrect_timeout: 60, url: "http://127.0.0.1:9202"}

iex(2)> ESx.Transport.Connection.conns  # Below is all of cluster connections.
[%ESx.Transport.Connection{client: HTTPoison, dead: false,
  dead_since: 1492099826, failures: 0,
  pidname: :"RWxpeGlyLkVTeC5UcmFuc3BvcnQuQ29ubmVjdGlvbl9odHRwOi8vMTI3LjAuMC4xOjkyMDE=",
  resurrect_timeout: 60, url: "http://127.0.0.1:9201"},
 %ESx.Transport.Connection{client: HTTPoison, dead: false,
  dead_since: 1492099826, failures: 0,
  pidname: :"RWxpeGlyLkVTeC5UcmFuc3BvcnQuQ29ubmVjdGlvbl9odHRwOi8vMTI3LjAuMC4xOjkyMDI=",
  resurrect_timeout: 60, url: "http://127.0.0.1:9202"},
 %ESx.Transport.Connection{client: HTTPoison, dead: false,
  dead_since: 1492099826, failures: 0,
  pidname: :"RWxpeGlyLkVTeC5UcmFuc3BvcnQuQ29ubmVjdGlvbl9odHRwOi8vMTI3LjAuMC4xOjkyMDA=",
  resurrect_timeout: 60, url: "http://127.0.0.1:9200"}]
```


## Usage

### Indexing

```elixir
MyApp.ESx.reindex, MyApp.Blog
MyApp.ESx.create_index, MyApp.Blog
MyApp.ESx.delete_index, MyApp.Blog
MyApp.ESx.index_exists?, MyApp.Blog
MyApp.ESx.refresh_index, MyApp.Blog

```

or

```elixir
ESx.Model.reindex, MyApp.Blog
ESx.Model.create_index, MyApp.Blog
ESx.Model.delete_index, MyApp.Blog
ESx.Model.index_exists?, MyApp.Blog
ESx.Model.refresh_index, MyApp.Blog

```

### ES Document

```elixir
MyApp.ESx.import, MyApp.Blog
MyApp.ESx.index_document, %MyApp.Blog{id: 1, title: "egg"}
MyApp.ESx.delete_document, %MyApp.Blog{id: 1, title: "ham"}
```

### Search & Response

```elixir
MyApp.ESx.search, MyApp.Blog, %{query: %{match: %{title: "foo"}}}
```

```elixir
response =
  MyApp.Blog
  |> MyApp.ESx.search(%{query: %{match: %{title: "foo"}}})
  |> MyApp.ESx.results

IO.inspect Enum.map(response, fn r ->
  r["_source"]["title"]
end)
# ["foo", "egg", "some"]
```

##### With Phoenix's Ecto

```elixir
response =
  MyApp.Blog
  |> MyApp.ESx.search(%{query: %{match: %{title: "foo"}}})
  |> MyApp.ESx.records

IO.inspect Enum.each(response, fn r ->
  r.title
end)
# ["foo", "egg", "some"]
```

###### API Docs

- https://hexdocs.pm/esx/ESx.Model.html

##### Pagination

[github.com/ikeikeikeike/scrivener_esx](https://github.com/ikeikeikeike/scrivener_esx)

```elixir
page =
  MyApp.Blog
  |> MyApp.ESx.search(%{query: %{match: %{title: "foo"}}})
  |> MyApp.ESx.paginate(page: 2, page_size: 5)
```


## Low-level APIs


There're Low-level APIs in `ESx.API` and `ESx.API.Indices`.

```elixir
ts = ESx.Transport.transport trace: true  # or: ts = ESx.Model.transport

ESx.API.search ts, %{index: "your_app", body: %{query: %{}}}

ESx.API.Indices.delete ts, %{index: "your_app"}
```

###### API Docs

- https://hexdocs.pm/esx/ESx.API.html
- https://hexdocs.pm/esx/ESx.API.Indices.html


## Testing

Download elasticsearch and build cluster

```ruby
$ ./test/build.sh
```

run mix test

```ruby
$ mix test
```


Probably won't make it.
- Search DSL