تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Plan voted-by-user check0:00

Okay, let's continue working on the voting functionality, and in this video, I want to check if the Idea is voted for by a certain User. So in this case, the logged in User. So if you take a look at the design, if an Idea is voted for by the logged in User, then the number of votes changes to blue, and also the button changes to blue. So this should happen on this page and also the show page here. So let's work on that. So I'm gonna start with the show page here. So let's go into that. And how about we write a test first, I usually write my tests after like you've seen, but let's try working with a TDD workflow, at least for the unit test. So like I said, we want to check if an Idea is voted for by a particular User. So how do we want our API to look? So I'm thinking something like this. So we have an Idea. And we check if isVotedByUser, and then

Write unit test0:43

is voted for by a particular User. So how do we want our API to look? So I'm thinking something like this. So we have an Idea. And we check if is voted by User, and then we pass in a User or a User ID. So let's make something like this work. So I'm going to grab this. And this is going to be on the Idea model. So let's make a unit test for idea. php artisan make:test Let's call it IdeaTest. And let's make sure it's a unit test. Okay, let's go to that IdeaTest. And let's get rid of this. I need to use factories. So let's use RefreshDatabase. And we also want to extend the Laravel test case here. So let's go ahead and write our test. Let's call it canCheckIfIdeaIsVotedForByUser. Okay. And let's just paste that API ahead. And in this case, I want to assert that this is true. So let's go ahead and use that assertion assertTrue. Okay. And it's

user. Okay. And let's just paste that API ahead. And in this case, I want to assert that this is true. So let's go ahead and use that assertion assertTrue. Okay. And it's going to paste in the setup here. So we have an Idea, a User and everything that we need for that. So I have two Users here. So import User and import everything else we need. So Category and Status and also Idea. So two Users. And this is just so the Idea works. And we have one Idea. So let's also create a Vote because as of right now, there's no votes in the system. So say Vote::factory()->create(). And let's just say the idea_id is this Idea that we created. And then the user_id is this User that we created or the first User. So user_id. So now after we implement this functionality, this should actually pass. So let's import Vote. And let's see our first error. First error is not related to what

User. So userId. So now after we implement this functionality, this should actually pass. So let's import Vote. And let's see our first error. First error is not related to what we're doing. I just add classes here. Run that again. Okay, so now we get this isVotedForByUser is an undefined method. So let's go ahead and define that on the Idea model. So isVotedForByUser or isVotedByUser is what I named it. So let's make a method isVotedByUser. And let's pass in a User. We can also pass in a userId. myUser is fine for now. And let's just return true here. Let's make our test pass and run the test again. Okay, so now it's passing. So let's add another case here in our test. So the other case is for the other User. So it should assert false isVotedForByUser that didn't vote for anything. So this should return false again. Now if you run

Implement model method3:54

So the other case is for the other User. So it should assert false is voted for by the User that didn't vote for anything. So this should return false again. Now if you run our test should fail. Okay, so let's go ahead and work on the actual functionality. So how do we check if a User voted for an Idea? So all we have to do is check the votes table and check if an Idea exists based on the user_id. So in this case, the user_id is the logged in User. So that's pretty straightforward. Let's go ahead and write the code for that. So let's go ahead and query the votes table. So Vote::where('user_id', $userId)->where('idea_id', $this->id) because we are on the Idea model. Okay. And forgot my comma here. We can also use the exists method to check if a row is returned. So if a row is returned, this User voted for this Idea. And if not, then they didn't vote for that Idea. So let's

We can also use the exists method to check if a row is returned. So if a row is returned, this User voted for this Idea. And if not, then they didn't vote for that Idea. So let's go ahead and see if this works. Let's rerun that test. And now we are passing. So I want to handle one more case here. And that's when we pass in no, because no is the case when the User is not logged in. So let's see what we get. So let's try no. Let's run the test. And I have a missing bracket. Let's run it again. Okay, so now we're getting must be an instance of App\Models\User, no given. So that's because of this not accepting no. So we can just make it optional. So to do that in php, just put a question mark here. So let's rerun that test. And now we get a different error, because we actually have to check if the User exists. So let's just do this if not User. So if it's no, basically,

Update show page UI5:49

So let's rerun that test. And now we get a different error, because we actually have to check if the User exists. So let's just do this if not $user. So if it's no, basically, then return false. And if not, then go ahead and do what we did before. So again, oops, forgot the dollar sign. $user. And let's run that test again. And now all three cases pass. Okay, so now let's actually make it work in the browser. So we want this to be blue, and this to be blue in the show page. And then we'll work on the index page as well. So remember, this is a Livewire component. So let's take a look at that. So it's called IdeaShow. And let's add one more piece of state here. Let's call it hasVoted. And let's initialize it in the mount method. So this hasVoted equals. And now we have that new method that checks if the Idea is voted for by a particular User. So let's use it. Idea is voted by $user. And we can pass in $user.

