Ruby on Rails
「設定より規約」を重視したフルスタックWebフレームワーク。Hotwireによりモダンなフロントエンド開発も可能になった。
GitHub概要
スター57,276
ウォッチ2,311
フォーク21,912
作成日:2008年4月11日
言語:Ruby
ライセンス:MIT License
トピックス
activejobactiverecordframeworkhtmlmvcrailsruby
スター履歴
データ取得日時: 2025/8/13 01:43
フレームワーク
Ruby on Rails
概要
Ruby on Railsは、Ruby言語で書かれたオープンソースのWebアプリケーションフレームワークです。
詳細
Ruby on Rails(Rails)は、2004年にDavid Heinemeier Hanssonによって作成された、Web開発における的命的なフレームワークです。「Convention over Configuration(設定よりも規約)」、「Don't Repeat Yourself(DRY)」、「RESTful設計」などの哲学を中核とし、開発者が簡単に高品質なWebアプリケーションを構築できるように設計されています。MVC(Model-View-Controller)アーキテクチャを採用し、データベース操作のActive Record、ルーティング管理のAction Controller、ビューレンダリングのAction Viewなど、包括的な機能を提供しています。ジェネレーターやスキャフォールディング機能により、コードの自動生成と高速なプロトタイピングが可能です。豊富なGemエコシステムにより、認証、ファイルアップロード、決済処理など、様々な機能を簡単に組み込めます。テストドリブン開発(TDD)や行動駆動開発(BDD)に必要なテストフレームワークやツールが最初から組み込まれており、保守性が高く反復可能なコードを書くことができます。GitHub、Shopify、Airbnb、Basecampなど多くの有名サービスで採用され、特にスタートアップやプロトタイプ開発、コンテンツ管理システムの構築に優れています。
メリット・デメリット
メリット
- 高い生産性: Convention over Configurationによる高速開発
- 豊富なGemエコシステム: 15万以上のライブラリが利用可能
- MVCアーキテクチャ: 整理されたコード構造と関心の分離
- Active Record: 直感的で強力なORM機能
- ジェネレーター: コードの自動生成とスキャフォールディング
- テスト統合: ビルトインのテストフレームワーク
- 大きなコミュニティ: 豊富なドキュメントと情報源
- 柔軟性: 様々な用途に対応できる拡張性
デメリット
- パフォーマンス: コンパイル言語と比較して実行速度が遅い
- メモリ使用量: リソース消費が大きい
- コンベンションの制約: 規約に従わない場合の複雑さ
- マジックの存在: 抽象化によるブラックボックス化
- バージョン互換性: メジャーアップデート時の互換性問題
- スケーリングの課題: 大規模アプリケーションでのスケール難易度
主要リンク
書き方の例
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
# 新しいRailsアプリケーションの作成
rails new myapp
cd myapp
rails server
MVC構造とルーティング
# 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: 'ユーザーが正常に作成されました。'
else
render :new, status: :unprocessable_entity
end
end
def update
if @user.update(user_params)
redirect_to @user, notice: 'ユーザーが正常に更新されました。'
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モデル
# 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
ビューとヘルパー
<!-- 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>ユーザー一覧</h1>
<%= link_to '新しいユーザー', 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>年齢: <%= user.age %>歳</p>
<div class="actions">
<%= link_to '詳細', user, class: 'btn btn-info' %>
<%= link_to '編集', edit_user_path(user), class: 'btn btn-warning' %>
<%= link_to '削除', user, method: :delete,
confirm: '本当に削除しますか?',
class: 'btn btn-danger' %>
</div>
</div>
<% end %>
</div>
JSON APIとシリアライザー
# 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
バックグラウンドジョブ(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|
# 画像処理ロジック
process_image(image)
end
post.update(processed: true)
end
private
def process_image(image)
# リサイズ、最適化などの処理
puts "Processing image: #{image.filename}"
end
end
# ジョブの実行
SendEmailJob.perform_later(user.id, 'welcome')
ProcessImageJob.set(wait: 5.minutes).perform_later(post.id)
テスト(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