Lycra
Usage Examples
Simple
class Vehicle < ApplicationRecord
# attributes:
# name string
# slug string
# speed integer
# wheels integer
# passengers integer
# description text
end
class VehicleDocument
include Lycra::Document
# a siple required attribute
attribute! :id, types.integer
# a simple attribute with a description
attribute :name, types.text, 'An optional description of the attribute'
# an attribute with custom mappings for elasticsearch
attribute :slug, types.text, mappings: {index: :not_analyzed}
# an attribute with a symbol indicating the method to call on your model
attribute :top_speed, types.integer, :speed
# an attribute using block syntax to define itself with a custom resolver
attribute :wheels do
type types.integer
description 'An optional description of the attribute'
resolve ->(model, args, context) do
model.wheels # this just calls the wheels method but you can do anything in here
end
end
# an attribute with an inline resolver
attribute :max_passengers,
types.integer,
->(mdl, arg, ctx) { mdl.passengers },
'An optional description of the attribute',
mappings: {index: :not_analyzed}
# a simple attribute defining everything in a block, falling back on defaults
attribute do
name :description
type types.text
end
end
STI
class Vehicle < ApplicationRecord
# attributes:
# type string
# name string
# slug string
# speed integer
# wheels integer
# passengers integer
# description text
end
class Car < Vehicle; end
class Truck < Vehicle; end
Shared Index
class VehicleDocument
include Lycra::Document
parent! # TODO need to implement this (and come up with a better name)
attribute :name, types.text
attribute :slug, types.text
attribute :speed, types.integer
attribute :wheels, types.integer
attribute :passengers, types.integer
attribute :description, types.text
end
class CarDocument < VehicleDocument
# index_name will be "vehicles"
# document_type will be "vehicle"
attribute :passengers, types.integer do
# ... do some custom stuff for an attribute for cars specifically ...
end
end
class TruckDocument < VehicleDocument
# index_name will be "vehicles"
# document_type will be "vehicle"
attribute :passengers, types.integer do
# ... do some custom stuff for an attribute for trucks specifically ...
end
end
Separate Indices
class VehicleDocument
include Lycra::Document
attribute :name, types.text
attribute :slug, types.text
attribute :speed, types.integer
attribute :wheels, types.integer
attribute :passengers, types.integer
attribute :description, types.text
end
class CarDocument < VehicleDocument
# index_name will be "cars"
# document_type will be "car"
attribute :passengers, types.integer do
# ... do some custom stuff for an attribute for cars specifically ...
end
end
class TruckDocument < VehicleDocument
# index_name will be "trucks"
# document_type will be "truck"
attribute :passengers, types.integer do
# ... do some custom stuff for an attribute for trucks specifically ...
end
end
IMPORT / ROTATE / REINDEX
All methods take a batch_size: 200
(default) keyword argument and can accept a block which will be yielded each batch result as it is processed.
For example if you wanted to perform an import while displaying a progress bar:
importer = Lycra::Import.new # you can pass an array of documents, otherwise defaults to all documents
bar = ProgressBar.new(importer.total) # total number of records to be processed
importer.import(batch_size: 200) do |batch|
bar.increment!(batch['items'].count) # batch is the result of the batch import
end
Importing
Lycra::Import.import
is used for (re)initializing new indices. It deletes and creates the index & alias, then perform a fresh import.
This will cause downtime as the index is being repopulated by the import.
Rotating
Lycra::Import.rotate
is used when index mappings are changed. It creates a new index, performs an import, then hot-swaps the alias to the new index.
When your mappings change, the index fingerprint will change, which requires that you create that new index and populate it.
Any callbacks and/or background jobs may fail until you create the new index. Once they're created, callbacks & jobs will be updating the new index.
The alias will continue pointing to your old index while the new one is being populated, and all search queries will cotinue to reference the alias. This allows your app to stay up using the old index until the new one is ready to be swapped in.
Reindexing
Lycra::Import.reindex
is used when data changes (but not mappings). It updates all documents in the index using bulk updates.
If your mappings have changed at all that means your index fingerprint will have changed, and you will need to use import
then swap your aliases manually, or use to rotate
to populate and swap in one shot.
TODO / IDEAS
- import scope
- merge document+proxy
- caching
- solidify conventions around bang methods / better error handling and raising
- chainable search classes and DSL
- search scopes
- facets/helpers
# returns results
Vehicle.search('car')
Vehicle.search('car', filters: {published: true})
Vehicle.search(filters: {published: true})
# chainable
Vehicle.search
Vehicle.search.term('car')
Vehicle.search.filter(published: true)
# DSL
Vehicle.search do
term 'car'
filter published: true
end
Vehicle.search do
term 'car' do
filters do
published true
end
end
end
# multi-index
Lycra.search