So this has voted equals. And now we have that new method that checks if the Idea is voted for by a particular User. So let's use it. Idea is voted by User. And we can pass in $user. And if we're not logged in, then that should be null, and it should return false. So that case should be handled. So now we can make use of this piece of state and the view. So idea.show. So let's start with the text here should be blue. So it should be what's it called votes, or votesCount. So I keep forgetting if this one is the mobile one or the desktop one. So let me just put something in there. So this one's a desktop one. Okay. So we just got to add a conditional in the classes here. So let's do that. So if hasVoted, then the text should be blue. So we have text-blue as a class. And we can end the if. Okay, does that work? And we're not logged in. So that case is working when we pass in null. So let's go ahead and log in.

text blue as a class. And we can end the if. Okay, does that work? And we're not logged in. So that case is working when we pass in null. So let's go ahead and log in. Okay, so now I believe the one with my Gravatar here, there's 20 votes, and I should have voted for this one. So it should be blue. And it is cool. For the other ones. For example, I didn't vote for this one. No one did. It should still be 0. I mean, not blue. Okay, so that is working. Let's continue with this one. Also, make sure to add this for the mobile view. So this should be blue as well on the 20. So same thing. Let me just grab this. And that should be the other place where there's a votes count right here. So let me just paste that in. And, okay, does work. Let's add the button here. So it should be blue. So in this case, there's two conditionals. There's the class and also the text here. So I'm just going to put the

in. And, okay, does work. Let's add the button here. So it should be blue. So in this case, there's two conditionals. There's the class and also the text here. So I'm just going to put the conditional on the button, even though there's some duplication there. For me, it's easier to read. So let's do everything. So I'm just going to put the conditional on the button. For me, it's easier to read. So let's do if hasVoted. And then we can do the else case as well. And we can put this in the else case. Okay. And copy this. Put this in here. So this one, the text is voted. The BG is BG-blue. Same with the border, or we can remove the border, but I'll keep it. Make sure it's blue. And also the hover, that should be blue. hover, blue hover. Okay. And let's see if this works. So this is for the mobile case. It should be text-white as well. So let's add that after here.

Fix index page N+111:51

So same deal here, blue text and blue vote button if we voted for it. So that's the IdeaIndex Livewire component. And we can do something similar, but we'll get an M+1 issue again. So let's do that first. So let's go to IdeaIndex and let's just grab what we have from IdeaShow. So hasVoted. So let's paste that in here. So now we have that new piece of state. And let's add that to our view. So IdeaIndex. So same deal here. So if hasVoted, then we can add text blue. So I'm not sure if this is the mobile or desktop. We'll see in a second. Okay. So take note of the queries here. So we have six refresh, and I have an error hasVoted is undefined. And I forgot to add it here. So let's do that. public hasVoted. Okay,

Okay. So take note of the queries here. So we have six refresh, and I have an error hasVoted is undefined. And I forgot to add it here. So let's do that. Public hasVoted. Okay, try that again. So it's six queries. And now we're up to 16. So again, we have the N+1 issue, because for each one of these, we're performing a new query. So it's not a huge deal for this app in particular, because we have pagination. But let's see how we can get rid of that and optimize our queries. So if you take a look at our IdeaController, we had the same issue with the votesCount, and we fixed it with this withCount method that Laravel provides. So let me just remove this hasVoted for now. I'll leave it in there. Actually, no, let me just take it out for now. We can leave this. But let's take this out. And let's remove it from the index. So we'll do this later. Okay. And we should be back down to six queries or whatever it was. Okay.

for now. We can leave this. But let's take this out. And let's remove it from the index. So we'll do this later. Okay. And we should be back down to six queries or whatever it was. Okay. So if you take a look at the queries being run here, you'll see the one where we do or where we get the votes count. So let me just grab this. And we'll take a look at the raw query in SQLAce. So let's go to queries or query and space that in. And let me just reformat this for you and make the text bigger. Okay, so that's the same query, but I reformatted it. So let's go ahead and run that. Let me get rid of this limit 11. Okay. So as you can see, this method that Laravel provides. So the withCount method is actually performing a subquery. And you can see that right here. So this subquery is responsible for grabbing the votes on this particular Idea. And what this does is it adds a new column right here called votes_count.

And you can see that right here. So this subquery is responsible for grabbing the votes on this particular Idea. And what this does is it adds a new column right here called votes_count. So we can use it directly. And there is no more N+1 issue because this votes_count is associated with the query or the one query that we did here, this query. Okay. So we want to do pretty much the same thing, but with a new column. And that new column has information about whether the User on this case, the logged in User voted for each particular Idea. So essentially, it's just going to be a Boolean that tells us if the User voted for this Idea, yes or no, this Idea, and so on and so forth. So yeah, it's going to be a new column, like I said. So how are we going to do that? So similar to what we did in the Idea model, with this query here, so this is basically how you tell if a User voted for a particular Idea.

