Upload
eunice-osborne
View
224
Download
3
Embed Size (px)
Citation preview
UC Berkeley
Routes & REST & URL-helpers & validations &
filtersCS 98-10/CS 198-10
Web 2.0 Programming Using Ruby on RailsArmando Fox
Administrivia
• Office hours switched (again...sorry)– Tuesdays 1:30-2:30 in 413 Soda
• Monday: Project brainstorming• Weds: Project groups informally chosen-up
• By following Monday: start planning your app (user stories, testing, deployment....)
• Technical material: Routes & REST (today), more on controllers & views
Outline...
• Routing basics: how do....– URLs get mapped to a controller and action?– link_to, redirect_to etc. generate their URLs?
• REST and RESTful routes– what is RESTful resource access?– how Rails 2.0 routing supports REST
• URL helpers– generating URL’s (for your pages) that will map to specific controller actions...RESTfully
• Further reading– “Rails Way” book has good content on this– “Agile” book has routes, but not 2.0 routes and REST
• Model validations, callbacks, controller filters
$APP_ROOT/config/routes.rb
• Ruby code (that makes use of high-level methods!) to declare “rules” for mapping incoming URLs to controllers/actions
• actually each rule has 2 purposes:1. map incoming URL to ctrler/action/params2. generate URL to match
ctrler/action/params– e.g. when using link_to, redirect_to, etc.
– What’s in a rule?– A URL template– Keywords stating what to do
Simple example
• In routes.rb:map.connect 'professors/:dept',
:controller => 'professors', :action => 'list'
• In one of your views:<%= link_to "List professors in EECS",
:controller => 'professors', :action => 'list',:dept => 'eecs', :hired_since => 2005 %>
– matching is determined by keywords– link_to uses underlying function url_for, which consults routing rules to build the URL:
http://www.yourapp.com/professors/eecs?hired_since=2005
Simple example cont.
• In routes.rb:map.connect 'professors/:dept',
:controller => 'professors', :action => 'list'
• Now if someone visits this URL:http://www.yourapp.com/professors/eecs
– Matching is determined by position• How about:http://www.yourapp.com/professors/eecs?
glub=1&hired_since=2006
• How about:http://www.yourapp.com/professors
Default routes
• URL is compared to routing rules, one at a time, until match found– then “wildcard” pieces of URL get put into
params[]
• If no match, default route (last one in routes.rb) is used– typically something like:
map.connect ':controller/:action/:id'
– e.g., catches things like professors/edit/35– Warning! Can lead to dangerous behaviors
• Use the root route to map the “empty” URL (e.g. http://www.myapp.com):map.root :controller=>'main', :action=>'index'
More on Routes
• Ordering of routes matters; more specific ones should come earlier so they’ll match firstmap.connect 'users/:action/:id'map.connect ':controller/:action/:id'
• Many, many apps will never need to use more than the “conventional” predefined routes
• If you want to, you should definitely read more about routes offline
REST is CRUD
• REST Idea: each HTTP interaction should specify, on its own, a CRUD operation and which object to do it on.– GET used for read operations; POST for writes (create, update, delete)
– Also guards against spidering/bots!
• Rails 2.0: routes, scaffolds and URL helpers are now all RESTful by default– result: syntax of link_to, etc. has changed
• Get them by saying map.resources :model
REST and URI’s
Action & named route method to pass to url_for
HTTP method
Old style URL
New (RESTful) style URL
show: student_url(@s) GET /:ctrl/show/:id
/:ctrl/:id
index (list): students_url
GET /:ctrl/list (or /:ctrl/index)
/:ctrl
new: new_student_url GET /:ctrl/new /:ctrl/new
create: students_url POST /:ctrl/create (why no ID?)
/:ctrl
destroy: student_url(@s)
DELETE /:ctrl/destroy/:id
/:ctrl (but see Hack!)
edit: edit_student_url(@s)
GET /:ctrl/edit/:id
/:ctrl/:id/edit
update: student_url(@s)
PUT /:ctrl/update/:id
/:ctrl/:id
The DELETE & PUT hack
• REST says: use HTTP method DELETE to request deletion; PUT to request Update – But: Web browsers only have GET and POST
• “Solution”: use POST, but include extra field _method with value DELETE or PUT– routing takes care of parsing this out to disambiguate dispatching
– done with JavaScript in link_to (but you shouldn’t be using link_to for this...why?)
– or use button_to which creates a self-contained form for a single button
How url_for has changed
Excerpted from REST Cheat Sheet which I’m trying to get a site license for (Peepcode)Note use of either _path or _url suffix
That whole thing withrespond_to do |format|
• Let’s make it easier to read by:– put the if...else outside the do block– change variable name format to wants
Easier-to-read version
• respond_to accepts a block, and yields to it passing the wants object
• wants’s instance methods named for possible MIME output types (HTML, XML, etc.)
• Each of those methods takes a block that specifies what to do for each format– Based on parsing HTTP headers from request– Many, many MIME types predefined (add more in environment.rb)
– A useful one when we do AJAX: js (runs .rjs template if available)
What about redirect_to?
• RESTful routing strikes again!– url_for and friends now assume RESTful routes by default
• Hint: consider url_for(@student)– Or redirect_to(edit_student_path(@student)), etc.
Nested routes
• Consider a Course that belongs_to :professor (and as well, Professor has_many :courses)
• In particular, it makes no sense to have a course without a professor– in our app, I mean
• When invoking CRUD methods on a course, we’d like to be able to specify which professor it belongs_to
Enter nested RESTful routes
map.resources :professors do |p| p.resources :coursesend
—or—map.resources :professors, :has_many=>:courses
• Now you can saycourse_path(:professor_id=>3, :id=>20)
and get a RESTful URI for course ID 20 that belongs to professor ID 3.
• Note! The route builder doesn’t check if the belongs_to relationship keys match what’s in the database!
In Courses controller...
• Need to set up the @professor that owns the course in each of the methods– Otherwise the URL-builder methods won’t know what the parent object ID is
• Ugly (we’ll learn a better way with filters):
def index @professor = Professor.find(params[:professor_id]) @courses = @professor.courses.find(:all)...etc...enddef update ...if update of Course fails... redirect_to([@professor,@course])end
What about views for nested models?
# example: views/courses/edit.html.erb
<% form_for([@professor,@course]) do |f| %> <%= f.text :description %> <%= f.text :ccn %> <%= f.submit "Save Changes" %><% end %><%= link_to "Show", [@professor,@course]) %> or... link_to "Show", course_path(@course) if makes sense <%= link_to "Back", professor_courses_path(@professor) %>
In your index (list) view...
<% @courses.each do |c| %> <tr> <td> c.name </td> <td><%= link_to "Edit",edit_course_path([@professor,c]) %>
</td> <td><%= link_to "Show",course_path([@professor,c])%></td> <td> <%= button_to "Destroy", course_path([@professor,c]),:method=>:delete %></td> </tr><% end %>
• Similarly for other views
Worth understanding...
• Routing and REST caused lots of changes in 2.0, but ultimately they will make life better
• Best tutorial we’ve found (thx Arthur!):
http://www.akitaonrails.com/2007/12/12/rolling-with-rails-2-0-the-first-full-tutorial
(Linked from course home page)
Controller predicates: verify
• A declarative way to assert various preconditions on calling controller methods
• You can check selectively (:only, :except) for:– HTTP request type (GET, POST, Ajax XHR)– Presence of a key in the flash or the session– Presence of a key in params[]
• And if the check fails, you can...– redirect_to somewhere else– add_to_flash a helpful message
• Example:verify :method => :post, :only =>
'dangerous_action', :redirect_to => {:action => 'index'},:add_to_flash => "Dangerous action requires Post"
More General Filters• Code blocks that can go before, after or around
controller actions; return Booleanbefore_filter :filter_method_namebefore_filter { |controller| ... }before_filter ClassName
– options include :only,:except, etc.– multiple filters allowed; calls provided to prepend or
append to filter chain– subclasses inherit filters but can use skip_filter methods to
selectively disable them• If any before-filter returns false, chain halted &
controller action method won’t be invoked– so filter should redirect_to, render, or otherwise deal with
the request• Simple useful example: a before-filter for nested routes!
before_filter :load_professordef load_professor @professor = Professor.find(params[:professor_id])end
A General Pattern:“Do It Declaratively”
• More and more ways to specify what should be done rather than how to do it
• Should always be asking yourself this question
• Especially when you find yourself (re)writing common code in multiple places!