Skip to content

Commit

Permalink
Merge pull request #1355 from moonshine-software/has-many-improvements
Browse files Browse the repository at this point in the history
[3.x] feat: HasMany/BelongsToMany
  • Loading branch information
lee-to authored Dec 1, 2024
2 parents 4c6486e + 869eeb9 commit f2a8d99
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 43 deletions.
46 changes: 24 additions & 22 deletions src/Laravel/src/Fields/Relationships/HasMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace MoonShine\Laravel\Fields\Relationships;

use Closure;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Contracts\Support\Renderable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
Expand All @@ -20,6 +21,7 @@
use MoonShine\Contracts\UI\TableBuilderContract;
use MoonShine\Laravel\Buttons\HasManyButton;
use MoonShine\Laravel\Collections\Fields;
use MoonShine\Laravel\Resources\ModelResource;
use MoonShine\Laravel\Traits\Fields\WithRelatedLink;
use MoonShine\UI\Components\ActionGroup;
use MoonShine\UI\Components\Table\TableBuilder;
Expand Down Expand Up @@ -340,13 +342,28 @@ public function prepareClonedFields(): FieldsContract
*/
protected function getTablePreview(): TableBuilderContract
{
$items = $this->toValue();
/** @var ModelResource $resource */
$resource = clone $this->getResource()
->disableSaveQueryState();

if (filled($items)) {
$items = $items->take($this->getLimit());
}
// If the records are already in memory (eager load) and there is no modifier, then we take the records from memory
if (\is_null($this->modifyBuilder) && $this->getRelatedModel()?->relationLoaded($this->getRelationName()) === true) {
$items = $this->toRelatedCollection();
} else {
$resource->disableQueryFeatures();

$casted = $this->getRelatedModel();
$relation = $casted?->{$this->getRelationName()}();

$resource = $this->getResource();
/** @var Builder $query */
$query = \is_null($this->modifyBuilder)
? $relation
: value($this->modifyBuilder, $relation);

$resource->customQueryBuilder($query->limit($this->getLimit()));

$items = $resource->getQuery()->get();
}

return TableBuilder::make(items: $items)
->fields($this->getFieldsOnPreview())
Expand Down Expand Up @@ -490,7 +507,7 @@ protected function prepareFill(array $raw = [], ?DataWrapperContract $casted = n

protected function resolveRawValue(): mixed
{
return collect($this->toValue())
return $this->toRelatedCollection()
->map(fn (Model $item) => data_get($item, $this->getResourceColumn()))
->implode(';');
}
Expand All @@ -500,12 +517,6 @@ protected function resolveRawValue(): mixed
*/
protected function resolvePreview(): Renderable|string
{
// resolve value before call toValue
if (\is_null($this->toValue())) {
$casted = $this->getRelatedModel();
$this->setValue($casted?->{$this->getRelationName()});
}

return $this->isRelatedLink()
? $this->getRelatedLink()->render()
: $this->getTablePreview()->render();
Expand All @@ -532,23 +543,14 @@ protected function resolveValue(): mixed
: value($this->modifyBuilder, $relation)
);

$items = $resource->getItems();

$this->setValue($items);

return $items;
return $resource->getItems();
}

/**
* @throws Throwable
*/
public function getComponent(): ComponentContract
{
// resolve value before call toValue
if (\is_null($this->toValue())) {
$this->setValue($this->getValue());
}

return $this->isRelatedLink()
? $this->getRelatedLink()
: $this->getTableValue();
Expand Down
25 changes: 13 additions & 12 deletions src/Laravel/src/Traits/Fields/WithRelatedLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace MoonShine\Laravel\Traits\Fields;

use Closure;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use MoonShine\Contracts\UI\ActionButtonContract;
use MoonShine\Laravel\Fields\Relationships\BelongsToMany;
Expand Down Expand Up @@ -37,16 +36,20 @@ public function relatedLink(?string $linkRelation = null, Closure|bool|null $con
return $this;
}

public function toRelatedCollection(): Collection
{
return $this->getRelatedModel()?->{$this->getRelationName()} ?? new Collection();
}

protected function isRelatedLink(): bool
{
if (\is_callable($this->isRelatedLink) && \is_null($this->toValue())) {
return (bool) value($this->isRelatedLink, 0, $this);
if ($this->isRelatedLink === false) {
return false;
}

if (\is_callable($this->isRelatedLink)) {
$count = $this->toValue() instanceof Collection
? $this->toValue()->count()
: $this->toValue()->total();
$value = $this->toRelatedCollection();
$count = $value->count();

return (bool) value($this->isRelatedLink, $count, $this);
}
Expand Down Expand Up @@ -75,22 +78,20 @@ protected function getRelatedLink(bool $preview = false): ActionButtonContract
{
$relationName = $this->getRelatedLinkRelation();

$value = $this->toValue();
$count = $value instanceof LengthAwarePaginator
? $value->total()
: $value->count();
$value = $this->toRelatedCollection();
$count = $value->count();

return ActionButton::make(
'',
url: $this->getResource()->getIndexPageUrl([
'_parentId' => $relationName . '-' . $this->getRelatedModel()?->getKey(),
])
]),
)
->badge($count)
->icon('eye')
->when(
! \is_null($this->modifyRelatedLink),
fn (ActionButtonContract $button) => value($this->modifyRelatedLink, $button, $preview)
fn (ActionButtonContract $button) => value($this->modifyRelatedLink, $button, $preview),
);
}

Expand Down
18 changes: 18 additions & 0 deletions src/Laravel/src/Traits/Resource/ResourceModelQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ trait ResourceModelQuery

protected ?Builder $customQueryBuilder = null;

protected bool $disableQueryFeatures = false;

/**
* @throws Throwable
*/
Expand Down Expand Up @@ -138,6 +140,10 @@ public function customQueryBuilder(Builder $builder): static
*/
protected function queryBuilderFeatures(): void
{
if ($this->isDisabledQueryFeatures()) {
return;
}

$this
->withCache()
->withTags()
Expand All @@ -148,6 +154,18 @@ protected function queryBuilderFeatures(): void
->withCachedQueryParams();
}

public function isDisabledQueryFeatures(): bool
{
return $this->disableQueryFeatures;
}

public function disableQueryFeatures(): static
{
$this->disableQueryFeatures = true;

return $this;
}

public function isItemExists(): bool
{
return ! \is_null($this->getItem()) && $this->getItem()->exists;
Expand Down
54 changes: 45 additions & 9 deletions tests/Feature/Fields/Relationships/HasManyFieldTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@
uses()->group('model-relation-fields');
uses()->group('has-many-field');

use Illuminate\Database\Eloquent\Relations\Relation;
use MoonShine\Laravel\Fields\Relationships\HasMany;
use MoonShine\Laravel\Pages\Crud\FormPage;
use MoonShine\Laravel\Pages\Crud\IndexPage;
use MoonShine\Tests\Fixtures\Models\Comment;
use MoonShine\Tests\Fixtures\Models\Item;
use MoonShine\Tests\Fixtures\Resources\TestCommentResource;
use MoonShine\Tests\Fixtures\Resources\TestResourceBuilder;
use MoonShine\UI\Fields\Field;
use MoonShine\UI\Fields\ID;
use MoonShine\UI\Fields\Text;

it('onlyLink preview', function () {
it('relatedLink preview', function () {
createItem(countComments: 6);

$resource = TestResourceBuilder::new(Item::class)->setTestFields([
Expand All @@ -31,7 +32,7 @@
;
});

it('onlyLink preview empty', function () {
it('relatedLink preview empty', function () {
createItem(countComments: 0);

$resource = TestResourceBuilder::new(Item::class)->setTestFields([
Expand All @@ -47,7 +48,7 @@
;
});

it('onlyLink value', function () {
it('relatedLink value', function () {
$item = createItem(countComments: 16);

$resource = TestResourceBuilder::new(Item::class)->setTestFields([
Expand All @@ -64,7 +65,7 @@
;
});

it('onlyLink value empty', function () {
it('relatedLink value empty', function () {
$item = createItem(countComments: 0);

$resource = TestResourceBuilder::new(Item::class)->setTestFields([
Expand All @@ -80,7 +81,7 @@
;
});

it('onlyLink preview condition', function () {
it('relatedLink preview condition', function () {
$item = createItem(countComments: 6);

$resource = TestResourceBuilder::new(Item::class)->setTestFields([
Expand All @@ -102,15 +103,15 @@
;
});

it('onlyLink value condition', function () {
it('relatedLink value condition', function () {
$item = createItem(countComments: 16);

$resource = TestResourceBuilder::new(Item::class)->setTestFields([
ID::make(),
Text::make('Name'),
HasMany::make('Comments title', 'comments', resource: TestCommentResource::class)
->relatedLink(condition: static function (int $count, Field $field): bool {
return $field->toValue()->total() > 20;
->relatedLink(condition: static function (int $count, HasMany $field): bool {
return $field->toRelatedCollection()->count() > 20;
})
,
]);
Expand Down Expand Up @@ -163,3 +164,38 @@
expect($hasMany->getResource()->getItemID())
->toBeNull();
});

it('modify builder', function () {
$item = createItem(countComments: 2);

$comments = Comment::query()->get();

$commentFirst = $comments->first();
$commentLast = $comments->last();

$resource = TestResourceBuilder::new(Item::class)->setTestFields([
ID::make(),
Text::make('Name'),
HasMany::make('Comments title', 'comments', resource: TestCommentResource::class)
->modifyBuilder(
fn (Relation $relation) => $relation->where('id', $commentFirst->id)
)
,
]);

asAdmin()
->get($this->moonshineCore->getRouter()->getEndpoints()->toPage(page: IndexPage::class, resource: $resource))
->assertOk()
->assertSee('Comments title')
->assertSee($commentFirst->content)
->assertDontSee($commentLast->content)
;

asAdmin()
->get($this->moonshineCore->getRouter()->getEndpoints()->toPage(page: FormPage::class, resource: $resource, params: ['resourceItem' => $item->id]))
->assertOk()
->assertSee('Comments title')
->assertSee($commentFirst->content)
->assertDontSee($commentLast->content)
;
});

0 comments on commit f2a8d99

Please sign in to comment.