So how are we going to do that? So similar to what we did in the Idea model, with this query here, so this is basically how you tell if a User voted for a particular Idea. So let's do that in raw SQL. So let me just comment this out. And let's do that. So it's basically select. And let's do * for now, from the votes table, where user_id equals where is logged in, let's just say 1, and idea_id, let's say 2, because I have that set to only vote for even ID votes in my Seeder. So this should result in a row. And it does. But if there is no Idea, then it should result in no row. So one, there's no Idea. And it's nothing. So we have to incorporate this into this query above using a sub query. So let's see how we can do that. So when making use of sub queries, you want to return only one column. So it doesn't matter what column, so I'm just going to select the ID. And it's going to

So let's see how we can do that. So when making use of sub queries, you want to return only one column. So it doesn't matter what column, so I'm just going to select the ID. And it's going to be the same thing. So it's going to be null if there is no result. But if there is, then it will return an ID. In this case, there it is right there. So let me just format this. So it's on its own line. And that should still work. And it does. And now let's incorporate this into the query above. Let's put this back. And let's put it underneath the other sub query. So after this, and I believe we need a comment here. And let's put in brackets. Let's paste it in. And let's alias it. So as it's a votedByUser. So let's see if this works. And it looks like it does. Cool. So now we should have that new column that tells us if this particular User, so in this case, one voted for this Idea. Okay, so we're getting one for each of these results, which is

does. Cool. So now we should have that new column that tells us if this particular user, so in this case, one voted for this idea. Okay, so we're getting one for each of these results, which is not correct, because we have to change this idea ID. So we don't want to hard code that we want to grab the ID from the parent query. So to grab that it's just ideas from up here, dot ID. So see if that works. Okay. And now it looks like it works. So this particular idea was voted for by the user because it's not no, this one to this one too. And now this one's null, so it wasn't for and so on and so forth. Okay, so that looks good. But how do we write this in Laravel? So there is a section in the documentation for sub queries. And if you take a look at the syntax here, we can do something similar. So let's go ahead and write that back to our IdeaController. So let's put it before the withCount. And let's say addSelect.

here, we can do something similar. So let's go ahead and write that back to our IdeaController. So let's put it before the withCount. And let's say addSelect. And votedForUser is what the alias is votedByUser sorry. So that's pretty much this part of the query right here. And let's say vote, select id. So that's this part. And let's import Vote. And let me put this on its own line, we can do where userId is say $id. So the logged in user. Okay. And that should be null if the user is not logged in. And then we can do where column ideaId is ideas.id. So that's basically what I just showed you right here. Okay, so let's see if this works. Actually, let me just dd this. So we can see if we get that new column. So let's do this. And let's see if we get that new column.

Okay, so let's see if this works. Actually, let me just dd this. So we can see if we get that new column. So let's do this. And let's see if we get that new column. So back here, refresh. And the items are in here. There are 10 of them. And let's check. So it looks like we do right here. So what is votedByUser or votedByUser. So in this particular case, the User voted for this Idea. So it's pretty much the same result as what we have here. So 50. So next one should be 1001. And then the one after that should be no. Okay, right here 1001. And then this one should be no. Actually, no, not no, it's 49. All right. Yeah, next one should be no. So let's verify that. And it is cool. So it looks like it is working. So let's get rid of the dd. And now we have access to this new column. So we can just reference that in our idea show.

And it is cool. So it looks like it is working. So let's get rid of the die and dump. And now we have access to this new column. So we can just reference that in our IdeaShow Livewire component, or sorry, IdeaIndex Livewire component. So instead of doing this, which is an extra query, we can use that new column. So it's called votedByUser. Idea, votedByUser, by User, okay, and authUser. So now let's see if the queries or the number of queries doesn't go up by too much. Actually, it shouldn't go up at all. Okay, so we get an error, what do we get here? Sorry, we no longer need to pass this in. Let's try that again. Okay, so we still have six queries. But now we should have that hasVoted piece of state. And we can do the same thing we did in the show view. So let's try that. So I guess I can put this back. Or redo. Yeah, let's put this back.

Add Livewire tests23:33

User who is logged in, shows voted if Idea already voted for. Okay. So we have all this set up here. Let's go ahead and create a new test. Okay. So we have all this set up here. Let's go ahead and vote for that idea. So I believe I did that in the IdeaTest. So let's just grab that. Okay. Put it here. So this User voted for this idea. Okay. And now we can test the IdeaShow class, but we want to make sure we're logged in so we can use the actingAs method acting as a User. Okay. Let's test this. We're passing in the idea and the votesCount. Okay. So that's the same. And now we are asserting that hasVoted is true. So that new piece of state that we added. So say hasVoted true, and we can assert that we see the text voted, or you can also assert the HTML if you want, but I'm okay with just the text. Okay.

