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

Reframing history scope0:00

Let's have a look at this test we wrote in the previous episode one more time. We got it to pass, which is great, but I think there might be room for improvement. Have a look at the name, ProductHistoryTest. This relates to product history, and yet, if I go to product, I don't see anything related to history here. I have to go to stock, and there, we record history for the stock, but that's not quite right, if you think about it. The history table doesn't show the history for that specific stock at this retailer. Instead, it shows the availability and pricing history, regardless of the stock, and across all retailers.

Instead, it shows the availability and pricing history, regardless of the stock, and across all retailers. So with that in mind, this feels a little bit off. So maybe we can change it up a bit. Let's go over the test. Given I have some seed data, we fake the endpoint, and we have no items in the history, well, rather than tracking a specific stock, let's instead track the product. So find the first product, and then track all of its stock, and of course, in this case, it's only one item there. So if we track that product, yeah, if I just get rid of this entirely, run it again, it

it's only one item there. So if we track that product, yeah, if I just get rid of this entirely, run it again, it still passes. Okay, but this lines up a little better. We're tracking a product, and then we're checking the history as a result of that. Next, if I bring this back, and we grab our stock, so that would be product hasMany stock, we'll just do that. And yeah, we get green. Okay, so now I want to refactor. It no longer makes sense to record history on the stock model.

Moving history to Product1:38

Okay, so now I want to refactor. It no longer makes sense to record history on the Stock model. So what I'm going to do here is comment all of this out. Okay, now when we track Stock, we don't do anything to the history, which means, of course, the test is going to fail. Let's see if we can make a pass, and I'll show you two different options you might consider. The first one, pretty basic. If a Stock is related to a Product, let's set that up. Then of course, we could say, well, on our Stock instance, grab the associated Product, and then just record the history there.

Then of course, we could say, well, on our stock instance, grab the associated product, and then just record the history there. And then maybe we pass in the stock instance. So now, on our Product model, we're effectively going to move all of this over here, like so. Now we'll make that public. And then we add history there, which means, once again, this commented bit of code can go down here. And we get that. Finally, if we go to recordHistory, that's now going to accept a stock instance, and

And we get that. Finally, if we go to record history, that's now going to accept a stock instance, and we would just update these here. And with any luck, we run it. No luck. Oh, yeah, yeah, yeah, yeah. So now, when we are referencing this history relationship, the productId will be set, because we're now on the Product model, right? So we only need to pass in the stockId. All right, one more time, and yeah, now that returns green.

So we only need to pass in the stockId. All right, one more time, and yeah, now that returns green. So that would be an option, where at least getting the active recordingHistory into the correct place. At least according to our current understanding. However, it still feels like the arrows are pointed in the wrong direction. Here's what I mean. On our Product model, we track a Product, and we do that by looping over all of the stockItems. And for each one, we call track on that.

Using callbacks for tracking3:34

stock items. And for each one, we call track on that. But then when we call that track method, we do what we need to, and then we go back to the product, and we tell the parent to record the history. So it's sort of like the parent is telling the child to do something, and then the child is telling the parent to do something, which isn't terrible. You're still sending messages back and forth. But I almost want to pass a closure. Something like this. Except the stock, we're going to set a closure here, where we record history, and we pass

Something like this. Except the stock, we're going to set a closure here, where we recordHistory, and we pass that through. Yeah, this can be a useful pattern to get into. So now we're saying loop over the stock, and for each one, track it. And when you're done, we're going to take over again and respond. It's almost like an inline listener. Waiting for when the stock is tracked, and then we're going to call recordHistory here. Now if we want to make that work, it looks like the track method on the Stock model here now is going to accept a closure, a callable, but it's not going to require one.

Now if we want to make that work, it looks like the track method on the Stock model here now is going to accept a closure, a callable, but it's not going to require one. Now I can remove this and just say, well, if you gave us some kind of callback, I don't care what it is. It can be anything you want, but if you gave us a callback, then like you requested, we'll trigger that function and pass through the current Stock instance, and then you can do whatever you want. Okay, so now, if I run it again, it still passes, but that's a slightly tweaked way to handle it. And of course, if we did not trigger that callback, the tests are going to fail, which

