Chapter 5. Saving User Input

We want to take the to-do item input from the user and send it to the server,
so that we can save it somehow and display it back to her later.

As I started writing this chapter, I immediately skipped to what I thought was
the right design: multiple models for lists and list items, a bunch of
different URLs for adding new lists and items, three new view functions, and about
half a dozen new unit tests for all of the above. But I stopped myself.
Although I was pretty sure I was smart enough to handle all those problems at
once, the point of TDD is to allow you to do one thing at a time, when you
need to. So I decided to be deliberately short-sighted, and at any given
moment only do what was necessary to get the functional tests a little further.

It’s a demonstration of how TDD can support an iterative style of development—it may not be the quickest route, but you do get there in the end. There’s
a neat side benefit, which is that it allows me to introduce new concepts like
models, dealing with POST requests, Django template tags, and so on one at a
time rather than having to dump them on you all at once.

None of this says that you shouldn’t try and think ahead, and be clever. In
the next chapter we’ll use a bit more design and up-front thinking, and show
how that fits in with TDD. But for now let’s plough on mindlessly and just do
what the tests tell us to.

Wiring Up Our Form to Send a POST Request

At the end of the last chapter, the tests were telling us we weren’t able to
save the user’s input. For now, we’ll use a standard HTML POST request. A
little boring, but also nice and easy to deliver—we can use all sorts of sexy
HTML5 and JavaScript later in the book.

When a functional test fails with an unexpected failure, there are several
things we can do to debug them:

Add print statements, to show, eg, what the current page text is.

Improve the error message to show more info about the current state.

Manually visit the site yourself.

Use time.sleep to pause the test during execution.

We’ll look at all of these over the course of this book, but the time.sleep
option is one I find myself using very often. Let’s try it now. We add
the sleep just before the error occurs:

functional_tests.py.

# When she hits enter, the page updates, and now the page lists# "1: Buy peacock feathers" as an item in a to-do list tableinputbox.send_keys(Keys.ENTER)importtimetime.sleep(10)table=self.browser.find_element_by_id('id_list_table')

Depending on how fast Selenium runs on your PC, you may have caught a glimpse
of this already, but when we run the functional tests again, we’ve got time to
see what’s going on: you should see a page that looks like
Figure 5-1, with lots of Django debug information.

Figure 5-1. Django DEBUG page showing CSRF error

Security: Surprisingly Fun!

If you’ve never heard of a Cross-Site Request Forgery exploit, why not look
it up now? Like all security exploits, it’s entertaining to read about, being
an ingenious use of a system in unexpected ways…

When I went back to university to get my Computer Science degree, I signed up
for the Security module out of a sense of duty: Oh well, it’ll probably be
very dry and boring, but I suppose I’d better take it. It turned out to be
one of the most fascinating modules of the whole course—absolutely full of
the joy of hacking, of the particular mindset it takes to think about how
systems can be used in unintended ways.

I want to recommend the textbook for my course, Ross Anderson’s
Security Engineering. It’s quite light on pure crypto, but it’s
absolutely full of interesting discussions of unexpected topics like
lock-picking, forging bank notes, inkjet printer cartridge economics, and
spoofing South African Air Force jets with replay attacks. It’s a huge tome,
about three inches thick, and I promise you it’s an absolute page-turner.

Django’s CSRF protection involves placing a little auto-generated token into
each generated form, to be able to identify POST requests as having come from
the original site. So far our template has been pure HTML, and in this step we
make the first use of Django’s template magic. To add the CSRF token we
use a template tag, which has the curly-bracket/percent syntax,
{% … %}—famous for being the world’s most annoying two-key touch-typing
combination:

Django will substitute that during rendering with an <input type="hidden">
containing the CSRF token. Rerunning the functional test will now give us an
expected failure:

AssertionError: False is not true : New to-do item did not appear in table

Since our time.sleep is still there, the test will pause on the final
screen, showing us that the new item text disappears after the form is
submitted, and the page refreshes to show an empty form again. That’s because
we haven’t wired up our server to deal with the POST request yet—it just
ignores it and displays the normal home page.

We can remove the time.sleep now though:

functional_tests.py.

# "1: Buy peacock feathers" as an item in a to-do list tableinputbox.send_keys(Keys.ENTER)table=self.browser.find_element_by_id('id_list_table')

Processing a POST Request on the Server

