Writing the photo upload component

How about uploading files?

We can create a feature to test it.

 1Feature: Photo Upload
 2  Scenario: Uploading link
 3    Given I am on homepage
 4    When I click "Upload photo" link
 5    Then I should be on the photo upload page
 6
 7  Scenario: Uploading photo
 8    Given I am on photo upload page
 9    When I upload a valid photo with title "Test"
10    Then I should see "Success"
11    And I should see "Test"
12
13  Scenario: Uploading invalid photo
14    Given I am on photo upload page
15    When I upload an invalid photo with title "Test"
16    Then I should see "Error"

Again, lot’s of errors/pendings and it is absolutely normal.

The first error is the missing “Upload photo” link. Easy one, Let’s create a link.

1<%= link_to 'Upload photo', '#' %>

Yes, link to '#' now because we just want minimal code to make the test works.

Then we add the pending step one by one. The next one is the I should be on the photo upload page.

1Then(/^I should be on the photo upload page$/) do
2  current_path.should == '/photos/new'
3end

Run the rake cucumber again and we get expected: "/photos/new" got: "/"' error. That’s good, it indicates that we can finally create the photo model and controllers. Don’t write unnecessary code until the last minute.

In order to make the case passes, we need the photo resource and a photo upload page.

First, it is about the routes.

1Gallery::Application.routes.draw do
2  devise_for :users
3
4  resources :photos
5
6  root to:'pages#index'
7end

Next, the model.

1$ rails generate model photo title:string
2$ bundle install
3$ rails generate paperclip photo image
4$ rake db:migrate

Then make the photo.rb file match the following.

1class Photo < ActiveRecord::Base
2  attr_accessible :title, :image
3
4  has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
5
6end

And the controller with new action.

$ rails generate controller photos new

The controller file.

1class PhotosController < ApplicationController
2  def new
3    @photo = Photo.new
4  end
5end

And the related view.

 1<%= form_for @photo, html: { multipart: true } do |f| %>
 2  <p>
 3    <%= f.label :title %>
 4    <%= f.text_field :title %>
 5  </p>
 6
 7  <p>
 8    <%= f.label :image %>
 9    <%= f.file_field :image %>
10  </p>
11
12  <p><%= f.submit 'Upload Photo' %></p>
13<% end %>

Nice, now we should now passes all existing cases with some pendings steps.

Cucumber result 2

The next pending is I am on photo upload page which should be just a visit method.

1Given(/^I am on photo upload page$/) do
2  visit '/photos/new'
3end

Then it is the I upload a valid photo step, which requires us to prepare a dummy PNG file and place it in the features/upload_files folder. (You can use any folder indeed)

But how to write the step for file uploading? Here it is.

1When(/^I upload a valid photo with title "(.*?)"$/) do |title|
2  fill_in 'Title', with: title
3  attach_file 'Image', File.join(Rails.root, 'features', 'upload_files', 'ok.png')
4  click_button "Upload Photo"
5end

Now we got the The action 'create' could not be found for PhotosController error. Well, we haven’t handle the form POST yet.

And here we define the create method in photos_controller file.

 1def create
 2  @photo = Photo.new params[:photo]
 3  if @photo.save
 4    redirect_to @photo, notice: "Success"
 5  else
 6    flash[:alert] = "Error uploading photo."
 7    render :new
 8  end
 9end

This time cucumber generates another great failing: The action 'show' could not be found for PhotosController. It is like a virtual mentor telling us what to do next – the show method.

In the photos controller.

1def show
2  @photo = Photo.find params[:id]
3end

The view views/photos/show.html.erb.

1<%= @photo.title %>
2
3<%= @photo.image.url %>

Great! The photo upload works and passes. Next one we will try to upload a non-image file.

1When(/^I upload an invalid photo with title "(.*?)"$/) do |title|
2  fill_in 'Title', with: title
3  attach_file 'Image', File.join(Rails.root, 'features', 'upload_files', 'bad.txt')
4  click_button "Upload Photo"
5end

The cucumber shows an error that the txt file is successfully uploaded. That’s because we haven’t added any file format validation to the model yet. We can do that by adding the following code to the Photo model class, photo.rb file.

1validates_attachment :image, presence: true, content_type: ["image/jpg", "image/png"]

Wonderful, all tests passed now.

19 scenarios (9 passed)
234 steps (34 passed)
30m1.883s

What’s next? We’re going to take a look at “What’s Next”.

overlaied image when clicked on thumbnail

Makzan | Ruby on rails 101 | Table of Content