Moving Pictures with Sinatra, Part II

Tweet
This entry is part 2 of 3 in the series Moving Pictures with Sinatra

Moving Pictures with Sinatra

mp_sinatra

In part I we set up a Sinatra site to read pictures from a directory. Now, we are going create a form to upload pictures, along with tests to make sure it works.

You will need a form with a file input for this to work. Let’s write a test to make sure that happens.

test/test.rb

...
def test_for_upload_form
  get '/upload'
  assert last_response.ok?
  assert last_response.body.include?("type=\"file\"")
end
...

This test checks that one of the inputs of the form will be a type “file”. It isn’t yet, so that test should fail.

$ ruby test/test.rb
Run options: --seed 34613

# Running tests:

..F

Finished tests in 0.022114s, 135.6607 tests/s, 180.8809 assertions/s.

  1) Failure:
test_for_upload_form(MyTest) [test/test.rb:27]:
Failed assertion, no message given.

3 tests, 4 assertions, 1 failures, 0 errors, 0 skips

My line 27 is

assert last_response.ok?

If you’re curious, start up the app and go to the /upload page.

mp-get-upload

Looks like we need a route handler for upload. Add that to the main.rb file.

main.rb

get '/upload' do
  "Hello World"
end

Do you think the test will pass or fail? Run it and find out.

$ ruby test/test.rb
Run options: --seed 10631

# Running tests:

F..

Finished tests in 0.022114s, 135.6607 tests/s, 226.1011 assertions/s.

  1) Failure:
test_for_upload_form(MyTest) [test/test.rb:28]:
Failed assertion, no message given.

3 tests, 5 assertions, 1 failures, 0 errors, 0 skips

The previous error is gone but now there’s a new one. We do not have a form for that page.

main.rb

...
get '/upload' do
  erb :upload
end
...
@@upload
<!DOCTYPE html>
<html>
  <head>
  <meta charset="UTF-8">
  <meta name="viewport" content="user-scalable=yes, width=device-width" />
<title>Upload Moving Pictures</title>
</head>
<body>
  <form action="/upload" enctype="multipart/form-data" method="post">
    <label for="file">File to upload:</label>
    <input id="file" type="file" name="file">
    <input id="submit" type="submit" name="submit" value="Upload picture!">
  </form>
</body>
</html>
...

If you remember, we are using Sinatra’s handy-dandy inline views. Now that we have a form that will allow us to upload a picture, do you think the test will pass this time?

$ ruby test/test.rb
Run options: --seed 6047

# Running tests:

...

Finished tests in 0.022980s, 130.5483 tests/s, 217.5805 assertions/s.

3 tests, 5 assertions, 0 failures, 0 errors, 0 skips

You have an upload page and the form that has the ability to select a file. What’s the next step? Yep, upload the file to the public/slideshow_pictures directory. Let’s write a test that will post to our new upload route handler and give an indication that the upload happened.

test/test.rb

...
def test_upload_file
  post '/upload'
  assert last_response.ok?
  assert last_response.body.include?("Uploaded")
end
...

Run the test.

$ ruby test/test.rb
Run options: --seed 59047

# Running tests:

...F

Finished tests in 0.023828s, 167.8697 tests/s, 251.8046 assertions/s.

  1) Failure:
test_upload_file(MyTest) [test/test.rb:33]:
Failed assertion, no message given.

4 tests, 6 assertions, 1 failures, 0 errors, 0 skips

The response was not OK. Just out of curiosity go ahead and start the server

$ ruby main.rb

Go to the upload page. and submit the form. Here is what the app gave me:
mp-post-upload
That suggests text to the main.rb file but let’s change “Hello World” to “Uploaded.”

main.rb

post "/upload" do
  "Uploaded."
end

Run the test. Did it pass? Cool. Congratulations, we just wrote a horrible test. The test name is test_upload_file., yet we didn’t even test to see if anything was uploaded. How would you test for that?

You can count the number of files in the slideshow_picture directory. Upload a file and check if the number of files have increased by one. Let’s rewrite the test.

test/test.rb

