CS 169 Software Engineering
git commit -m 'deal with it' &&
git push --force origin master
1
Today’s Concept Outline
• Quick whirlwind tour of Rails
• ActiveRecord for persisting app data
• ActiveRecord models as resources, and
resource-oriented routes to manipulate them
• Rails tour revisited, putting concepts into
context
• Comparison of concepts & mechanisms in
Rails vs. Sinatra
2
Hello Rails :
from Zero to CRUD
(Engineering Software as a Service §4.1)
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
Rails as an MVC Framework
Model, View, Controller Relational Persistence: mysql or
tables Database sqlite3
Subclasses of
ActiveRecord::Base,
an object-relational models/*.rb
mapping layer
Logic: your code &
your app Rack appserver
views/*.[Link] controllers/*.rb
Rails rendering Rails routing
Presentation:
Subclasses of WEBrick,
Subclasses of Apache, etc.
ActionView
ApplicationController
Client: Firefox
A trip through a Rails app
1. Routes (in [Link]) map incoming URL’s to controller
actions and extract any optional parameters
– Route’s “wildcard” parameters (eg :id), plus any stuff after “?” in URL,
are put into params[] hash accessible in controller actions
2. Controller actions set instance variables, visible to views
– Subdirs and filenames of views/ match controllers & action names
3. Controller action eventually renders a view
app/controllers/movies_controller.rb app/views/movies/[Link]
def show %li
id = params[:id] Rating:
@m=[Link](id) = @[Link]
config/[Link] end
get '/movies/:id' => 'movies#show'
Rails Philosophy
• Convention over configuration
– If naming follows certain conventions,
no need for config files
MoviesController#show in movies_controller.rb
è views/movies/[Link]
• Don’t Repeat Yourself (DRY)
– mechanisms to
extract common functionality
• Both rely heavily on Ruby features:
– introspection and metaprogramming
– blocks (closures)
– modules (mix-ins)
Why must every interaction with a
Rails app eventually cause
something to be rendered?
☐Because of convention over configuration
☐ Because HTTP is a request-reply protocol
Because MVC implies that every action renders
☐
its own View, and Rails is strongly MVC
☐ Actually, some interactions using AJAX
do contact the server but don’t render
anything
7
cut
8
Models, Databases, and
Active Record
Engineering Software as a Service §2.6/4.3
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
9
• How should we store and retrieve
record-oriented structured data?
• What is the relationship between
data as stored and data as
manipulated in a programming
language?
10
11
In-Memory vs. In-Storage
objects
#<Movie:0x1295580>
[Link], [Link], ... marshal/serialize
#<Movie:0x32ffe416> unmarshal/deserialize ?
[Link], [Link], ...
• How to represent persisted object in storage
– Example: Movie with name & rating attributes
• Basic operations on object: CRUD (Create,
Read, Update, Delete)
• ActiveRecord: every model knows how to
CRUD itself, using common mechanisms
12
Rails Models Store Data in
Relational Databases (RDBMS)
• Each type of model gets its own database table
– All rows in table have identical structure
– one row in table == one instance of model’s class
– Each column stores value of an attribute of the model
– Each row has unique value for primary key (by
convention, in Rails this is an integer and is called id)
id rating title release_date
2 G Gone With the Wind 1939-12-15
11 PG Casablanca 1942-11-26
... ... ... ...
35 PG Star Wars 1977-05-25
• Schema: Collection of all tables and their structure
“Ted” Codd
CRUD in SQL
• Structured Query Language (SQL) is the
query language used by RDBMS’s
• Rails generates SQL statements at
runtime, based on your Ruby code
• 4 basic operations on a table row:
Create, Read, Update attributes, Delete
INSERT INTO users (username, email, birthdate)
VALUES ("fox", "fox@[Link]", "1968-05-12"),
("patterson", "pattrsn@[Link]", "????")
SELECT *
FROM users
WHERE (birthdate BETWEEN "1987-01-01" AND “2000-01-01”)
UPDATE users
SET email = "armandofox@[Link]"
WHERE username="fox"
DELETE FROM users WHERE id=1
The Ruby side of a model
• Subclassing from ActiveRecord::Base
– “connects” a model to the database
– provides CRUD operations on the model
[Link]
• Database table name derived from
model’s name: Movieèmovies
• Database table column names are getters &
setters for model attributes
• Observe: the getters and setters do not
simply modify instance variables!
Creating: new ≠ save
• Must call save or save! on an AR model
instance to actually save changes to DB
– '!' version throws exception if operation fails
– create just combines new and save
• Once created, object acquires a primary key
(id column in every AR model table)
– if [Link] is nil or x.new_record? is true, x
has never been saved
– These behaviors inherited from ActiveRecord::
Base—not true of Ruby objects in general
Assume table fortune_cookies
has column fortune_text
Which of these instance methods of
FortuneCookie < ActiveRecord::Base
will NOT return a silly fortune (if any)?
☐ def silly_fortune_1
@fortune_text + 'in bed'
end
def silly_fortune_2
☐ self.fortune_text + 'in bed'
end
def silly_fortune_3
☐ fortune_text + 'in bed'
end
☐ They will all return a silly fortune
17
Alternative: DataMapper
• Data Mapper associates separate mapper with
each model
– Idea: keep mapping independent of particular data store
used => works with more types of databases
– Used by Google AppEngine
– Con: can’t exploit
RDBMS features to
simplify complex
queries & relationships
• We’ll revisit when
talking about
associations
18
cut
19
RESTful Resource Routes
in Rails
Engineering Software as a Service §2.7
(revisited)
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
20
21
Basics of config/[Link]
get '/pastries/:flavor'=> 'pastries#eat',
:as => 'eat_dessert'
• Routes matching route to
PastriesController#eat
• Populates params[:flavor] from URL
• In a view:
link_to 'Eat', eat_dessert_path('cherry')
è <a href="/pastries/cherry">Eat</a>
• DRY idea: route logic runs both ways
• Quick reference:
[Link] 22
CRUD on a RESTful resource:
resources :movies
get '/movies' => 'movies#index', :as => 'movies' I
get '/movies/:id/new'=>'movies#new', :as=>'new_movie' C
post '/movies' => 'movies#create', :as => 'movie'
get '/movies/:id' => 'movies#show', :as => 'movie' R
get '/movies/:id/edit' => 'movies#edit', :as => 'edit_movie' U
put '/movies/:id'=>'movies#update', :as=>'movie'
delete '/movies/:id' => 'movies#destroy', :as=>'movie'
D
Route Action
GET /movies/3 Show info about movie whose ID=3
POST /movies Create new movie from attached form data
PUT /movies/5 Update movie ID 5 from attached form data
DELETE /movies/5 Delete movie whose ID=5
23
CRUD on a RESTful resource:
resources :movies
get '/movies' => 'movies#index', :as => 'movies'
get '/movies/:id/new'=>'movies#new', :as=>'new_movie'
post '/movies' => 'movies#create', :as => 'movie'
get '/movies/:id' => 'movies#show', :as => 'movie'
get '/movies/:id/edit' => 'movies#edit', :as => 'edit_movie'
put '/movies/:id'=>'movies#update', :as=>'movie'
delete '/movies/:id' => 'movies#destroy', :as=>'movie'
form_for movie_path(5)
<form action="/movies/5" method="post">
form_for movie_path(5), :method => :put
<form action="/movies/5" method="post">
<input type="hidden" name="_method" value="put"> ...
link_to 'Edit', edit_movie_path(5)
<a href="/movies/5/edit">Edit</a>
link_to 'List All', movies_path
<a href="/movies">List All</a> 24
Demo: rake routes
25
Which statement is NOT true regarding
Rails RESTful routes and the
resources to which they refer:
☐ A resource may be existing content or a request
to modify something.
☐ In an MVC app, every route must eventually
trigger a controller action.
☐ One common set of RESTful actions is the
CRUD actions on models.
☐ The route always contains one or more
"wildcard" parameters, such as :id, to identify
the particular resource instance in the operation
26
cut
27
Databases & Migrations
(Engineering Software as a Service §4.2)
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
Your customer data is golden!
• How do we avoid messing it up when
experimenting/developing new
features?
• How do we track and manage schema
changes ?
• …the answer to both is automation!
29
Multiple environments,
multiple databases
• Rails solution: development, production and
test environments each have own DB
– Different DB types appropriate for each!
• How to make changes to DB, since will have
to repeat changes on production DB?
• Rails solution: migration—script describing
changes, portable across DB types
Migration Advantages
• Can identify each migration, and know
which one(s) applied and when
– Many migrations can be created to be reversible
• Can manage with version control
• Automated == reliably repeatable
• Theme: don’t do it—automate it
– specify what to do, create tools to automate
Meet a Code Generator
rails generate migration CreateMovies
• Note, this just creates the [Link]
migration. We haven’t applied it.
• Apply migration to development:
rake db:migrate
• Apply migration to production:
heroku rake db:migrate
• Applying migration also records in DB itself
which migrations have been applied
Rails Cookery #1
• Augmenting app functionality ==
adding models, views, controller actions
To add a new model to a Rails app:
– (or change/add attributes of an existing model)
1. Create a migration describing the changes:
rails generate migration (gives you boilerplate)
2. Apply the migration: rake db:migrate
3. If new model, create model file
app/models/[Link]
4. Update test DB schema: rake db:test:prepare
5. Eventually deploy: heroku rake db:migrate
Demo: run the app, apply a
migration, see routes
34
Based on what you’ve seen of Rails, what kind of
object is likely being yielded in the migration code:
def up
create_table 'movies' do |t|
[Link] 'release_date' ...
end
end
☐An object representing a database
☐ An object representing an instance of a model
☐ An object representing a table
☐ Give me a break, it could be anything
35
cut
36
Models: Finding, Updating,
Deleting
(Engineering Software as a Service §4.3)
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
Read: finding things in DB
• class method where selects objects based on
attributes; returns an enumerable
[Link]("rating='PG'")
[Link]('release_date < :cutoff and
rating = :rating',
:rating => 'PG', :cutoff => [Link])
[Link]("rating=#{rating}") # BAD IDEA!
[Link](3) #exception if not found
• Can be chained together efficiently
kiddie = [Link]("rating='G'")
old_kids_films =
[Link]("release_date < ?",[Link])
Update: 2 ways
• Let m=[Link](title: 'The Help')
• Modify attributes, then save object
m.release_date='2011-Aug-10'
[Link]!
• Update attributes on existing object
m.update_attributes(
release_date: '2011-Aug-10')
• Transactional: either all attributes are
updated, or none are
Deleting is straightforward
• Note! destroy is an instance method
m = [Link](name: 'The Help')
[Link]
• There’s also delete, which doesn’t trigger
lifecycle callbacks we’ll discuss later (so,
avoid it)
• Once an AR object is destroyed, you can
access but not modify in-memory object
[Link] = 'Help' # FAILS
...btw, how is this implemented?
Summary: ActiveRecord intro
• Subclassing from ActiveRecord::Base
“connects” a model to database
– C (save/create), R (where, find), U
(update_attributes), D (destroy)
• Convention over configuration maps:
– model name to DB table name
– getters/setters to DB table columns
• Object in memory ≠ row in database!
– save must be used to persist
– destroy doesn’t destroy in-memory copy
Suppose we’ve done
movie = [Link](:title => 'Amelie’)
Then another user of the app changes the
movie’s rating, and updates the database. Just
after that instant, the value of movie:
☐ will be updated automatically because an ActiveRecord
model has “connected” your app to the database
will be updated automatically because of ActiveRecord’s
☐ use of metaprogramming
will not be updated automatically, but can be updated
☐ manually by re-executing
movie = [Link]("title='Amelie'")
☐ may be undefined or implementation-dependent
42
cut
43
Controllers & Views
(Engineering Software as a Service §4.4)
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
Rails Cookery #2
• To add a new action to a Rails app
[Link] route in config/[Link] if needed
[Link] the action (method) in the appropriate
app/controllers/*_controller.rb
[Link] there is something for the action to
render in app/views/model/[Link]
• We’ll do Show action & view (book walks
through Index action & view)
MVC responsibilities
• Model: methods to get/manipulate data
[Link](...), [Link](...)
• Controller: get data from Model, make available to
View Instance variables
def show set in Controller
@movie = [Link](params[:id]) available in View
end Absent other info, Rails will look for app/views/
movies/[Link]
• View: display data, allow user interaction
– Show details of a movie (description, rating)
• But… [Link]
– What else can user do from this page?
– How does user get to this page?
How we got here: URI helpers
link_to movie_path(3)
def show
index. @movie =
html.
<a href="/movies/3">...</a> [Link](params[:id])
haml
end
GET /movies/:id
{:action=>"show", :controller=>"movies"}
params[:id]ç3
What else can we do?
• How about letting user return to movie list?
• RESTful URI helper to the rescue again:
• movies_path with no arguments links to Index
action
=link_to 'Back to List', movies_path
ESaaS, Fig. 4.7
Which statements are TRUE:
a) A route consists of both a URI and an HTTP method
b) A route URI must be generated by Rails URI helpers
c) A route URI may be generated by Rails URI helpers
☐ Only (a) is true
☐ Only (c) is true
☐ Only (a) and (b) are true
☐ Only (a) and (c) are true
49
cut
50
When things go wrong:
Debugging
(Engineering Software as a Service §4.5)
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
Preview: Joel Spolsky
• Joel On Software
blogger
• Co-Founder of Trello &
Fog Creek Software
• CEO, StackExchange
• How not to get flame-
broiled on
StackOverflow
• 2/24, right here…don’t
miss it
Debugging SaaS can be tricky
• Errors early in flow may manifest much later
URIèrouteècontrollerèmodelèviewèrender
• Error may be hard to localize/reproduce if
affects only some users, routes, etc.
What Dev? Prd?
“printf debugging” ✔
rails console ✔
Logging ✔ ✔
rails server --debugger ✔
RASP
• Debugging is a fact of life.
• Read the error message. Really read it.
• Ask a colleague an informed question.
• Search using StackOverflow, a search
engine, etc.
– Especially for errors involving specific versions
of gems, OS, etc.
• Post on StackOverflow, class forums, etc.
– Others are as busy as you. Help them help you
by providing minimal but complete information
Reading Ruby error messages
• The backtrace shows you the call stack
(where you came from) at the stop point
• A very common message:
undefined method 'foo' for nil:NilClass
• Often, it means an assignment silently failed
and you didn’t error check:
@m = Movie.find_by_id(id) # could be nil
@[Link] # will fail: 'undefined method'
Instrumentation (a/k/a “Printing
the values of things”)
• In views:
= debug(@movie)
= @[Link]
• In the log, usually from controller method:
[Link](@[Link])
• Don’t just use puts or printf! It has
nowhere to go when in production.
Search: Use the Internet to
answer questions
• Google it
– “How do I format a date in Ruby?”
– “How do I add Rails routes beyond CRUD?”
• Check the documentation
– [Link], complete searchable Rails
docs (make sure correct Rails version!)
– [Link], complete searchable Ruby docs
(including standard libraries)
• Check StackOverflow
1. DRY & RASP: Reuse
Others’ Work
• “With a little searching for gems/libraries, we could
work on new features rather than reinventing
wheel”
• “Learning to effectively use Google and
StackOverflow made us really productive”
• “For every 5 minutes I spent writing code, I spent
an hour searching the internet.”
• “Wish we had sought/asked for help sooner, vs.
banging head against the wall solo”
• “I didn't realize how much time was required to
create something from scratch without starter code
and student mentors.”
Other observations
• RASP
– for errors/bugs: give a narrative of what you’ve
already done so far (not a stack trace, and not a
bare message without filename or line#)
– for refactoring: what goal are you trying to
achieve by refactoring? what have you already
thought about doing?
• RTFB or WTFV (read the * book or watch
the * video)
cut
60
Forms
(Engineering Software as a Service §4.6)
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
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)?
Rails Cookery #3
• To create a new submittable form:
[Link] the action that serves the form itself
[Link] the action that receives submission
[Link] routes, actions, views for each
• Form elements’ name attributes will appear
as keys in params[]
• Helpers provided for many common
elements
Creating the Form
• Anatomy of a form in HTML [Link]
– 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 [Link])
generate conveniently-named form inputs
[Link]
Which of these would be valid for
generating the form that, when submitted,
would call the Create New Movie action?
☐ = form_tag movies_path do
... end
☐ %form{:action => movies_path,
:method => :post}
☐ %form{:action => '/movies',
:method => 'post'}
☐ All of the above
65
cut
66
Microquiz
[Link]/NqkrBW
[Link]/CS169-quiz4
cut
68
Redirection, the Flash and the
Session
(Engineering Software as a Service §4.7)
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
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
• To notice: params[:movie] is a hash,
because of the way we named form fields
– Conveniently, just what [Link]! wants
What view should be rendered
for create action?
• Idiom: redirect user to a more useful page.
– e.g., list of movies, if create 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”
Flash & Session
• session[]: like a hash that persists forever
– reset_session nukes the whole thing
– [Link](:some_key), like a hash
• By default, cookies store entire contents of
session & flash
– Alternative: store Rails sessions in DB table
– (Google “rails session use database table”)
– Another alternative: store sessions in a
“NoSQL” storage system, like memcached
cut
73
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!
☐ True—but a bad idea!
☐ False, because you can’t put arbitrary
objects into a hash
☐ False, because session[] isn’t really
a hash, it just quacks like one
74
cut
75
Finishing CRUD
(Engineering Software as a Service §4.8)
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
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:
retrieve existing Movie first [Link]
– Form action uses PUT rather than POST
[Link]
Destroy is easy
• Remember, destroy is an instance method
– Find the movie first...then destroy it
– Send user back to Index
def destroy
@movie = [Link](params[:id])
@[Link]
flash[:notice] =
"Movie '#{@[Link]}' deleted."
redirect_to movies_path
end
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 and the next request
☐ Only this request—once the view is
rendered, the variable is reset to nil
☐ It depends on whether the instance
variable was declared static
79
cut
80
Summary & Reflections:
SaaS Architecture,
Rails, From Sinatra to Rails
(Engineering Software as a Service
§2.9-2.10, 4.11)
Armando Fox
© 2013 Armando Fox & David Patterson, all rights reserved
81
Connecting Architectural Concepts to
Rails apps
Gemfile
Rakefile
app /models/, views/, controllers/
/helpers
/assets/stylesheets/[Link]
config /[Link]
/[Link]
db /development.sqlite3
/test.sqlite3
/migrate/
log /[Link], [Link]
Pitfall: Fat controllers &
code in your 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
Code Clinic part 2
Where, for the love of God,
should you not put code?
☐ Controllers
☐ Controllers
☐ Controllers
☐ Controllers
85
SIMPLIFY YOUR CONTROLLERS
OR THE BUNNY GETS IT
Architecture is about
Alternatives
Pattern we’re using Alternatives
Client-Server Peer-to-Peer
Shared-nothing (cloud computing) Symmetric multiprocessor, shared
global address space
Model-View-Controller Page controller, Front controller,
Template view
Active Record Data Mapper
RESTful URIs (all state affecting Same URI does different things
request is explicit) depending on internal state
As you work on other SaaS apps beyond this course, you
should find yourself considering different architectural
choices and questioning the choices being made.
87
Summary:
From Sinatra to Rails
Create app with Gemfile, [Link], Create app with rails new appname; specific
[Link] roles for various app subdirectories
No particular app architecture Strongly MVC, using relational-database-
defined backed ActiveRecord for models
Inline routes: Separate route descriptions in config/
post '/new_game' do...end [Link], supports CRUD RESTful resource
routes by default, can add others
Names (app, controller methods, Convention over configuration
view templates) arbitrary
No built-in database connectivity ActiveRecord relational database connector
and support for migrations
Same files, dependencies, settings, Three environments by default (development,
etc. for development & production production, testing); can define others
View templating is up to you MVC defines default rendering flow, supports
various templating engines
Start app with rackup Start app with rails server 88
From Sinatra to Rails cont’d.
• More moving parts between route & render
– Match route in config/[Link]
– Call appropriate controller action
– Find view template using convention over config
– Also use [Link] to generate routes in views
• Cool things Rails gives you over Sinatra
– rails console interactive REPL
– Routes that use PUT & DELETE
– Automatically reloads classes when you modify app
• Things that are similar
– session[], flash[], params[]
89
Frameworks, Apps, Design
patterns
• Many design patterns so far, more to come
• In 1995, it was the wild west: biggest Web
sites were minicomputers, not 3-tier/cloud
• Best practices (patterns) “extracted” from
experience and captured in frameworks
• But API’s transcended it: 1969 protocols +
1960s markup language + 1990 browser +
1992 Web server works in 2015
90
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/[Link] (or .[Link], etc)
(b) Ensure a route exists in config/[Link]
(c) Implement helper method to generate
necessary route-helper URIs
☐ Only (a) and (b)
☐ Only (b)
☐ Only (b) and (c)
☐ Only (a) and (c)
91
cut
92