Skip to content

Commit 530665c

Browse files
authored
Merge pull request #3797 from Laravel-Backpack/replace-belongs-to-names
Replace belongs relation names with foreign key
2 parents df9bcd2 + 51584a1 commit 530665c

File tree

5 files changed

+101
-69
lines changed

5 files changed

+101
-69
lines changed

src/app/Library/CrudPanel/Traits/Create.php

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ public function create($data)
2525
{
2626
$data = $this->decodeJsonCastedAttributes($data);
2727
$data = $this->compactFakeFields($data);
28+
$data = $this->changeBelongsToNamesFromRelationshipToForeignKey($data);
2829

2930
// omit the n-n relationships when updating the eloquent item
3031
$nn_relationships = Arr::pluck($this->getRelationFieldsWithPivot(), 'name');
32+
3133
$item = $this->model->create(Arr::except($data, $nn_relationships));
3234

3335
// if there are any relationships available, also sync those
@@ -237,25 +239,4 @@ private function getRelationDataFromFormData($data)
237239

238240
return $relationData;
239241
}
240-
241-
public function getOnlyRelationEntity($relation_field)
242-
{
243-
$entity_array = explode('.', $relation_field['entity']);
244-
245-
$relation_model = $this->getRelationModel($relation_field['entity'], -1);
246-
247-
$related_method = Arr::last($entity_array);
248-
249-
if (! method_exists($relation_model, $related_method)) {
250-
if (count($entity_array) <= 1) {
251-
return $relation_field['entity'];
252-
} else {
253-
array_pop($entity_array);
254-
}
255-
256-
return implode('.', $entity_array);
257-
}
258-
259-
return $relation_field['entity'];
260-
}
261242
}

src/app/Library/CrudPanel/Traits/FieldsProtectedMethods.php

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -151,24 +151,6 @@ protected function makeSureFieldHasEntity($field)
151151
return $field;
152152
}
153153

