Ruby on Rails - Schnelle Webanwendungsentwicklung
Ruby on Rails
backendRuby on Rails - Schnelle Webanwendungsentwicklung
Ruby on Rails, allgemein einfach als Rails bekannt, ist ein in Ruby geschriebenes Web-Framework, das seit uber zwei Jahrzehnten Standards in der schnellen Anwendungsentwicklung setzt. Es wurde 2004 von David Heinemeier Hansson entwickelt und aus dem Basecamp-Projekt extrahiert. Rails hat die Herangehensweise an die Entwicklung von Webanwendungen revolutioniert. GitHub, Shopify, Airbnb, Basecamp, Twitch und Hulu sind nur einige der Unternehmen, die ihre Produkte auf diesem Framework aufgebaut haben.
In diesem Artikel werden wir die wichtigsten Eigenschaften von Ruby on Rails im Detail untersuchen, seine Architektur, wesentliche Werkzeuge und die Techniken, die es zu einem der produktivsten Frameworks fur die Entwicklung von Webanwendungen machen.
Die Convention-over-Configuration-Philosophie#
Das grundlegende Prinzip von Rails ist Convention over Configuration (CoC) - Konvention vor Konfiguration. Das bedeutet, dass das Framework fur praktisch jeden Aspekt einer Anwendung sinnvolle Standardwerte bereitstellt. Anstatt Dutzende von Konfigurationsdateien zu schreiben, folgen Entwickler etablierten Namenskonventionen und Projektstrukturen.
Wie funktioniert das in der Praxis?#
# Model - Datei app/models/article.rb
# Rails mappt es automatisch auf die Tabelle 'articles' in der Datenbank
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
Im obigen Beispiel mussen Sie weder den Tabellennamen noch den Primarschlussel oder die Spalten definieren. Rails geht davon aus, dass:
- Das
Article-Modell der Tabellearticlesentspricht - Der Primarschlussel
idist - Der Fremdschlussel in der
comments-Tabellearticle_idist - Zeitstempel
created_atundupdated_atautomatisch verwaltet werden
Das zweite Schlussel-Prinzip ist Don't Repeat Yourself (DRY) - wiederhole dich nicht. Rails fordert aktiv die Eliminierung von Code-Duplikation durch Abstraktionen, Helper und gemeinsam genutzte Komponenten.
MVC-Architektur in Rails#
Rails implementiert das Model-View-Controller (MVC)-Architekturmuster, das die Anwendungslogik in drei Schichten unterteilt:
Model - Die Datenschicht#
Modelle reprasentieren Anwendungsdaten und Geschaftslogik. Sie sind uber Active Record mit Datenbanktabellen verknupft:
# 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 - Die Logikschicht#
Controller verarbeiten HTTP-Anfragen, kommunizieren mit Modellen und rendern Antworten:
# 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: "Artikel wurde veroeffentlicht."
else
render :new, status: :unprocessable_entity
end
end
def update
if @article.update(article_params)
redirect_to @article, notice: "Artikel wurde aktualisiert."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@article.destroy
redirect_to articles_path, notice: "Artikel wurde geloescht."
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 - Die Prasentationsschicht#
Views generieren HTML-Antworten mithilfe von ERB-Templates (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>Kommentare (<%= @comments.count %>)</h2>
<%= render @comments %>
<%= render "comments/form", comment: @comment, article: @article %>
</section>
</article>
Active Record ORM#
Active Record ist das Herzstuck von Rails - die ORM-Schicht (Object-Relational Mapping), die Ruby-Objekte elegant mit Datenbanktabellen verbindet.
Datenbank-Migrationen#
Migrationen ermoglichen die Versionierung des Datenbankschemas und das Teilen von Anderungen im Team:
# 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
Die Ausfuhrung von Migrationen ist unkompliziert:
# Alle ausstehenden Migrationen ausfuehren
rails db:migrate
# Letzte Migration rueckgaengig machen
rails db:rollback
# Migrationsstatus pruefen
rails db:migrate:status
# Datenbank zuruecksetzen
rails db:reset
Assoziationen#
Active Record bietet einen umfangreichen Satz von Assoziationen zur Modellierung von Beziehungen:
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
Validierungen#
Rails bietet ein umfangreiches Validierungssystem:
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, "darf das Wort 'admin' nicht enthalten")
end
end
end
Datenbankabfragen#
Active Record bietet eine elegante Schnittstelle zum Erstellen von Abfragen:
# Suche
Article.where(published: true)
.where("created_at > ?", 1.week.ago)
.order(created_at: :desc)
.limit(10)
# Eager Loading - Loesung des N+1-Problems
Article.includes(:author, :tags, :comments)
.where(published: true)
# Aggregation
Article.group(:author_id)
.count
Article.where(published: true)
.average(:views_count)
# Erweiterte Abfragen
Article.joins(:author)
.where(authors: { active: true })
.select("articles.*, authors.username as author_name")
Scaffolding und Generatoren#
Eine der leistungsstarksten Funktionen von Rails ist das Generatorsystem, das die Erstellung von Boilerplate-Code automatisiert:
# Scaffold - generiert Model, Controller, Views, Migration, Tests
rails generate scaffold Product name:string description:text \
price:decimal{10,2} stock:integer category:references
# Model-Generator
rails generate model Order user:references total:decimal \
status:string shipped_at:datetime
# Controller-Generator
rails generate controller Api::V1::Products index show create update destroy
# Migrations-Generator
rails generate migration AddSlugToArticles slug:string:uniq
# Job-Generator
rails generate job SendWelcomeEmail
# Mailer-Generator
rails generate mailer UserMailer welcome reset_password
Nach der Ausfuhrung von rails generate scaffold Product erstellt Rails einen vollstandigen Satz von Dateien:
- Modell mit Validierungen
- Datenbank-Migration
- Controller mit vollstandigen CRUD-Operationen
- Views (index, show, new, edit, _form)
- Unit- und Systemtests
- Routing
Action Cable - WebSockets in Rails#
Action Cable integriert WebSockets direkt in das Framework und ermoglicht Echtzeit-Kommunikation:
# 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
# Bereinigung bei Trennung
end
end
Client-seitige Implementierung mit JavaScript:
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
const chatChannel = consumer.subscriptions.create(
{ channel: "ChatChannel", room_id: roomId },
{
connected() {
console.log("Mit Chat verbunden")
},
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 - Hintergrundverarbeitung#
Active Job bietet eine einheitliche Schnittstelle zur Verarbeitung von Hintergrundaufgaben, unabhangig vom gewahlten Backend (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
# Job einreihen
SendNewsletterJob.perform_later(newsletter)
# Fuer spaeter planen
SendNewsletterJob.set(wait: 1.hour).perform_later(newsletter)
# Fuer einen bestimmten Zeitpunkt planen
SendNewsletterJob.set(wait_until: Date.tomorrow.noon).perform_later(newsletter)
Konfiguration mit 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 und Hotwire - Modernes Frontend in Rails 7+#
Rails 7 hat Hotwire als Standardansatz fur den Aufbau interaktiver Oberflachen eingefuhrt, ohne umfangreichen JavaScript-Code schreiben zu mussen. Hotwire besteht aus drei Komponenten:
Turbo Drive#
Wandelt die Seitennavigation automatisch in asynchrone Anfragen um:
<%# Turbo Drive funktioniert automatisch - die Navigation ist schneller
ohne zusaetzlichen Code %>
<nav>
<%= link_to "Startseite", root_path %>
<%= link_to "Artikel", articles_path %>
<%= link_to "Profil", profile_path %>
</nav>
Turbo Frames#
Ermoglichen partielle Seitenaktualisierungen:
<%# app/views/articles/index.html.erb %>
<%= turbo_frame_tag "articles_list" do %>
<% @articles.each do |article| %>
<%= render article %>
<% end %>
<%# Paginierung wird im Frame geladen, ohne die Seite neu zu laden %>
<%= link_to "Naechste Seite",
articles_path(page: @page + 1),
data: { turbo_frame: "articles_list" } %>
<% end %>
Turbo Streams#
Ermoglichen Echtzeit-Aktualisierungen direkt vom Server:
# 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#
Ein leichtgewichtiges JavaScript-Framework zum Hinzufugen von Verhaltensweisen zu 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()
}
}
Testen mit RSpec und Minitest#
Rails hat eingebaute Unterstutzung fur Tests. Minitest ist das Standard-Test-Framework, aber viele Entwickler bevorzugen 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: "Neuer Artikel", body: "Inhalt" } } }
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
Sicherheit in Rails#
Rails enthalt eingebaute Schutzmechanismen gegen die haufigsten Angriffe:
CSRF-Schutz#
# Automatisch im ApplicationController aktiviert
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
Das CSRF-Token wird automatisch in Formulare eingefugt:
<%= form_with(model: @article) do |form| %>
<%# CSRF-Token wird automatisch hinzugefuegt %>
<%= form.text_field :title %>
<%= form.submit "Speichern" %>
<% end %>
XSS-Schutz#
Rails escapt Ausgaben in Views automatisch:
<%# Sicher - automatisches Escaping %>
<p><%= @article.title %></p>
<%# Unsicher - nur mit vertrauenswuerdigen Daten verwenden %>
<p><%= sanitize @article.body %></p>
<%# Content Security Policy %>
<%# config/initializers/content_security_policy.rb %>
SQL-Injection-Schutz#
# Sicher - parametrisierte Abfragen
User.where("email = ?", params[:email])
User.where(email: params[:email])
# Unsicher - NIEMALS so machen!
# User.where("email = '#{params[:email]}'")
Strong Parameters#
# Kontrolle, welche Parameter massenweise zugewiesen werden koennen
def article_params
params.require(:article).permit(:title, :body, :published, tag_ids: [])
end
Rails API-Modus#
Rails eignet sich hervorragend zum Erstellen von APIs. Der API-Modus entfernt unnoetige Middleware und bietet eine schlankere Konfiguration:
# Neue Anwendung im API-Modus erstellen
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
Serialisierung mit 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
Das Gems-Oekosystem#
Einer der groessten Vorteile von Rails ist sein reichhaltiges Bibliotheks-Oekosystem (Gems). Hier sind die beliebtesten:
| Gem | Zweck | |-----|-------| | Devise | Benutzerauthentifizierung | | Pundit | Autorisierung und Zugriffsrichtlinien | | Sidekiq | Hintergrund-Job-Verarbeitung | | Kaminari | Paginierung | | Ransack | Erweiterte Suche | | Active Admin | Admin-Panel | | CarrierWave | Datei-Uploads | | RuboCop | Code-Linter und -Formatierer | | FactoryBot | Testdaten-Fabriken | | Capistrano | Deployment-Automatisierung | | Bullet | N+1-Abfragenerkennung | | Pagy | Hochleistungs-Paginierung |
Beispiel-Gemfile-Konfiguration#
# Gemfile
source "https://rubygems.org"
gem "rails", "~> 7.1"
gem "pg", "~> 1.5"
gem "puma", ">= 6.0"
gem "redis", ">= 5.0"
# Authentifizierung und Autorisierung
gem "devise", "~> 4.9"
gem "pundit", "~> 2.3"
# API
gem "jbuilder"
gem "rack-cors"
# Hintergrundverarbeitung
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
Wer verwendet Ruby on Rails?#
Rails ist eine produktionsreife Technologie, die einige der groessten Plattformen im Internet antreibt:
- GitHub - die groesste Code-Hosting-Plattform (uber 100 Millionen Benutzer)
- Shopify - E-Commerce-Plattform fur Millionen von Shops
- Basecamp - Projektmanagement-Tool (Erfinder von Rails)
- Airbnb - globaler Marktplatz fur Unterkunftsvermietung
- Twitch - Streaming-Plattform
- Hulu - Streaming-Dienst
- Zendesk - Kundensupport-Plattform
- Kickstarter - Crowdfunding-Plattform
- Dribbble - Designer-Community
Diese Unternehmen beweisen, dass Rails hervorragend skaliert und Millionen von Benutzern bedienen kann.
Leistung - Praktische Tipps#
Rails wird manchmal wegen seiner Leistung kritisiert, aber mit den richtigen Praktiken erzielt es ausgezeichnete Ergebnisse:
Abfrageoptimierung#
# Eager Loading - Eliminierung von N+1
Article.includes(:author, :tags, comments: :user).published
# Counter Cache - Vermeidung von COUNT-Abfragen
# Migration: add_column :articles, :comments_count, :integer, default: 0
belongs_to :article, counter_cache: true
# Datenbankindizierung
add_index :articles, [:published, :created_at]
add_index :articles, :slug, unique: true
Caching#
# 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
Produktions-Deployment#
# 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
Zusammenfassung#
Ruby on Rails bleibt eines der produktivsten Frameworks fur die Entwicklung von Webanwendungen. Seine wichtigsten Vorteile sind:
- Entwicklungsgeschwindigkeit - Convention over Configuration und Generatoren beschleunigen die Entwicklung
- Reife - uber 20 Jahre Entwicklung und bewahrte Loesungen
- Vollstaendigkeit - alles, was Sie brauchen, in einem einzigen Framework
- Oekosystem - Tausende von Gems fur jeden Bedarf
- Sicherheit - eingebauter Schutz gegen CSRF, XSS und SQL Injection
- Testen - TDD-Kultur im Framework verankert
- Hotwire - modernes Frontend ohne SPA-Komplexitaet
- Skalierbarkeit - GitHub und Shopify beweisen, dass Rails hervorragend skaliert
Brauchen Sie Unterstutzung?#
Bei MDS Software Solutions Group helfen wir Unternehmen beim Aufbau moderner Webanwendungen mit Ruby on Rails und anderen Backend-Technologien. Wir bieten:
- Entwicklung von Webanwendungen von Grund auf mit Ruby on Rails
- Migration bestehender Systeme zu moderner Architektur
- Aufbau von APIs und Integrationen mit externen Systemen
- Leistungsoptimierung bestehender Rails-Anwendungen
- Architekturberatung und Code Review
Kontaktieren Sie uns, um Ihr Projekt zu besprechen und zu erfahren, wie Rails die Entwicklung Ihres Produkts beschleunigen kann!
Team von Programmierexperten, die sich auf moderne Webtechnologien spezialisiert haben.