Considering event-based alternatives4:57

to handle it. And of course, if we did not trigger that callback, the tests are going to fail, which is what we'd expect. So now our Stock model looks like this. When it's tracked, we check the availability, we update the local table, and then if a callback is provided, we trigger it. Now you can imagine if you didn't do something like this, and you didn't want to reach for the parent, yeah, the only other option would be something like firing an event listener. So you could either fire it here, and then you'll have to create a new event class and a listener class that listens for the event and then responds by recording history.

So you could either fire it here, and then you'll have to create a new event class and a listener class that listens for the event and then responds by recording history. In general, I like to avoid that as much as I can because it does add complexity, creates all these new files that you have to search through to figure out what you're doing. So when I can, I like to keep it simple like this. But yeah, otherwise, if you didn't want to fire the event in that way, what you might do is set up dispatches events, and you might say something like, anytime the Stock model is updated, when Eloquent fires that update event, we're going to map that to something like stockUpdated. And this would be your custom event class that you can then listen for anywhere in your

Faking ClientFactory in tests6:02

like stock updated. And this would be your custom event class that you can then listen for anywhere in your application. So that would be another way to hook into a model event. But I'm going to stick with this. So now I want you to notice that if I go back to our ProductHistoryTest, we actually see references to a Product in history, which makes more sense to me. Finally, one last thing, rather than faking the HTTP call, we could instead just fake the ClientFactory. ClientFactory should receive a make call, then a checkAvailability call, and then that

the ClientFactory. ClientFactory should receive a make call, then a checkAvailability call, and then that will return our new stock status, where available is true, and the price is $99. Yeah, it's a slight tweak, but now we're never actually even going to a specific retailer client like this. We don't care about this class, so I have no desire to execute anything here. We'll fake it instead. So let's reformat, run it. Yeah, that's good. Maybe one or two more quick things.

Cleaning up test assertions7:02

Yeah, that's good. Maybe one or two more quick things. I still think we can clean this up a little bit. I'm noticing we have this $history variable in a grouping of assertions. Maybe we can wrap that. And then further, if we now know that a Product has $history, maybe we don't have to use the History model here. Maybe I could just say assertCount(0, $product). I'll have to grab that. But then something like this.

Product I'll have to grab that. But then something like this. And then we'll say product. Grab the first one. And then we can get rid of that. Yeah, let's have a look. So grab the first product, and at that point, a product should have no history. But then as soon as we track that product, well, it should have one item worth of history. So we give that a run. Oh, that doesn't work.

So we give that a run. Oh, that doesn't work. Oh yeah, we have to refresh. Because remember, whenever you load those relationships, they're cached. So on future calls, it's not performing a brand new database query. In this case, we want it though. So we run it again, and now that returns green. Now yeah, finally, these are related. So we could wrap it in tap. I sometimes do that, but I'm not sure it's going to be cleaner, because I'll still have

So we could wrap it in tap. I sometimes do that, but I'm not sure it's going to be cleaner, because I'll still have to pass in things with the use keyword. So instead, let's bring this all back. Does that pass? Yeah. Instead of history first, I could say productHistory, and then give me the first item there. And that'll still work. And what I could now do is say, well, instead of looking at the stock instance, which is fine, I already have these declared.

And what I could now do is say, well, instead of looking at the stock instance, which is fine, I already have these declared. So could we do this, and then available, and then the productId, and then the stockId. So I think that would work as well, and it does. And now it looks like I only referenced that stock variable once. So if it's only used once, I'm going to end line it. And finally, maybe we could do tap around product. Let's see what this looks like. So if I took that approach, these are related, aren't they? That'll still work.

So if I took that approach, these are related, aren't they? That'll still work. But yeah, by wrapping it, we can now see, okay, given the first product, it should not have history. But then we do a thing, and then if we refresh it, it should have one item worth of history. So yeah, tap would be an option there. I think it's superfluous. I'm not sure it makes much of a difference either way, but again, it's a preference thing. All right, good enough for a little demo here. So in the next episode, we'll figure out how to, quote, touch the product timestamps every

All right, good enough for a little demo here. So in the next episode, we'll figure out how to, quote, touch the product timestamps every time we track a piece of stock.

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