Because we haven’t specified an action= attribute in the form, it is
submitting back to the same URL it was rendered from by default (ie, /),
which is dealt with by our home_page function. Let’s adapt the view to be
able to deal with a POST request.

That means a new unit test for the home_page view. Open up lists/tests.py,
and add a new method to HomePageTest—I copied the previous method,
then adapted it to add our POST request and check that the returned
HTML will have the new item text in it:

Are you wondering about the line spacing in the test? I’m grouping
together three lines at the beginning which set up the test, one line in
the middle which actually calls the function under test, and the
assertions at the end. This isn’t obligatory, but it does help see the
structure of the test. Setup, Exercise, Assert is the typical structure
for a unit test.

You can see that we’re using a couple of special attributes of the
HttpRequest: .method and .POST (they’re fairly self-explanatory,
although now might be a good time for a peek at the Django
request and
response documentation). Then we check that the text from our POST request
ends up in the rendered HTML. That gives us our expected fail:

How can we test that our view is passing in the correct value for
new_item_text? How do we pass a variable to a template? We can find out by
actually doing it in the unit test—we’ve already used the render_to_string
function in a previous unit test to manually render a template and compare it
with the HTML the view returns. Now let’s add the variable we want to pass in:

As you can see, the render_to_string function takes, as its second parameter,
a mapping of variable names to values. We’re giving the template a variable
named new_item_text, whose value is the expected item text from our
POST request.

When we run the unit test, render_to_string will substitute
{{ new_item_text }} for A new list item inside the <td>. That’s
something the actual view isn’t doing yet, so we should see a test failure:

If you remember the rules for reading tracebacks, you’ll spot that it’s
actually a failure in a different test. We got the actual test we
were working on to pass, but the unit tests have picked up an unexpected
consequence, a regression: we broke the code path where there is no POST
request.

This is the whole point of having tests. Yes, we could have predicted
this would happen, but imagine if we’d been having a bad day or weren’t paying
attention: our tests have just saved us from accidentally breaking our
application, and, because we’re using TDD, we found out immediately. We didn’t
have to wait for a QA team, or switch to a web browser and click through our
site manually, and we can get on with fixing it straight away. Here’s how:

The unit tests should now pass. Let’s see what the functional tests say:

AssertionError: False is not true : New to-do item did not appear in table

Hmm, not a wonderfully helpful error. Let’s use another of our FT debugging
techniques: improving the error message. This is probably the most
constructive technique, because those improved error messages stay around to
help debug any future errors:

AssertionError: False is not true : New to-do item did not appear in table --
its text was:
Buy peacock feathers

You know what could be even better than that? Making that assertion a bit less
clever. As you may remember, I was very pleased with myself for using the
any function, but one of my Early Release readers (thanks Jason!) suggested
a much simpler implementation. We can replace all six lines of the
assertTrue with a single assertIn:

functional_tests.py.

self.assertIn('1: Buy peacock feathers',[row.textforrowinrows])

Much better. You should always be very worried whenever you think you’re being
clever, because what you’re probably being is overcomplicated. And we get
the error message for free:

