Ruby on Rails
Full-stack web framework emphasizing 'Convention over Configuration'. Now enables modern frontend development with Hotwire.
GitHub Overview
Topics
Star History
Framework
Ruby on Rails
Overview
Ruby on Rails is an open-source web application framework written in the Ruby programming language.
Details
Ruby on Rails (Rails) is a revolutionary framework in web development, created by David Heinemeier Hansson in 2004. Designed around core philosophies of "Convention over Configuration," "Don't Repeat Yourself (DRY)," and "RESTful design," it enables developers to easily build high-quality web applications. Adopting the MVC (Model-View-Controller) architecture, it provides comprehensive functionality including Active Record for database operations, Action Controller for routing management, and Action View for view rendering. Generator and scaffolding features enable automatic code generation and rapid prototyping. The rich Gem ecosystem allows easy integration of various features like authentication, file uploads, and payment processing. Testing frameworks and tools necessary for Test-Driven Development (TDD) and Behavior-Driven Development (BDD) are built-in from the start, enabling the writing of maintainable and repeatable code. Adopted by many famous services including GitHub, Shopify, Airbnb, and Basecamp, it excels particularly in startup and prototype development, and content management system construction.
Pros and Cons
Pros
- High Productivity: Rapid development through Convention over Configuration
- Rich Gem Ecosystem: Over 150,000 libraries available
- MVC Architecture: Organized code structure and separation of concerns
- Active Record: Intuitive and powerful ORM functionality
- Generators: Automatic code generation and scaffolding
- Test Integration: Built-in testing framework
- Large Community: Rich documentation and information sources
- Flexibility: Extensibility for various use cases
Cons
- Performance: Slower execution speed compared to compiled languages
- Memory Usage: High resource consumption
- Convention Constraints: Complexity when not following conventions
- Magic Presence: Black-boxing through abstraction
- Version Compatibility: Compatibility issues during major updates
- Scaling Challenges: Scaling difficulties in large-scale applications
Key Links
- Ruby on Rails Official Site
- Rails Guides
- Rails API Documentation
- Rails GitHub Repository
- RubyGems
- Ruby Official Site
Code Examples
Hello World
# Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 8.0.0'
# config/routes.rb
Rails.application.routes.draw do
root 'application#hello'
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def hello
render plain: "Hello, Rails!"
end
end
# Create new Rails application
rails new myapp
cd myapp
rails server
MVC Structure and Routing
# config/routes.rb
Rails.application.routes.draw do
root 'home#index'
resources :users do
member do
get :profile
end
collection do
get :search
end
end
namespace :api do
namespace :v1 do
resources :posts, only: [:index, :show, :create, :update, :destroy]
end
end
end
# app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy, :profile]
def index
@users = User.all
end
def show
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
redirect_to @user, notice: 'User was successfully created.'
else
render :new, status: :unprocessable_entity
end
end
def update
if @user.update(user_params)
redirect_to @user, notice: 'User was successfully updated.'
else
render :edit, status: :unprocessable_entity
end
end
private
def set_user
@user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:name, :email, :age)
end
end
Active Record Models
# app/models/user.rb
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, through: :posts
has_one_attached :avatar
validates :name, presence: true, length: { minimum: 2, maximum: 50 }
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :age, presence: true, numericality: { greater_than: 0, less_than: 120 }
scope :adults, -> { where('age >= ?', 18) }
scope :by_name, ->(name) { where('name ILIKE ?', "%#{name}%") }
before_save :normalize_email
after_create :send_welcome_email
def full_name
"#{first_name} #{last_name}"
end
def adult?
age >= 18
end
private
def normalize_email
self.email = email.downcase.strip
end
def send_welcome_email
UserMailer.welcome(self).deliver_later
end
end
# app/models/post.rb
class Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
has_rich_text :content
has_many_attached :images
validates :title, presence: true, length: { maximum: 100 }
validates :content, presence: true
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
def excerpt(limit = 100)
content.to_plain_text.truncate(limit)
end
end
Views and Helpers
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title>My Rails App</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<nav class="navbar">
<%= link_to "Home", root_path, class: "nav-link" %>
<%= link_to "Users", users_path, class: "nav-link" %>
<% if user_signed_in? %>
<%= link_to "Profile", current_user, class: "nav-link" %>
<%= link_to "Logout", destroy_user_session_path,
method: :delete, class: "nav-link" %>
<% else %>
<%= link_to "Login", new_user_session_path, class: "nav-link" %>
<% end %>
</nav>
<main>
<% flash.each do |type, message| %>
<div class="alert alert-<%= type %>"><%= message %></div>
<% end %>
<%= yield %>
</main>
</body>
</html>
<!-- app/views/users/index.html.erb -->
<h1>Users List</h1>
<%= link_to 'New User', new_user_path, class: 'btn btn-primary' %>
<div class="users-grid">
<% @users.each do |user| %>
<div class="user-card">
<%= image_tag user.avatar, alt: user.name if user.avatar.attached? %>
<h3><%= link_to user.name, user %></h3>
<p><%= user.email %></p>
<p>Age: <%= user.age %> years old</p>
<div class="actions">
<%= link_to 'Show', user, class: 'btn btn-info' %>
<%= link_to 'Edit', edit_user_path(user), class: 'btn btn-warning' %>
<%= link_to 'Delete', user, method: :delete,
confirm: 'Are you sure?',
class: 'btn btn-danger' %>
</div>
</div>
<% end %>
</div>
JSON API and Serializers
# app/controllers/api/v1/posts_controller.rb
class Api::V1::PostsController < ApplicationController
before_action :set_post, only: [:show, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
def index
@posts = Post.published.includes(:user).recent.page(params[:page])
render json: @posts, include: [:user], meta: pagination_meta(@posts)
end
def show
render json: @post, include: [:user, :comments]
end
def create
@post = current_user.posts.build(post_params)
if @post.save
render json: @post, status: :created
else
render json: { errors: @post.errors }, status: :unprocessable_entity
end
end
def update
if @post.update(post_params)
render json: @post
else
render json: { errors: @post.errors }, status: :unprocessable_entity
end
end
def destroy
@post.destroy
head :no_content
end
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :content, :published, images: [])
end
def pagination_meta(collection)
{
current_page: collection.current_page,
total_pages: collection.total_pages,
total_count: collection.total_count
}
end
end
# app/serializers/post_serializer.rb
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :content, :published, :created_at, :updated_at
belongs_to :user
has_many :comments
def content
object.excerpt(200)
end
end
Background Jobs (Active Job)
# app/jobs/send_email_job.rb
class SendEmailJob < ApplicationJob
queue_as :default
def perform(user_id, email_type)
user = User.find(user_id)
case email_type
when 'welcome'
UserMailer.welcome(user).deliver_now
when 'newsletter'
UserMailer.newsletter(user).deliver_now
else
raise ArgumentError, "Unknown email type: #{email_type}"
end
end
end
# app/jobs/process_image_job.rb
class ProcessImageJob < ApplicationJob
queue_as :high_priority
retry_on StandardError, wait: 5.seconds, attempts: 3
def perform(post_id)
post = Post.find(post_id)
post.images.each do |image|
# Image processing logic
process_image(image)
end
post.update(processed: true)
end
private
def process_image(image)
# Processing like resize, optimization
puts "Processing image: #{image.filename}"
end
end
# Job execution
SendEmailJob.perform_later(user.id, 'welcome')
ProcessImageJob.set(wait: 5.minutes).perform_later(post.id)
Testing (RSpec)
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'validations' do
it { should validate_presence_of(:name) }
it { should validate_presence_of(:email) }
it { should validate_uniqueness_of(:email) }
it { should validate_numericality_of(:age).is_greater_than(0) }
end
describe 'associations' do
it { should have_many(:posts).dependent(:destroy) }
it { should have_one_attached(:avatar) }
end
describe 'scopes' do
let!(:adult_user) { create(:user, age: 25) }
let!(:minor_user) { create(:user, age: 16) }
it 'returns only adult users' do
expect(User.adults).to include(adult_user)
expect(User.adults).not_to include(minor_user)
end
end
describe '#adult?' do
it 'returns true for users 18 and older' do
user = build(:user, age: 18)
expect(user.adult?).to be true
end
it 'returns false for users under 18' do
user = build(:user, age: 17)
expect(user.adult?).to be false
end
end
end
# spec/controllers/users_controller_spec.rb
require 'rails_helper'
RSpec.describe UsersController, type: :controller do
describe 'GET #index' do
it 'returns a success response' do
get :index
expect(response).to be_successful
end
it 'assigns all users to @users' do
user = create(:user)
get :index
expect(assigns(:users)).to eq([user])
end
end
describe 'POST #create' do
context 'with valid parameters' do
let(:valid_attributes) do
{ name: 'Test User', email: '[email protected]', age: 25 }
end
it 'creates a new User' do
expect {
post :create, params: { user: valid_attributes }
}.to change(User, :count).by(1)
end
it 'redirects to the created user' do
post :create, params: { user: valid_attributes }
expect(response).to redirect_to(User.last)
end
end
context 'with invalid parameters' do
let(:invalid_attributes) { { name: '', email: 'invalid' } }
it 'does not create a new User' do
expect {
post :create, params: { user: invalid_attributes }
}.not_to change(User, :count)
end
it 'renders the new template' do
post :create, params: { user: invalid_attributes }
expect(response).to render_template(:new)
end
end
end
end