So say hasVoted true, and we can assert that we see the text voted, or you can also assert the HTML if you want, but I'm okay with just the text. Okay. So did I do that correctly? Semicolon. I had to run that test. Hopefully it does work. Okay. So it does. So let's double check. So if I didn't vote for that Idea, it should fail. So let's comment that out, rerun the test and it does fail because hasVoted is false. Okay. So let's put that back and I'm going to do the same thing for Idea, but I'll just paste it in because it's pretty much the same thing. So the test is actually a bit different on the index page. So the only change I've made here is I changed the Livewire component. And the problem is we're testing the Livewire component in isolation. So here's the test for it. And the Idea comes from here. So where we created it up here, however, we're doing that sub query in the controller right here.

live wire component in isolation. So here's the test for it. And the Idea comes from here. So where we created it up here, however, we're doing that sub query in the controller right here. So it's adding these two new columns to the Idea instance. And when we're testing it, it's not adding those two columns. So that probably doesn't make sense to you. So let me just show you what I mean. So let's go to our idea index, livewire component, and let's just dd the idea here. So this idea is not going to have the votesCount, and it's not going to have votedByUser. So let's run that test. And there you see, there are only nine fields here, and there should be 11 when we add those two new votes fields. So let's fix this. And let's make sure to hit that part of the controller that adds those two new fields. So let's get rid of this dd. Actually, let's leave it in there so we can see it work. So we're actually not going

sure to hit that part of the controller that adds those two new fields. So let's get rid of this time dump. Actually, let's leave it in there so we can see it work. So we're actually not going to make use of this Idea here. But we'll leave it in anyways. Okay, so let's visit that route. So let's say response, this acting as so logged in, we can make use of the User. And we're going to visit the index route. So get idea.index. Okay. So when we do that, we can actually grab the actual data. So where is it, we can grab this data from the response. So let's do that. Actually, we are making use of this because by the time we get to here, there's one Idea in the system. Okay, so let's do idea with votes. So this Idea will have those two new fields. So let's just say response ideas. Actually, that should be an array

Okay, so let's do Idea with votes. So this Idea will have those two new fields. So let's just say responseIdeas. Actually, that should be an array like this. Okay. And let's just dd that for now to see if we are getting the correct thing. Okay, let's run this test. Sorry, this should be route here. So add that. Try again. Okay, so now what are we getting? Okay, so this time dd is coming from the Livewire component. But if you look here, we do have those two new fields. Okay, so let's get rid of that for now. So this time dd right here, actually, just get rid of it, because we know it exists. But now let's dd responseIdeas. Okay, so what do we get here? So we have a field name items, and that is a collection.

But now let's die and dump response ideas. Okay, so what do we get here? So we have a field name items, and that is a collection. That is a collection. So let's see if we can grab that items. Let's run that again. Okay, so that's a collection of all of our ideas. Let's see. And it looks like it is, we only have one. So let's grab the first one. So there should be an Idea with those two new votes field attached to it. Okay, let's run that again. So that's an array. So can we just do 0 here? Okay, so looks like that works. Okay, so this is an Idea. But this Idea now has these two new votes fields. Okay, so now instead of passing in this original Idea, then we can pass in this new one. So Idea with votes. So now, this should work, hopefully, after all that explaining. Okay, so it does work. So yeah, we just have to make sure that we were going through the Idea.

new one. So Idea with votes. So now, this should work, hopefully, after all that explaining. Okay, so it does work. So yeah, we just have to make sure that we were going through the IdeaController here to get those two new votes fields that we added using subqueries. So let's go ahead and run our entire test suite to make sure everything is still working. And some stuff is now failing. So this test is failing. So yeah, I think it's because of these two assertSeeHtml. So this is pretty brittle. I mean, you can add if you want if you don't change it often, but you can either remove it. So let's try removing it first and see if it passes. Or you can update the HTML. Okay, so now passes. So I believe we just added the text blue to these HTML elements. And I believe it's at the end. So again, this is pretty brittle. You can add them if they give you more confidence.

And it does. So that further proves my point that using these types of asserts, the HTML can be very brittle if you're changing that element often. So yeah, I'm just gonna remove it. This is enough confidence for me to know that it's working correctly. So let's just remove those. And let's run the suite again. Oops, forgot my semicolon. And I believe the same thing for the index page. So 97. Yeah. So I'm gonna remove these. And now everything is passing. Cool. So yeah, that took longer than expected. Let's go ahead and finish up and make a commit. So let's git add, git commit. Actually, what is this? 19. git commit. Episode 19. Check if Idea voted for.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