If, instead, your FT seems to be saying the table is empty ("not found in
[]"), check your <input> tag — does it have the correct name="item_text"
attribute? Without it, the user’s input won’t be associated with the right
key in request.POST.

The point is that the FT wants us to enumerate list items with a "1:" at the
beginning of the first list item. The fastest way to get that to pass is with a
quick "cheating" change to the template:

lists/templates/home.html.

<tr><td>1: {{ new_item_text }}</td></tr>

Red/Green/Refactor and Triangulation

The unit-test/code cycle is sometimes taught as Red, Green, Refactor:

Start by writing a unit test which fails (Red).

Write the simplest possible code to get it to pass (Green), even if
that means cheating.

Refactor to get to better code that makes more sense.

So what do we do during the Refactor stage? What justifies moving from
an implementation where we "cheat" to one we’re happy with?

One methodology is eliminate duplication: if your test uses a magic constant
(like the "1:" in front of our list item), and your application code also uses
it, that counts as duplication, so it justifies refactoring. Removing the magic
constant from the application code usually means you have to stop cheating.

I find that leaves things a little too vague, so I usually like to
use a second technique, which is called triangulation: if your
tests let you get away with writing "cheating" code that you’re not happy
with, like returning a magic constant, write another test that forces you to
write some better code. That’s what we’re doing when we extend the FT to
check that we get a "2:" when inputting a second list item.

Now we get to the self.fail('Finish the test!'). If we extend our FT to
check for adding a second item to the table (copy and paste is our friend), we
begin to see that our first cut solution really isn’t going to, um, cut it:

functional_tests.py.

# There is still a text box inviting her to add another item. She# enters "Use peacock feathers to make a fly" (Edith is very# methodical)inputbox=self.browser.find_element_by_id('id_new_item')inputbox.send_keys('Use peacock feathers to make a fly')inputbox.send_keys(Keys.ENTER)# The page updates again, and now shows both items on her listtable=self.browser.find_element_by_id('id_list_table')rows=table.find_elements_by_tag_name('tr')self.assertIn('1: Buy peacock feathers',[row.textforrowinrows])self.assertIn('2: Use peacock feathers to make a fly',[row.textforrowinrows])# Edith wonders whether the site will remember her list. Then she sees# that the site has generated a unique URL for her -- there is some# explanatory text to that effect.self.fail('Finish the test!')# She visits that URL - her to-do list is still there.

Sure enough, the functional tests return an error:

AssertionError: '1: Buy peacock feathers' not found in ['1: Use peacock
feathers to make a fly']

Three Strikes and Refactor

Before we go further—we’ve got a bad
code smell[6]
in this FT. We have three
almost identical code blocks checking for new items in the list table. There’s
a principle called don’t repeat yourself (DRY), which we like to apply by
following the mantra three strikes and refactor. You can copy and paste code
once, and it may be premature to try and remove the duplication it causes, but
once you get three occurrences, it’s time to remove duplication.

We start by committing what we have so far. Even though we know our site
has a major flaw—it can only handle one list item—it’s still further ahead
than it was. We may have to rewrite it all, and we may not, but the rule
is that before you do any refactoring, always do a commit:

Back to our functional test refactor: we could use an inline function, but that
upsets the flow of the test slightly. Let’s use a helper method—remember,
only methods that begin with test_ will get run as tests, so you can use
other methods for your own purposes:

I like to put helper methods near the top of the class, between the tearDown
and the first test. Let’s use it in the FT:

functional_tests.py.

# When she hits enter, the page updates, and now the page lists# "1: Buy peacock feathers" as an item in a to-do list tableinputbox.send_keys(Keys.ENTER)self.check_for_row_in_list_table('1: Buy peacock feathers')# There is still a text box inviting her to add another item. She# enters "Use peacock feathers to make a fly" (Edith is very# methodical)inputbox=self.browser.find_element_by_id('id_new_item')inputbox.send_keys('Use peacock feathers to make a fly')inputbox.send_keys(Keys.ENTER)# The page updates again, and now shows both items on her listself.check_for_row_in_list_table('1: Buy peacock feathers')self.check_for_row_in_list_table('2: Use peacock feathers to make a fly')# Edith wonders whether the site will remember her list. Then she sees[...]

We run the FT again to check that it still behaves in the same way…

AssertionError: '1: Buy peacock feathers' not found in ['1: Use peacock
feathers to make a fly']

Good. Now we can commit the FT refactor as its own small, atomic change:

$ git diff # check the changes to functional_tests.py
$ git commit -a

And back to work. If we’re ever going to handle more than one list item,
we’re going to need some kind of persistence, and databases are a stalwart
solution in this area.

The Django ORM and Our First Model

An Object-Relational Mapper (ORM) is a layer of abstraction for data stored in
a database with tables, rows, and columns. It lets us work with databases using
familiar object-oriented metaphors which work well with code. Classes map to
database tables, attributes map to columns, and an individual instance of the
class represents a row of data in the database.

Django comes with an excellent ORM, and writing a unit test that uses it is
actually an excellent way of learning it, since it exercises code by specifying
how we want it to work.

Let’s create a new class in lists/tests.py:

lists/tests.py.

fromlists.modelsimportItem[...]classItemModelTest(TestCase):deftest_saving_and_retrieving_items(self):first_item=Item()first_item.text='The first (ever) list item'first_item.save()second_item=Item()second_item.text='Item the second'second_item.save()saved_items=Item.objects.all()self.assertEqual(saved_items.count(),2)first_saved_item=saved_items[0]second_saved_item=saved_items[1]self.assertEqual(first_saved_item.text,'The first (ever) list item')self.assertEqual(second_saved_item.text,'Item the second')

You can see that creating a new record in the database is a relatively simple
matter of creating an object, assigning some attributes, and calling a
.save() function. Django also gives us an API for querying the database via
a class attribute, .objects, and we use the simplest possible query,
.all(), which retrieves all the records for that table. The results are
returned as a list-like object called a QuerySet, from which we can extract
individual objects, and also call further functions, like .count(). We then
check the objects as saved to the database, to check whether the right
information was saved.

Django’s ORM has many other helpful and intuitive features; this might be a
good time to skim through the
Django
tutorial, which has an excellent intro to them.

I’ve written this unit test in a very verbose style, as a way of
introducing the Django ORM. You can actually write a much shorter test for a
model class, which we’ll see later on, in Chapter 11.

Terminology 2: Unit Tests Versus Integrated Tests, and the Database

Purists will tell you that a "real" unit test should never touch the database,
and that the test I’ve just written should be more properly called an
integrated test, because it doesn’t only test our code, but also relies on
an external system, ie a database.

It’s OK to ignore this distinction for now—we have two types of test,
the high-level functional tests which test the application from the user’s
point of view, and these lower-level tests which test it from the programmer’s
point of view.

We’ll come back to this and talk about unit tests and integrated tests in
Chapter 19, towards the end of the book.

To give our Item class a save method, and to make it into a real Django
model, we make it inherit from the Model class:

lists/models.py.

fromdjango.dbimportmodelsclassItem(models.Model):pass

Our First Database Migration

The next thing that happens is a database error:

django.db.utils.OperationalError: no such table: lists_item

In Django, the ORM’s job is to model the database, but there’s a second
system that’s in charge of actually building the database called migrations.
Its job is to give you the ability to add and remove tables and columns,
based on changes you make to your models.py files.

One way to think of it is as a version control system for your database.
As we’ll see later, it comes in particularly useful when we need to
upgrade a database that’s deployed on a live server.

For now all we need to know is how to build our first database migration,
which we do using the makemigrations command:

The Test Gets Surprisingly Far

That’s a full eight lines later than the last failure—we’ve been all the way
through saving the two Items, we’ve checked they’re saved in the database, but
Django just doesn’t seem to have remembered the .text attribute.

Incidentally, if you’re new to Python, you might have been surprised we were
allowed to assign the .text attribute at all. In something like Java, that
would probably give you a compilation error. Python is more relaxed about
things like that.

Classes that inherit from models.Model map to tables in the database. By
default they get an auto-generated id attribute, which will be a primary key
column in the database, but you have to define any other columns you want
explicitly. Here’s how we set up a text field:

lists/models.py.

classItem(models.Model):text=models.TextField()

Django has many other field types, like IntegerField, CharField,
DateField, and so on. I’ve chosen TextField rather than CharField because
the latter requires a length restriction, which seems arbitrary at this point.
You can read more on field types in the Django
tutorial
and in the
documentation.

A New Field Means a New Migration

Running the tests gives us another database error:

django.db.utils.OperationalError: no such column: lists_item.text

It’s because we’ve added another new field to our database, which means we need
to create another migration. Nice of our tests to let us know!

Let’s try it:

$ python3 manage.py makemigrations
You are trying to add a non-nullable field 'text' to item without a default; we
can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows)
2) Quit, and let me add a default in models.py
Select an option:2

Ah. It won’t let us add the column without a default value. Let’s pick option
2 and set a default in models.py. I think you’ll find the syntax reasonably
self-explanatory:

So, two new lines in models.py, two database migrations, and as a result,
the .text attribute on our model objects is now
recognised as a special attribute, so it does get saved to the database, and
the tests pass…

Saving the POST to the Database

Let’s adjust the test for our home page POST request, and say we want the view to save a new item to the database instead of just passing it through to its response. We can do that by adding three new lines to the existing test called
test_home_page_can_save_a_POST_request:

lists/tests.py.

deftest_home_page_can_save_a_POST_request(self):request=HttpRequest()request.method='POST'request.POST['item_text']='A new list item'response=home_page(request)self.assertEqual(Item.objects.count(),1)#new_item=Item.objects.first()#self.assertEqual(new_item.text,'A new list item')#self.assertIn('A new list item',response.content.decode())expected_html=render_to_string('home.html',{'new_item_text':'A new list item'})self.assertEqual(response.content.decode(),expected_html)

We check that one new Item has been saved to the database.
objects.count() is a shorthand for objects.all().count().

objects.first() is the same as doing objects.all()[0].

We check that the item’s text is correct.

This test is getting a little long-winded. It seems to be testing lots of
different things. That’s another code smell—a long unit test either needs
to be broken into two, or it may be an indication that the thing you’re testing
is too complicated. Let’s add that to a little to-do list of our own, perhaps
on a piece of scrap paper:

Code smell: POST test is too long?

Writing it down on a scratchpad like this reassures us that we won’t forget, so
we are comfortable getting back to what we were working on. We rerun the
tests and see an expected failure:

I’ve coded a very naive solution and you can probably spot a very obvious
problem, which is that we’re going to be saving empty items with every request
to the home page. Let’s add that to our list of things to fix later. You
know, along with the painfully obvious fact that we currently have no way at
all of having different lists for different people. That we’ll keep ignoring
for now.

Remember, I’m not saying you should always ignore glaring problems like this in
"real life". Whenever we spot problems in advance, there’s a judgement call
to make over whether to stop what you’re doing and start again, or leave them
until later. Sometimes finishing off what you’re doing is still worth it, and
sometimes the problem may be so major as to warrant a stop and rethink.

Let’s see how the unit tests get on … they pass! Good. We can do a bit of
refactoring:

lists/views.py.

returnrender(request,'home.html',{'new_item_text':item.text})

Let’s have a little look at our scratchpad. I’ve added a couple of the other
things that are on our mind:

Don’t save blank items for every request

Code smell: POST test is too long?

Display multiple items in the table

Support more than one list!

Let’s start with the first one. We could tack on an assertion to an existing
test, but it’s best to keep unit tests to testing one thing at a time, so let’s
add a new one:

We use a variable called new_item_text, which will either
hold the POST contents, or the empty string.

.objects.create is a neat shorthand for creating a new Item, without
needing to call .save().

And that gets the test passing:

Ran 5 tests in 0.010s
OK

Redirect After a POST

But, yuck, that whole new_item_text = '' dance is making me pretty unhappy.
Thankfully the next item on the list gives us a chance to fix it.
Always redirect after a POST,
they say, so let’s do that. Once again we change our unit test for
saving a POST request to say that, instead of rendering a response with
the item in it, it should redirect back to the home page:

We no longer expect a response with a .content rendered by a template, so we
lose the assertions that look at that. Instead, the response will represent
an HTTP redirect, which should have status code 302, and points the browser
towards a new location.

That gives us the error 200 != 302. We can now tidy up our view
substantially:

Better Unit Testing Practice: Each Test Should Test One Thing

Our view now does a redirect after a POST, which is good practice,
and we’ve shortened the unit test somewhat, but we can still do better. Good
unit testing practice says that each test should only test one thing. The
reason is that it makes it easier to track down bugs. Having multiple
assertions in a test means that, if the test fails on an early assertion, you
don’t know what the status of the later assertions is. As we’ll see in the next
chapter, if we ever break this view accidentally, we want to know whether it’s
the saving of objects that’s broken, or the type of response.

You may not always write perfect unit tests with single assertions on your
first go, but now feels like a good time to separate out our concerns:

lists/tests.py.

deftest_home_page_can_save_a_POST_request(self):request=HttpRequest()request.method='POST'request.POST['item_text']='A new list item'response=home_page(request)self.assertEqual(Item.objects.count(),1)new_item=Item.objects.first()self.assertEqual(new_item.text,'A new list item')deftest_home_page_redirects_after_POST(self):request=HttpRequest()request.method='POST'request.POST['item_text']='A new list item'response=home_page(request)self.assertEqual(response.status_code,302)self.assertEqual(response['location'],'/')

And we should now see six tests pass instead of five:

Ran 6 tests in 0.010s
OK

Rendering Items in the Template

Much better! Back to our to-do list:

Don’t save blank items for every request

Code smell: POST test is too long?

Display multiple items in the table

Support more than one list!

Crossing things off the list is almost as satisfying as seeing tests pass!

The third item is the last of the "easy" ones. Let’s have a new unit test
that checks that the template can also display multiple list items:

This is one of the major strengths of the templating system. Now the template
will render with multiple <tr> rows, one for each item in the variable
items. Pretty neat! I’ll introduce a few more bits of Django template
magic as we go, but at some point you’ll want to go and read up on the rest of
them in the
Django docs.

Just changing the template doesn’t get our tests to pass; we need to actually
pass the items to it from our home page view:

That does get the unit tests to pass … moment of truth, will the functional
test pass?

$ python3 functional_tests.py
[...]
AssertionError: 'To-Do' not found in 'OperationalError at /'

Oops, apparently not. Let’s use another functional test debugging technique,
and it’s one of the most straightforward: manually visiting the site! Open
up http://localhost:8000 in your web browser, and you’ll see a Django debug
page saying "no such table: lists_item", as in Figure 5-2.

Figure 5-2. Another helpful debug message

Creating Our Production Database with migrate

Another helpful error message from Django, which is basically complaining that
we haven’t set up the database properly. How come everything worked fine
in the unit tests, I hear you ask? Because Django creates a special test
database for unit tests; it’s one of the magical things that Django’s
TestCase does.

To set up our "real" database, we need to create it. SQLite databases
are just a file on disk, and you’ll see in settings.py that Django,
by default, will just put it in a file called db.sqlite3 in the base
project directory:

We’ve told Django everything it needs to create the database, first via
models.py and then when we created the migrations file. To actually apply
it to creating a real database, we use another Django Swiss Army knife
manage.py command, migrate:

But, as it’s running, you may notice something is amiss, like in
Figure 5-3.

Figure 5-3. There are list items left over from the last run of the test

Oh dear. It looks like previous runs of the test are leaving stuff lying around
in our database. In fact, if you run the tests again, you’ll see it gets
worse:

1: Buy peacock feathers
2: Use peacock feathers to make a fly
3: Buy peacock feathers
4: Use peacock feathers to make a fly
5: Buy peacock feathers
6: Use peacock feathers to make a fly

Grrr. We’re so close! We’re going to need some kind of automated way of
tidying up after ourselves. For now, if you feel like it, you can do it
manually, by deleting the database and re-creating it fresh with migrate:

$ rm db.sqlite3
$ python3 manage.py migrate --noinput

And then reassure yourself that the FT still passes.

Apart from that little bug in our functional testing, we’ve got some code
that’s more or less working. Let’s do a commit.

Start by doing a git status and a git diff, and you should see changes
to home.html, tests.py, and views.py. Let’s add them:

You might find it useful to add markers for the end of each chapter, like
git tag end-of-chapter-05.

Where are we?

We’ve got a form set up to add new items to the list using POST.

We’ve set up a simple model in the database to save list items.

We’ve used at least three different FT debugging techniques.

But we’ve got a couple of items on our own to-do list, namely getting the FT to
clean up after itself, and perhaps more critically, adding support for more
than one list.

I mean, we could ship the site as it is, but people might find it
strange that the entire human population has to share a single to-do list. I
suppose it might get people to stop and think about how connected we all are to
one another, how we all share a common destiny here on Spaceship Earth, and how
we must all work together to solve the global problems that we face.

But in practical terms, the site wouldn’t be very useful.

Ah well.

Useful TDD Concepts

Regression

When new code breaks some aspect of the application which used to work.

Unexpected failure

When a test fails in a way we weren’t expecting. This either means that
we’ve made a mistake in our tests, or that the tests have helped us find
a regression, and we need to fix something in our code.

Red/Green/Refactor

Another way of describing the TDD process. Write a test and see it fail
(Red), write some code to get it to pass (Green), then Refactor to improve
the implementation.

Triangulation

Adding a test case with a new specific example for some existing code, to
justify generalising the implementation (which may be a "cheat" until that
point).

Three strikes and refactor

A rule of thumb for when to remove duplication from code. When two pieces
of code look very similar, it often pays to wait until you see a third
use case, so that you’re more sure about what part of the code really
is the common, re-usable part to refactor out.

The scratchpad to-do list

A place to write down things that occur to us as we’re coding, so that
we can finish up what we’re doing and come back to them later.

[6] If you’ve not come across the concept, a "code smell" is
something about a piece of code that makes you want to rewrite it. Jeff Atwood
has a compilation on
his blog Coding Horror. The more experience you gain as a programmer, the more
fine-tuned your nose becomes to code smells…

[7] If you get a different error at this point, try restarting your
dev server—it may have gotten confused by the changes to the database
happening under its feet.