How to create a child by one SQL-request in Laravel? - laravel

If an author has many books ("one to many" relationship) and I want to create a child by $author_id I should do this:
$author = Author::find($author_id);
$author->books()->create([...]);
But this code produces two SQL-requests as well as this:
Author::find($author_id)->books()->create([...]);
To reduce the number of SQL-requests I should add author_id field into the $fillable array in my Book model and do this:
Book::create([
'author_id' => $author_id,
...
]);
Which approach is better? As for me, the first one looks more correct, more Eloquent way, but 2 SQL-requests are too much for such simple case. Are there any other ways to make only one SQL-request without touching the $fillable array?

The old school:
$book = new Book;
$book->author_id = $author_id;
//...
$book->save();
Or you could forceCreate, which bypasses fillable:
Book::forceCreate(['author_id' => $author_id, ...]);
As for which approach is better: if you know the author_id, then 2nd (without using the relationship). But from my experience, that's rarely the case, since you usually want to check whether the related model actually exists. But if you're confident in the correctness of your input, no need for 2 queries.

Related

How to dissociate elements from a HasMany relation?

There's the save and saveMany methods on the HasMany relation class, but where are the dissociate(Many)/detach(Many) methods? There's also no built-in way to get the inverse relationship method, so what's the best way to dissociate an array of id's/models from a HasMany relationship object.
Currently I'm using:
$hasMany = $parent->theRelationship(); // Get the relationship object.
$child = $hasMany->getRelated(); // Get an empty related model.
$key = $hasMany->getForeignKeyName(); // Get the name of the column on the child to set to NULL.
$child->findMany($IDs)->each(function($model) use ($key) {
$model->$key = NULL;
$model->save();
});
This could be alot shorter with something like:
$hasMany = $parent->theRelationship();
$hasMany->dissociate($IDs);
Bonus points if you have any official answers from Taylor as to why he hasn't implemented this, I've seen him close feature requests of this kind on GitHub.
I am not sure why there isn't a function, but to be more performant than your example, you could use the DB class like:
\DB::table('child_table')->where('parent_id', $parent->id)->update(['parent_id' => null]);
You could use detach like so;
$parent->theRelationship()->detach([1,2,3])
Where you pass an array of IDs.
From Laravel documentation:
"For convenience, attach and detach also accept arrays of IDs as input"
The performatic way (1 db update):
$partent->theRelationship()->update(['parent_id' => null]);
The readable way (multiple db updates):
$parent->theRelationship->each->parentRelationship()->dissociate();

How to set field for ::create in Laravel?

I use this method to create new row in DB:
$input = $request->all();
return Recipient::create([$input]);
How I can add additional field to $input with an value?
I tried:
$input["user_id"] = Auth::id();
But when I display query INSERT, I can not see field user_id
The problem here is that you probably don't set $fillable property in Recipient model. You should add user_id there:
$fillable = [ ..., 'user_id'];
However merging input is usually not the best way to deal with such things. If you have set relationships properly, it should be possible to do something like this:
Auth::user()->recipients()->create($request->all());
Update
Like #Marcin Nabialek said: add 'user_id' to the $fillable array. And, his solution is cleaner.
Merge
The method merge is what you're looking for: Merge new input into the current request's input array.
$request->merge(['user_id' => Auth::user()->id]);
Recipient::create($request->all());
See the api documentation here: https://laravel.com/api/5.2/Illuminate/Http/Request.html#method_merge
I'm still not sure what exactly you are looking for. Suppose you have 2-3 fields user_id, title, content. For inserting into the database you need to do following:
$input = new Recipient(['title'=>'You title', 'content'=>'Your content']);
$input->save();
It will save two fields with the incremented user_id.
Bro, You're using wrong method,
Try this one:
$input["user_id"] =Auth::user()->id;

Laravel hasOne + group_by, get sum

