Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 4 additions & 21 deletions src/Resources/BuildoraResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Ginkelsoft\Buildora\Exceptions\BuildoraException;
use Ginkelsoft\Buildora\Resources\Concerns\HasResourceActions;
use Ginkelsoft\Buildora\Resources\Concerns\HasResourceNavigation;
use Ginkelsoft\Buildora\Resources\Concerns\HasResourceQuery;
use Illuminate\Database\Eloquent\Model;
use Ginkelsoft\Buildora\Fields\Field;
use Exception;
Expand All @@ -20,6 +21,7 @@ abstract class BuildoraResource
{
use HasResourceActions;
use HasResourceNavigation;
use HasResourceQuery;

protected ?Model $parentModel = null;
protected string $modelClass;
Expand Down Expand Up @@ -196,27 +198,8 @@ public function getModelClass(): string

// slug() lives in HasResourceNavigation.

/**
* Return the query builder for this resource WITHOUT eager-loading relations.
* Use this for index/list views for optimal performance.
*
* @return \Ginkelsoft\Buildora\BuildoraQueryBuilder
*/
public static function query(): \Ginkelsoft\Buildora\BuildoraQueryBuilder
{
return QueryFactory::forList(new static());
}

/**
* Return the query builder for this resource WITH eager-loading of panel relations.
* Use this for detail/show views where relations are needed.
*
* @return \Ginkelsoft\Buildora\BuildoraQueryBuilder
*/
public static function queryWithRelations(): \Ginkelsoft\Buildora\BuildoraQueryBuilder
{
return QueryFactory::forDetail(new static());
}
// query() and queryWithRelations() live in HasResourceQuery
// (Resources\Concerns\HasResourceQuery).

public function setDetailView(string $view): static
{
Expand Down
38 changes: 38 additions & 0 deletions src/Resources/Concerns/HasResourceQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Ginkelsoft\Buildora\Resources\Concerns;

use Ginkelsoft\Buildora\BuildoraQueryBuilder;
use Ginkelsoft\Buildora\Resources\QueryFactory;

/**
* Static query entry points, extracted from BuildoraResource as the third
* decomposition step for #135.
*
* The two methods here are intentionally minimal — they forward to
* QueryFactory, which is where the actual list/detail split lives. Putting
* them in a trait keeps the resource-level call sites (`Order::query()`,
* `Order::queryWithRelations()`) intact while taking the surface out of
* BuildoraResource.
*/
trait HasResourceQuery
{
/**
* Build a query for *list* contexts (datatable, exports, picklists).
* Panel relations are NOT eager-loaded for performance — see
* QueryFactory::forList() vs forDetail() / make() semantics.
*/
public static function query(): BuildoraQueryBuilder
{
return QueryFactory::make(new static(), false);
}

/**
* Build a query for the detail/show view. Panel relations declared by
* getRelationResources() are eager-loaded.
*/
public static function queryWithRelations(): BuildoraQueryBuilder
{
return QueryFactory::make(new static(), true);
}
}
113 changes: 113 additions & 0 deletions tests/Unit/Resources/Concerns/HasResourceQueryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

namespace Ginkelsoft\Buildora\Tests\Unit\Resources\Concerns;

use Ginkelsoft\Buildora\BuildoraQueryBuilder;
use Ginkelsoft\Buildora\Resources\BuildoraResource;
use Ginkelsoft\Buildora\Resources\Concerns\HasResourceQuery;
use Ginkelsoft\Buildora\Tests\TestCase;
use Ginkelsoft\Buildora\Traits\HasBuildora;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Schema;
use PHPUnit\Framework\Attributes\Test;
use ReflectionClass;

class RQArticle extends Model
{
use HasBuildora;
protected $table = 'rq_articles';
protected $guarded = [];
public $timestamps = false;
}

class RQArticleBuildora extends BuildoraResource
{
public static function modelClass(): string
{
return RQArticle::class;
}

public function defineFields(): array
{
return [];
}

public function getRelationResources(): array
{
return [
['relationName' => 'comments'],
];
}
}

class HasResourceQueryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

Schema::create('rq_articles', function ($t) {
$t->increments('id');
$t->string('title')->nullable();
});
}

#[Test]
public function query_returns_a_buildora_query_builder(): void
{
$this->assertInstanceOf(BuildoraQueryBuilder::class, RQArticleBuildora::query());
}

#[Test]
public function query_does_not_eager_load_panel_relations(): void
{
// Behavioural sanity: query() must skip the panel eager-load path
// (that's the whole point of the list/detail split).
$eagerLoads = RQArticleBuildora::query()->getEagerLoads();

$this->assertSame([], array_keys($eagerLoads));
}

#[Test]
public function query_with_relations_eager_loads_declared_panels(): void
{
$eagerLoads = RQArticleBuildora::queryWithRelations()->getEagerLoads();

$this->assertContains('comments', array_keys($eagerLoads));
}

#[Test]
public function buildora_resource_uses_the_query_trait(): void
{
$traits = (new ReflectionClass(BuildoraResource::class))->getTraitNames();

$this->assertContains(HasResourceQuery::class, $traits);
}

#[Test]
public function query_methods_are_no_longer_inlined_in_buildora_resource(): void
{
$resourceSource = file_get_contents(
(new ReflectionClass(BuildoraResource::class))->getFileName()
);

foreach (['query', 'queryWithRelations'] as $movedMethod) {
$this->assertStringNotContainsString(
"public static function {$movedMethod}(",
$resourceSource,
"Method '{$movedMethod}' has been re-inlined into BuildoraResource.php — it should live in HasResourceQuery trait."
);
}

$traitSource = file_get_contents(
(new ReflectionClass(HasResourceQuery::class))->getFileName()
);

foreach (['query', 'queryWithRelations'] as $movedMethod) {
$this->assertStringContainsString(
"function {$movedMethod}(",
$traitSource,
);
}
}
}
Loading