Im making a site where users can post and read each others posts.
I want to make it possible for all users to mark the posts they have read, thus showing only posts which are not read. It should also be possible to undo this. So I have a model and table for both Users and Posts.
The way I thought of doing it was creating a PostsRead table which would contain the unique id for the user and for the post. Then I could fetch all the posts and remove the ones from the PostsRead table. So the question(s) is:
1) Is this a good (best practice-ish) way to do it?
2) How? I guess I need to check if there is a record in the PostsRead-table containing the user_id/post_id pair. If it is not, insert a new row. And if it is, I guess I need to update the current row. It should maybe have a boolean field telling if the post is read or not. How would the code for this be?
When I go through it in my head, this approach seems....weird. Am I on the wrong track here? I hope the question was understandable.
Yes, it's the best practice to do it this way.
Create a table called 'user_read' with 'user_id' and 'post_id' (both as primary key)
Schema::create('user_read', function (Blueprint $table) {
$table->integer('user_id')->unsigned()->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->integer('post_id')->unsigned()->index();
$table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
$table->timestamps();
$table->primary(['user_id', 'post_id']);
});
To set it up you should have this relation in your model.
User.php
public function reads()
{
return $this->belongsToMany(Post::class, 'user_read', 'user_id', 'post_id')
->withTimestamps();
}
To store a new post that the user read you can just do
//1 and 3 are the posts ids
$user->reads()->sync([1, 3]);
Related
So in laravel I have a User table holding info such as id, home_address, email_address, phone_number, etc.
Users can participate in dinners (activity). If they don't show up to the dinners, admins should be able to ban the user from participating until they are manually unbanned.
The user table is "large enough" that it doesn't make sense to add new columns to the user table, but instead create a new table to keep the information seperate.
In my User migration file I have
Schema::create('dinner_ban', function (Blueprint $table) {
$table->unsignedBigInteger('id');
$table->foreign('id')->references('id')->on('users');
$table->dateTime('date_ban');
$table->dateTime('date_unban');
$table->string('reason')->nullable();
});
and in my User Model file I have
public function userBanned() {
return $this->belongsTo('App\Models\User', 'dinner_ban');
}
NOTE: dinner_ban does not have it's own individual Model Class.
The table is one way. When a user is banned, they are added to the dinner_ban table with today's date, an estimate future date where they would be unbanned (but of course can be before or after this date (in the future it would be automatic), and a reason for the ban (which doesn't need to be added/provided). When they are unbanned, they are removed from the table. No tracking is done as for how long or how many times someone has been banned. Its a simple check to see the user is banned or not. If they are banned, then they can not participate in dinners.
What I have above, is this a correct way of adding dinner_ban?
In the future there are 3 ways of using it.
If it's the admins, I want to return the whole DB and display the results on the page.
If it's the individual user, I want to check if their id exists in the ban database and return true (with when the ban started, when it ends, and the reason) or false.
And of course admins can edit the individual banned members and edit the reason/estimated unban list.
It is not a good idea to create a custom table like "dinner_ban" to store this kind of information.
It is better to create (if you don not already have) a "user_preferences" table with something like the following structure:
Schema::create('user_preferences', function (Blueprint $table) {
$table->unsignedBigInteger('id');
$table->foreign('user_id')->references('id')->on('users');
$table->json('value')->nullable();
});
and define the relation (and Cast) in "User" Model:
public function userPreferences() {
return $this->hasOne(UserPreferences::class);
}
Note: It's better to make "user_id" unique in preferences table and set "value" as json.
So, as the title says, I have two pivot tables in my Laravel project (I am still new to Laravel). My project is an Instagram clone and I have made the like function, which uses one of the two pivot tables I made and now I'm trying to make a "Save" function, similar to the actual Instagram app where people can save posts and view them at a later time. The problem is, when I check my tinker and do:
As you can see, it looks like the likes and saves are using the pivot table for likes, I have not inserted anything into the pivot table for saves yet. Here are my migrations
For likes:
Schema::create('post_user', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('post_id');
$table->timestamps();
});
And for saves:
Schema::create('post_user_saves', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('post_id');
$table->timestamps();
});
I'm aware that they are literally the same but, just like the Instagram app, a user can like a post without saving it and vice-versa and they can also like and save it at the same time. I want to insert into the post_user_saves when I click a button. Here are pics of my database as well, as you can see the post_user_saves table is empty while post_user is not, meaning that tinker is getting the pivot data from post_user
and lastly, here are the codes from my model:
public function likes()
{
return $this->belongsToMany(Post::class);
}
public function saves()
{
return $this->belongsToMany(Post::class);
}
EDIT:
here is my store method for the likes:
public function store(Post $post){
return auth()->user()->likes()->toggle($post->id);
}
I don't have the store method for my saves yet but my plan is to make it the same as the one above hence why I need to be able to store to post_user_saves.
If your relationships are correct you can use attach() to save to the pivot:
auth()->user()->likes()->attach(auth::id(), ['column' => 'value']);
The reason your save is updating the same table is because you are pretty much using the exact same relationship so eloquent doesn't know about your post_user_saves table what you would need to do is Create a model Save() then rename your migration to save_users and update your relationship accordingly.
I have two tables like:
User:
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('loginid')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
IP:
Schema::create('i_p_s', function (Blueprint $table) {
$table->id();
$table->string('address')->unique();
$table->foreignId('user_id')->nullable();
$table->string('hostname');
$table->string('description')->nullable();
$table->timestamps();
$table->index('user_id');
});
IP Model:
public function User() {
return $this->belongsTo(User::class);
}
User Model:
public function IPs() {
return $this->hasMany(IP::class);
}
The user_id column means this IP is using by which user.
And now I want to add a new column last_modified which means who is the last editor of this row.
So I think the last_modified should be $table->foreignId('user_id')->nullable(); too.
But how to define the relationship in IP model?
Additionally, I call the user_id like this now.
$ips = IP::with('user')->get();
#foreach ($ips as $ip)
{{ $ip->user }}
#endforeach
So how can I call the last_modified after the definition?
Thanks a lot
As shown in the docs (https://laravel.com/docs/7.x/migrations#foreign-key-constraints),
$table->foreignId('user_id')->nullable();
is just a shortcut of the "old" way
Schema::table('i_p_s', function (Blueprint $table) {
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
});
The problem with your code would be, that you need also the constrained()-method. It will dissolve a given column name like user_id into like "Laravel, please use the column id of the table users here".
I'm not sure if the nullable()-method will be useable for this shortcut.
In the same way, your relations will be dissolved within your models. If you're not adding additional values to the belongsTo() and haveMany()-methods, Laravel will try to find its way through your databse by assuming standard naming conventions for primary keys and table names (if the table names are not set within your model).
primary keys are assumed as id. This is also the reason why $table->ip() works.
table names are assumed as the plural of the model name. That means also, you have to make sure to set the name of your i_p_s table within your IP-model as it does not follow the convention. Event better would be to think about an adaption to the convention and call your table ips.
foreign keys should be (to be able to dissolve things that way) named by the singular table name, underscore, primary key. in other words user_id.
So, your assumption should be right apart from the fact that you cannot add a second column called user_id. For your second foreign key, your code should look like the "normal/ traditional" way of doing this.
Schema::table('i_p_s', function (Blueprint $table) {
$table->unsignedBigInteger('last_modified')->nullable();
$table->foreign('last_modified')->references('id')->on('users');
});
I'm pretty sure that this will work, although I didn't tested this yet. Unfortunately, I'm not sure if you can provide the column name and table also within the constrained method. If so, that would be pretty handy. Give it a try, otherwise use the traditional way.
The relation within the model should then look like this:
public function hasChanged() {
$this->hasMany(IP::class, 'last_modified', 'id');
}
last_modified is the foreign key within the i_p_s-table while id is the local column of your owning User-model.
Bringing this into reverse for the IP-model:
public function wasChangedBy() {
$this->belongsTo(User::class, 'last_modified', 'id');
}
In both cases you can dispense on setting the 'id' column as primary key of your users-table as it is standard.
The relations are the same as in your example because of the construction/ architecture. In 99% this is always a one-to-many relation.
Last but not least, it was a bit strange to see this construction of the same foreign key two times referencing in the same table. But I found this post, which says it is eventually totally normal to do so.
Mysql: using two foreign keys to the same table
The only other way I could think of would be to have an intermediate table between i_p_s and users but this would lead to a loop in your database between these two tables which also is a thing you should avoid. So I would say this is ok.
Hope this helps ;)
I am working on an application in Laravel where users can be matched up with one users. I am trying to figure out how to setup the hasOne relationship between users.
Here is how I have done it so far. Users when they are created have a match_id which represents the user that they are matched to. When they are matched this record gets updated. Here is my table:
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('event_id');
$table->foreign('event_id')->references('id')->on('events')->onDelete('cascade');
$table->unsignedBigInteger('match_id')->nullable();
$table->foreign('match_id')->references('id')->on('users')->onDelete('cascade');
$table->string('name');
$table->string('email');
$table->timestamps();
});
In the user model I have set up this relationship:
public function match() {
return $this->hasOne(User::class);
}
but as of right now I cannot do something like this and get the expected matched user's information:
$user->match()->get();
After reading the docs here, I see that in such situations you can setup the foreign key and local key. Nevertheless I am having a difficult time figuring this out.
Could someone give me some guidance on how I could do this? I am also open to any suggestions on different implementations.
-----edit------
after discussing with some people in the comments I made some progress in my endeavors. I am able to get some results and have experimented by logging the matches. For whatever reason though I am getting the wrong matches. Look below for more information:
in my controller I log the users name and match after it has been formed:
Log::error($user->match);
Log::error('has matched with:');
Log::error($user);
Log::error('participant:');
in the database here are the users and their matches:
yet in the logs, the match is not the same:
Since the match_id is on the users table, the relationship should be belongsTo, not hasOne.
belongsTo is specified on the model that contains the foreign key.
Also, you should specify the foreign key:
return $this->belongsTo('App\User', 'match_id');
I am trying to develop a Restful API with Laravel 5 and i have a table as below
Schema::create('reports', function (Blueprint $table) {
$table->increments('id');
$table->string('company_name');
$table->string('direction');
$table->integer('user_id')->unsigned();
$table->timestamps();
});
The field user_id is a foreign key and i want to create a new Report record when make a POST request via Android App.
My question is, what should be the status of user_id field ? Guarded or fillable ?
Any help would be appreciated
you should set it to fillable, because you want to set this value on model creation.
For more details see the documentation:
https://laravel.com/docs/5.0/eloquent#mass-assignment
Honestly, I'd suggest to rethink that strategy, however, here's something you can try if you really need to do it that way.
Don't add user_id's to either $fillable or $guarded. Set up a model observer and assign the id right there.
That way you can pass the call safely to the Report model and still be able to hook into the process of creating/reading the Report/User