Lessons: 14Length: 2.1 hours

Next lesson playing in 5 seconds

  • Overview
  • Transcript

2.6 A Refactoring Opportunity

Let's take a moment now to consider our codebase and look for refactoring opportunities. In this lesson I'll debate some possibilities and then proceed to extract an important part of the code into its own class, supported by tests.

2.6 A Refactoring Opportunity

In this lesson we'll pick up on the pay method. Let me focus it right away. The pay method, as you can see, has a lot of information. I think this is a good opportunity for us to refactor this into its own class. I mean the pay method, even though it's simple enough, I mean it's just an initial stage, I think it can get pretty complex later on. Maybe we really want to implement that gateway, or we wanna have some more complex business logic. I wanna refactor this into its own class, but first I wanna implement a test into this order's controller. Let me go to test > integration, and create a new orders_test.rb. I don't really don't like to use the default testing set up for rails, I'd much rather have either unit tests or integration tests. So for that, I'm gonna create this very file, and I'm going to include a snippet of code that contains the tests that I want. As you can see here, I have this mini test, test inherited class. We're going to assert on the request, not really the controller, but better test a full application. So we're gonna pick up on the Rail's application as the app, because rack test depends on it, and then we'll create this test. We're gonna have a table and an order, and finally, we're gonna pay. So we're gonna create a new post request to that table, order and then /pay. The amount is going to be the order's total_amount, and then the payment method might be something like "cash", the same way as we did previously. At the end, we're going to assert that the recede key is there. We're going to use the response_hash method, which is a method that I've created in the test helper, as well as the spawn_order, and spawn_table methods. In the test_helper, you can see here all of these methods. Spawning tables, items, orders, as well as the response_hash that takes the response as an argument, we're basically going to parse the response that comes from JSON. You can check it out with more detail in the source code. Now let's go back here. If you run this right away, we won't be able to do it. First of all, because we need to add in some gems into the Gemfile. So let me just clean this up a little bit, and add some gems. First of all, let's specify the test group, because all of these gems should be part of this group. And then define the minitest-utils, and then we need to require something like minitest/utils, there you go. This will allow us to do something like test with a string and a block. We have it in the test case, so we're good. So, this is taken care of, let's move on to adding a rack-test, which will also need to require, let's see, rack/test. It can be a little daunting at first, but you'll get used to it, I'm sure. So we'll need to require at least these two gems. Let's bundle the changes right away, there you go. And now we can try and run this file, so let's run rake test. Well, this is going to be a little hard to understand at first, because we have loads of tests that are not running at all. We haven't been using all of these tests, so instead what I'm gonna do is, I'm gonna run this particular script. I'm going to include ruby -Itest, which will basically include the test folder. And then we're gonna go to test/integration/orders_test.rb. Let's see if this works, let's do this, and there you go. Now you can see that we have a failing test, we expected the message to include receipt. Let's take a look at it. We are spawning a new table, and an order with that table reference, and then we're trying to pay for it, but the method is failing. Let's take a look now at the order's controller. Judging by the pay method, let's see def pay, put it on the top, from here you can see that we have an error. Most likely, line 46 has been hit, because we are indeed, rendering a JSON message. So let's see if we can do a straight debug session. Let me go to the gem file again, and include 'pry'. Let's bundle the changes really quick, so we can have access to the tool, and run the same Ruby script again. Oh by the way, of course that we need to have at least a binding here, so that it can stop the execution, and now we can run the test. Okay, as you can see, I was right. This condition here in line 37 is not being met, so let's take care of the total amount. Let's check its value, it's zero. And params[:amount}, let's check its value as well. Oh it's a string, so indeed that specific condition here, matching it to params[:account] or rather [:amount] it is going to be false. So instead we need to make sure that this is a number. If I type that condition right away, I will get true as you can see, never mind the repetition. Basically, what we need to do is make that a number. So, it is actually a good thing that we introduced a test. It allowed us to fix a bug, so running the test now, so let's do that. Now, we expected something different. We expected all of these keys, at least the array of the keys, to include receipt. Even though we know for sure that this is working fine, we are going to do something like this. Let me see, we are gonna have to change some of this. I'm gonna pass in root true here. This will make sure that we have a receipt key that will include all of that data. So, if I run the test again, it will pass, so there you go. Actually if I type in the bin/curl command and with pay_order, you will now see a result like that. We have a receipt which has data inside, instead of just the data without the key. So this is awesome, with this test we have managed to fix a couple of bugs, and now we can perform this refactoring. Now, about that refactoring process, let's go to the order's controller again, just to tidy this up. And I want to extract the contents that are in the pay method here, I want to extract this into its own class. So how do we do that? Well, for starters, we'll need a new location so I'll create a lib directory with an order_payer.rp file. Let's start by creating the folder in which this file is enclosed, or will be, with mkdir %:h, this is a vim notation. Of course you can go to your file manager and create that folder yourself. There you go, and now I can save the file. From here, I can create an OrderPayer class with a constructor, let's start by doing that right away. I want to register an order first, so we'll do that, let's register that order. And then I also wanna make sure that I have an attr_reader key word, so we use that with :order. This will just make sure that we can access the order data from the outside, and now we go back to the order's controller test. The only dependency that we have here is the order, the amount as you see here, and also the payment method. Everything else relates to these three variables. Now I wanna create a new instance of that order pair and call a pay method, which gets an amount and a payment method, so, I'll do that. Let's see, I'll create an OrderPayer object, we'll send in the order, and this will be something like a service variable, and then I want to call the service.pay method. This method doesn't yet exist, but don't worry about that. Lets just start by shoving in the amount as a number and the payment method. Okay, now I want to make sure that I render something in case the service is successful. So, if the service was indeed successful, I want to render something. There, I want to render the receipt. Otherwise, I want to render some error message, this one in specific, so let's just do that. It looks like we're just extracting little bits and pieces but the behavior is still there or the structure is still there. But it's actually better off to create this kind of abstraction, and resort on messaging rather than on details. So from here it seems that we have it right. Now we need to extract this piece of code. Let me just go here and delete all of that, and move it to the previous one. Let me just save this, go back to the previous buffer, and create that ping method which contains this entire logic. So, from here, you will know that this is failing. I could show this to you, but really, we shouldn't do that. After all, we will have some errors regarding the render method, which doesn't exist in this class. So first of all, we'll just delete that because it's not part of this class. We need to establish the payment method and amount variables in here, and use them appropriately in the remaining of the business logic, there you go. Next we're going to supply a status variable here. In this case it will just mean that the receipt was saved successfully, and here the opposite case. So I'll just type in error, for example. In here I can type in the same thing, so @status = :error, because this is indeed an error. And if I want to, I can pass in a message here, let me just copy this exact message. So let's do that, paste it right here, and provide the error status. And then if you want to, the message will be that, there you go. Now let's just indent this properly, delete the JSON instruction, and we're good. In here I can also type in @receipt.errors, and I can expose these variables to the outside. So, @status and @message will do the trick. So whenever we need to display some sort of error message, we just print it out. We reached the message as exposed in line two. Going back to the orders controller here, let's just make sure we have the right variables. In here, I want the receipt exposed as well, but here, I want the services message, so we'll do service.message. We need to expose that receipt variable, so we'll do that right away. Let's see, the receipt here is also going to need to be exposed, so receipt it is. Now let's see if this works by running the ruby test. If pressing Enter, you will see that OrderPayer isn't there. Remember that this only regards the testing suite. So what we'll do is, we'll go to the orders test, and from here we'll require order_payer. Let's see if this now works, and now you will see that the okay method is not defined. If you paid close attention, which I didn't, you would know that we need this method. The okay method will simply check if the status is okay, that's it. Let's run this test again, and it's green, so great job. So let's recap this one more time. We've created a test that has a table and order pre populated. We want to pay for that order, and we expect a receipt to be there. Next in the controller, we have a pay method in which we instentiate an order, and then a service that holds that data, and then tries to pay for that order using the data that we've submitted. If the service is successful, we'll render the respective JSON, otherwise we'll render an error message. In the OrderPayer class, we will use the order, generate the receipt, and try to save the receipt. If we do, we set an :ok status, otherwise we establish an error message and status respectively. So there you go, this is a successful refactoring. We've extracted the core logic of our business into its own class.

Back to the top