MVC, Routes & Controllers
Rails: MVC, Routes & Controllers Ruby on Rails is an opinionated MVC web framework. Convention over configuration means predictable structure: routes map URLs t…
Rails: MVC, Routes & Controllers
Ruby on Rails is an opinionated MVC web framework. Convention over configuration means predictable structure: routes map URLs to controller actions, controllers coordinate models and views. Rails 7+ uses import maps or Vite for JS.
Getting Started
# Install Rails
gem install rails
# New app
rails new myapp --database=postgresql # PostgreSQL
rails new myapp --api # API-only mode (no views)
rails new myapp --database=postgresql --css=tailwind
cd myapp
rails db:create
rails server # http://localhost:3000
# Generators
rails generate model User name:string email:string:uniq
rails generate controller Users index show
rails generate scaffold Post title:string body:text user:references
rails generate migration AddAgeToUsers age:integer
rails db:migrate
rails routes # list all routesRoutes
# config/routes.rb
Rails.application.routes.draw do
root 'home#index'
# RESTful resources — generates 7 standard routes
resources :posts # index, show, new, create, edit, update, destroy
resources :posts, only: [:index, :show] # limit actions
resources :posts, except: [:destroy]
# Nested resources
resources :users do
resources :posts, only: [:index, :create] # /users/:user_id/posts
member { post :activate } # POST /users/:id/activate
collection { get :search } # GET /users/search
end
# Namespace
namespace :admin do
resources :users # /admin/users → Admin::UsersController
root 'dashboard#index'
end
# API versioning
namespace :api do
namespace :v1 do
resources :posts, only: [:index, :show, :create]
end
end
# Custom routes
get '/login', to: 'sessions#new', as: :login
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy', as: :logout
# Constraints
get '/users/:id', to: 'users#show', constraints: { id: /d+/ }
endControllers
class PostsController < ApplicationController
before_action :authenticate_user!
before_action :set_post, only: [:show, :edit, :update, :destroy]
def index
@posts = Post.published
.includes(:author, :tags) # eager load
.order(created_at: :desc)
.page(params[:page]).per(20)
respond_to do |format|
format.html
format.json { render json: @posts }
end
end
def show; end # @post set by before_action
def new
@post = Post.new
end
def create
@post = current_user.posts.build(post_params)
if @post.save
redirect_to @post, notice: 'Post was successfully created.'
else
render :new, status: :unprocessable_entity
end
end
def update
if @post.update(post_params)
redirect_to @post, notice: 'Post updated.'
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@post.destroy
redirect_to posts_url, status: :see_other, notice: 'Post deleted.'
end
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :body, :published_at, tag_ids: [])
end
endFilters & Flash
class ApplicationController < ActionController::Base
before_action :set_locale
around_action :log_request_duration
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from Pundit::NotAuthorizedError, with: :forbidden
private
def authenticate_user!
redirect_to login_path unless current_user
end
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
def not_found
render :not_found, status: :not_found
end
def forbidden
render :forbidden, status: :forbidden
end
def log_request_duration
start = Time.now
yield
logger.info "Request took #{(Time.now - start).round(3)}s"
end
end
# Flash messages — survive exactly one redirect
redirect_to @post, notice: 'Created!' # flash[:notice]
redirect_to root_path, alert: 'Error!' # flash[:alert]
flash.now[:notice] = 'Saved' # only for current render (no redirect)