Laravel 5: Chicken and egg when creating a one-to-one relationship - laravel

Let's assume I have two Model elements:
Person
Computer
In my application a Person May have one computer (a person has 0..1 computers).
So
In my User Model, I have:
public function computer()
{
return $this->hasOne('App\Model\Computer');
}
In my Computer Model I have:
public function owner_user() {
return $this->belongsTo('App\Model\User');
}
Sometimes in my application I need to create a computer. computers cannot be without owners, so when I get the information about the computer to create, I also get information about the owner.
What is the most elegant way to create BOTH new objects (computer and person), associate them with the reference IDs without doing multiple saves?
My problem is: the IDs are only being generated when the eloquent model is saved. because of that, I have a chicken and egg problem where I can't save a computer, because I don't know the ID of the owner person, and I can't save a person because I don't know the ID of the owned computer.
I could only do this by doing multiple saves and having a stub ID temporarily saved until the other objects is saved, and then I update the ID of the first one saved.
There must be a more elegant way to save two or more items at the same time, with reference IDs in place, with a single call - and without having to worry about database ID integrity in a high volume web server.
It seems the associate function is for existing records. Is there no other way for my situation than having to save person + save computer + associate?
Here's the schema of my people (users) and computers.
users
+----------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| username | varchar(45) | NO | UNI | NULL | |
| email | varchar(45) | NO | | NULL | |
| password | text | NO | | NULL | |
| remember_token | varchar(100) | NO | | NULL | |
| created_at | timestamp | YES | MUL | NULL | |
| updated_at | timestamp | YES | MUL | NULL | |
+----------------+---------------------+------+-----+---------+----------------+
computers
+----------------------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| user_id | bigint(20) | NO | UNI | NULL | |
| name | varchar(45) | NO | MUL | NULL | |
| created_at | timestamp | YES | MUL | NULL | |
| updated_at | timestamp | YES | MUL | NULL | |
+----------------------------+---------------------+------+-----+---------+----------------+

For the least amount of database work, you want to create the user first and then create the computer. This way, you can have one save for the user, and then one save for the computer, which is only two writes to the database.
If you create the computer first, you would have to create the computer, create the user, and then update the computer with the user id, which would be three writes to the database instead of two.
However, since you're creating two new records (user and computer), the fact is that you're going to have at least two writes to the database, no matter what you do.
This is what you're looking at:
// create the user object in the database (first write)
$user = \App\Model\User::create([
'name' => $request->input('user_name'),
'password' => $request->input('user_password')
]);
// the save on the relationship will update the foreign key on the
// given model and save the whole model to the database (second write).
$user->computer()->save(new \App\Model\Computer([
'name' => $request->input('computer_name')
]));
It sounds like another concern you may have is the referential integrity. What happens if the user saves fine, but there is a problem saving the computer? Do you want to keep the user, or should it seem like the user save never happened?
If you have a multiple database writes that should be treated as an all-or-nothing situation, you'll need a database transaction. If your work is done inside a database transaction, you can have all the database writes automatically rolled back if one of them fails.
DB::transaction(function() {
$user = \App\Model\User::create([
'name' => $request->input('user_name'),
'password' => $request->input('user_password')
]);
$user->computer()->save(new \App\Model\Computer([
'name' => $request->input('computer_name')
]));
});
With this code, if the save of the computer throws an exception, then the user that was created will automatically be undone. Laravel's documentation on transactions is here.
One thing to note regarding transactions is that you need to make sure the database you're using supports them. For example, for MySQL, only the InnoDB and BDB storage engines support transactions. All other storage engines (e.g. MyISAM) do not. The code will not throw any errors, but the database writes that complete will not be rolled back.

This can be achieved easily with the push() method.
Sometimes you may wish to save not only a model, but also all of its relationships. To do so, you may use the push method:
Saving A Model And Relationships
$user->push();
Example code:
$user = new Person();
$user->email = Request::get('email');
$user->password = Request::get('password');
$user->computer = new Computer();
$user->computer->name = Request::get('computer_name');
$user->push();
You can read more about Eloqeunt relations in the documentation.

You are not suppose to have foreign ids in both tables.
You should have user_id only on computers table.
So you will create a user and then a computer.

Related

Codeigniter 3 Joins in One table