...
def test_upload_file
  pictures_before = load_pictures.length
  post '/upload'
  pictures_after = load_pictures.length
  assert_equal (pictures_before + 1), pictures_after
end
...

Let’s try that. Run the test.

$ ruby test/test.rb
Run options: --seed 51246

# Running tests:

..F.

Finished tests in 0.030883s, 129.5211 tests/s, 194.2816 assertions/s.

  1) Failure:
test_upload_file(MyTest) [test/test.rb:35]:
Expected: 2
  Actual: 1

4 tests, 6 assertions, 1 failures, 0 errors, 0 skips

Better. This shouldn’t be some fly by night test. We don’t have any code to upload the file in the post '/upload' route handler. Let’s add that.

main.rb

post "/upload" do 
  File.open('public/slideshow_pictures/' + params['file'][:filename], "w") do |f|
    f.write(params['file'][:tempfile].read)
  end
  "Uploaded."
end

Here we take the file from the form, open it in the appropriate directory, and write the file. Think the test will pass now? We have the code to write the file. Run it and see.

$ ruby test/test.rb
Run options: --seed 51246

# Running tests:

..F.

Finished tests in 0.030883s, 129.5211 tests/s, 194.2816 assertions/s.

  1) Failure:
test_upload_file(MyTest) [test/test.rb:35]:
Expected: 2
  Actual: 1

4 tests, 6 assertions, 1 failures, 0 errors, 0 skips

What happened? We posted to the upload method. Wait, did we send a file? We need to send a file param to upload method.

test.rb

...
post '/upload', {:file =>{:filename=>"cat.jpg"}}
...

This will create an empty file named cat.jpg How about this time…will the test past?

$ ruby test/test.rb
Run options: --seed 42496

# Running tests:

....

Finished tests in 0.078357s, 51.0484 tests/s, 76.5726 assertions/s.

4 tests, 6 assertions, 0 failures, 0 errors, 0 skips

Sweet. King of the world!
Just for fun, let’s rerun the test and look at the awesomeness.

$ ruby test/test.rb
Run options: --seed 25680

# Running tests:

F...

Finished tests in 0.083972s, 47.6349 tests/s, 71.4524 assertions/s.

  1) Failure:
test_upload_file(MyTest) [test/test.rb:35]:
Expected: 3
  Actual: 2

4 tests, 6 assertions, 1 failures, 0 errors, 0 skips

Now it’s like a farewell to kings! What happened? If you look in the public/slideshow_pictures directory, there is already a cat.jpg. We’re just writing over it when we upload another cat.jpg file in the test. This means we are not increasing the number of files, so the test fails.
How would you delete that file? One option is to see if the file already exists. If it does, go ahead and delete it.

test/test.rb

...
def test_upload_file
  if File.exists?("public/slideshow_pictures/cat.jpg")
    File.delete("public/slideshow_pictures/cat.jpg")
  end
  pictures_before = load_pictures.length
  post '/upload', {:file =>{:filename=>"cat.jpg"}}
  pictures_after = load_pictures.length
  assert_equal (pictures_before + 1), pictures_after
end
...

Rerun the test.

$ ruby test/test.rb
Run options: --seed 12414

# Running tests:

....

Finished tests in 0.069093s, 57.8930 tests/s, 86.8395 assertions/s.

4 tests, 6 assertions, 0 failures, 0 errors, 0 skips

You should get the same result if you run the test again.
Now we have a site that can upload images and will display them.

Let’s go over what we did.

  • We wrote and can now recognize a worthless test.
  • We learned how to see if files exists.
  • We learned how to delete files.

In the next (and final) article in the series, we’ll add some CSS, check for specific file types, and wrap up our basic image display application.

Moving Pictures with Sinatra

<< Moving Pictures with Sinatra, Part IMoving Pictures with Sinatra, Part III >>

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Rodney Blevins

    Hi, I believe there’s a typo where you say to add the line post ‘/upload’, {:file =>{:filename=>”cat.jpg”}}. Above that you list the file as being “main.rb” but shouldn’t that be “test.rb”? It becomes obvious a little later when you show the code for the whole block, but if you just add that line to main.rb you get an error, obviously.

  • Anonymous

    Thanks Rodney, I have made that change.