RESTful Controllers
REST (Representational State Transfer) is an architectural style that Rails embraces fully. RESTful controllers follow conventions that make your code predictable and maintainable.
The Seven RESTful Actions
ruby
1class ArticlesController < ApplicationController
2 # GET /articles
3 def index
4 @articles = Article.all
5 end
6
7 # GET /articles/:id
8 def show
9 @article = Article.find(params[:id])
10 end
11
12 # GET /articles/new
13 def new
14 @article = Article.new
15 end
16
17 # POST /articles
18 def create
19 @article = Article.new(article_params)
20
21 if @article.save
22 redirect_to @article, notice: "Article created!"
23 else
24 render :new, status: :unprocessable_entity
25 end
26 end
27
28 # GET /articles/:id/edit
29 def edit
30 @article = Article.find(params[:id])
31 end
32
33 # PATCH/PUT /articles/:id
34 def update
35 @article = Article.find(params[:id])
36
37 if @article.update(article_params)
38 redirect_to @article, notice: "Article updated!"
39 else
40 render :edit, status: :unprocessable_entity
41 end
42 end
43
44 # DELETE /articles/:id
45 def destroy
46 @article = Article.find(params[:id])
47 @article.destroy
48 redirect_to articles_url, notice: "Article deleted!"
49 end
50
51 private
52
53 def article_params
54 params.require(:article).permit(:title, :body, :published)
55 end
56endRESTful Routes
ruby
1# config/routes.rb
2Rails.application.routes.draw do
3 resources :articles
4endThis generates:
| HTTP Verb | Path | Action | Purpose |
|---|---|---|---|
| GET | /articles | index | List all |
| GET | /articles/new | new | Form for new |
| POST | /articles | create | Create new |
| GET | /articles/:id | show | Show one |
| GET | /articles/:id/edit | edit | Form to edit |
| PATCH/PUT | /articles/:id | update | Update one |
| DELETE | /articles/:id | destroy | Delete one |
Nested Resources
ruby
1# Routes
2resources :articles do
3 resources :comments
4end
5
6# Controller
7class CommentsController < ApplicationController
8 before_action :set_article
9
10 # GET /articles/:article_id/comments
11 def index
12 @comments = @article.comments
13 end
14
15 # POST /articles/:article_id/comments
16 def create
17 @comment = @article.comments.build(comment_params)
18
19 if @comment.save
20 redirect_to @article, notice: "Comment added!"
21 else
22 render "articles/show"
23 end
24 end
25
26 # DELETE /articles/:article_id/comments/:id
27 def destroy
28 @comment = @article.comments.find(params[:id])
29 @comment.destroy
30 redirect_to @article, notice: "Comment deleted!"
31 end
32
33 private
34
35 def set_article
36 @article = Article.find(params[:article_id])
37 end
38
39 def comment_params
40 params.require(:comment).permit(:body)
41 end
42endShallow Nesting
Avoid deeply nested routes:
ruby
1# Routes
2resources :articles do
3 resources :comments, shallow: true
4end
5
6# Generates:
7# /articles/:article_id/comments (index, new, create)
8# /comments/:id (show, edit, update, destroy)Custom Actions
Member Actions (on a specific resource)
ruby
1# Routes
2resources :articles do
3 member do
4 post :publish
5 post :unpublish
6 get :preview
7 end
8end
9
10# Or single action
11resources :articles do
12 post :publish, on: :member
13end
14
15# Controller
16class ArticlesController < ApplicationController
17 # POST /articles/:id/publish
18 def publish
19 @article = Article.find(params[:id])
20 @article.update(published: true, published_at: Time.current)
21 redirect_to @article, notice: "Article published!"
22 end
23
24 # GET /articles/:id/preview
25 def preview
26 @article = Article.find(params[:id])
27 render layout: "preview"
28 end
29endCollection Actions (on the collection)
ruby
1# Routes
2resources :articles do
3 collection do
4 get :search
5 get :published
6 get :drafts
7 delete :clear_old
8 end
9end
10
11# Controller
12class ArticlesController < ApplicationController
13 # GET /articles/search
14 def search
15 @articles = Article.where("title LIKE ?", "%#{params[:q]}%")
16 render :index
17 end
18
19 # GET /articles/published
20 def published
21 @articles = Article.where(published: true)
22 render :index
23 end
24
25 # DELETE /articles/clear_old
26 def clear_old
27 Article.where("created_at < ?", 1.year.ago).destroy_all
28 redirect_to articles_path, notice: "Old articles cleared!"
29 end
30endRespond To Different Formats
ruby
1class ArticlesController < ApplicationController
2 def index
3 @articles = Article.all
4
5 respond_to do |format|
6 format.html # renders index.html.erb
7 format.json { render json: @articles }
8 format.xml { render xml: @articles }
9 format.csv { send_data @articles.to_csv }
10 end
11 end
12
13 def show
14 @article = Article.find(params[:id])
15
16 respond_to do |format|
17 format.html
18 format.json { render json: @article }
19 format.pdf do
20 pdf = ArticlePdf.new(@article)
21 send_data pdf.render, filename: "#{@article.slug}.pdf"
22 end
23 end
24 end
25endTurbo Stream Responses (Rails 7+)
ruby
1class CommentsController < ApplicationController
2 def create
3 @article = Article.find(params[:article_id])
4 @comment = @article.comments.build(comment_params)
5
6 if @comment.save
7 respond_to do |format|
8 format.turbo_stream
9 format.html { redirect_to @article }
10 end
11 else
12 render :new
13 end
14 end
15enderb
1<!-- app/views/comments/create.turbo_stream.erb -->
2<%= turbo_stream.prepend "comments", @comment %>
3<%= turbo_stream.update "comment_form", partial: "comments/form", locals: { comment: Comment.new } %>Error Handling
ruby
1class ArticlesController < ApplicationController
2 rescue_from ActiveRecord::RecordNotFound, with: :not_found
3 rescue_from ActionController::ParameterMissing, with: :bad_request
4
5 def show
6 @article = Article.find(params[:id])
7 end
8
9 private
10
11 def not_found
12 respond_to do |format|
13 format.html { render "errors/not_found", status: :not_found }
14 format.json { render json: { error: "Not found" }, status: :not_found }
15 end
16 end
17
18 def bad_request(exception)
19 render json: { error: exception.message }, status: :bad_request
20 end
21endSkinny Controllers
Keep controllers focused. Move logic to models or services:
ruby
1# Fat controller (bad)
2class ArticlesController < ApplicationController
3 def create
4 @article = Article.new(article_params)
5 @article.user = current_user
6 @article.published_at = Time.current if params[:publish]
7 @article.slug = @article.title.parameterize
8
9 if @article.save
10 ArticleMailer.new_article(@article).deliver_later
11 AdminNotifier.notify(@article)
12 redirect_to @article
13 else
14 render :new
15 end
16 end
17end
18
19# Skinny controller (good)
20class ArticlesController < ApplicationController
21 def create
22 @article = current_user.articles.build(article_params)
23
24 if @article.save
25 @article.notify_subscribers
26 redirect_to @article
27 else
28 render :new
29 end
30 end
31endRESTful controllers make your Rails applications predictable and easy to maintain!