I want to display the User Name Who created, but in the database the create name is in the form of int which is the id of the author.
How can I display the id to be the real name by using Join in one table.
Or if there is another way I will try.
For the table as below
+----+--------------+-------------+
| Id | user_create | name |
+----+--------------+-------------+
| 1 | Null | Admin |
| 2 | 1 | User |
+----+--------------+-------------+
and I want to display it like this
Detail User
Name : User
User Create : Admin
if I remember correctly the CI 3 syntax
$this->db
->select(['t1.name', 't2.name user_create'])
->join('thetable t2', 't2.user_create = t1.id', 'left')
->get('thetable t1');

Multi user type design in Laravel

I'm a bit puzzled on how to best create an architecture for a multiple user type account. Watching the Laracasts episode on users and roles I figured a single User model with a Role model would be a good approach. However, rethinking the data I need for my different type of users varies, as for some users I need full address data and for others a name would be enough (to gently great them upon login). However, a Vistor might turn into a Customer. In fact, even the MerchantEmployee could become a Customer. This makes me wondering what my database design would become, e.g.
users => UserModel <fields> email, password
role: visitor => VistorModel <fields> name
role: customer => CustomerModel <fields> name, street, zipcode, ...
role: merchantowner => MerchantOwnerModel <fields> name, street, zipcode, ...
role: merchantemployee => MerchantEmployeeModel <fields> name
Would then all the Vistor, Customer, MerchantOwner and MerchantEmployee models have a user_id-column? Or how would you design such functionality?
I think to create two tables
First one is users
+----------+---------+-------+
| Col | Type | Notes |
+----------+---------+-------+
| id | bigInt | PK/AI |
| name | VARCHAR | NN |
| email | VARCHAR | NN|UN |
| password | VARCHAR | NN |
| role | ENUM | NN |
+----------+---------+-------+
The second one is user_data
+----------------------+---------+-------+
| Col | Type | Notes |
+----------------------+---------+-------+
| id | bigInt | PK/AI |
| user_id | bigInt | FK |
| address | VARCHAR | NN |
| {WHAT EVER YOU WANT) | | |
+----------------------+---------+-------+
And the relation to be one to one.
I recommend to use Laravel validation "required_if" to valid data before process it to database.
$this->validation($request, [
'address' => 'required_if:role,merchant_owner'
]);

Query data based on pivot table

I have a database design which essentially takes the following form (I have removed a lot of fields as they are not really needed for demonstration purposes). I have a users table
users
+----+---------------+-----------------+
| id | name | email |
+----+---------------+-----------------+
| 1 | ProjectA | Something |
+----+---------------+-----------------+
A user can have many projects
projects
+----+---------------+-----------------+-----------------+
| id | name | description | user_id |
+----+---------------+-----------------+-----------------+
| 1 | ProjectA | Something | 1 |
+----+---------------+-----------------+-----------------+
So that is straight forward enough and very easy to query. If I want all projects for the logged in user I can do
$loggedInUser = Auth::user()->getFirstName() . ' ' . Auth::user()->getLastName();
$loggedInUserId = User::where('userName', '=', $loggedInUser)->first();
$projectss = Project::all()->where('user_id', $loggedInUserId);
This is where things get a little more tricky. I have a pivot table for a users groups. It is essentially this
users_user_groups
+----+---------------+-----------------+
| id | user_id | group_id |
+----+---------------+-----------------+
| 1 | 1 | 2 |
+----+---------------+-----------------+
I then have a user_groups table
user_groups
+----+---------------+
| id | group_name |
+----+---------------+
| 1 | Group A |
+----+---------------+
If I want to find what groups a user is a part of I can do
$userGroups = Auth::user()->getGroups();
My question is this. Above, I demonstrate how I can get all projects for a user. Now I know there is a user_group called Group A. What I essentially want to do is get all projects where the user is apart of Group A. So if 5 users create a project and they are all in Group A, then I should be returned 5 projects.
How would I go about doing something like this?
Thanks
Firstly you can get user id using simply Auth::user()->id
Assuming you are trying to get all the projects that belongs to users of a group You can do this.
$group = Group::with('users.projects')->find(1);
Group model
public function users()
{
return $this->belongsToMany('App\User');
}
User Model
public function projects()
{
return $this->hasMany('App\Project');
}
Of course you would have to make the appropriate relation methods depending on the forign/primary keys you used.

