Controllers & Actions

Ruby on Rails Fundamentals
Course 1 ยท Chapter 3 ยท Controllers & Actions

๐ŸŽฎ Controllers & Actions

Chapter 2's 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

โš  index and show above never call render or res.json โ€” and that's correct

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

ConceptExpressRails
Handler shapeA plain function: (req, res) => { ... }An instance method inside a class inheriting ApplicationController
Route paramsreq.paramsparams (merged with query + body)
Query stringreq.queryparams (same hash, no separate access)
Request bodyreq.body (needs body-parsing middleware)params (parsed automatically)
Must you send a response?Yes, always, explicitlyNo โ€” 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.

โ†’ Solution

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.

โ†’ Solution

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.

โ†’ Solution

๐Ÿ’Ž Implicit Rendering Is Convention Over Configuration's Purest Form

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.