Model Relationships in Elasticsearch
Defining Relationships
Just like in a traditional Laravel Eloquent model, you can define belongsTo
, hasMany
, hasOne
, morphOne
, and morphMany
relationships in models that use Elasticsearch as their data storage. Here’s a full example illustrating various relationship types in an Elasticsearch context:
Common relationships example
Using belongsTo
, hasMany
, hasOne
, morphOne
, and morphMany
relationships.
Relationship Diagram
Illustrates how different models can interconnect within Elasticsearch, maintaining structured data interactions without the need for traditional joins.
Company Model
/** * App\Models\Company * ******Fields******* * @property string $_id * @property string $name * @property integer $status * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * ******Relationships******* * @property-read CompanyLog $companyLogs * @property-read CompanyProfile $companyProfile * @property-read Avatar $avatar * @property-read Photos $photos * * @mixin \Eloquent * */class Company extends Model{ protected $connection = 'elasticsearch';
//Relationships =====================================
public function companyLogs() { return $this->hasMany(CompanyLog::class); }
public function companyProfile() { return $this->hasOne(CompanyProfile::class); }
public function avatar() { return $this->morphOne(Avatar::class, 'imageable'); }
public function photos() { return $this->morphMany(Photo::class, 'photoable'); }}
CompanyLog Model
/** * App\Models\CompanyLog * ******Fields******* * @property string $_id * @property string $company_id * @property string $title * @property integer $code * @property mixed $meta * @property Carbon|null $created_at * @property Carbon|null $updated_at * ******Relationships******* * @property-read Company $company * * @mixin \Eloquent * */class CompanyLog extends Model{ protected $connection = 'elasticsearch';
//Relationships =====================================
public function company() { return $this->belongsTo(Company::class); }}
Avatar Model
/** * App\Models\Avatar * ******Fields******* * @property string $_id * @property string $url * @property string $imageable_id * @property string $imageable_type * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * ******Relationships******* * @property-read Company $company * * @mixin \Eloquent * */class Avatar extends Model{ protected $connection = 'elasticsearch';
//Relationships =====================================
public function imageable() { return $this->morphTo(); }}
Photo Model
/** * App\Models\Photo * ******Fields******* * @property string $_id * @property string $url * @property string $photoable_id * @property string $photoable_type * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at * ******Relationships******* * @property-read Company $company * * @mixin \Eloquent * */class Photo extends Model{ protected $connection = 'elasticsearch';
//Relationships =====================================
public function photoable() { return $this->morphTo(); }}
Example Usage
$company = Company::first();$companyLogs = $company->companyLogs->toArray(); //Shows all company logs (has many)$companyProfile = $company->companyProfile->toArray(); //Shows the company profile (has one)$companyAvatar = $company->avatar->toArray(); //Shows the company avatar (morph one)$companyPhotos = $company->photos->toArray(); //Shows the company photos (morph many)
This comprehensive example demonstrates the application of various relationship types in a Laravel-Elasticsearch context, providing insights into effective data structuring for complex applications.
Many to Many (belongsToMany)
Relationship Diagram
The following diagram illustrates how a many-to-many (polymorphic) relationship functions in Elasticsearch:
Migration
public function up(): void{ Schema::create('users', function (Blueprint $index): void { $index->text('name', hasKeyword: true); });
Schema::create('roles', function (Blueprint $index): void { $index->text('name', hasKeyword: true); });
// Pivot index, automatically managed by belongsToMany from the User and Role models Schema::create('role_user', function (Blueprint $index): void { $index->keyword('role_id'); $index->keyword('user_id'); });}
User Model
/** * App\Models\User * ******Fields******* * @property string $_id * @property string $name * ******Relationships******* * @property-read Role[] $roles */class User extends Model{ protected $connection = 'elasticsearch';
public function roles() { return $this->belongsToMany(Role::class); }}
Role Model
/** * App\Models\Role * ******Fields******* * @property string $_id * @property string $name * ******Relationships******* * @property-read User[] $users */class Role extends Model{ protected $connection = 'elasticsearch';
public function users() { return $this->belongsToMany(User::class); }}
Example Usage
// Returing relationships$user = User::first();$roles = $user->roles->toArray(); //Shows all roles (belongs to many)$role = Role::first();$users = $role->users->toArray(); //Shows all users (belongs to many)
// Creating relationships$user = User::first();$user->roles()->attach([$roleId1,$roleId2]); //Attach roles to a user$user->roles()->create(['name' => 'Can sing']); //Create a role and attach to user
One to Many (Polymorphic)
The one-to-many relationship typically involves a single parent model (e.g., a post) linked to multiple child models (e.g., comments). This relationship is crucial for scenarios where an entity can own or be associated with multiple other entities.
Relationship Diagram
Illustrates a straightforward one-to-many relationship between posts and their comments in an Elasticsearch environment.
Index Structure
Documents in Elasticsearch are designed to support relationships directly within their structures:
posts _id - objectid name - keyword / text
videos _id - objectid name - keyword / text
comments _id - objectid body - text commentable_id - keyword commentable_type - keyword
Migration
The schema for posts and comments is defined to facilitate direct referencing between a post and its associated comments:
public function up(): void{ Schema::create('posts', function (Blueprint $index): void { $index->text('name', hasKeyword: true); });
Schema::create('videos', function (Blueprint $index): void { $index->text('name', hasKeyword: true); });
Schema::create('comments', function (Blueprint $index): void { $index->text('body'); $index->keyword('commentable_id'); $index->keyword('commentable_type'); });}
Implementation Details
Managing one-to-many relationships in Laravel with Elasticsearch does not involve complex pivot table operations. Instead, the relationship is maintained through direct document references:
- Creation: When creating comments, the
post_id
field is used to link each comment to its respective post. - Querying: To retrieve all comments for a post, Elasticsearch queries are designed to look up all comments where the
post_id
matches the post’s ID. - Updates and Deletes: Modifying or deleting a post or its comments is handled through direct operations on the respective documents.
This approach enhances the performance and scalability of applications by leveraging Elasticsearch’s powerful indexing and search capabilities.
Many to Many (Polymorphic)
In a polymorphic many-to-many setup, each model type can relate to multiple instances of another model type through a shared identifier. This is managed in Elasticsearch by storing arrays of references directly on the model documents, avoiding the use of an additional table.
Relationship Diagram
The following diagram illustrates how a many-to-many (polymorphic) relationship functions in Elasticsearch:
Index Structure
In Elasticsearch, the document structure reflects the flexibility needed to accommodate the polymorphic nature of relationships:
posts _id - objectid name - keyword / text tag_id - keyword
videos _id - objectid name - keyword / text tag_id - keyword
tags _id - objectid name - keyword / text taggables - nested taggable_id - keyword taggable_type - keyword
Each ‘tag’ can relate to ‘posts’ and ‘videos’ through the taggables
nested field, which stores multiple relationships in the form of taggable_id
and taggable_type
.
Migration
public function up(): void{ Schema::create('posts', function (Blueprint $index): void { $index->text('name', hasKeyword: true); $index->keyword('tag_id'); });
Schema::create('videos', function (Blueprint $index): void { $index->text('name', hasKeyword: true); $index->keyword('tag_id'); });
Schema::create('tags', function (Blueprint $index): void { $index->text('name', hasKeyword: true); $index->keyword('tag_id'); $index->nested('taggables'); });}
Implementation Details
In Laravel, managing these relationships involves standard methods like attach()
, detach()
, and sync()
, which are adapted to work without pivot tables. For example, when you associate a post with a tag, the tag document’s taggables
field is updated to include the post’s _id
and a taggable_type
of ‘post’.
This Elasticsearch-driven approach provides a scalable and efficient way to manage polymorphic relationships, leveraging Elasticsearch’s capabilities for handling nested structures and dynamic queries.