diff --git a/src/Fields/BelongsTo.php b/src/Fields/BelongsTo.php index 88983ebb3..c9952db31 100644 --- a/src/Fields/BelongsTo.php +++ b/src/Fields/BelongsTo.php @@ -3,6 +3,7 @@ namespace Binaryk\LaravelRestify\Fields; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; +use Binaryk\LaravelRestify\Repositories\Repository; use Closure; use Illuminate\Database\Eloquent\Model; @@ -10,6 +11,18 @@ class BelongsTo extends EagerField { public ?Closure $storeParentCallback; + public function __construct($attribute, $relation, $parentRepository) + { + if (! is_a(app($parentRepository), Repository::class)) { + abort(500, "Invalid parent repository [{$parentRepository}]. Expended instance of ".Repository::class); + } + + parent::__construct($attribute); + + $this->relation = $relation; + $this->parentRepository = $parentRepository; + } + public function storeParent(RestifyRequest $request, Model $child): self { if (is_callable($this->storeParentCallback)) { @@ -23,7 +36,7 @@ public function storeParent(RestifyRequest $request, Model $child): self $child->{$this->attribute} = null; - $child->{$this->attribute} = $child->{$this->relationship}()->create( + $child->{$this->attribute} = $child->{$this->relation}()->create( $request->input($this->attribute) ); @@ -36,4 +49,13 @@ public function storeParentCallback(callable $callback) return $this; } + + public function resolve($repository, $attribute = null) + { + $model = $repository->resource; + + $this->value = $this->parentRepository::resolveWith( + $model->{$this->relation}()->first() + ); + } } diff --git a/src/Fields/BelongsToMany.php b/src/Fields/BelongsToMany.php new file mode 100644 index 000000000..f25d78118 --- /dev/null +++ b/src/Fields/BelongsToMany.php @@ -0,0 +1,11 @@ +relation = $relation; + $this->parentRepository = $parentRepository; + } + + public function storeChild(RestifyRequest $request, Model $parent): self + { + if (is_callable($this->storeParentCallback)) { + call_user_func_array($this->storeParentCallback, [ + $request, + $parent, + ]); + + return $this; + } + + $parent->{$this->attribute} = null; + + $repository = $this->parentRepository::resolveWith( + $model = $parent->{$this->relation}()->getModel() + )->allowToStore($request, $request->input($this->attribute)); + + $model = new $model; + + if ($model instanceof CreationAware) { + $model::createWithAttributes($request->input($this->attribute)); + + return $this; + } + + $parent->{$this->attribute} = $repository::resolveWith( + $model->create($request->input($this->attribute)) + ); + + return $this; + } + + public function storeParentCallback(callable $callback) + { + $this->storeParentCallback = $callback; + + return $this; + } + + public function resolve($repository, $attribute = null) + { + $model = $repository->resource; + + $this->value = $this->parentRepository::resolveWith( + $model->{$this->relation}()->first() + ); + } +} diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index 2a3ae52c5..6664b2ef2 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -5,8 +5,10 @@ use Binaryk\LaravelRestify\Contracts\RestifySearchable; use Binaryk\LaravelRestify\Controllers\RestResponse; use Binaryk\LaravelRestify\Exceptions\InstanceOfException; +use Binaryk\LaravelRestify\Fields\EagerField; use Binaryk\LaravelRestify\Fields\Field; use Binaryk\LaravelRestify\Fields\FieldCollection; +use Binaryk\LaravelRestify\Fields\HasOne; use Binaryk\LaravelRestify\Filter; use Binaryk\LaravelRestify\Http\Requests\RepositoryStoreBulkRequest; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; @@ -319,10 +321,19 @@ private function updateBulkFields(RestifyRequest $request) private function storeFields(RestifyRequest $request) { return $this->collectFields($request) + ->filter(fn (Field $field) => ! $field instanceof EagerField) ->forStore($request, $this) ->authorizedStore($request); } + private function hasOneFields(RestifyRequest $request) + { + return $this->collectFields($request) + ->forStore($request, $this) + ->filter(fn (Field $field) => $field instanceof HasOne) + ->authorizedStore($request); + } + private function storeBulkFields(RestifyRequest $request) { return $this->collectFields($request) @@ -641,6 +652,9 @@ public function store(RestifyRequest $request) } } + $this->hasOneFields($request) + ->each(fn (HasOne $field) => $field->storeChild($request, $this->resource)); + $this->storeFields($request)->each(fn (Field $field) => $field->invokeAfter($request, $this->resource)); }); diff --git a/tests/Fixtures/User/User.php b/tests/Fixtures/User/User.php index acb2e642b..eef8d485f 100644 --- a/tests/Fixtures/User/User.php +++ b/tests/Fixtures/User/User.php @@ -82,6 +82,11 @@ public function posts() return $this->hasMany(Post::class); } + public function post() + { + return $this->hasOne(Post::class); + } + public function companies() { return $this->belongsToMany(Company::class, 'company_user', 'user_id', 'company_id')->withPivot([ diff --git a/tests/Unit/BelongsToFieldTest.php b/tests/Unit/BelongsToFieldTest.php new file mode 100644 index 000000000..2dc81d9d8 --- /dev/null +++ b/tests/Unit/BelongsToFieldTest.php @@ -0,0 +1,79 @@ +create([ + 'user_id' => factory(User::class), + ]); + + $this->getJson('/restify-api/'.PostWithUserRepository::uriKey()) + ->assertJsonStructure([ + 'data' => [ + [ + 'attributes' => [ + 'user' => [ + 'attributes', + ], + ], + ], + ], + ]); + } + + public function test_belongs_to_field_is_ignored_when_storing() + { + factory(Post::class)->create([ + 'user_id' => factory(User::class), + ]); + + $this->postJson('/restify-api/'.PostWithUserRepository::uriKey(), [ + 'title' => 'New Post with user', + 'user' => [ + 'name' => 'Eduard Lupacescu', + 'email' => 'eduard.lupacescu@binarcode.com', + ], + ]) + ->assertCreated(); + } +} + +class PostWithUserRepository extends Repository +{ + public static $model = Post::class; + + public function fields(RestifyRequest $request) + { + return [ + Field::make('title'), + + BelongsTo::make('user', 'user', UserRepository::class), + ]; + } + + public static function uriKey() + { + return 'posts-with-user-repository'; + } +} diff --git a/tests/Unit/HasOneFieldTest.php b/tests/Unit/HasOneFieldTest.php new file mode 100644 index 000000000..abede4bad --- /dev/null +++ b/tests/Unit/HasOneFieldTest.php @@ -0,0 +1,99 @@ +create(); + factory(Post::class)->create([ + 'user_id' => $user->id, + ]); + + $this->getJson('/restify-api/'.UserWithPostRepository::uriKey()) + ->assertJsonStructure([ + 'data' => [ + [ + 'attributes' => [ + 'name', + 'post', + ], + ], + ], + ]); + } + + public function test_has_one_field_is_saved_when_storing() + { + factory(Post::class)->create([ + 'user_id' => factory(User::class), + ]); + + $this->postJson('/restify-api/'.UserWithPostRepository::uriKey(), [ + 'name' => 'Eduard Lupacescu', + 'email' => 'eduard.lupacescu@binarcode.com', + 'password' => 'secret!', + 'post' => [ + 'title' => 'New Post with user', + 'description' => 'New Post description', + ], + ]) + ->dump() + ->assertCreated(); + } +} + +class UserWithPostRepository extends Repository +{ + public static $model = User::class; + + public function fields(RestifyRequest $request) + { + return [ + Field::new('name'), + Field::new('email'), + Field::new('password'), + + HasOne::make('post', 'post', PostRepository::class), + ]; + } + + public static function uriKey() + { + return 'user-with-post-repository'; + } +} + +class PostRepository extends Repository +{ + public static $model = Post::class; + + public function fields(RestifyRequest $request) + { + return [ + Field::new('title')->storingRules('required')->messages([ + 'required' => 'This field is required', + ]), + + Field::new('description')->storingRules('required'), + ]; + } +}