Controllers & Actions
๐ฎ Controllers & Actions
resources :posts pointed seven routes at seven controller actions. This chapter writes those actions โ and covers the single biggest behavioral surprise coming from Express: a Rails action doesn't have to explicitly send a response at all.
๐๏ธ ApplicationController
class PostsController < ApplicationController # actions go here end
Every Rails controller inherits from ApplicationController, itself a subclass of ActionController::Base โ real Ruby classes using the inheritance and class syntax from both Ruby courses. Express has no equivalent concept at all: a "controller" there (as Chapter 1's example showed) is just a plain object of exported functions, with no shared base class or built-in framework hooks.
๐ฌ The Seven Actions
class PostsController < ApplicationController def index @posts = Post.all # Active Record -- fully covered in Chapter 5 end def show @post = Post.find(params[:id]) end def new @post = Post.new end def create @post = Post.new(post_params) if @post.save redirect_to @post else render :new end end def edit @post = Post.find(params[:id]) end def update @post = Post.find(params[:id]) if @post.update(post_params) redirect_to @post else render :edit end end def destroy Post.find(params[:id]).destroy redirect_to posts_path # the named route helper from Chapter 2 end end
Every method name matches an action from Chapter 2's route table exactly โ that's not a coincidence, it's the same naming convention linking routes to controllers that Chapter 1 introduced.
๐ป Implicit Rendering โ the Biggest Surprise Coming From Express
In Express, forgetting to call res.send/res.json/res.render leaves the request hanging with no response at all โ the framework never assumes what you meant to send back. Rails does the opposite: if an action doesn't explicitly call render or redirect_to, Rails automatically renders the view matching the action's name โ index implicitly renders app/views/posts/index.html.erb, show implicitly renders app/views/posts/show.html.erb. This is convention over configuration applied to the controller/view boundary itself, and it's genuinely disorienting the first time a page renders correctly despite the action "doing nothing" to send a response.
๐ฅ params โ One Hash for Everything
# GET /posts/5?highlight=true def show params[:id] # => "5" -- from the route (:id in the URL pattern) params[:highlight] # => "true" -- from the query string end # POST /posts, with a form body of { post: { title: "Hi" } } def create params[:post][:title] # => "Hi" -- from the request body end
Rails merges route parameters, query string parameters, and request body parameters into a single params hash-like object โ no need to know or care which source a given value came from. Express keeps these genuinely separate: req.params (route), req.query (query string), and req.body (parsed body, requiring middleware like express.json() to populate at all).
๐ render vs redirect_to
render โ same request, different content
Renders a view (or a different one than the action's default) for the current request โ no new HTTP round trip, the URL in the browser doesn't change. Used above in create's failure branch, to redisplay the form with validation errors still in place.
redirect_to โ a brand new request
Sends an HTTP redirect response, telling the browser to make an entirely new GET request to a different URL โ the browser's address bar changes. Used above in create's success branch, sending the user to the new post's show page.
This maps directly onto Express's res.render(...) vs res.redirect(...) โ same underlying HTTP distinction, just spelled with Rails' own method names.
๐ Handling a Request: Express vs Rails
Side by Side
| Concept | Express | Rails |
|---|---|---|
| Handler shape | A plain function: (req, res) => { ... } | An instance method inside a class inheriting ApplicationController |
| Route params | req.params | params (merged with query + body) |
| Query string | req.query | params (same hash, no separate access) |
| Request body | req.body (needs body-parsing middleware) | params (parsed automatically) |
| Must you send a response? | Yes, always, explicitly | No โ implicit rendering falls back to a matching view |
๐ป Coding Challenges
Challenge 1: Write a Full Controller
Write a CommentsController with all seven actions, following the same pattern as this chapter's PostsController (you can use placeholder Active Record calls like Comment.find(params[:id]) even before Chapter 5 covers Active Record in depth). Comment above index and show explaining specifically which view file Rails will implicitly render for each.
Goal: Get the shape of all seven action methods, and the implicit-render convention, under your fingers.
Challenge 2: Reading From params
Given a request to GET /posts/12/comments?sort=newest, with route resources :posts { resources :comments } (Chapter 2), write the index action for CommentsController and show exactly how you'd read both the post's ID and the sort query parameter out of params.
Goal: Practice pulling values from Rails' unified params hash regardless of where they originated.
Challenge 3: render vs redirect_to
Write an update action for a ProductsController that, on a successful save, redirects to the product's show page, and on failure, re-renders the :edit view so the user sees their validation errors. Add a comment explaining what would go wrong (from the user's perspective) if redirect_to were used in the failure branch instead of render.
Goal: Internalize exactly when each of the two response methods is the correct choice.
No routing table, no folder structure โ just a controller action's name silently determining what gets rendered if you don't say otherwise. It's the single clearest example in this course so far of Chapter 1's warning made concrete: extremely little code, and genuinely confusing the first time a page renders correctly despite an action that appears to do nothing at all.
๐ฏ What's Next
Next chapter: Views & ERB โ the templates those implicitly-rendered actions actually render, embedding Ruby directly in HTML, and how ERB compares to the EJS templating already covered in Express.