# Algoliax
[![CircleCI](https://circleci.com/gh/StephaneRob/algoliax/tree/master.svg?style=svg)](https://circleci.com/gh/StephaneRob/algoliax/tree/master)
This package let you easily integrate Algolia to your elixir application. It can be used with built in elixir struct or with [ecto](https://github.com/elixir-ecto/ecto) schemas.
## Installation
The package can be installed by adding `algoliax` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:algoliax, "~> 0.3.0-alpha"}
]
end
```
The docs can be found at [https://hexdocs.pm/algoliax](https://hexdocs.pm/algoliax).
## Configuration
Algoliax needs only `:api_key` and `:application_id` config. These configs can either be on config files or using environment variables `ALGOLIA_API_KEY` and `ALGOLIA_APPLICATION_ID`.
```elixir
config :algoliax,
api_key: "<API_KEY>",
application_id: "<APPLICATION_ID>",
batch_size: 500
```
## Usage
```elixir
defmodule People do
use Algoliax.Indexer,
index_name: :algoliax_people,
object_id: :reference,
algolia: [
attributes_for_faceting: ["age"],
searchable_attributes: ["full_name"],
custom_ranking: ["desc(updated_at)"]
]
defstruct reference: nil, last_name: nil, first_name: nil, age: nil
attributes([:first_name, :last_name, :age])
attribute(:updated_at, DateTime.utc_now() |> DateTime.to_unix())
attribute :full_name do
Map.get(model, :first_name, "") <> " " <> Map.get(model, :last_name, "")
end
attribute :nickname do
Map.get(model, :first_name, "") |> String.downcase()
end
end
```
By default all object are indexed, but it's possible to change this behaviour by overriding the function `to_be_indexed?`
```elixir
defmodule People do
...
@impl Algoliax
def to_be_indexed?(model) do
model.age > 20
end
end
```
```elixir
# This object will be indexed
people1 = %People{reference: 10, last_name: "Doe", first_name: "John", age: 13}
# This object will not be indexed
people2 = %People{reference: 87, last_name: "Fred", first_name: "Al", age: 70}
```
#### Index name at runtime
It's possible to define an index name at runtime, useful if `index_name` depends on environment or comes from an environment variable.
To do this just define a function with an arity of 0 that will be used as `index_name`
```elixir
defmodule People do
use Algoliax.Indexer,
index_name: :algoliax_people,
object_id: :reference,
algolia: [...]
def algoliax_people do
System.get_env("PEOPLE_INDEX_NAME")
end
end
```
#### After build object callback
To modify object before send to algolia, add `prepare_object` option. Must be a function of arity two and must return a `Map`
```elixir
defmodule People do
use Algoliax.Indexer,
index_name: :algoliax_people,
object_id: :reference,
prepare_object: &__MODULE__.prepare/2,
algolia: [...]
def prepare(object, model) do
object |> Map.put(:after_build_attribute, "test")
end
end
```
#### Index functions
```elixir
# Get people index settings
People.get_settings()
# Delete index
People.delete_index()
# Configure index
People.configure_index()
```
#### Object functions
```elixir
# Save object
People.save_object(people1)
# Save multiple objects
People.save_objects([people1, people2])
# Save multiple objects, and ensure object that they can't be indexed anymore are deleted from the index
People.save_objects([people1, people2], force_delete: true)
# Get object
People.get_object(people1)
# Delete object
People.delete_object(people1)
```
#### Search functions
```elixir
# search in index
People.search("john")
# search facet
People.search_facet("age")
```
#### Ecto specific
First you will need to add the Repo to the algoliax config:
```elixir
use Algoliax.Indexer,
index_name: :algoliax_people,
object_id: :reference
repo: MyApp.Repo,
algolia: [...]
```
If using Agoliax with an Ecto schema it is possible to use `reindex` functions. Reindex will go through all entries in the corresponding table (or part if query is provided). Algoliax will save_objects by batch of 500.
`batch_size` can be configured
```elixir
config :algoliax,
batch_size: 250
```
** ⚠️ _Important_**: Algoliax use by default the `id` column to order and go through the table. (cf [Custom order column](#custom-order-column))
```elixir
import Ecto.Query
# Reindex all
People.reindex()
# Reindex all people with age greater than 20
query = from(p in People, where: p.age > 20)
People.reindex(query)
# Reindex can also `force_delete`
query = from(p in People, where: p.age > 20)
People.reindex(query, force_delete: true)
People.reindex(force_delete: true)
# Reindex atomicly (create a temporary index and move it to initial index)
People.reindex_atomic()
```
##### Custom cursor column
If you don't have an `id` column, you can change it by setting the `cursor_field` option either in the global settings or in schema specific settings.
Make sure this column ensure a consistent order even when new records are created.
Using the global config:
```elixir
config :algoliax,
batch_size: 250,
cursor_field: :reference
```
Schema specific:
```elixir
defmodulePeople do
use Algoliax.Indexer,
index_name: :algoliax_people,
object_id: :reference,
repo: MyApp.Repo,
cursor_field: :inserted_at,
algolia: [...]
end
```
##### Preloads (`reindex` and `reindex_atomic`)
Sometimes indexed attributes depend on association. To allow reindexing functions to work, you can add `preloads` to your schema settings.
**Associations need to be defined in your Ecto Schema as well**
```elixir
defmodulePeople do
use Algoliax.Indexer,
index_name: :algoliax_people,
object_id: :reference,
preloads: [:animals],
algolia: [...]
attribute(:animals) do
Enum.map(model.animals, fn a ->
a.kind
end)
end
schema "peoples" do
field(:reference, Ecto.UUID)
field(:last_name)
field(:first_name)
field(:age, :integer)
field(:gender, :string)
has_many(:animals, Animal)
timestamps()
end
end
```