Skip to content

Commit a3118f4

Browse files
authored
Attach related repositories (#182)
* Attach related repositories * Apply fixes from StyleCI (#181)
1 parent 36c26b2 commit a3118f4

15 files changed

+335
-2
lines changed

routes/api.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Binaryk\LaravelRestify\Http\Controllers\GlobalSearchController;
4+
use Binaryk\LaravelRestify\Http\Controllers\RepositoryAttachController;
45
use Binaryk\LaravelRestify\Http\Controllers\RepositoryDestroyController;
56
use Binaryk\LaravelRestify\Http\Controllers\RepositoryFilterController;
67
use Binaryk\LaravelRestify\Http\Controllers\RepositoryIndexController;
@@ -22,3 +23,6 @@
2223
Route::patch('/{repository}/{repositoryId}', '\\'.RepositoryUpdateController::class);
2324
Route::put('/{repository}/{repositoryId}', '\\'.RepositoryUpdateController::class);
2425
Route::delete('/{repository}/{repositoryId}', '\\'.RepositoryDestroyController::class);
26+
27+
// Attach related repository id
28+
Route::post('/{repository}/{repositoryId}/attach/{relatedRepository}', '\\'.RepositoryAttachController::class);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Http\Controllers;
4+
5+
use Binaryk\LaravelRestify\Http\Requests\RepositoryAttachRequest;
6+
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
7+
use DateTime;
8+
use Illuminate\Support\Arr;
9+
10+
class RepositoryAttachController extends RepositoryController
11+
{
12+
public function __invoke(RepositoryAttachRequest $request)
13+
{
14+
$model = $request->findModelOrFail();
15+
$repository = $request->repository()->allowToUpdate($request);
16+
17+
return $repository->attach(
18+
$request, $request->repositoryId,
19+
collect(Arr::wrap($request->input($request->relatedRepository)))
20+
->map(fn ($relatedRepositoryId) => $this->initializePivot(
21+
$request, $model->{$request->viaRelationship ?? $request->relatedRepository}(), $relatedRepositoryId
22+
))
23+
);
24+
}
25+
26+
/**
27+
* Initialize a fresh pivot model for the relationship.
28+
*
29+
* @param RestifyRequest $request
30+
* @param $relationship
31+
* @return mixed
32+
* @throws \Binaryk\LaravelRestify\Exceptions\Eloquent\EntityNotFoundException
33+
* @throws \Binaryk\LaravelRestify\Exceptions\UnauthorizedException
34+
*/
35+
protected function initializePivot(RestifyRequest $request, $relationship, $relatedKey)
36+
{
37+
$parentKey = $request->repositoryId;
38+
39+
$parentKeyName = $relationship->getParentKeyName();
40+
$relatedKeyName = $relationship->getRelatedKeyName();
41+
42+
if ($parentKeyName !== $request->model()->getKeyName()) {
43+
$parentKey = $request->findModelOrFail()->{$parentKeyName};
44+
}
45+
46+
if ($relatedKeyName !== ($request->newRelatedRepository()::newModel())->getKeyName()) {
47+
$relatedKey = $request->findRelatedModelOrFail()->{$relatedKeyName};
48+
}
49+
50+
($pivot = $relationship->newPivot())->forceFill([
51+
$relationship->getForeignPivotKeyName() => $parentKey,
52+
$relationship->getRelatedPivotKeyName() => $relatedKey,
53+
]);
54+
55+
if ($relationship->withTimestamps) {
56+
$pivot->forceFill([
57+
$relationship->createdAt() => new DateTime,
58+
$relationship->updatedAt() => new DateTime,
59+
]);
60+
}
61+
62+
return $pivot;
63+
}
64+
}

src/Http/Requests/InteractWithRepositories.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,41 @@ public function findModelQuery($repositoryId = null, $uriKey = null)
152152
);
153153
}
154154

155+
public function findModelOrFail($id = null)
156+
{
157+
if ($id) {
158+
return $this->findModelQuery($id)->firstOrFail();
159+
}
160+
161+
return once(function () {
162+
return $this->findModelQuery()->firstOrFail();
163+
});
164+
}
165+
166+
public function findRelatedModelOrFail()
167+
{
168+
return once(function () {
169+
return $this->findRelatedQuery()->firstOrFail();
170+
});
171+
}
172+
173+
public function findRelatedQuery($relatedRepository = null, $relatedRepositoryId = null)
174+
{
175+
return $this->repository($relatedRepository ?? request('relatedRepository'))::newModel()
176+
->newQueryWithoutScopes()
177+
->whereKey($relatedRepositoryId ?? request('relatedRepositoryId'));
178+
}
179+
180+
protected function findPivot(RestifyRequest $request, $model)
181+
{
182+
$pivot = $model->{$request->relatedRepository}()->getPivotAccessor();
183+
184+
return $model->{$request->viaRelationship}()
185+
->withoutGlobalScopes()
186+
->lockForUpdate()
187+
->findOrFail($request->relatedRepositoryId)->{$pivot};
188+
}
189+
155190
public function viaParentModel()
156191
{
157192
$parent = $this->repository($this->viaRepository);
@@ -163,4 +198,21 @@ public function viaQuery()
163198
{
164199
return $this->viaParentModel()->{$this->viaRelationship}();
165200
}
201+
202+
/**
203+
* Get a new instance of the "related" resource being requested.
204+
*
205+
* @return Repository
206+
*/
207+
public function newRelatedRepository()
208+
{
209+
$resource = $this->relatedRepository();
210+
211+
return new $resource($resource::newModel());
212+
}
213+
214+
public function relatedRepository()
215+
{
216+
return Restify::repositoryForKey($this->relatedRepository);
217+
}
166218
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Http\Requests;
4+
5+
class RepositoryAttachRequest extends RestifyRequest
6+
{
7+
}

src/Http/Requests/RestifyRequest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Binaryk\LaravelRestify\Http\Requests;
44

5+
use Binaryk\LaravelRestify\Restify;
56
use Illuminate\Foundation\Http\FormRequest;
67
use Illuminate\Support\Facades\App;
78

src/Repositories/Repository.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,18 @@ public function update(RestifyRequest $request, $repositoryId)
574574
->success();
575575
}
576576

