Forms
(Engineering Software as a Service §4.6)
© 2013 Armando Fox & David Patterson, all rights reserved
1
Dealing with Forms
• Creating a resource
usually takes 2 interactions
– new: Retrieve blank form
– create: Submit filled form
• How to generate/display?
• How to get values filled in
by user?
• What to “return” (render)?
2
Rails Cookery #3
• To create a new submittable form:
1. Identify the action that serves the form itself
2. Identify the action that receives submission
3. Create routes, actions, views for each
• Form elements’ name attributes will appear
as keys in params[]
• Helpers provided for many common
elements
3
Creating the Form
• Anatomy of a form in HTML
http://pastebin.com/k8Y49EhE
– the action and method attributes (i.e., the route)
– only named form inputs will be submitted
• Generating the form in Rails
– often can use URI helper for action, since it’s
just the URI part of a route (still need method)
– form field helpers (see api.rubyonrails.org)
generate conveniently-named form inputs
http://pastebin.com/3dGWsSq8
4
5
Redirection, the Flash and the
Session
(Engineering Software as a Service §4.7)
© 2013 Armando Fox & David Patterson, all rights reserved
8
Receiving the Form
• A neat trick: use debugger to inspect what’s
going on
– start with rails server --debugger
– insert debugger where you want to stop
– details & command summary: ESaaS §4.7
• NOTE: params[:movie] is a hash, because
of the way we named form fields
– Conveniently, just what Movie.create! wants
9
What View Should Be Rendered for
Create Action?
• Idiom: redirect user to a more useful page
– e.g., list of movies, if create is successful
– e.g., New Movie form, if unsuccessful
• Redirect triggers a whole new HTTP request
– How to inform user why they were redirected?
• Solution: flash[]—quacks like a hash that
persists until end of next request
– flash[:notice] conventionally for information
– flash[:warning] conventionally for “errors”
10
Flash & Session
• session[]: like a hash that persists forever
– reset_session nukes the whole thing
– session.delete(:some_key), like a hash
• By default, cookies store entire contents of
session & flash
– Alternative: store Rails sessions in DB table
– (Search “rails session use database table”)
– Another alternative: store sessions in a
“NoSQL” storage system, like memcached
11
12
Ben Bitdiddle says: “You can put arbitrary
objects (not just “simple” ones like ints and
strings) into the session[].” What do you
think?
☐ True - knock yourself out!
☐
☐ False, because you can’t put arbitrary
objects into a hash
☐ False, because session[] isn’t really
a hash, it just quacks like one
13
14
Administrivia
• Get account for “public projects” on
PivotalTracker.com, add the TAs and me
– Public projects are free
• Get account on CodeClimate.com
– Provides code analysis
– Free for an open source (OSS) repo
– Sign in with your GitHub account
15
16
Finishing CRUD
(Engineering Software as a Service §4.8)
© 2013 Armando Fox & David Patterson, all rights reserved
17
Edit/Update Pair is Analogous to
New/Create Pair
• What’s the same?
– 1st action retrieves form, 2nd action submits it
– “submit” uses redirect (to show action for movie)
rather than rendering its own view
• What’s different?
– Form should appear with existing values filled in:
http://pastebin.com/VV8ekFcn
retrieve existing Movie first
– Form action uses PUT rather than POST
http://pastebin.com/0drjjxGa
18
Destroy is Easy
• Remember, destroy is an instance method
– Find the movie first...then destroy it
– Send user back to Index
def destroy
@movie = Movie.find(params[:id])
@movie.destroy
flash[:notice] =
"Movie '#{@movie.title}' deleted."
redirect_to movies_path
end
19
20
If you set an instance variable in a
controller method, its value will be
retained for how long?
☐This request and all subsequent requests
☐
☐ Only this request - once the view is
rendered, the variable is reset to nil
☐ It depends on whether the instance
variable was declared static
21
22
Fallacies, Pitfalls, and
Perspectives on SaaS-on-Rails
(Engineering Software as a Service §4.9 4.11)
© 2013 Armando Fox & David Patterson, all rights reserved
23
Fat Controllers & Fat Views
• Really easy to fall into “fat controllers” trap
– Controller is first place touched in your code
– Temptation: start coding in controller method
• Fat views
– “All I need is this for-loop”
– “....and this extra code to sort the list of movies
differently”
– “...and this conditional, in case user is not
logged in”
• No! That’s for model, controller, helpers
24
Designing for Service-Oriented
Architecture
• A benefit of thin controllers & views: easy to
retarget your app to SOA
• Typically, SOA calls will expect XML or
JSON (JavaScript Object Notation, looks
like nested hashes) instead of HTML
• A trivial controller change accomplishes this
http://pastebin.com/bT16LhJ4
25
26
Which steps are ALWAYS required when adding a new action
'foo' to the Movie model of a Rails app:
(a) Ensure there is a template to render in
app/views/movies/foo.html.haml (or .html.erb, etc)
(b) Ensure a route exists in config/routes.rb
(c) Implement helper method to generate necessary routehelper URIs
☐ Only (a) and (b)
☐
☐ Only (b) and (c)
☐ Only (a) and (c)
28
Introducing
Cucumber &
Capybara
(Engineering
Software as a
Service §7.6)
© 2013 Armando Fox & David Patterson, all rights reserved
29
User Stories
=> Acceptance Tests?
• Wouldn’t it be great to automatically map
3x5 card user stories into tests for user to
decide if accept the app?
• How would you match the English text to
test code?
• How could you run the tests without a
human in the loop to perform the actions?
30
Cucumber: Big Idea
• Tests from customer-friendly user stories
– Acceptance: ensure satisfied customer
– Integration: ensure interfaces between modules
consistent assumptions, communicate correctly
• Cucumber meets halfway between customer
and developer
– User stories are not code, so clear to customer
and can be used to reach agreement
– Also not completely freeform, so can connect to
real tests
31
Example User Story
Feature: User can manually add movie 1 Feature
Scenario: Add a movie
≥1 Scenarios / Feature
Given I am on the RottenPotatoes home page
When I follow "Add new movie"
Then I should be on the Create New Movie page
When I fill in "Title" with "Men In Black"
And I select "PG-13" from "Rating"
And I press "Save Changes"
Then I should be on the RottenPotatoes home page
And I should see "Men In Black"
3 to 8 Steps / Scenario
32
Cucumber User Story, Feature, and
Steps
• User story: refers to single feature
• Feature: ≥1 scenarios that show
different ways a feature is used
– Keywords Feature and Scenario
identify respective components
– Kept in .feature files
• Scenario: 3 - 8 steps that describe
scenario
• Step definitions: Ruby code to test steps
– Kept in X_steps.rb files
33
5 Step Keywords
1. Given steps represent state of
world before event: preconditions
2. When steps represent event
– e.g., simulate user pushing a button
3. Then steps represent expected
postconditions; check if true
4. / 5. And & But extend previous step
34
Steps => Step Definitions via
Regular Expressions
• Regexes match English phrases in
steps of scenarios to step definitions!
• Given /^(?:|I )am on (.+)$/
• “I am on the Rotten
Potatoes home page”
• Step definitions (Ruby code)
likely use captured string
– “Rotten Potatoes home page”
35
More on “Cuke”
• Need to install Cucumber Gem
– Just for test and development environment, not
for production environment
• When Cucumber installed, it creates
commonly used step definitions
• Need a test database to run app
• Then edit .features file to add features
36
Fake User to Try Scenarios?
• Need tool that pretends to be the user
to follow scenarios of user stories
• Capybara simulates browser
– Can interact with app to
receive pages
– Parse the HTML
– Submit forms as a user would
37
38
Which is FALSE about Cucumber and
Capybara?
1. Step definitions are in Ruby, and are similar to
method calls, while steps are in English and are
similar to method definitions
2.
3. Steps use Given for current state, When for
actions, and Then for consequences of actions
4. Cucumber matches step definitions to scenario
steps using regexes, and Capybara pretends to
be a user that interacts with the SaaS app
accordingly
39
40
Running
Cucumber
and
Capybara
(Engineering
Software as a
Service §7.7)
© 2013 Armando Fox & David Patterson, all rights reserved
41
Red-Yellow-Green Analysis
• Cucumber colors steps
• Green for passing
for not yet implemented
• Red for failing
(then following steps are Blue)
• Goal: Make all steps green
for pass
(Hence green vegetable
for name of tool)
42
Demo
• Add feature to cover existing functionality
– Note: This example is doing it in wrong order –
should write tests first
– Just done for pedagogic reasons
• (Or can look at screencast:
http://vimeo.com/34754747)
43
44
Enhancing
Rotten Potatoes
Again
(Engineering Software
as a Service §7.8)
© 2013 Armando Fox & David Patterson, all rights reserved
45
Add a Real New Feature?
• What if we add something harder?
– e.g., includes form to fill in
– e.g., needs a User Interface
– e.g., needs to add route to connect view to
controller
– e.g., includes both a happy path and a sad path
46
Integrated with
The Movie Database (TMDb)
• New Feature: Populate from TMDb,
versus enter information by hand
• Need to add ability to search TMDb from
Rotten Potatoes home page
• Need LoFi UI and Storyboard
47
Storyboard
TMDb
• Figure 7.6 of
Engineering
Software as a
Service
48
Search TMDb User Story
(Fig. 7.7 ESAAS)
Feature: User can add movie by searching in The Movie
Database (TMDb)
As a movie fan
So that I can add new movies without manual tedium
I want to add movies by looking up their details in TMDb
Scenario: Try to add nonexistent movie (sad path)
Given I am on the RottenPotatoes home page
Then I should see "Search TMDb for a movie"
When I fill in "Search Terms" with "Movie That Does Not
Exist"
And I press "Search TMDb"
Then I should be on the RottenPotatoes home page
And I should see "'Movie That Does Not Exist' was not
found in TMDb."
49
Haml for Search TMDb Page
(Fig. 7.8 ESAAS)
-# add to end of
app/views/movies/index.html.haml:
%h1 Search TMDb for a movie
= form_tag :action => 'search_tmdb' do
%label{:for => 'search_terms'} Search Terms
= text_field_tag 'search_terms'
= submit_tag 'Search TMDb'
http://pastebin/18yYBVbC
50
Haml Expansion Last Two Lines
• This Haml:
= text_field_tag 'search_terms'
= submit_tag 'Search TMDb’
• Turns into this HTML:
<label for='search_terms'>Search
Terms</label>
<input id="search_terms" name="search_terms"
type="text" />
• for attribute of label tag matches id attribute
of input tag, from text_field_tag helper
(above)
51
Try Cucumber?
• If try Cucumber, it fails
• Missing the route
• Also MoviesController#search_tmdb
is controller action that should receive form,
yet not in movies_controller.rb
• Should use Test Driven Development (future
lecture) to implement method search_tmdb
• Instead, to finish sad path, add fake
controller method that always fails
52
Trigger Fake Controller When Form
is POSTed (Fig. 7.9)
# add to routes.rb, just before or just after
'resources :movies' :
# Route that posts 'Search TMDb' form
post '/movies/search_tmdb'
http://pastebin/FrfkF6pd
53
Fake Controller Method:
Will Fail Finding Movie (Fig. 7.9)
# add to movies_controller.rb, anywhere inside
# 'class MoviesController <
ApplicationController':
def search_tmdb
# hardwired to simulate failure
flash[:warning] = "'#{params[:search_terms]}'
was not found in TMDb."
redirect_to movies_path
end
http:/pastebin/smwxv70i
54
55
Which statement is TRUE?
1. Usually you complete the Behavior Driven
Design phase with Cucumber before starting
the Test Driven Development phase with RSpec
2.
3. A sad path can pass without having code written
needed to make a happy path pass
4. None of the above is true
56
57
Running Rotten
Potatoes Again
(Engineering Software
as a Service §7.8)
© 2013 Armando Fox & David Patterson, all rights reserved
58
Demo
• Add feature to search for movie in TMDb
– Note: This will be a sad path, in that won’t find
it
– Will use fake method
(until future when implement it using TDD)
• (Or can look at screencast:
http://vimeo.com/34754766)
59
Happy Path of TMDb
• Find an existing movie, should return to
Rotten Potatoes home page
• But some steps same on sad path and
happy path
• How to make it DRY?
• Background means steps performed
before each scenario
60
TMDb w/2 Scenarios: Background
(Fig. 7.10)http://pastebin/icQGrYCV
Feature: User can add movie by
When I fill in "Search Terms"
searching for it in The Movie
with "Movie That Does Not
Database (TMDb)
Exist"
As a movie fan
And I press "Search TMDb"
So that I can add new movies
Then I should be on the
without manual tedium
RottenPotatoes home page
I want to add movies by
And I should see "'Movie That
looking up their details in
Does Not Exist' was not found
TMDb
in TMDb.”
Background: Start from the
Scenario: Try to add existing
Search form on the home page
movie (happy path)
Given I am on the
When I fill in "Search Terms"
RottenPotatoes home page
with "Inception"
Then I should see "Search TMDb
And I press "Search TMDb"
for a movie”
Then I should be on the
RottenPotatoes home page
Scenario: Try to add nonexistent
And I should see "Inception”
movie (sad path)
61
Cucumber Summary
• New feature => UI for feature, write new
step definitions, even write new methods
before Cucumber can color steps green
• Usually do happy paths first
• Background lets us DRY out scenarios of
same feature
• BDD/Cucumber test behavior; TDD/RSpec
in folllowing chapter is how write methods to
make all scenarios pass
62
63
And in Conclusion
• Cucumber – “magically” maps 3x5 card
user stories onto acceptance tests and
integration tests for the application
64