Open eduardoarandah opened 4 years ago
I'm gonna leave this code here, this is how I solved it (and it works perfectly)
private $_fake_fields = [
[
'name' => 'age',
'label' => 'Age',
'type' => 'number',
'tab' => 'Features',
],
[
'label' => "Skin",
'type' => "select_from_array",
'name' => 'skin_id',
'tab' => 'Features',
'options' => [
'Blanca',
'Morena',
'Negra' ,
],
],
...
];
// helper for backpack backend
public function saveMeta($meta)
{
foreach ($meta as $key => $value) {
$this->adMetas()->updateOrCreate(
[
'ad_id' => $this->id,
'key' => $key,
],
[
'value' => $value,
]
);
}
}
note: adMetas() is a hasMany relationship
public function adMetas()
{
return $this->hasMany('App\AdMeta');
}
wait... why can't it be a mutator? because the mutator will fail when creating a new model. You can't save related meta if your model doesn't exist.
So they call "saveMeta" and store in related table
store: get meta, remove from request, save and call meta save
public function store()
{
// collect meta
$meta= [];
foreach ($this->_fake_fields as $field) {
$key = $field['name'];
$value = $this->crud->request->request->get($key);
$meta[$key] = $value;
// remove extras
$this->crud->request->request->remove($key);
$this->crud->removeField($key);
}
// save model
$response = $this->traitStore();
// save meta
$this->crud->entry->saveMeta($meta);
// return
return $response;
}
update: get meta, remove from request, save and call meta save
public function update()
{
// collect meta
$meta= [];
foreach ($this->_fake_fields as $field) {
$key = $field['name'];
$value = $this->crud->request->request->get($key);
$meta[$key] = $value;
// remove extras
$this->crud->request->request->remove($key);
$this->crud->removeField($key);
}
// save model
$response = $this->traitUpdate();
// save meta
$this->crud->entry->saveMeta($meta);
// return
return $response;
}
edit: in edit, we restore values from meta table
public function edit($id)
{
$entry = $this->crud->getEntry($id);
foreach ($entry->adMetas as $adMeta) {
$entry[$adMeta->key] = $adMeta->value;
}
return $this->traitEdit($id);
}
don't forget to define traitEdit, traitUpdate, traitStore
use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation {
create as traitCreate;
}
use \Backpack\CRUD\app\Http\Controllers\Operations\UpdateOperation {
edit as traitEdit;
update as traitUpdate;
}
this is an example migration of a meta table
it's called ad_meta
because main table is ad
class CreateAdMetaTable extends Migration
{
public function up()
{
Schema::create(
'ad_meta', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('ad_id');
$table->string('key');
$table->text('value')->nullable();
$table->timestamps();
$table->foreign('ad_id')->references('id')->on('ad');
}
);
}
}
I'm sure there could be a better way to integrate this kind of functionality into backpack without all this boilerplate, what do you think?
Hello @eduardoarandah
I am just wondering, if you create a model for ad_meta table, and use it as a regular crud fields wouldn't that be easier and do what you are trying to achieve ?? Is there something that prevents you from doing that ?
Best, Pedro
@pxpm well, take this as an example:
I need like 300 fields for a person, and those fields are better in a meta table.
If I need, let's say... another kind of information about that person, I can create another crud with only those specific fields.
Meta tables have proven to be very usable in wordpress (post-meta tag-meta user-meta comment-meta)
Queries are easy to do in eloquent (find a person with meta key=age and value>30)
https://laravel.com/docs/5.6/eloquent-relationships#querying-relationship-existence
https://laravel.com/docs/5.6/eloquent-resources#adding-meta-data
Json is not usable via raw SQL (or is it? 🤔)
Saving meta is dead simple, maybe this code can be futher simplified with some collection-eloquent magic to run in one query.
public function saveMeta($meta)
{
foreach ($meta as $key => $value) {
$this->adMetas()->updateOrCreate(
[
'ad_id' => $this->id,
'key' => $key,
],
[
'value' => $value,
]
);
}
}
fake=>true
but using meta=>true
with convention being _meta
( post
saves meta in post_meta
)@eduardoarandah u can make sql query to json field, if in your db column has type json/jsonb https://laravel.com/docs/7.x/queries#json-where-clauses (postgresql, mysql 5.7, percona 5.7) And when u need extra speed u can create virtual colum with index based on field in json https://www.compose.com/articles/mysql-for-json-generated-columns-and-indexing/
But, i think, it will be nice, if backpack can support some EAV package, like https://github.com/rinvex/laravel-attributes
Have a look at my settings package.
It's pinned in my profile.
Best
A quinta, 12/03/2020, 18:01, Bogdan Lotarev notifications@github.com escreveu:
@eduardoarandah https://github.com/eduardoarandah u can make sql query to json field, if in your db column has type json/jsonb (postgresql, mysql8, percona 5.7) And when u need extra speed u can create virtual colum with index based on field in json
https://www.compose.com/articles/mysql-for-json-generated-columns-and-indexing/
But, i think, it will be nice, if backpack can support some EAV package, like https://github.com/rinvex/laravel-attributes
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Laravel-Backpack/CRUD/issues/2543#issuecomment-598338326, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABW25P7P2HXSJ3SS7KEPVDLRHEPOHANCNFSM4LGAUEBA .
Wow - thanks a lot for sharing this @eduardoarandah . It looks great! I agree with most of your assessment about JSON vs meta table, except for a few:
That being said, I'm not excluding this, and the EAV package @lotarbo mentioned looks very similar in concept - maybe we can support it as-is or use it as inspiration.
But we can't work on this in the next few weeks either, we're hard at work trying to get 4.1 out the door, and this being a "could have" feature, we can't prioritise it over older feature requests. I'll take another look at this once 4.1 is launched, sorry.
Until then, at first glance, a few ways your implementation could be improved (not 100% sure):
store()
or update()
methods; maybe that can be better done inside the model, by overwriting the save()
method?save()
, the getter, the setter and the relationship could be put together in a simple trait like HasMetas
, that can be used on the model;meta_
);That's all I've got right now 😄 I'm pretty sure there are more ways to clean this up, though. Or maybe better ways to do this altogether. But for now, in 4.1, JSON will do.
Now that I think about it, there has to be a package out there doing metas for Eloquent, in a separate table, like you want them. I've just searched packagist for "laravel metas" and sure enough, here are a few:
getMeta()
and setMeta()
;Maybe they help 😉
@tabacitu
I like the idea of not having a polymorphic table, just a tablename_meta
table
"I don't think it's needed to change the controller store() or update() methods; maybe that can be better done inside the model, by overwriting the save() method?" yes, agree on that, it's just that "meta" information has to be saved AFTER the parent method first insertion, otherwise we don't have an ID. (is there an event for that?)
Is there a way of grabbing "fake" fields and send them as an array to a model mutator? (without backpack trying to save them to an "extras" db column)
Also, reading back fake fields from a model accesor?
This way, this idea of meta tables can be implemented with one of those packages, or a custom solution I guess
@tabacitu I'm mentioned EAV mb its offtopic: we have crud page and table pages:
All pages - has common fields like title, is_active and etc. But in most cases, they have many specific fields and it's difficult to manage all this using only fake fields. Also, when using fake and translate - all fake fields automatically marks as need to translate. If we have field, common for all language but marked as fake - this field becomes multilingual. So i think, it would be nice to support some eav package in future)
@eduardoarandah :
saved
;meta_
; then inside the Model you could have a catch-all method by overwriting the magic __call()
method, to get all calls to the meta_smth
accessors and mutators;
Bug report
This is not a bug, see below
What I did
I want to store fake fields in meta table instead of json
What I expected to happen
--
What happened
--
What I've already tried to fix it
--
Backpack, Laravel, PHP, DB version
PHP VERSION:
PHP 7.3.15-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Feb 20 2020 12:23:37) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.15, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.3.15-1+ubuntu18.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies
LARAVEL VERSION:
laravel/framework v6.17.1 The Laravel Framework.
BACKPACK VERSION:
backpack/crud 4.0.42 Quickly build an admin interfaces using Laravel 6, CoreUI, Boostrap 4 and jQuery.
backpack/generators 2.0.6 Generate files for laravel projects
backpack/permissionmanager 5.0.7 Users and permissions management interface for Laravel 5 using Backpack CRUD.
Feature Request
I have an "ad" table and an "ad_meta" table.
I want to save extra fields into ad_meta table.
Right now, there's the option of having "fake fields" which is awesome:
https://backpackforlaravel.com/docs/4.0/crud-fields#fake-fields-all-stored-as-json-in-the-database
I'm being creative and creating a
mutator
and anaccessor
so I can save and retrieve fields from meta table instead of creating an "extra" fieldProblem is
$value
would be better as a simple array instead of json.Some thoughts:
There's advantages of having a meta table over json, for example: easier to query, translate, and manipulate in SQL
This is a popular arquitecture, for example: wordpress (see image)
1) add a
as_array
to field definition, to skip converting to json and let the user decide how to use data in an accesor an mutator2) add a
meta_table
to automatically save in meta_table