Creating Vote Model Migration0:00
Okay, let's start working on the core functionality of the app, which is the voting system. We'll start with the database structure and the relationship between our models, and then we'll work on showing the correct number of votes for our ideas. In the next videos, we'll work on the actual voting functionality when we click on this vote button. So, let's start with a Vote model and the migrations. So php artisan make:model Vote, and let's just generate everything for now. So let's set up our migration first. So let's go to create_votes_table, and this table will store all of our votes for the users and the ideas.
So let's go to create votes table, and this table will store all of our votes for the users and the ideas. So we only need two fields here. That's the user_id and the idea_id. So I'm going to try to stick to Laravel conventions and put them in alphabetical order, but you don't have to do that. So let's say foreignId, and the first one is idea_id, but you can put user_id if you want first. And let's say constraints, so this will look for the ideas table. And then the next one is our user_id, and this will look for a User in the users table.
Enforcing Unique Votes1:05
And let's say constraints, so this will look for the ideas table. And then the next one is our userId, and this will look for a User in the users table. So if you think about it, one row in this table should represent one vote in our system. So a userId one and an ideaId one, that means that User liked that Idea. So again, if you think about it, it wouldn't make sense to have duplicate rows because that would mean that User liked that Idea twice, which doesn't make sense in our system. So we should enforce that the combination of ideaId and userId should be unique. So we can do that using table.unique. And we can just say the combination of ideaId and userId, like I just said. And this will enforce data integrity within our database, which is a good thing.
And we can just say the combination of idea_id and user_id, like I just said. And this will enforce data integrity within our database, which is a good thing. So let's save that. Let's migrate fresh --seed. And let's take a look at our database. And there should be a new table here called votes. And we have these two rows or sorry, these two fields. And let's just add something in here for now. Let's say 1 and 1. So like I just said, this represents one vote in our system.
Updating Seeders and Tests2:22
Let's say one and one. So like I just said, this represents one vote in our system. This User voted for this Idea. So if I try to add another row with the same user_id and the same idea_id, that should result in an error because of the constraint we put. And there it is duplicate key. Okay, so now I just want to rework our database either. So we have a bunch of voting information when we're doing testing. So I am going to change a few things in my factories and my seeders. So let's start with the IdeaFactory.
So I am going to change a few things in my factories and my seeders. So let's start with the IdeaFactory. Before I'm just making a new User for each new Idea. And I don't want that. So I'm gonna change this to a number between one and 20. And I'll make sure I'll always have 20 Users when I'm seeding the database. So let's go to our DatabaseSeeder. And let's make a few changes here. So first, I am going to create 20 Users. So say UserFactory, actually, I'm gonna put one for myself.
So first, I am going to create 20 Users. So say userFactory, actually, I'm gonna put one for myself. So I'd have to keep creating Users when I'm trying to log in. So say name me. And let's put my real email here. So I can see that Gravatar is working. And then I'll make 19 other Users here. So we have a total of 20. So we can just say userFactory, 19, create. Okay, so now we should have 20 Users.
So we can just say UserFactory, 19, create. Okay, so now we should have 20 Users. And I'm going to create 100 Ideas here instead of 30. Okay, so some of our tests might not pass anymore because of the change on the IdeaFactory. So let's fix that first. So let's look at our tests. So let's run the createIdea test, see if this one's still okay. And it is. Gravatar test should be fine.
And it is. Gravatar test should be fine. Okay, so it looks like just the showIdeas test we have to make changes to. Okay, so we just have to create users manually and assign them here when we're creating new ideas. So let's just grab all the IdeaFactorys. I'm going to use multiple cursors here. Okay, and I believe that's it. And let's just say $userId is $userId. And we'll create a User up here.
And let's just say userId is userId. And we'll create a User up here. Okay, say $user is UserFactory::create(). Let's make sure to import User. And let's add this wherever there's an IdeaFactory. Okay, so IdeaFactory. That's fine. Add it here. Okay, let's add it here. And I believe there's one more.
Generating Seed Vote Data5:49
So I don't have to keep creating new entries here when I'm trying to test the new functionality. So let's do that here. Let's put a comment, say generateUniqueVotes, say ensure ideaId and userId are unique for each row. Okay, so I'm just going to do it using two for loops here, it's probably a better way to do it functionally. But I'm just going to use two for loops, like I said, say foreach, I'm going to use range(1, 20), because we have 20 users. And we'll reference that as userId. And let's make another foreach.
And we'll reference that as userId. And let's make another for each. And same thing. But now we're looping over the ideas. So that's one to 100 as ideaId. And for each of these ideas, I want to add a vote for them. So we can use the vote factory. Actually, I didn't add that yet. So let's do that. Actually, let me finish writing this first vote factory create.
So let's do that. Actually, let me finish writing this first VoteFactory create. And I just want to have userId to be the userId. This and the ideaId to be the ideaId. Okay. And this should generate, let me import this, this should generate the 100 times 20 entries in our votes table. So let's make sure we have something in our vote factory. We're not going to actually make use of this is when I have, I mean, we are going to make use of it.
We're not going to actually make use of this is when I have, I mean, we are going to make use of it. But every time we do, we're going to make sure we add our own idea ID in our own user ID. But let's just put it here just in case. So let's just say this figure number between. So this is one in 100. And for the user, let's say one in 20. Okay. So back to our seeder.
Okay. So back to our seeder. So each User is going to generate a Vote for every single Idea. Now I want each User to vote for every single Idea. So let's add a conditional here. Let's say if I just add votes for the ideas that have an ID that are even just so we don't vote for every single Idea. So if idea->id mod two is zero, then vote for it. So only if it's even, okay. So now instead of 2000 votes, we should have 1000.
So only if it's even, okay. So now instead of 2000 votes, we should have 1000. So let's run this php artisan migrate:fresh --seed. Okay. So there's no database errors. So we know each row is unique. So I did that correctly. And let's check our votes table. And now we have 1000 rows. So each User should have voted for 50 different Ideas.
And now we have 1000 rows. So each User should have voted for 50 different Ideas. So we can verify that by just looking for a User. Okay. And you see 50 rows here. And each Idea should be voted for 20 times. So again, any Idea, any Idea that's even because that's what I did for my code. Okay, so we have 20. Okay, so now we can actually work on the app in the browser. So for this video, I just want to show the number of votes and they show up here and
Displaying Votes on Show9:21
Okay, so now we can actually work on the app in the browser. So for this video, I just want to show the number of votes and they show up here and the index page and also on the show page. And this doesn't show because I have to refresh. So this and this, okay, so let's start with the show page because that's a bit easier. So since I know I'm going to use Livewire for these components, when we work on the vote component, I'm going to go ahead and wrap this whole thing in a Livewire component. And same for this container in the index page. So let's do that first. So that would be in the show blade.
So let's do that first. So that would be in the show blade file. So we have a buttons container, and I believe that's this. And we also have an idea container. So let's verify that. So that's the buttons container. Where does it start? Starts right there. We also have this idea container. So they are sibling elements.
We also have this Idea container. So they are sibling elements. And usually for Livewire components, you want one root div or one root element. So let's wrap this and say div buttons or idea and buttons container, okay? And let's go all the way down here after our buttons container. So let me scroll down. So this should be it. Let me indent that and close that div. And let's put a comment here and idea and button container. Okay, let's grab the whole thing.
And let's put a comment here and Idea and Button container. Okay, let's grab the whole thing. Hopefully, I didn't mess that up. And it looks correct. Let's cut it out. Didn't take the comment, but that's fine. And let's make a new Livewire component. So Livewire, I'm going to call it IdeaShow. And the one on the index page will be called IdeaIndex. So IdeaShow, we're going to pass in some information here.
And the one on the index page will be called ideaIndex. So ideaShow, we're going to pass in some information here. But for now, we'll just render out what we had here. So let's go ahead and make that Livewire component, php artisan livewire:make ideaShow. Okay, cool. Let's go to ideaShow. Let's paste that in. Okay, and let's see if it still renders. And it doesn't because we have to pass in the idea. So let's go ahead and do that.
And it doesn't because we have to pass in the Idea. So let's go ahead and do that. So here, let's pass in the Idea. So $idea, sorry, it's $idea = $idea. And I believe I don't even have to use the mount method. Okay, maybe I do. So let's go to our IdeaShow class. And let's pass in that Idea. So it's a public $idea. Let's make a mount method, which is pretty much a constructor.
So it's a public Idea. Let's make a mount method, which is pretty much a constructor. And let's pass that in, idea, idea, import Idea, and say, this idea is idea. Okay, so does that work? Okay, so that works. And I'm also going to pass in a second parameter to the constructor or to the mount method. And that's going to be the voteCount. Now you can get the voteCount directly from the idea. But later on, when we make the voteCount dynamic, when we hit the vote button, it's a bit easier to update the voteCount when it's just passed in as a variable.
But later on, when we make the voteCount dynamic, when we hit the vote button, it's a bit easier to update the voteCount when it's just passed in as a variable. So let's say votesCount or voteCount, whatever you want. Okay, and let's add a public variable, votesCount. And this votesCount is votesCount, okay. So if you save that, we have to pass that in, in our show blade. So let's say votesCount. And for now, let's just pass in whatever here. So let's say five. Okay, and we might as well just use that variable being passed in, in the actual template here.
So let's say five. Okay, and we might as well just use that variable being passed in, in the actual template here or in the actual Livewire view. So let's go to idea.show. And we have it in two places, actually, because if you remember, we have here, and we also have it in a responsive version. So right here. So in this case, we have it here. So let's say votesCount.
So let's say votesCount. Okay. And we also have it somewhere else, 12, right here. Okay, pass that in. And that should be five now, because I hardcoded five. And it is. Okay, so I forgot to set up our belongsToMany relationship. So let's do that now. So let's go to User. And down here, let's set up a new method or right here.
So let's go to User. And down here, let's set up a new method or right here. Let's say votes. And this will return a belongsToMany relationship, belongsToMany. And it's to Idea class, and the table is votes. So if we were to say User votes, it will give us a collection of all the Ideas that the User voted for. So we can also add this or something similar to the Idea class. Same method name. Let's put it after this, but we can change this to User.
Same method name. Let's put it after this, but we can change this to User. So this would be a list of Users that voted for this particular Idea. Okay. So now in our IdeaController, for the show method, we can pass in the votesCount here as well. Or we can grab it from the idea, but I'm going to pass it like this. So votesCount is, so we have an Idea. So idea, you can do votes like this. And we can just call count on it.
So Idea, you can do votes like this. And we can just call count on it. And that should give us the count of the votes based on this idea. So now instead of hard coding that five in our show here, we can do votes count. And that should be correct. So like I showed you earlier, based on the seed data that we have here, it should be 20 if the ID is an even ID. So I'm not sure if this is even, it is, and it is 20. So that does work. So if you were to go to one that has an odd ID and be this one's odd, it should be zero.
So that does work. So if you were to go to one that has an odd ID and be this one's odd, it should be zero and it is. So every other one is 20 and zero. So it should be 20 and it seems to be working. Cool. So let's go back to this one. And it's important to know the difference between, let's go back to our controller Idea of votes as a method or votes as a field like this. So this should still work.
of votes as a method or votes as a field like this. So this should still work. If I save that should still be 20. But if you look at the number of models that went up to 24, so let's put back to 20. I mean to votes as a method, you'll see it four. But if it's votes as a field, you'll see it increased to 24. So the easiest way to see the difference between them is just to dd it. So let's dd votes as a field. And you'll see this is a list of users that voted for this particular Idea. And there we go.
And you'll see this is a list of users that voted for this particular Idea. And there we go. We have 20 users that voted for this Idea. So we're loading 20 users into memory and then counting them. So in this case, it's only 20 users. So it's not that much. But you can imagine if this Idea had several votes, say a few thousand, then it's going to load however many users voted for this Idea. So whatever thousand, however many thousand people and then count them. But all we want is the actual count.
So whatever thousand, however many thousand people and then count them. But all we want is the actual count. So the actual number. So yeah, it doesn't make sense to load in the users into memory when we just want the count. So let me dd idea of votes. This is not a collection of users, but it belongs to many relationship. So when we call count on this, this operation is being done on the database layer, which is much more performant. So yeah, make sure to use the correct method.
is much more performant. So yeah, make sure to use the correct method. Okay, so let's refresh. And I don't think I checked if the mobile version is showing the correct number of votes. So let's make sure it is. Okay, so it does. And let's add one more to the database to make sure the votes count is working. So this is the last vote, I believe, or the last idea, I mean. So let's go to our database. Should be this idea, and it is, let's make sure it is.
So let's go to our database. Should be this Idea, and it is, let's make sure it is. Okay, so that's ideaId 100. Okay, so let's add one more there to make sure it goes up to 21. So ideaId is 100. Actually, that's not going to work because there's going to be a duplicate. So let's go with 99 because that one is not even and say userId 1. So this shouldn't be in the database. So this should be added. Okay, as you see, we have 1001 now.
Optimizing Index Vote Counts19:17
So this should be added. Okay, as you see, we have 1001 now. And if I go back here, this one should have one vote now, and it does. Cool. So now let's work on the index page here. This is still hard coded. So let's make sure that that is working correctly. So if you go into our IdeaController, you'll see that we have the index method here, and we're passing through the ideas, and we're eager loading the relationships. So how do we get the vote count for each of these ideas?
we're passing through the ideas, and we're eager loading the relationships. So how do we get the vote count for each of these ideas? So one way would be to just grab it from the idea itself. So just like how we did it in the show method here. So ideaVotesCount for each idea. So that would be in the index page. Let's go to that. And I'm going to put this in a Livewire component as well. But let me just show you what I'm talking about. So let's look for 12.
But let me just show you what I'm talking about. So let's look for 12. Let's start with this one. And if I were to do ideaVotesCount, that would work. So let's put that in. And before I refresh, take note of the number of queries we're doing. So 4. And now we're up to 14, but it does work. So again, we have the n+1 issue here, because for each Idea here, we are performing another query.
So again, we have the n plus one issue here, because for each Idea here, we are performing another query. So again, we want to do something similar to the eager loads we're doing here, but with the vote count. So how do we do that? So Laravel actually provides that functionality with a withCount function. So let's see if that can work for us. And then we'll move this to a Livewire component. So let's put this back. Okay, and pretty straightforward.
So let's put this back. Okay, and pretty straightforward. We can just do countVotes, and should be in quotes. Let's see if this works for us. Actually, can we actually die and dump this like this? Or do I have to put it in a variable? Let's see if I can die and dump it. So I can show you what I'm trying to do here. Okay, so it does work. It's a Paginator, though.
Okay, so it does work. It's a paginator, though. Let me just change this to get for now. So I can show you what I'm trying to do. Okay, so now we get the 100 ideas, but now each one of these ideas has a voteCount associated with it. So we should no longer have that n plus one issue because they appear here. So it's not showing because I think there's too many ideas. So let me either take five or something. Okay.
So let me either take five or something. Okay. Try it again, and we have to run get on this. Sorry about that. Okay. So now we have a collection, but each Idea should have a votesCount. So where is it? Is it in attributes right there? votesCount. So this did not exist before because we didn't call withVotes or withCount votes.
Votes counts. So this did not exist before because we didn't call with votes or with count votes. So if I remove this, then we won't get that votes count field. So as you see, there's only nine fields and there's no votes count. So I hope that makes sense. Actually performs a sub query, which we will do in the next video. So let's put this all back to remove the dine dump. And now we can make use of the votes count, which should be on the Idea directly. So let's go back to our index, and it's a idea votes count. So we can do idea votes count, and we can put this on the other one as well.
So let's go back to our index, and it's an Idea votesCount. So we can do ideaVotesCount, and we can put this on the other one as well. So let me grab this. Let's look for the 12. There it is. Put that in there. And now this should work. And the number of queries should stay low, and it does. This works. 21.
This works. 21. 20. 20. 20. Okay. And everything is working. Cool. Okay. So let's move this into its own Livewire component like we did for the Idea show.
Okay. So let's move this into its own Livewire component like we did for the Idea show. So let's do that. So that would be, I believe I have it in an Idea container. So yeah, this whole div here. So let's grab this, cut it up. And the comment didn't go with it, but that's fine. Let's say livewire.idea.index is what I'll call it. We have to pass in some data. So let me just close it down here, and let's make that php artisan make:livewire idea.index.
We have to pass in some data. So let me just close it down here, and let's make that livewire:make IdeaIndex. Okay. Let's go back. IdeaIndex. So pretty much the same thing. Actually, let me paste in the markup first. Okay. Let's re-indent. And there is one root div here, so that should be okay.
Let's re-indent. And there is one root div here, so that should be okay. Let's pass in the data. Actually, let's do votesCount here instead of ideaVotesCount, because I'm going to pass that in as well. Say votesCount. Okay. Back to IdeaIndex, and it should be pretty much the same thing. So I'm just going to copy it from IdeaShow. So let's grab...
So I'm just going to copy it from idea.show. So let's grab... Actually, everything should be the same. Okay. Import. And does this work? Oh, we have to pass in the information. So let's say idea, idea. And the votesCount is ideaVotesCount.count. Okay.
And the votesCount is ideaVotesCount, underscoreCount. Okay. So did I do everything correctly? Looks like I did, but now it's a Livewire component. Cool. You can even see that the bug bar shows how many Livewire components there are on the page. So we have 10, and it shows 10 here. So that's pretty cool. So yeah, I did want to do testing in this video, but my explanation took longer than
So that's pretty cool. So yeah, I did want to do testing in this video, but my explanation took longer than I expected, so we'll do it in the next video. So one more thing I want to do here is just format this the same way that I formatted it on the index page. So just put it on its own line. Okay. So that looks good to me. Let's go ahead and run our tests to make sure I didn't break anything else parallel. Okay.
Let's go ahead and run our tests to make sure I didn't break anything else parallel. Okay. Everything else looks good. So let's make a commit here. Okay. Episode 17, git add, git commit, episode 17. It's a voting database structure.
