Skip to content

Commit

Permalink
🌾 Eager loading for non-relation fields (#124)
Browse files Browse the repository at this point in the history
* Add `with()` method to Field to declare eager loaded relations

* Apply fixes from StyleCI

* formatting
  • Loading branch information
erikgaal committed Sep 23, 2019
1 parent 34386ac commit dcf3478
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 29 deletions.
4 changes: 4 additions & 0 deletions src/Eloquent/ModelSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ public function getRelationFields(): Collection
$field->accessor($key);
}

if (! $field->getWith()) {
$field->with($field->getAccessor());
}

return $field;
});
}
Expand Down
26 changes: 26 additions & 0 deletions src/Fields/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ class Field
*/
protected $fillable = true;

/**
* @var array
*/
protected $with;

/**
* @var bool
*/
Expand Down Expand Up @@ -361,6 +366,27 @@ public function isFillable(): bool
return $this->fillable;
}

/**
* Set the relations that should be eager loaded.
*
* @param string[]|string $relations
* @return Field
*/
public function with($relations): self
{
$this->with = Arr::wrap($relations);

return $this;
}

/**
* Get the relations that should be eager loaded.
*/
public function getWith(): ?array
{
return $this->with;
}

/**
* Set if the field is searchable.
*
Expand Down
52 changes: 23 additions & 29 deletions src/Queries/Concerns/EagerLoadRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace Bakery\Queries\Concerns;

use Bakery\Fields\Field;
use Bakery\Eloquent\ModelSchema;
use Bakery\Support\TypeRegistry;
use Bakery\Fields\PolymorphicField;
use Illuminate\Database\Eloquent\Builder;

trait EagerLoadRelationships
Expand All @@ -17,39 +17,33 @@ trait EagerLoadRelationships
/**
* Eager load the relations.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param array $fields
* @param ModelSchema $schema
* @param string $path
* @param Builder $query
* @param bool[]|array $fields
* @param ModelSchema $schema
* @param string $path
*/
protected function eagerLoadRelations(Builder $query, array $fields, ModelSchema $schema, $path = '')
{
$relations = $schema->getRelations()->keys()->toArray();

foreach ($fields as $key => $field) {
if (in_array($key, $relations)) {
$relationField = $this->getRelationFieldByKey($key, $schema);
$column = $relationField->getAccessor();
$eagerLoadPath = $path ? $path.'.'.$column : $column;
$query->with($eagerLoadPath);
$related = $schema->getModel()->{$column}()->getRelated();
foreach ($fields as $key => $subFields) {
$field = $schema->getFieldByKey($key);

// Skip if this is not a defined field.
if (! $field) {
continue;
}

$with = array_map(function ($with) use ($path) {
return $path ? "{$path}.{$with}" : $with;
}, $field->getWith() ?? []);

$query->with($with);

if ($field->isRelationship() && ! $field instanceof PolymorphicField) {
$accessor = $field->getAccessor();
$related = $schema->getModel()->{$accessor}()->getRelated();
$relatedSchema = $this->registry->getSchemaForModel($related);
$this->eagerLoadRelations($query, $field, $relatedSchema, $eagerLoadPath);
$this->eagerLoadRelations($query, $subFields, $relatedSchema, $path ? "{$path}.{$accessor}" : $accessor);
}
}
}

/**
* Get a relation field by it's key.
*
* @param string $key
* @param ModelSchema $schema
* @return Field
*/
protected function getRelationFieldByKey(string $key, ModelSchema $schema): Field
{
return $schema->getRelationFields()->first(function (Field $field, $relation) use ($key) {
return $relation === $key;
});
}
}
23 changes: 23 additions & 0 deletions tests/Feature/CollectionQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,29 @@ public function it_eager_loads_sibling_relationships()
$this->assertCount(5, DB::getQueryLog());
}

/** @test */
public function it_eager_loads_fields_that_explicitly_declare_with()
{
factory(Article::class, 25)->create();

$query = '
query {
articles {
items {
authorName
}
}
}
';

DB::enableQueryLog();
$response = $this->graphql($query);
DB::disableQueryLog();

$response->assertJsonStructure(['data' => ['articles' => ['items' => [['authorName']]]]]);
$this->assertCount(3, DB::getQueryLog());
}

/** @test */
public function it_cannot_query_models_that_are_not_indexable()
{
Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/Schemas/ArticleSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public function fields(): array
'slug' => Field::string()->unique(),
'title' => Field::string()->searchable(),
'name' => Field::string()->accessor('title')->searchable()->readOnly(),
'authorName' => Field::string()->with('user')->readOnly()->resolve(function (Article $article) {
return $article->user->name;
}),
'created_at' => Field::type('Timestamp')->readOnly(),
'createdAt' => Field::type('Timestamp')->accessor('created_at')->readOnly(),
];
Expand Down

0 comments on commit dcf3478

Please sign in to comment.