Esse::Index is the main building block. An index class defines:
- Settings — shards, replicas, refresh interval, analyzers.
- Mappings — field types and analysis rules.
- Repositories — how to load and transform data.
- Plugins — optional behavior extensions.
Every index class inherits from Esse::Index:
class UsersIndex < Esse::Index # ...endNaming
By convention the class name is underscored and the Index suffix is stripped:
UsersIndex.index_name # => "myapp_users" (with cluster prefix)UsersIndex.uname # => "users_index"You can override the generated name explicitly:
class UsersIndex < Esse::Index self.index_name = 'users' self.index_prefix = 'app1' # overrides cluster prefixend
UsersIndex.index_name # => "app1_users"UsersIndex.index_name(suffix: 'v2') # => "app1_users_v2"Index suffixes
Suffixes are used for zero-downtime reindexing. The common pattern:
bundle exec esse index reset UsersIndex --suffix 20240401This creates users_20240401, imports into it, and swaps the alias users → users_20240401.
Settings
class UsersIndex < Esse::Index settings do { index: { number_of_shards: 2, number_of_replicas: 1, refresh_interval: '1s' }, analysis: { analyzer: { my_analyzer: { type: 'standard' } } } } endendOr inline:
settings(number_of_shards: 2, refresh_interval: '1s')Simplified keys (number_of_shards, number_of_replicas, refresh_interval, mapping) are auto-nested under index.*.
The cluster’s global settings are deep-merged into each index’s settings.
Mappings
class UsersIndex < Esse::Index mappings do { properties: { name: { type: 'text' }, email: { type: 'keyword' }, age: { type: 'integer' }, roles: { type: 'keyword' }, created: { type: 'date' } } } endendThe cluster’s global mappings are merged into each index.
Repositories
A repository defines how to load data into the index. One index can host many repositories.
class UsersIndex < Esse::Index repository :user do collection do |**context, &block| User.where(context).find_in_batches { |b| block.call(b, context) } end
document do |user, **| { _id: user.id, name: user.name } end end
repository :admin do collection { |**ctx, &b| User.admins.find_in_batches { |b2| b.call(b2, ctx) } } document { |u, **| { _id: u.id, name: u.name, role: 'admin' } } endendSee Repository for the full DSL.
Access:
UsersIndex.repo # default (when only one defined)UsersIndex.repo(:admin) # specificUsersIndex.repo?(:admin) # => true / falseUsersIndex.repo_hash # => { 'user' => ..., 'admin' => ... }Plugins
class UsersIndex < Esse::Index plugin :active_record plugin MyCustomPlugin, option: 'value'endSee Plugins.
Lifecycle methods
These are the most common class-level operations. All accept suffix: and other ES options.
Create / delete
UsersIndex.create_index(alias: true) # create and point alias at itUsersIndex.delete_indexUsersIndex.index_exist? # => Booleancreate_index options:
| Option | Type | Description |
|---|---|---|
suffix | String | Concrete index suffix (for zero-downtime) |
alias | Boolean | Also create the alias index_name → suffix |
settings | Hash | Override settings |
body | Hash | Pass the full body manually |
wait_for_active_shards, timeout, master_timeout, headers | pass-through | Native ES options |
Reset (zero-downtime)
UsersIndex.reset_index( suffix: Time.now.strftime('%Y%m%d'), optimize: true, # temporarily drops replicas/refresh during import import: true, # import from collection reindex: false # use _reindex API instead of collection)The reset flow:
- Create new index with suffix.
- (If
optimize: true) reduce replicas to 0, disable refresh. - Import data (or reindex from previous index).
- Restore settings.
- Swap the alias.
- Delete old concrete index.
Alias management
UsersIndex.update_aliases(suffix: '20240401')UsersIndex.aliases # => alias infoUsersIndex.indices_pointing_to_alias # => concrete index namesOpen / close / refresh
UsersIndex.closeUsersIndex.openUsersIndex.refreshUpdate settings / mapping
UsersIndex.update_settings(settings: { number_of_replicas: 0 })UsersIndex.update_mappingDocument-level operations
UsersIndex.get(id: 1)UsersIndex.mget(ids: [1, 2, 3])UsersIndex.exist?(id: 1)UsersIndex.count(body: { query: { match_all: {} } })
UsersIndex.index(id: 1, body: { name: 'John' })UsersIndex.update(id: 1, body: { doc: { name: 'Jane' } })UsersIndex.delete(id: 1)
UsersIndex.bulk( index: [doc1, doc2], create: [doc3], update: [doc4], delete: [doc5])Import
UsersIndex.import # import all repositoriesUsersIndex.import(:user) # specific repositoryUsersIndex.import(context: { active: true }) # pass context to the collectionUsersIndex.import( suffix: '20240401', eager_load_lazy_attributes: [:roles], update_lazy_attributes: [:comment_count])See Import for details.
Search
UsersIndex.search(q: 'john')UsersIndex.search(body: { query: { match: { name: 'john' } } })See Search.
Request customization
class UsersIndex < Esse::Index request_params :index, :update, pipeline: 'my_pipeline' do |doc| { routing: doc.routing_key } endendValid operations are :index, :create, :update, :delete.
Inheritance
Index classes support inheritance — settings, mappings, plugins, and repositories are inherited. Subclass and override what you need:
class BaseIndex < Esse::Index settings do { index: { number_of_replicas: 1 } } endend
class UsersIndex < BaseIndex # inherits settings, can override mappings, repositories, etc.endModule breakdown
For reference, Esse::Index composes these modules (under lib/esse/index/):
| Module | Purpose |
|---|---|
Base | Cluster binding and naming |
Inheritance | Class inheritance rules |
Plugins | Plugin loading |
Attributes | Directory paths, template dirs |
Type | Repository definition |
Settings | Settings DSL |
Mappings | Mappings DSL |
Indices | Create/delete/reset operations |
Documents | Single-doc ops (get, mget, index, update, bulk…) |
Aliases | Alias ops |
Search | Search entrypoint |
ObjectDocumentMapper | Bulk serialization |
RequestConfigurable | request_params DSL |
Descendants | Index tracking |