Ruby on Rails - szybkie tworzenie aplikacji webowych
Ruby on Rails
backendRuby on Rails - szybkie tworzenie aplikacji webowych
Ruby on Rails, znany po prostu jako Rails, to framework webowy napisany w języku Ruby, który od ponad dwóch dekad wyznacza standardy w szybkim tworzeniu aplikacji. Stworzony przez Davida Heinemeiera Hanssona w 2004 roku i wyekstrahowany z projektu Basecamp, Rails zrewolucjonizował podejście do budowania aplikacji webowych. GitHub, Shopify, Airbnb, Basecamp, Twitch czy Hulu - to tylko niektóre z firm, które zbudowały swoje produkty właśnie na tym frameworku.
W tym artykule szczegółowo omówimy kluczowe cechy Ruby on Rails, jego architekturę, najważniejsze narzędzia i techniki, które sprawiają, że jest to jeden z najproduktywniejszych frameworków do budowy aplikacji webowych.
Filozofia Convention over Configuration#
Fundamentalną zasadą Rails jest Convention over Configuration (CoC) - konwencja ponad konfigurację. Oznacza to, że framework dostarcza sensowne wartości domyślne dla praktycznie każdego aspektu aplikacji. Zamiast pisać dziesiątki plików konfiguracyjnych, programista stosuje ustalone konwencje nazewnictwa i struktury projektu.
Jak to działa w praktyce?#
# Model - plik app/models/article.rb
# Rails automatycznie mapuje go na tabelę 'articles' w bazie danych
class Article < ApplicationRecord
belongs_to :author
has_many :comments, dependent: :destroy
has_many :taggings
has_many :tags, through: :taggings
validates :title, presence: true, length: { minimum: 5 }
validates :body, presence: true
end
W powyższym przykładzie nie musisz definiować nazwy tabeli, klucza głównego ani kolumn. Rails zakłada, że:
- Model
Articleodpowiada tabeliarticles - Klucz główny to
id - Klucz obcy w tabeli
commentstoarticle_id - Znaczniki czasu
created_atiupdated_atsą obsługiwane automatycznie
Druga kluczowa zasada to Don't Repeat Yourself (DRY) - nie powtarzaj się. Rails aktywnie promuje eliminowanie duplikacji kodu poprzez abstrakcje, helpery i współdzielone komponenty.
Architektura MVC w Rails#
Rails implementuje wzorzec architektoniczny Model-View-Controller (MVC), który rozdziela logikę aplikacji na trzy warstwy:
Model - warstwa danych#
Modele reprezentują dane aplikacji i logikę biznesową. Są powiązane z tabelami bazy danych za pośrednictwem Active Record:
# app/models/user.rb
class User < ApplicationRecord
has_secure_password
has_many :articles, foreign_key: :author_id
has_many :comments
has_one :profile
validates :email, presence: true,
uniqueness: { case_sensitive: false },
format: { with: URI::MailTo::EMAIL_REGEXP }
validates :username, presence: true,
uniqueness: true,
length: { in: 3..30 }
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc).limit(10) }
before_save :normalize_email
private
def normalize_email
self.email = email.downcase.strip
end
end
Controller - warstwa logiki#
Kontrolery obsługują żądania HTTP, komunikują się z modelami i renderują odpowiedzi:
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_article, only: [:show, :edit, :update, :destroy]
before_action :authorize_article!, only: [:edit, :update, :destroy]
def index
@articles = Article.includes(:author, :tags)
.published
.order(created_at: :desc)
.page(params[:page])
.per(15)
end
def show
@comments = @article.comments.includes(:user).order(created_at: :asc)
@comment = Comment.new
end
def create
@article = current_user.articles.build(article_params)
if @article.save
redirect_to @article, notice: "Artykuł został opublikowany."
else
render :new, status: :unprocessable_entity
end
end
def update
if @article.update(article_params)
redirect_to @article, notice: "Artykuł został zaktualizowany."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@article.destroy
redirect_to articles_path, notice: "Artykuł został usunięty."
end
private
def set_article
@article = Article.find(params[:id])
end
def article_params
params.require(:article).permit(:title, :body, :published, tag_ids: [])
end
def authorize_article!
redirect_to root_path unless @article.author == current_user
end
end
View - warstwa prezentacji#
Widoki generują odpowiedzi HTML za pomocą szablonów ERB (Embedded Ruby):
<%# app/views/articles/show.html.erb %>
<article class="article-detail">
<header>
<h1><%= @article.title %></h1>
<div class="meta">
<span>Autor: <%= @article.author.username %></span>
<time datetime="<%= @article.created_at.iso8601 %>">
<%= l(@article.created_at, format: :long) %>
</time>
</div>
<div class="tags">
<% @article.tags.each do |tag| %>
<%= link_to tag.name, tag_path(tag), class: "tag" %>
<% end %>
</div>
</header>
<div class="content">
<%= @article.body.html_safe %>
</div>
<section class="comments">
<h2>Komentarze (<%= @comments.count %>)</h2>
<%= render @comments %>
<%= render "comments/form", comment: @comment, article: @article %>
</section>
</article>
Active Record ORM#
Active Record to serce Rails - warstwa ORM (Object-Relational Mapping), która elegancko łączy obiekty Ruby z tabelami bazy danych.
Migracje bazy danych#
Migracje pozwalają na wersjonowanie schematu bazy danych i współdzielenie zmian w zespole:
# db/migrate/20250128_create_articles.rb
class CreateArticles < ActiveRecord::Migration[7.1]
def change
create_table :articles do |t|
t.string :title, null: false
t.text :body, null: false
t.boolean :published, default: false
t.references :author, null: false, foreign_key: { to_table: :users }
t.datetime :published_at
t.integer :views_count, default: 0
t.timestamps
end
add_index :articles, :published
add_index :articles, :published_at
add_index :articles, [:author_id, :created_at]
end
end
Uruchomienie migracji jest proste:
# Uruchom wszystkie oczekujące migracje
rails db:migrate
# Cofnij ostatnią migrację
rails db:rollback
# Sprawdź status migracji
rails db:migrate:status
# Zresetuj bazę danych
rails db:reset
Asocjacje#
Active Record oferuje bogaty zestaw asocjacji do modelowania relacji:
class Author < ApplicationRecord
has_many :articles, dependent: :destroy
has_many :comments, through: :articles
has_one :profile, dependent: :destroy
has_and_belongs_to_many :roles
end
class Article < ApplicationRecord
belongs_to :author
has_many :comments, dependent: :destroy
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
has_one_attached :cover_image # Active Storage
has_rich_text :body # Action Text
end
class Comment < ApplicationRecord
belongs_to :article, counter_cache: true
belongs_to :user
has_many :replies, class_name: "Comment", foreign_key: :parent_id
belongs_to :parent, class_name: "Comment", optional: true
end
Walidacje#
Rails dostarcza rozbudowany system walidacji:
class User < ApplicationRecord
validates :email, presence: true,
uniqueness: true,
format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i }
validates :password, length: { minimum: 8 },
if: :password_required?
validates :age, numericality: { greater_than_or_equal_to: 18 },
allow_nil: true
validate :custom_validation
private
def custom_validation
if username&.include?("admin") && !admin?
errors.add(:username, "nie może zawierać słowa 'admin'")
end
end
end
Zapytania do bazy danych#
Active Record udostępnia elegancki interfejs do budowania zapytań:
# Wyszukiwanie
Article.where(published: true)
.where("created_at > ?", 1.week.ago)
.order(created_at: :desc)
.limit(10)
# Eager loading - rozwiązanie problemu N+1
Article.includes(:author, :tags, :comments)
.where(published: true)
# Aggregacja
Article.group(:author_id)
.count
Article.where(published: true)
.average(:views_count)
# Zaawansowane zapytania
Article.joins(:author)
.where(authors: { active: true })
.select("articles.*, authors.username as author_name")
Scaffolding i generatory#
Jedną z najpotężniejszych cech Rails jest system generatorów, który automatyzuje tworzenie powtarzalnego kodu:
# Scaffold - generuje model, kontroler, widoki, migrację, testy
rails generate scaffold Product name:string description:text \
price:decimal{10,2} stock:integer category:references
# Generator modelu
rails generate model Order user:references total:decimal \
status:string shipped_at:datetime
# Generator kontrolera
rails generate controller Api::V1::Products index show create update destroy
# Generator migracji
rails generate migration AddSlugToArticles slug:string:uniq
# Generator zadań
rails generate job SendWelcomeEmail
# Generator mailera
rails generate mailer UserMailer welcome reset_password
Po uruchomieniu rails generate scaffold Product, Rails tworzy kompletny zestaw plików:
- Model z walidacjami
- Migrację bazy danych
- Kontroler z pełnym CRUD
- Widoki (index, show, new, edit, _form)
- Testy jednostkowe i systemowe
- Routing
Action Cable - WebSockets w Rails#
Action Cable integruje WebSockets bezpośrednio z frameworkiem, umożliwiając komunikację w czasie rzeczywistym:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
@room = ChatRoom.find(params[:room_id])
stream_for @room
end
def receive(data)
message = @room.messages.create!(
user: current_user,
body: data["body"]
)
ChatChannel.broadcast_to(@room, {
id: message.id,
body: message.body,
user: message.user.username,
created_at: message.created_at.strftime("%H:%M")
})
end
def unsubscribed
# Czyszczenie po rozłączeniu
end
end
Strona klienta z użyciem JavaScript:
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
const chatChannel = consumer.subscriptions.create(
{ channel: "ChatChannel", room_id: roomId },
{
connected() {
console.log("Połączono z czatem")
},
received(data) {
const messagesContainer = document.getElementById("messages")
messagesContainer.insertAdjacentHTML("beforeend", `
<div class="message">
<strong>${data.user}:</strong>
<span>${data.body}</span>
<time>${data.created_at}</time>
</div>
`)
},
sendMessage(body) {
this.perform("receive", { body: body })
}
}
)
Active Job - przetwarzanie w tle#
Active Job zapewnia jednolity interfejs do obsługi zadań w tle, niezależnie od wybranego backendu (Sidekiq, Resque, Delayed Job):
# app/jobs/send_newsletter_job.rb
class SendNewsletterJob < ApplicationJob
queue_as :mailers
retry_on Net::SMTPError, wait: 5.minutes, attempts: 3
discard_on ActiveJob::DeserializationError
def perform(newsletter)
newsletter.subscribers.find_each do |subscriber|
NewsletterMailer.weekly_digest(subscriber, newsletter).deliver_now
end
newsletter.update!(sent_at: Time.current)
end
end
# Wywołanie zadania
SendNewsletterJob.perform_later(newsletter)
# Zaplanowanie na później
SendNewsletterJob.set(wait: 1.hour).perform_later(newsletter)
# Zaplanowanie na konkretną datę
SendNewsletterJob.set(wait_until: Date.tomorrow.noon).perform_later(newsletter)
Konfiguracja z Sidekiq#
# config/application.rb
config.active_job.queue_adapter = :sidekiq
# config/sidekiq.yml
:concurrency: 10
:queues:
- [critical, 3]
- [default, 2]
- [mailers, 1]
- [low, 1]
Turbo i Hotwire - nowoczesny frontend w Rails 7+#
Rails 7 wprowadził Hotwire jako domyślne podejście do budowy interaktywnych interfejsów bez konieczności pisania dużej ilości JavaScript. Hotwire składa się z trzech komponentów:
Turbo Drive#
Automatycznie zamienia nawigację pełnostronicową na asynchroniczne żądania:
<%# Turbo Drive działa automatycznie - nawigacja jest szybsza
bez jakiegokolwiek dodatkowego kodu %>
<nav>
<%= link_to "Strona główna", root_path %>
<%= link_to "Artykuły", articles_path %>
<%= link_to "Profil", profile_path %>
</nav>
Turbo Frames#
Pozwalają na częściowe aktualizacje strony:
<%# app/views/articles/index.html.erb %>
<%= turbo_frame_tag "articles_list" do %>
<% @articles.each do |article| %>
<%= render article %>
<% end %>
<%# Paginacja ładuje się w ramce bez przeładowania strony %>
<%= link_to "Następna strona",
articles_path(page: @page + 1),
data: { turbo_frame: "articles_list" } %>
<% end %>
Turbo Streams#
Umożliwiają aktualizacje w czasie rzeczywistym bezpośrednio z serwera:
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
@comment = @article.comments.build(comment_params)
@comment.user = current_user
if @comment.save
respond_to do |format|
format.turbo_stream
format.html { redirect_to @article }
end
else
render :new, status: :unprocessable_entity
end
end
end
<%# app/views/comments/create.turbo_stream.erb %>
<%= turbo_stream.append "comments" do %>
<%= render @comment %>
<% end %>
<%= turbo_stream.update "comment_count", @article.comments.count %>
<%= turbo_stream.replace "comment_form" do %>
<%= render "comments/form", comment: Comment.new, article: @article %>
<% end %>
Stimulus#
Lekki framework JavaScript do dodawania zachowań do HTML:
// app/javascript/controllers/search_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["input", "results"]
static values = { url: String }
connect() {
this.timeout = null
}
search() {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.fetchResults()
}, 300)
}
async fetchResults() {
const query = this.inputTarget.value
if (query.length < 2) return
const response = await fetch(`${this.urlValue}?q=${query}`)
this.resultsTarget.innerHTML = await response.text()
}
}
Testowanie z RSpec i Minitest#
Rails ma wbudowane wsparcie dla testowania. Minitest jest domyślnym frameworkiem testowym, ale wielu programistów preferuje RSpec.
Minitest#
# test/models/article_test.rb
class ArticleTest < ActiveSupport::TestCase
setup do
@article = articles(:valid_article)
end
test "should not save article without title" do
@article.title = nil
assert_not @article.valid?
assert_includes @article.errors[:title], "can't be blank"
end
test "should not save article with short title" do
@article.title = "Hi"
assert_not @article.valid?
end
test "should belong to author" do
assert_respond_to @article, :author
assert_instance_of User, @article.author
end
end
RSpec#
# spec/models/article_spec.rb
RSpec.describe Article, type: :model do
describe "validations" do
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_length_of(:title).is_at_least(5) }
it { is_expected.to validate_presence_of(:body) }
end
describe "associations" do
it { is_expected.to belong_to(:author).class_name("User") }
it { is_expected.to have_many(:comments).dependent(:destroy) }
it { is_expected.to have_many(:tags).through(:taggings) }
end
describe "#publish!" do
let(:article) { create(:article, published: false) }
it "marks article as published" do
article.publish!
expect(article.published).to be true
expect(article.published_at).to be_present
end
end
describe ".recent" do
it "returns articles from last week" do
recent = create(:article, created_at: 2.days.ago)
old = create(:article, created_at: 2.weeks.ago)
expect(Article.recent).to include(recent)
expect(Article.recent).not_to include(old)
end
end
end
# spec/requests/articles_spec.rb
RSpec.describe "Articles API", type: :request do
describe "GET /api/v1/articles" do
before { create_list(:article, 5, published: true) }
it "returns published articles" do
get "/api/v1/articles"
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body).size).to eq(5)
end
end
describe "POST /api/v1/articles" do
let(:user) { create(:user) }
let(:valid_params) { { article: { title: "Nowy artykuł", body: "Treść" } } }
it "creates article for authenticated user" do
post "/api/v1/articles",
params: valid_params,
headers: auth_headers(user)
expect(response).to have_http_status(:created)
expect(Article.count).to eq(1)
end
end
end
Bezpieczeństwo w Rails#
Rails posiada wbudowane mechanizmy ochrony przed najczęstszymi atakami:
Ochrona przed CSRF#
# Automatycznie włączone w ApplicationController
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
Token CSRF jest automatycznie dołączany do formularzy:
<%= form_with(model: @article) do |form| %>
<%# Token CSRF jest dodawany automatycznie %>
<%= form.text_field :title %>
<%= form.submit "Zapisz" %>
<% end %>
Ochrona przed XSS#
Rails automatycznie escapuje wyjście w widokach:
<%# Bezpieczne - automatyczne escapowanie %>
<p><%= @article.title %></p>
<%# Niebezpieczne - użyj tylko z zaufanymi danymi %>
<p><%= sanitize @article.body %></p>
<%# Content Security Policy %>
<%# config/initializers/content_security_policy.rb %>
Ochrona przed SQL Injection#
# Bezpieczne - parametryzowane zapytania
User.where("email = ?", params[:email])
User.where(email: params[:email])
# Niebezpieczne - NIE rób tego!
# User.where("email = '#{params[:email]}'")
Strong Parameters#
# Kontrola, które parametry mogą być masowo przypisywane
def article_params
params.require(:article).permit(:title, :body, :published, tag_ids: [])
end
Rails API Mode#
Rails doskonale nadaje się do budowy API. Tryb API eliminuje niepotrzebne middleware i zapewnia lżejszą konfigurację:
# Tworzenie nowej aplikacji w trybie API
rails new my_api --api
# app/controllers/api/v1/articles_controller.rb
module Api
module V1
class ArticlesController < ApplicationController
before_action :authenticate_api_user!
def index
articles = Article.includes(:author, :tags)
.published
.page(params[:page])
.per(25)
render json: articles,
each_serializer: ArticleSerializer,
meta: pagination_meta(articles)
end
def show
article = Article.find(params[:id])
render json: article, serializer: ArticleDetailSerializer
end
def create
article = current_user.articles.build(article_params)
if article.save
render json: article,
serializer: ArticleSerializer,
status: :created
else
render json: { errors: article.errors.full_messages },
status: :unprocessable_entity
end
end
private
def pagination_meta(collection)
{
current_page: collection.current_page,
total_pages: collection.total_pages,
total_count: collection.total_count
}
end
end
end
end
Serializacja z ActiveModel::Serializer#
# app/serializers/article_serializer.rb
class ArticleSerializer < ActiveModel::Serializer
attributes :id, :title, :excerpt, :published_at, :views_count
belongs_to :author, serializer: AuthorSerializer
has_many :tags, serializer: TagSerializer
def excerpt
object.body.truncate(200)
end
end
Ekosystem gemów#
Jedną z największych zalet Rails jest bogaty ekosystem bibliotek (gemów). Oto najpopularniejsze:
| Gem | Przeznaczenie | |-----|---------------| | Devise | Uwierzytelnianie użytkowników | | Pundit | Autoryzacja i polityki dostępu | | Sidekiq | Przetwarzanie zadań w tle | | Kaminari | Paginacja | | Ransack | Zaawansowane wyszukiwanie | | Active Admin | Panel administracyjny | | CarrierWave | Upload plików | | RuboCop | Linter i formatowanie kodu | | FactoryBot | Fabryki danych testowych | | Capistrano | Automatyzacja wdrożeń | | Bullet | Wykrywanie zapytań N+1 | | Pagy | Wydajna paginacja |
Przykład konfiguracji Gemfile#
# Gemfile
source "https://rubygems.org"
gem "rails", "~> 7.1"
gem "pg", "~> 1.5"
gem "puma", ">= 6.0"
gem "redis", ">= 5.0"
# Uwierzytelnianie i autoryzacja
gem "devise", "~> 4.9"
gem "pundit", "~> 2.3"
# API
gem "jbuilder"
gem "rack-cors"
# Przetwarzanie w tle
gem "sidekiq", "~> 7.0"
# Frontend
gem "turbo-rails"
gem "stimulus-rails"
gem "tailwindcss-rails"
group :development, :test do
gem "rspec-rails", "~> 6.0"
gem "factory_bot_rails"
gem "faker"
gem "rubocop-rails", require: false
gem "bullet"
end
group :development do
gem "web-console"
gem "letter_opener"
end
Kto używa Ruby on Rails?#
Rails jest technologią produkcyjną, na której działają jedne z największych platform internetowych:
- GitHub - największa platforma do hostowania kodu (ponad 100 mln użytkowników)
- Shopify - platforma e-commerce obsługująca miliony sklepów
- Basecamp - narzędzie do zarządzania projektami (twórcy Rails)
- Airbnb - globalny marketplace wynajmu mieszkań
- Twitch - platforma streamingowa
- Hulu - serwis streamingowy
- Zendesk - platforma obsługi klienta
- Kickstarter - platforma crowdfundingowa
- Dribbble - społeczność designerów
Te firmy udowadniają, że Rails doskonale radzi sobie ze skalowaniem i obsługą milionów użytkowników.
Wydajność - praktyczne porady#
Rails bywa krytykowany za wydajność, ale z odpowiednimi praktykami osiąga doskonałe rezultaty:
Optymalizacja zapytań#
# Eager loading - eliminacja N+1
Article.includes(:author, :tags, comments: :user).published
# Counter cache - unikanie COUNT queries
# Migracja: add_column :articles, :comments_count, :integer, default: 0
belongs_to :article, counter_cache: true
# Indeksowanie bazy danych
add_index :articles, [:published, :created_at]
add_index :articles, :slug, unique: true
Cache'owanie#
# Fragment caching
<% cache @article do %>
<article>
<h2><%= @article.title %></h2>
<p><%= @article.body %></p>
</article>
<% end %>
# Russian Doll caching
<% cache @article do %>
<%= render @article.comments %>
<% end %>
# Low-level caching
Rails.cache.fetch("stats/articles_count", expires_in: 1.hour) do
Article.published.count
end
Wdrożenie produkcyjne#
# config/environments/production.rb
Rails.application.configure do
config.cache_classes = true
config.eager_load = true
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] }
config.active_job.queue_adapter = :sidekiq
config.force_ssl = true
end
Podsumowanie#
Ruby on Rails pozostaje jednym z najproduktywniejszych frameworków do budowy aplikacji webowych. Jego kluczowe zalety to:
- Szybkość tworzenia - Convention over Configuration i generatory przyspieszają development
- Dojrzałość - ponad 20 lat rozwoju i sprawdzone rozwiązania
- Kompletność - wszystko, czego potrzebujesz, w jednym frameworku
- Ekosystem - tysiące gemów na każdą potrzebę
- Bezpieczeństwo - wbudowana ochrona przed CSRF, XSS, SQL Injection
- Testowanie - kultura TDD wbudowana w framework
- Hotwire - nowoczesny frontend bez złożoności SPA
- Skalowalność - GitHub i Shopify udowadniają, że Rails skaluje się doskonale
Potrzebujesz wsparcia?#
W MDS Software Solutions Group pomagamy firmom w budowaniu nowoczesnych aplikacji webowych z wykorzystaniem Ruby on Rails i innych technologii backendowych. Oferujemy:
- Tworzenie aplikacji webowych od podstaw w Ruby on Rails
- Migrację istniejących systemów do nowoczesnej architektury
- Budowę API i integracji z systemami zewnętrznymi
- Optymalizację wydajności istniejących aplikacji Rails
- Konsultacje architektoniczne i code review
Skontaktuj się z nami, aby omówić Twój projekt i przekonać się, jak Rails może przyspieszyć rozwój Twojego produktu!
Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.