577+
public function attach(RestifyRequest $request, $repositoryId, Collection $pivots)
578+
{
579+
DB::transaction(function () use ($request, $pivots) {
580+
return $pivots->map(fn ($pivot) => $pivot->forceFill($request->except($request->relatedRepository)))
581+
->map(fn ($pivot) => $pivot->save());
582+
});
583+
584+
return $this->response()
585+
->data($pivots)
586+
->created();
587+
}
588+
577589
public function destroy(RestifyRequest $request, $repositoryId)
578590
{
579591
$status = DB::transaction(function () {

tests/Controllers/RepositoryFilterControllerTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ public function test_can_get_available_filters()
1616
$response = $this
1717
->withoutExceptionHandling()
1818
->getJson('restify-api/posts/filters')
19-
->dump()
2019
->assertStatus(200);
2120

2221
$this->assertCount(3, $response->json('data'));
@@ -39,7 +38,6 @@ public function test_the_boolean_filter_is_applied()
3938
$response = $this
4039
->withoutExceptionHandling()
4140
->getJson('restify-api/posts?filters='.$filters)
42-
->dump()
4341
->assertStatus(200);
4442

4543
$this->assertCount(1, $response->json('data'));
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Tests\Controllers;
4+
5+
use Binaryk\LaravelRestify\Tests\Fixtures\Company\Company;
6+
use Binaryk\LaravelRestify\Tests\IntegrationTest;
7+
8+
class RepositoryPivotControllerTest extends IntegrationTest
9+
{
10+
public function test_attach_a_user_to_a_company()
11+
{
12+
$user = $this->mockUsers(2)->first();
13+
$company = factory(Company::class)->create();
14+
15+
$response = $this->postJson('restify-api/companies/'.$company->id.'/attach/users', [
16+
'users' => $user->id,
17+
'is_admin' => true,
18+
])
19+
->assertStatus(201);
20+
21+
$response->assertJsonFragment([
22+
'company_id' => '1',
23+
'user_id' => $user->id,
24+
'is_admin' => true,
25+
]);
26+
}
27+
28+
public function test_attach_multiple_users_to_a_company()
29+
{
30+
$user = $this->mockUsers(2)->first();
31+
$company = factory(Company::class)->create();
32+
$usersFromCompany = $this->getJson('/restify-api/users?viaRepository=companies&viaRepositoryId=1&viaRelationship=users');
33+
$this->assertCount(0, $usersFromCompany->json('data'));
34+
35+
$response = $this->postJson('restify-api/companies/'.$company->id.'/attach/users', [
36+
'users' => [1, 2],
37+
'is_admin' => true,
38+
])
39+
->assertStatus(201);
40+
41+
$response->assertJsonFragment([
42+
'company_id' => '1',
43+
'user_id' => $user->id,
44+
'is_admin' => true,
45+
]);
46+
47+
$usersFromCompany = $this->getJson('/restify-api/users?viaRepository=companies&viaRepositoryId=1&viaRelationship=users');
48+
$this->assertCount(2, $usersFromCompany->json('data'));
49+
}
50+
51+
public function test_after_attach_a_user_to_company_number_of_users_increased()
52+
{
53+
$user = $this->mockUsers()->first();
54+
$company = factory(Company::class)->create();
55+
56+
$usersFromCompany = $this->getJson('/restify-api/users?viaRepository=companies&viaRepositoryId=1&viaRelationship=users');
57+
$this->assertCount(0, $usersFromCompany->json('data'));
58+
59+
$this->postJson('restify-api/companies/'.$company->id.'/attach/users', [
60+
'users' => $user->id,
61+
]);
62+
63+
$usersFromCompany = $this->getJson('/restify-api/users?viaRepository=companies&viaRepositoryId=1&viaRelationship=users');
64+
$this->assertCount(1, $usersFromCompany->json('data'));
65+
}
66+
}

tests/Factories/CompanyFactory.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
use Binaryk\LaravelRestify\Tests\Fixtures\Company\Company;
4+
use Faker\Generator as Faker;
5+
6+
/*
7+
|--------------------------------------------------------------------------
8+
| Model Factories
9+
|--------------------------------------------------------------------------
10+
|
11+
| This directory should contain each of the model factory definitions for
12+
| your application. Factories provide a convenient way to generate new
13+
| model instances for testing / seeding your application's database.
14+
|
15+
*/
16+
17+
$factory->define(Company::class, function (Faker $faker) {
18+
return [
19+
'name' => $faker->name,
20+
];
21+
});

tests/Fixtures/Company/Company.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Tests\Fixtures\Company;
4+
5+
use Binaryk\LaravelRestify\Tests\Fixtures\User\User;
6+
use Illuminate\Database\Eloquent\Model;
7+
8+
class Company extends Model
9+
{
10+
protected $fillable = [
11+
'id',
12+
'name',
13+
];
14+
15+
public function users()
16+
{
17+
return $this->belongsToMany(User::class, 'company_user', 'company_id', 'user_id')
18+
->withPivot([
19+
'is_admin',
20+
])
21+
->withTimestamps();
22+
}
23+
}

0 commit comments

Comments
 (0)