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

[QUESTION] CRUD 1-1 relationship #637

Closed
ivanmart opened this issue Apr 25, 2017 · 9 comments
Closed

[QUESTION] CRUD 1-1 relationship #637

ivanmart opened this issue Apr 25, 2017 · 9 comments

Comments

@ivanmart
Copy link

I have 3 models: Product, Type1 and Type2. The product can be one of two types, that have different set of attributes. A Product model has one-to-one relations with both Type1 and Type2 models. What I need - is a single edit page for Product model in Backpack, where Type1 or Type2 model fields should appear, depend on product's type. I have two questions:

1.- What could be the best way to integrate it in Backpack?

2.- How do I add a field from related model Type1, for example?
I've tried (in ProductCrudController.php):

$this->crud->addField([
    'name'  => 'type1->field1', // it works in Tinker: $p->type1->field1
    'label' => 'Field1',
    'type' => 'text',
]);

doesn't work.

@ivanmart ivanmart changed the title CRUD 1-1 relationship [QUESTION] CRUD 1-1 relationship Apr 25, 2017
@lloy0076
Copy link
Contributor

@ivanmart - there's request for support of one-to-one relationships at #250

Depending on what you need, the way I've implemented such functionality is the following:

  1. Define an accessor and mutator for the field (see https://laravel.com/docs/5.4/eloquent-mutators) on the model
  2. Add the name to the $fillable array on the model
  3. Then stop using $this->crud->createFromDb() because it only looks at the DB for columns to make the list view, edit fields and create fields and defined all the columns/edit/update fields manually.

At the moment, if you're using the Ajax Table support, the field can't/won't be shown in the list view (i.e. what you get at /admin/test) but there's support coming soon that I think will solve that issue.

@ivanmart
Copy link
Author

@lloy0076 thank you, doing by this I'm able to add fields to form at least.

But is it possible to show certain fields depending on model's attribute value? E.g. if we have Product of type1, we show first set of fields, if not - we show another set. For now I cannot see any way to do that inside the setUp method.

@ivanmart
Copy link
Author

BTW, how the mutator should look like? I've tried this:

public function setFooAttribute($value)
{
    $this->type1->foo = $value;
    $this->type1->save();
}

It works, but it seems a bad idea to save the related model in each mutator. Where would it be better to call this save() method?

@lloy0076
Copy link
Contributor

    /**
     * Sets the status attribute.
     *
     *
     * @param  $status The status (0 or 1) however true/false will also be
     *                 accepted.
     */
    public function setStatusAttribute($status = 0) {
        $master = $this->masterCustomers()->first();

        if (isset($master)) {
            $master->status = $status ? 1 : 0;
            $result = $master->save();

            return $result;
        } else {
            Log::warning('Unable to find master record.');
        }

        return;
    }

@lloy0076
Copy link
Contributor

lloy0076 commented Apr 26, 2017

#165 - this pull request allows you to hide/show fields depending on a value; it's using a radio button but I can't see why it couldn't be made more generic.

Also, if you know the item in edit you can setup the fields manually depending on what type of product it is, something like:

if ($this->crud->model->type_of_model == 'car') {
  $data = [ // the car fields ];
} else {
  $data = [ // some other fields ];
}

$this->crud->addFields($data, 'update');

Create would be more difficult but it's probably possible - I think one could make two or more create buttons (or use a radio box or whatever) and depending on which one got clicked or the form parameter one could override the create() method and change the fields dynamically (however this is just off the top of my head - it mightn't work at all).

@ivanmart
Copy link
Author

@lloy0076 thank you again, your answers are very helpful. Could you explain though two things in your mutator example:
1.- what you mean by if (isset($master))? The condition will always be true, even if $this->masterCustomers()->first(); return false
2.- so you call ->save() in mutator, but what if I have about 40 fields and so about 40 mutators? Am I right that each of them will result in separate sql-query like 'update table ... set ... = ..."? It doesn't seem to be the right behavior

@lloy0076
Copy link
Contributor

lloy0076 commented Apr 26, 2017

If the model has not been saved, first returns something that causes isset to be false; first seems to be in the BuildsQuery trait and is this:

 63     /**
 64      * Execute the query and get the first result.
 65      *
 66      * @param  array  $columns
 67      * @return mixed
 68      */
 69     public function first($columns = ['*'])
 70     {
 71         return $this->take(1)->get($columns)->first();
 72     }

Line 71 returns something not defined which makes Laravel itself blow up with a 'using undefined variable' error IIRC.

As for your second point, you are correct; however because there isn't 1-1 relationship support the easier and probably more efficient Eloquent sync() method isn't available. On this last note, though, it might be a premature optimisation to worry about all these updates...

@ivanmart
Copy link
Author

@lloy0076 I'm having trouble creating a new record. The mutator fires before the parent model is saved, which results in error "Indirect modification of overloaded property". In your mutator, you only prevent this situation, but how do you actually save the value of a related model when creating a new record?

@lloy0076
Copy link
Contributor

lloy0076 commented Apr 27, 2017

@ivanmart - I override the store method in the controller instead:

public function store(StoreRequest $request)
{
        DB::transaction(function() use ($request, &$redirect_location) {
            $redirect_location = parent::storeCrud();

            /*
             * This method morphs the model.
             */
            $this->storeMaster($request);
        });

       return $redirect_location;
    }

    /**
     * Stores the master record.
     *
     * @param  $request The request.
     * @return The result of the save.
     */
    protected function storeMaster($request)
    {
        /*
          * I forget why this is here - I think this is me being paranoid; I think it
          * is to prevent a case where the entry is created and there is an ID but
          * I'm not sure.
          */
        if (! (isset($this->crud->entry) && isset($this->crud->entry->cust_id))) {
            return;
        }

        $master = new Master();

        $data   = [
           // various data values
        ];

        foreach ($data as $check => $default) {
            $value = $request->has($check) 
                ? $request->input($check)
                : $default;

            $master->$check = $value;
        }
        return $this->crud->entry->master()->save($master);
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants