Skip to content

Replication process improvement and code refactoring #25

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
58 changes: 50 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,66 @@ Allow users to duplicate a record through the Laravel Nova Admin Panel along wit
composer require jackabox/nova-duplicate-field
```

### Basic usage
Reference the duplicate field at the top of your Nova resource and then include the necessary code within the fields.

```php
use Jackabox\DuplicateField\DuplicateField

// ...

DuplicateField::make('Duplicate', $this->model(), static::uriKey()),
```

### Customization

#### Except attributes
Pass an array of attributes to not replicate.

```php
DuplicateField::make('Duplicate', $this->model(), static::uriKey())
->except(['status']),
```

#### Override attributes
Pass an array of attributes with values to override.

```php
DuplicateField::make('Duplicate', $this->model(), static::uriKey())
->override(['status' => 'pending']),
```

#### Relations
Pass an array of relations to replicate also.

```php
DuplicateField::make('Duplicate', $this->model(), static::uriKey())
->relations(['translations']),
```

#### Relations except attributes
Pass an array of attributes for each relation to not replicate.

```php
DuplicateField::make('Duplicate', $this->model(), static::uriKey())
->relations(['translations'])
->relationsExcept([
'translations' => ['slug'],
]),
```

#### Relations except attributes
Pass an array of attributes for each relation to override.

```php
DuplicateField::make('Duplicate')
->withMeta([
'resource' => 'specialisms', // resource url
'model' => 'App\Models\Specialism', // model path
'id' => $this->id, // id of record
'relations' => ['one', 'two'], // an array of any relations to load (nullable).
'except' => ['status'], // an array of fields to not replicate (nullable).
'override' => ['status' => 'pending'] // an array of fields and values which will be set on the modal after duplicating (nullable).
DuplicateField::make('Duplicate', $this->model(), static::uriKey())
->relations(['translations'])
->relationsOverride([
'translations' => ['title' => 'New value'],
]),
```

### Note
Duplicate field only works on the index view at the moment (plans to expand this are coming) and already passes through `onlyOnIndex()` as an option.

### Hooking Into Replication
Expand Down
15 changes: 9 additions & 6 deletions resources/js/components/IndexField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ export default {
onClick() {
axios
.post("/nova-vendor/jackabox/nova-duplicate", {
model: this.field.model ? this.field.model : "",
id: this.field.id ? this.field.id : "",
resource: this.field.resource ? this.field.resource : "",
relations: this.field.relations ? this.field.relations : "",
except: this.field.except ? this.field.except : "",
override: this.field.override ? this.field.override : ""
model_class: this.field.model_class,
model_key_name: this.field.model_key_name,
model_key_value: this.field.model_key_value,
resource: this.field.resource,
except: this.field.except || null,
override: this.field.override || null,
relations: this.field.relations || null,
relations_except: this.field.relations_except || null,
relations_override: this.field.relations_override || null,
})
.then(response => {
window.location.replace(response.data.destination);
Expand Down
56 changes: 54 additions & 2 deletions src/DuplicateField.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,62 @@ class DuplicateField extends Field
*/
public $component = 'duplicate-field';

public function __construct(string $name, ? string $attribute = null, ? mixed $resolveCallback = null)
public function __construct(string $name, Model $model, string $resource)
{
parent::__construct(null, null, null);
parent::__construct($name);

$this->withMeta([
'model_class' => get_class($model),
'model_key_name' => $model->getKeyName(),
'model_key_value' => $model->getKey(),
'resource' => $resource,
]);

$this->onlyOnIndex();
}

/**
* @param string[] $attributes
* @return $this
*/
public function except(array $attributes)
{
return $this->withMeta(['except' => $attributes]);
}

/**
* @param string[] $attributes
* @return $this
*/
public function override(array $attributes)
{
return $this->withMeta(['override' => $attributes]);
}

/**
* @param string[] $relations
* @return $this
*/
public function relations(array $relations)
{
return $this->withMeta(['relations' => $relations]);
}

/**
* @param string[] $except
* @return $this
*/
public function relationsExcept(array $except)
{
return $this->withMeta(['relations_except' => $except]);
}

/**
* @param string[] $override
* @return $this
*/
public function relationsOverride(array $override)
{
return $this->withMeta(['relations_override' => $override]);
}
}
98 changes: 71 additions & 27 deletions src/Http/Controllers/DuplicateController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,103 @@

namespace Jackabox\DuplicateField\Http\Controllers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Arr;

class DuplicateController extends Controller
{
/**
* Duplicate a nova field and all of the relations defined.
* @param Request $request
* @return array
*/
public function duplicate(Request $request)
{
// Replicate the model
$model = $request->model::where('id', $request->id)->first();
$modelClass = $request->post('model_class');
$modelKeyName = $request->post('model_key_name');
$modelKeyValue = $request->post('model_key_value');
$resource = $request->post('resource');

$except = Arr::wrap($request->post('except'));
$override = Arr::wrap($request->post('override'));
$relations = Arr::wrap($request->post('relations'));
$relationsExcept = Arr::wrap($request->get('relations_except'));
$relationsOverride = Arr::wrap($request->post('relations_override'));

/** @var Model|null $model */
$model = $modelClass::where($modelKeyName, $modelKeyValue)->first();

if (!$model) {
return [
'status' => 404,
'message' => 'No model found.',
'destination' => config('nova.url') . config('nova.path') . '/resources/' . $request->resource . '/'
'destination' => config('nova.url') . config('nova.path') . '/resources/' . $resource . '/'
];
}

$newModel = $model->replicate($request->except);
$newModel = $this->replicate($model, $except, $override, $relations, $relationsExcept, $relationsOverride);

if (is_array($request->override)) {
foreach ($request->override as $field => $value) {
$newModel->{$field} = $value;
}
// return response and redirect.
return [
'status' => 200,
'message' => 'Done',
'destination' => url(config('nova.path') . '/resources/' . $resource . '/' . $newModel->getKey())
];
}

/**
* @param Model $model
* @param array $except
* @param array $override
* @param array $relations
* @param array $relationsExcept
* @param array $relationsOverride
* @return Model
*/
private function replicate(Model $model, array $except = [], array $override = [], array $relations = [], array $relationsExcept = [], array $relationsOverride = [])
{
$newModel = $model->replicate(array_keys($except));

foreach ($override as $field => $value) {
$newModel->{$field} = $value;
}

$newModel->push();

if (isset($request->relations) && !empty($request->relations)) {
// load the relations
$model->load($request->relations);
$this->replicateRelations($model, $newModel, $relations, $relationsExcept, $relationsOverride);

foreach ($model->getRelations() as $relation => $items) {
// works for hasMany
foreach ($items as $item) {
// clean up our models, remove the id and remove the appends
unset($item->id);
$item->setAppends([]);
return $newModel;
}

// create a relation on the new model with the data.
$newModel->{$relation}()->create($item->toArray());
}
}
/**
* todo: implement deep relations replication
* @param Model $originalModel
* @param Model $newModel
* @param array $relations
* @param array $except
* @param array $override
*/
private function replicateRelations(Model $originalModel, Model $newModel, array $relations = [], array $except = [], array $override = [])
{
// tested only with hasMany

if (!count($relations)){
return;
}

// return response and redirect.
return [
'status' => 200,
'message' => 'Done',
'destination' => url(config('nova.path') . '/resources/' . $request->resource . '/' . $newModel->id)
];
$originalModel->load($relations);

foreach ($originalModel->getRelations() as $relationName => $items) {
/** @var BelongsTo $relation */
$relation = $newModel->{$relationName}();
$relationOverride = array_merge(Arr::wrap(Arr::get($override, $relationName)), [$relation->getForeignKeyName() => $newModel->getKey()]);
/** @var Model $item */
foreach ($items as $item) {
$this->replicate($item, $except, $relationOverride);
}
}
}
}