Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] feat: HasMany/BelongsToMany #1355

Merged
merged 7 commits into from
Dec 1, 2024
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
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)
;
});
Loading