Ruby on Rails

Full-stack web framework emphasizing 'Convention over Configuration'. Now enables modern frontend development with Hotwire.

RubyFrameworkMVCWeb DevelopmentFull-stack

GitHub Overview

rails/rails

Ruby on Rails

Stars57,276
Watchers2,311
Forks21,912
Created:April 11, 2008
Language:Ruby
License:MIT License

Topics

activejobactiverecordframeworkhtmlmvcrailsruby

Star History

rails/rails Star History
Data as of: 8/13/2025, 01:43 AM

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

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