154-
protected function makeSureFieldHasRelationshipData($field)
155-
{
156-
// only do this if "entity" is defined on the field
157-
if (! isset($field['entity'])) {
158-
return $field;
159-
}
160-
161-
$extraFieldAttributes = $this->inferFieldAttributesFromRelationship($field);
162-
163-
if (! empty($extraFieldAttributes)) {
164-
$field = array_merge($field, $extraFieldAttributes);
165-
} else {
166-
abort(500, 'Unable to process relationship data: '.$field['name']);
167-
}
168-
169-
return $field;
170-
}
171-
172154
protected function overwriteFieldNameFromEntity($field)
173155
{
174156
// if the entity doesn't have a dot, it means we don't need to overwrite the name

src/app/Library/CrudPanel/Traits/Relationships.php

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Backpack\CRUD\app\Library\CrudPanel\Traits;
44

55
use Illuminate\Support\Arr;
6+
use Illuminate\Support\Str;
67

78
trait Relationships
89
{
@@ -15,33 +16,19 @@ trait Relationships
1516
public function getRelationInstance($field)
1617
{
1718
$entity = $this->getOnlyRelationEntity($field);
18-
$entity_array = explode('.', $entity);
19-
$relation_model = $this->getRelationModel($entity);
20-
21-
$related_method = Arr::last($entity_array);
22-
if (count(explode('.', $entity)) == count(explode('.', $field['entity']))) {
23-
$relation_model = $this->getRelationModel($entity, -1);
24-
}
25-
$relation_model = new $relation_model();
26-
27-
//if counts are diferent means that last element of entity is the field in relation.
28-
if (count(explode('.', $entity)) != count(explode('.', $field['entity']))) {
29-
if (in_array($related_method, $relation_model->getFillable())) {
30-
if (count($entity_array) > 1) {
31-
$related_method = $entity_array[(count($entity_array) - 2)];
32-
$relation_model = $this->getRelationModel($entity, -2);
33-
} else {
34-
$relation_model = $this->model;
35-
}
36-
}
37-
}
38-
if (count($entity_array) == 1) {
39-
if (method_exists($this->model, $related_method)) {
40-
return $this->model->{$related_method}();
19+
$possible_method = Str::before($entity, '.');
20+
$model = $this->model;
21+
22+
if (method_exists($model, $possible_method)) {
23+
$parts = explode('.', $entity);
24+
// here we are going to iterate through all relation parts to check
25+
foreach ($parts as $i => $part) {
26+
$relation = $model->$part();
27+
$model = $relation->getRelated();
4128
}
42-
}
4329

44-
return $relation_model->{$related_method}();
30+
return $relation;
31+
}
4532
}
4633

4734
/**
@@ -70,6 +57,41 @@ public function inferRelationTypeFromRelationship($field)
7057
return Arr::last(explode('\\', get_class($relation)));
7158
}
7259

60+
public function getOnlyRelationEntity($relation_field)
61+
{
62+
$relation_model = $this->getRelationModel($relation_field['entity'], -1);
63+
$related_method = Str::afterLast($relation_field['entity'], '.');
64+
65+
if (! method_exists($relation_model, $related_method)) {
66+
return Str::beforeLast($relation_field['entity'], '.');
67+
}
68+
69+
return $relation_field['entity'];
70+
}
71+
72+
/**
73+
* Get the fields for relationships, according to the relation type. It looks only for direct
74+
* relations - it will NOT look through relationships of relationships.
75+
*
76+
* @param string|array $relation_types Eloquent relation class or array of Eloquent relation classes. Eg: BelongsTo
77+
*
78+
* @return array The fields with corresponding relation types.
79+
*/
80+
public function getFieldsWithRelationType($relation_types): array
81+
{
82+
$relation_types = (array) $relation_types;
83+
84+
return collect($this->fields())
85+
->where('model')
86+
->whereIn('relation_type', $relation_types)
87+
->filter(function ($item) {
88+
$related_model = get_class($this->model->{Str::before($item['entity'], '.')}()->getRelated());
89+
90+
return Str::contains($item['entity'], '.') && $item['model'] !== $related_model ? false : true;
91+
})
92+
->toArray();
93+
}
94+
7395
/**
7496
* Parse the field name back to the related entity after the form is submited.
7597
* Its called in getAllFieldNames().
@@ -96,6 +118,21 @@ public function parseRelationFieldNamesFromHtml($fields)
96118
return $fields;
97119
}
98120

121+
protected function changeBelongsToNamesFromRelationshipToForeignKey($data)
122+
{
123+
$belongs_to_fields = $this->getFieldsWithRelationType('BelongsTo');
124+
125+
foreach ($belongs_to_fields as $relation_field) {
126+
$relation = $this->getRelationInstance($relation_field);
127+
if (Arr::has($data, $relation->getRelationName())) {
128+
$data[$relation->getForeignKeyName()] = Arr::get($data, $relation->getRelationName());
129+
unset($data[$relation->getRelationName()]);
130+
}
131+
}
132+
133+
return $data;
134+
}
135+
99136
/**
100137
* Based on relation type returns the default field type.
101138
*

src/app/Library/CrudPanel/Traits/Update.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public function update($id, $data)
2626
$data = $this->compactFakeFields($data);
2727
$item = $this->model->findOrFail($id);
2828

29+
$data = $this->changeBelongsToNamesFromRelationshipToForeignKey($data);
30+
2931
$this->createRelations($item, $data);
3032

3133
// omit the n-n relationships when updating the eloquent item

tests/Unit/CrudPanel/CrudPanelCreateTest.php

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ class CrudPanelCreateTest extends BaseDBCrudPanelTest
106106
],
107107
];
108108

109+
private $articleInputBelongsToRelationName = [
110+
[
111+
'name' => 'user',
112+
],
113+
];
114+
109115
public function testCreate()
110116
{
111117
$this->crudPanel->setModel(User::class);
@@ -124,26 +130,50 @@ public function testCreate()
124130
$this->assertEmpty($entry->articles);
125131
}
126132

127-
/**
128-
* @group failing
129-
*/
130133
public function testCreateWithOneToOneRelationship()
131134
{
132135
$this->crudPanel->setModel(User::class);
133136
$this->crudPanel->addFields($this->userInputFieldsNoRelationships);
134137
$this->crudPanel->addFields($this->userInputHasOneRelation);
135138
$faker = Factory::create();
139+
$account_details_nickname = $faker->name;
136140
$inputData = [
137141
'name' => $faker->name,
138142
'email' => $faker->safeEmail,
139143
'password' => bcrypt($faker->password()),
140144
'accountDetails' => [
141-
'nickname' => $faker->name,
145+
'nickname' => $account_details_nickname,
142146
'profile_picture' => 'test.jpg',
143147
],
144148
];
145149
$entry = $this->crudPanel->create($inputData);
146-
$this->markTestIncomplete('Has one relation is not created in tests.');
150+
$account_details = $entry->accountDetails()->first();
151+
152+
$this->assertEquals($account_details->nickname, $account_details_nickname);
153+
}
154+
155+
public function testCreateBelongsToWithRelationName()
156+
{
157+
$this->crudPanel->setModel(Article::class);
158+
$this->crudPanel->addFields($this->articleInputFieldsOneToMany);
159+
$this->crudPanel->removeField('user_id');
160+
$this->crudPanel->addFields($this->articleInputBelongsToRelationName);
161+
$faker = Factory::create();
162+
$inputData = [
163+
'content' => $faker->text(),
164+
'tags' => $faker->words(3, true),
165+
'user' => 1,
166+
'metas' => null,
167+
'extras' => null,
168+
'cast_metas' => null,
169+
'cast_tags' => null,
170+
'cast_extras' => null,
171+
];
172+
$entry = $this->crudPanel->create($inputData);
173+
$userEntry = User::find(1);
174+
$article = Article::where('user_id', 1)->with('user')->get()->last();
175+
$this->assertEquals($article->user_id, $entry->user_id);
176+
$this->assertEquals($article->id, $entry->id);
147177
}
148178

149179
public function testCreateWithOneToManyRelationship()

0 commit comments

Comments
 (0)