CB: What we need to do here is check, before we try to destroy a record, that it doesn't have any child records. I'll use one of the standard Rails Callbacks:
before_destroy. If a
before_ Callback returns false, the associated action is canceled. So, first I'm going to add the call to the Callback...
And then I'll add the Callback method itself...
def check_for_children recipes = Recipe.find_all_by_category_id(self.id) if !recipes.empty? return false end end
So we end up with...
CB: And now, before we forget, let's make sure there aren't any recipe records in the database to cause us problems again. We could just rerun our recipe test and let the
teardown method clean things up. But now that we've seen what happens when we do things out of order, I think we ought to address the "order" problem. I put the
teardown in the Unit test for recipes because that's where the records were getting created. But our problem really isn't that recipe records exist in the table at the end of the recipe test. Our problem happens when recipes exist at the beginning of the category test. So, what say we move the code to remove them from the teardown method in recipe_test.rb to a setup method in category_test.rb ?
Paul: Sounds good to me.
CB: Yeah, me too. So, I'll copy the teardown method from recipe_test.rb to category_test.rb and rename it...
def setup recipes = Recipe.find(:all) recipes.each do |this_recipe| this_recipe.destroy end end
We need to add the recipes fixture...
fixtures :categories, :recipes
And then we'll rerun our category test,
CB: Ta daaa!!!
Paul: Hold on, CB. We went down that road before. Why don't we just make sure our app's fixed this time?
CB: No problem, Paul. You still got Mongrel running? OK. Go ahead and browse to our category list page and try deleting one again.
Paul: OK. OK. It's not deleting the category, and it's not crashing, so it looks like we've got it taken care of. But now I'm wondering, if I've got to test it in the browser anyway, why spend the time to create the Unit test?
CB: That's a good question, Paul. We found out that we were missing a test by looking at the app from a higher level. And since we found the problem at the higher level, we wanted to go back there to make sure that what we'd done had actually fixed it. That's pretty typically going to be the case. But now we know that we've got a low-level test that catches the problem. So we don't have to test it again at the higher level. We can if we want to, and I probably will because I think of test suites like a layered defense, but the value of doing it again at that higher level will primarily be in making sure our test suite's not broken rather than making sure the app's not broken. It'll be easier to show you. If you're ready, I think we should get started on our Functional tests.
Paul: I'm ready. Let's do it.
CB: Cool. Let's take a look at the Functional test stub that Rails produced for us. Open up test\functional\category_controller_test.rb.
Paul: That looks a lot more complete than the Unit test scaffolds. More like the scaffolding Rails produces for the app itself.
CB: Yep. I think the reason Rails doesn't do more with the Unit test scaffold is, like I said before, the model is where we put validations and business logic. Rails can't predict a whole lot about what we'll want to do with those. But since it's generating the basic CRUD functionality in scaffold code for the controller, it can generate the tests for that. We've got test methods for all of the default methods in the controller, plus a setup method to prepare for each test method execution.
Paul: Yeah. I can see it's setting up four instance variables. But I only see one of them getting used in any of the methods. How're the other ones getting used?