Relation between 3 Models in Laravel 5.1 ("like many-to-many-through")

I'm developing a simple Schools management app for an exam and have so far built a simple Many-To-Many relation between a School model and the Field model and of course a pivot table.
Now I want to relate my User model to them so that I can query for example
{user} is studying {field_of_study} at {school}
At the end I want to be able to query for example
how many users are studying at School XY or,
how many are studying Field Z or,
how many are studying Field Z at School XY.
Furthermore I want to be able to query all fields of study for a given school and vice versa.
My tables so far
Table users:
+------------+---------------------+
| Field | Type |
+------------+---------------------+
| id | bigint(20) unsigned |
| username | varchar(60) |
| password | varchar(60) |
+------------+---------------------+
Table schools:
+------------+---------------------+
| Field | Type |
+------------+---------------------+
| id | bigint(20) unsigned |
| name | varchar(60) |
+------------+---------------------+
Table fields:
+------------+---------------------+
| Field | Type |
+------------+---------------------+
| id | bigint(20) unsigned |
| name | varchar(60) |
+------------+---------------------+
Table schools_pivot:
+------------+---------------------+
| Field | Type |
+------------+---------------------+
| id | bigint(20) unsigned |
| school_id | bigint(20) |
| field_id | bigint(20) |
+------------+---------------------+
Unfortunatly I absolutly have no clue how to relate three Eloquent models in such a way and couldn't find any thing on the web (I probably searched for the wrong terms).
I'm pretty new to Laravel and Eloquent, so please be kind with me ;)
it's better to define relationship between two table and then alternately the other one.you can read this artical. maybe it will help you three-way-pivot-table-in-eloquent

How to repeat query in Oracle Forms upon dynamically changing ORDER_BY clause?

I have an Oracle Forms 6i form with a data block that consists of several columns.
------------------------------------------------------------------------------
| FIRST_NAME | LAST_NAME | DEPARTMENT | BIRTH_DATE | JOIN_DATE | RETIRE_DATE |
------------------------------------------------------------------------------
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
------------------------------------------------------------------------------
The user can press F7 (to Enter in Query Mode, for example, he/she types JOH% in the first_name and H% in the DEPARTMENT field) , then F8 to execute the query and see the results. In this example, a list of all employees with their last name starting with JOH and working in any department starting with H will be listed. Here is a sample output of that query
------------------------------------------------------------------------------
| FIRST_NAME | LAST_NAME | DEPARTMENT | BIRTH_DATE | JOIN_DATE | RETIRE_DATE |
------------------------------------------------------------------------------
| MIKE | JOHN | HUMAN RES. | 05-MAY-82 | 02-FEB-95 | |
| BEN | JOHNATHAN | HOUSING | 23-APR-76 | 16-AUG-98 | |
| SMITH | JOHN | HOUSING | 11-DEC-78 | 30-JUL-91 | |
| | | | | | |
------------------------------------------------------------------------------
I then added a small button on top of each column to allow the user to sort the data by the desired column, by executing WHEN-BUTTON-PRESSED trigger:
set_block_property('dept', order_by, 'first_name desc');
The good news is that the ORDER_BY does change. The bad news is that the user never notice the change because he/she will need to do another query and execute to see the output ordered by the column they selected. In other words, user will only notice the change in the next query he/she will execute.
I tried to automatically execute the query upon changing the ORDER_BY clause like this:
set_block_property('dept', order_by, 'first_name desc');
go_block('EMPLOYEE');
do_key('EXECUTE_QUERY');
/* EXECUTE_QUERY -- same thing */
but what happens is that all data from the table is selected, ignoring the criteria that the user has initially set during the query mode entry.
I also searched for a solution to this problem and most of them deal with SYSTEM.LAST_QUERY and default_where. The problem is, last_query can refer to a different block from a different form, that is not valid on the currently displayed data bloc.
How can do the following in just one button press:
1- Change the ORDER_BY clause of the currently active datablock
and: 2- Execute the last query that the user has executed, using the same criteria that was set?
Any help will be highly appreciated.
You can get the last query of the block with get_block_property built-in function:
GET_BLOCK_PROPERTY('EMPLOYEE', LAST_QUERY);
Another option is to provide separate search field(s) on the form, instead of using the QBE functionality.

Resources