I'm currently scratching my head with this one.
What I have.
$entries = Meta::whereIn('settlement_id', [1])->groupBy('client_id')->get();
Meta::model() has a hasOne relation with other tables, in my example with a Sport table. With one attribute in my array I can achieve what I want.
So in a loop I have my field available $entry->Sport->amount this works perfectly.
What I need.
$entries = Meta::whereIn('settlement_id', [1,2,3])->groupBy('client_id')->get();
Now when I expand my array with multiple id's, I expect that $entry->Sport->amount returns the sum of all id's. But it doesn't.
I can't figure out what i'm doing wrong.
All help is appreciated.
Your hasOne relation means that each Meta will have a separate Sport, so when you loop $entries each $entry->Sport will have the amount related to the current Meta, not a sum of all of them.
If you want to have the sum of Sport amount for specific Metas it might be easier to make a query from the Sport model, something like this (I don't know how your relations is setup so this is just an example):
Sport::whereIn('meta_id', [1,2,3])->sum('amount');
Thats correct, is there a way that I can achieve this? By changing hasOne to something else?
I don't want to query each specific related Meta table.
public function Sport()
{
return $this->hasOne('App\Sport', 'settlement_meta_id');
}
my database table Sport looks like this:
protected $fillable = [
'settlement_meta_id',
'bet',
'payout'
];

Yii2, few tables with uncomfortable generic field names for 1 Model

I'm building REST++ service with Yii2, which should be able to deal with 2 table schemes, (databases storing ~the~same~ data):
a_new_one, which is perfect to be handled with ActiveRecord;
the_old_one, which has 1 generic table 'Object' with columns like 'field1', 'field2', etc, and few additional tables.
I want my request methods and controllers to be all the same for both schemes (would be very great, really). That means, that I need to have THE SAME MODELS for both cases.
The troubles all relate to the_old_one database:
Let's say data of model User is stored in tables Object and UserProfile. If I make 2 ActiveRecord classes for both tables, will it be possible to make hasOne() relations to them in 3d class User (not sure about inheritance here), so it can be manipulated like this:
$user = User::find()->someFilters()->someOrderingAndPagination()->one();
$user->Name = $your_new_name;
$user->Age = $why_not_90;
$user->save();
// and so on.
Because I really don't want to be forced to write it like this:
$user = User::find()->notSureAboutRelations()
->filters()->ordering()->pagination()->one();
$user->object->field7 = $your_new_name;
$user->userProfile->Age = $why_not_90;
$user->save();
I learned, Doctrine has easy model mapping ($user->Name can be mapped to db's Object.field7), but it's still impossible to map to 2 different tables within 1 Entity. Or am I incorrect ? If so, would it be a good idea to use Doctrine within Yii2 ?
So finished with idea to have the same controllers and models. Only URLs remain the same.
The old db Model data could be gained this way:
$user = User::find()
->select([
'Object.field7 as Name',
'UserProfile.Age as Age',
// ...
])->from('Object')
->join('UserProfile', ['UserProfile.ObjectId = Object.Id'])
->andWhere('User.Id' => $id);
And it's an ugly mess up when saving this models. Cos you need to assign a lot of fields in BEFORE_INSERT, BEFORE_UPDATE events.

Saving a model with multiple foreign keys in Laravel 4

I understand that in order to save a foreign key, one should use the related model and the associate() function, but is it really worth the trouble of going through this
$user = new User([
'name' => Input::get('name'),
'email' => Input::get('email')
]);
$language = Language::find(Input::get('language_id');
$gender = Gender::find(Input::get('gender_id');
$city = City::find(Input::get('city_id');
$user->language()->associate($language);
$user->gender()->associate($gender);
$user->city()->associate($city);
$user->save();
when one can simply do this?
User::create(Input::all());
I feel like I'm missing something here, maybe there's an even simpler and cleaner way to handle foreign keys in controllers (and views)?
You can use push() method instead which would allow you to push to related models.
This link should answer your query.
Eloquent push() and save() difference
I really don't see anything wrong at all with doing User::create(Input::all());.
Obviously you'd want some validation, but it's doing the same thing.
I think the associate() method is more useful for the inverse of your situation.
For example, say you had a form which a user could fill out to add their city to your app, and upon doing so, they should automatically be assigned to that city.
$city = City::create(Input::all()); would only achieve the first half of your requirements because the user has not yet been attached as city does not have a user_id column.
You'd then need to do something like $city->user()->associate(User::find(Auth::user()->id));

Resources