Ruby on Rails Mulakat Sorulari: 2026 En Iyi 25
En cok sorulan 25 Ruby on Rails mulakat sorusu. MVC mimarisi, Active Record, migration, RSpec testi, REST API ayrintili cevaplar ve kod ornekleri ile.

Ruby on Rails mülakatları, en popüler Ruby framework'üne hakimiyeti, MVC mimarisinin anlaşılmasını, Active Record ORM'sini ve "Convention over Configuration" felsefesine uygun sağlam web uygulamaları geliştirme yeteneğini değerlendirir. Bu rehber, Rails temellerinden ileri düzey üretim desenlerine kadar en sık sorulan 25 soruyu kapsar.
İşe alım uzmanları, Rails felsefesini anlayan adayları takdir eder: "Convention over Configuration", DRY (Don't Repeat Yourself) ve Rails Way kalıpları. Rails'in belirli mimari tercihleri neden yaptığını açıklamak fark yaratır.
Ruby on Rails temelleri
Soru 1: Ruby on Rails'te MVC desenini açıklayın
Model-View-Controller (MVC) deseni, Rails'in mimari çekirdeğidir. Sorumlulukları üç ayrı katmana bölerek kodun bakımını ve test edilebilirliğini iyileştirir.
# app/models/article.rb
# Model verileri ve iş mantığını yönetir
class Article < ApplicationRecord
# Veri doğrulamaları
validates :title, presence: true, length: { minimum: 5 }
validates :body, presence: true
# Diğer modellerle ilişkiler
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
has_many :tags, through: :article_tags
# Yeniden kullanılabilir sorgular için scope'lar
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc).limit(10) }
# Yaşam döngüsü callback'leri
before_save :generate_slug
private
def generate_slug
self.slug = title.parameterize if title_changed?
end
end# app/controllers/articles_controller.rb
# Controller istekleri alır ve yanıtı düzenler
class ArticlesController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_article, only: [:show, :edit, :update, :destroy]
def index
@articles = Article.published.recent.includes(:author)
end
def show
@comments = @article.comments.includes(:user)
end
def create
@article = current_user.articles.build(article_params)
if @article.save
redirect_to @article, notice: 'Makale başarıyla oluşturuldu.'
else
render :new, status: :unprocessable_entity
end
end
private
def set_article
@article = Article.find(params[:id])
end
def article_params
params.require(:article).permit(:title, :body, :published, tag_ids: [])
end
end<%# app/views/articles/show.html.erb %>
<%# View verileri HTML formatında gösterir %>
<article class="article-detail">
<header>
<h1><%= @article.title %></h1>
<p class="meta">
Yazan: <%= @article.author.name %> •
<%= l @article.created_at, format: :long %>
</p>
</header>
<div class="content">
<%= simple_format @article.body %>
</div>
<%# Yorumlar için partial %>
<%= render @comments %>
</article>Tipik akış: istek Router'a ulaşır ve uygun Controller'a yönlendirilir. Controller verileri almak veya değiştirmek için Model ile etkileşime girer, ardından bu verileri HTML render için View'a aktarır.
Soru 2: Active Record nedir ve Rails ORM'si nasıl çalışır?
Active Record, Active Record desenini uygulayan Rails ORM'sidir (Object-Relational Mapping). Her Model sınıfı bir veritabanı tablosunu, her örnek ise bir satırı temsil eder.
# app/models/user.rb
# Active Record sütunları otomatik olarak niteliklere eşler
class User < ApplicationRecord
# 'users' tablosu otomatik olarak ilişkilendirilir
# Sütunlar: id, email, name, created_at, updated_at
has_secure_password # Parola için BCrypt
has_many :articles, foreign_key: :author_id
has_one :profile, dependent: :destroy
has_and_belongs_to_many :roles
# Doğrulamalar
validates :email, presence: true,
uniqueness: { case_sensitive: false },
format: { with: URI::MailTo::EMAIL_REGEXP }
# Callback'ler
before_save :normalize_email
# Sorgular için sınıf metotları
def self.admins
joins(:roles).where(roles: { name: 'admin' })
end
private
def normalize_email
self.email = email.downcase.strip
end
end# Active Record sorgu örnekleri
# Rails konsolu veya bir servis içinde
# Oluşturma
user = User.create!(email: 'dev@example.com', name: 'Alice', password: 'secret123')
# Koşullarla okuma
active_users = User.where(active: true).order(:name)
user = User.find_by(email: 'dev@example.com')
# Zincirleme sorgular (lazy evaluation)
recent_admins = User.admins
.where('created_at > ?', 1.month.ago)
.includes(:profile)
.limit(10)
# Eager loading ile N+1 önleme
articles = Article.includes(:author, :comments).published
# Güncelleme
user.update!(name: 'Alice Martin')
# İşlemler
User.transaction do
user.debit_balance!(100)
recipient.credit_balance!(100)
Payment.create!(from: user, to: recipient, amount: 100)
endActive Record, Ruby metotlarını optimize edilmiş SQL sorgularına dönüştürür. where, joins, includes gibi metotlar tembeldir: sorgu yalnızca yineleme yapıldığında veya to_a çağrıldığında çalıştırılır.
Soru 3: Rails migration sistemini açıklayın
Migration'lar veritabanı şemasını Ruby ile sürümlendirmeye olanak tanır. Geri alınabilirler ve veri yapısının kontrollü gelişimine izin verirler.
# db/migrate/20260203100000_create_products.rb
# Tablo oluşturmak için migration
class CreateProducts < ActiveRecord::Migration[7.1]
def change
create_table :products do |t|
t.string :name, null: false
t.text :description
t.decimal :price, precision: 10, scale: 2, null: false
t.integer :stock_quantity, default: 0
t.references :category, null: false, foreign_key: true
t.boolean :active, default: true
t.timestamps # created_at ve updated_at otomatik
end
# Performans için indeksler
add_index :products, :name
add_index :products, [:category_id, :active]
end
end# db/migrate/20260203110000_add_slug_to_products.rb
# Mevcut bir tabloyu değiştirmek için migration
class AddSlugToProducts < ActiveRecord::Migration[7.1]
def change
add_column :products, :slug, :string
add_index :products, :slug, unique: true
# Mevcut slug'ları doldur
reversible do |dir|
dir.up do
Product.find_each do |product|
product.update_column(:slug, product.name.parameterize)
end
end
end
# Doldurma sonrası null'a izin verme
change_column_null :products, :slug, false
end
end# Temel migration komutları
rails db:migrate # Bekleyen migration'ları çalıştır
rails db:rollback # Son migration'ı geri al
rails db:rollback STEP=3 # Son 3 migration'ı geri al
rails db:migrate:status # Migration durumunu görüntüle
rails db:seed # db/seeds.rb'yi çalıştır
rails db:reset # Drop, create, migrate, seedMigration'lar geri alınabilir olmalıdır. change metodu akıllıdır ve yaygın işlemleri otomatik olarak tersine çevirebilir. Karmaşık durumlar için up ve down ayrı ayrı kullanılmalıdır.
İleri düzey Active Record
Soru 4: Rails'te N+1 sorgularını nasıl optimize edersiniz?
N+1 problemi, ilk sorgunun ardından ilişkileri yüklemek için N ek sorgu yapılmasıyla ortaya çıkar. Rails bu sorunu çözmek için çeşitli eager loading metotları sunar.
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def index
# ❌ N+1 PROBLEMİ: 1 sorgu + sipariş başına N sorgu
# @orders = Order.all
# View'da: order.user.name sipariş başına bir sorgu üretir
# ✅ ÇÖZÜM includes ile (eager loading)
@orders = Order.includes(:user, :items)
.where(status: 'completed')
.order(created_at: :desc)
# Toplamda yalnızca 3 sorgu üretir
end
def show
# includes: ilişkileri ayrı yükler (2-3 sorgu)
@order = Order.includes(items: :product).find(params[:id])
# preload: ayrı yüklemeyi zorlar
@order = Order.preload(:items, :user).find(params[:id])
# eager_load: LEFT OUTER JOIN'i zorlar (1 sorgu)
@order = Order.eager_load(:items).find(params[:id])
end
end# app/models/order.rb
class Order < ApplicationRecord
belongs_to :user
has_many :items, class_name: 'OrderItem'
has_many :products, through: :items
# Varsayılan includes ile scope
scope :with_details, -> { includes(:user, items: :product) }
# COUNT sorgularını önlemek için counter cache
# Gerekli: add_column :users, :orders_count, :integer, default: 0
belongs_to :user, counter_cache: true
end# Bullet gem ile N+1 tespiti (geliştirme)
# config/environments/development.rb
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.bullet_logger = true
Bullet.rails_logger = true
end
# Bullet şu durumlarda uyarı verir:
# - Bir N+1 sorgusu tespit edildiğinde
# - Gereksiz eager loading olduğunda
# - Counter cache kullanılması gerektiğindeKural: varsayılan olarak includes kullanın (Rails optimal stratejiyi seçer), ayrı sorguları zorlamak istediğinizde preload, ilişkiler üzerinde filtreleme yaptığınızda eager_load.
Soru 5: Rails'te Scope'ları ve Query Object'leri açıklayın
Scope'lar yeniden kullanılabilir sorgu koşullarını kapsüller. Karmaşık sorgular için Query Object'ler daha iyi organizasyon ve test edilebilirlik sunar.
# app/models/product.rb
class Product < ApplicationRecord
# Basit scope'lar
scope :active, -> { where(active: true) }
scope :in_stock, -> { where('stock_quantity > 0') }
scope :featured, -> { where(featured: true) }
# Parametreli scope'lar
scope :cheaper_than, ->(price) { where('price < ?', price) }
scope :in_category, ->(category) { where(category: category) }
# Zincirlenebilir scope'lar
scope :available, -> { active.in_stock }
# Joins ile scope
scope :with_recent_orders, -> {
joins(:order_items)
.where('order_items.created_at > ?', 30.days.ago)
.distinct
}
# Alt sorgu ile scope
scope :bestsellers, -> {
where(id: OrderItem.group(:product_id)
.order('COUNT(*) DESC')
.limit(10)
.select(:product_id))
}
end# app/queries/products_search_query.rb
# Karmaşık aramalar için Query Object
class ProductsSearchQuery
def initialize(relation = Product.all)
@relation = relation
end
def call(params)
@relation = filter_by_category(params[:category])
@relation = filter_by_price_range(params[:min_price], params[:max_price])
@relation = filter_by_search(params[:q])
@relation = apply_sorting(params[:sort])
@relation
end
private
def filter_by_category(category)
return @relation if category.blank?
@relation.where(category_id: category)
end
def filter_by_price_range(min, max)
@relation = @relation.where('price >= ?', min) if min.present?
@relation = @relation.where('price <= ?', max) if max.present?
@relation
end
def filter_by_search(query)
return @relation if query.blank?
@relation.where('name ILIKE ? OR description ILIKE ?',
"%#{query}%", "%#{query}%")
end
def apply_sorting(sort)
case sort
when 'price_asc' then @relation.order(price: :asc)
when 'price_desc' then @relation.order(price: :desc)
when 'newest' then @relation.order(created_at: :desc)
else @relation.order(:name)
end
end
end
# Controller'da kullanım
@products = ProductsSearchQuery.new(Product.active).call(params)Scope'lar basit, yeniden kullanılabilir koşullar için mükemmeldir. Query Object'ler birden fazla isteğe bağlı filtre ve kompozisyon mantığı içeren karmaşık aramalar için uygundur.
Ruby on Rails mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Routing ve Controller'lar
Soru 6: Rails'te RESTful routing nasıl çalışır?
Rails, HTTP fiillerini CRUD eylemlerine eşleyen RESTful rotaları teşvik eder. Router URL'leri belirli controller çağrılarına çevirir.
# config/routes.rb
Rails.application.routes.draw do
# Standart RESTful rotalar (7 eylem)
resources :articles do
# İç içe rotalar
resources :comments, only: [:create, :destroy]
# Member rotalar (bir örnek üzerinde işlem yapar)
member do
post :publish
delete :archive
end
# Collection rotalar (koleksiyon üzerinde işlem yapar)
collection do
get :drafts
get :search
end
end
# Namespace ile API rotaları
namespace :api do
namespace :v1 do
resources :products, only: [:index, :show, :create, :update] do
resources :reviews, shallow: true
end
end
end
# Özel rota
get 'dashboard', to: 'dashboard#index'
# Rota kısıtlamaları
constraints(SubdomainConstraint.new) do
resources :admin_settings
end
# Kök rotası
root 'home#index'
end# rails routes - Üretilen tüm rotaları gösterir
#
# Verb URI Pattern Controller#Action
# GET /articles articles#index
# POST /articles articles#create
# GET /articles/new articles#new
# GET /articles/:id/edit articles#edit
# GET /articles/:id articles#show
# PATCH /articles/:id articles#update
# DELETE /articles/:id articles#destroy
# POST /articles/:id/publish articles#publish
# GET /articles/drafts articles#draftsÜretilen rota yardımcıları (article_path(@article), new_article_path) URL'lere dinamik ve sürdürülebilir şekilde başvurmayı sağlar.
Soru 7: Controller'larda callback'leri ve filtreleri açıklayın
Callback'ler (before_action, after_action, around_action) controller eylemlerinden önce, sonra veya etrafında kod çalıştırmaya olanak tanır.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# CSRF koruması varsayılan olarak etkin
protect_from_forgery with: :exception
# Kimlik doğrulama için global callback
before_action :authenticate_user!
# Genel hata yönetimi
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActionController::ParameterMissing, with: :bad_request
private
def not_found
render json: { error: 'Kaynak bulunamadı' }, status: :not_found
end
def bad_request(exception)
render json: { error: exception.message }, status: :bad_request
end
end# app/controllers/admin/products_controller.rb
class Admin::ProductsController < ApplicationController
# Seçeneklerle callback'ler
before_action :require_admin
before_action :set_product, only: [:show, :edit, :update, :destroy]
after_action :log_activity, only: [:create, :update, :destroy]
# Koşullu callback
before_action :check_stock, only: [:update], if: :stock_changed?
def create
@product = Product.new(product_params)
if @product.save
redirect_to [:admin, @product], notice: 'Ürün oluşturuldu.'
else
render :new, status: :unprocessable_entity
end
end
def update
if @product.update(product_params)
redirect_to [:admin, @product], notice: 'Ürün güncellendi.'
else
render :edit, status: :unprocessable_entity
end
end
private
def require_admin
redirect_to root_path unless current_user&.admin?
end
def set_product
@product = Product.find(params[:id])
end
def stock_changed?
params[:product][:stock_quantity].present?
end
def log_activity
ActivityLog.create!(
user: current_user,
action: action_name,
resource: @product
)
end
def product_params
params.require(:product).permit(:name, :price, :description, :stock_quantity)
end
endCallback'ler bildirim sırasına göre çalışır. Miras alınan callback'leri devre dışı bırakmak için alt sınıflarda skip_before_action kullanın. Çok fazla iş mantığı içeren callback'lerden kaçının: Service Object'leri tercih edin.
Servisler ve mimari
Soru 8: Rails'te Service Object'leri nasıl uygulanır?
Service Object'ler Modellere veya Controller'lara ait olmayan karmaşık iş mantığını kapsüller. Test edilebilirliği artırır ve tek sorumluluk ilkesine uyar.
# app/services/order_processor.rb
# Standart arayüze sahip Service Object
class OrderProcessor
def initialize(order, payment_method:)
@order = order
@payment_method = payment_method
end
def call
return failure('Sipariş zaten işlendi') if @order.processed?
ActiveRecord::Base.transaction do
validate_stock!
process_payment!
update_inventory!
send_confirmation!
@order.update!(status: 'completed', processed_at: Time.current)
end
success(@order)
rescue PaymentError => e
failure("Ödeme başarısız: #{e.message}")
rescue InsufficientStockError => e
failure("Stok yetersiz: #{e.message}")
end
private
def validate_stock!
@order.items.each do |item|
unless item.product.stock_quantity >= item.quantity
raise InsufficientStockError, item.product.name
end
end
end
def process_payment!
result = PaymentGateway.charge(
amount: @order.total,
method: @payment_method,
description: "Sipariş ##{@order.id}"
)
raise PaymentError, result.error unless result.success?
@order.update!(payment_reference: result.transaction_id)
end
def update_inventory!
@order.items.each do |item|
item.product.decrement!(:stock_quantity, item.quantity)
end
end
def send_confirmation!
OrderMailer.confirmation(@order).deliver_later
end
def success(data)
Result.new(success: true, data: data)
end
def failure(error)
Result.new(success: false, error: error)
end
Result = Struct.new(:success, :data, :error, keyword_init: true) do
def success? = success
def failure? = !success
end
end# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def create
@order = current_user.orders.build(order_params)
if @order.save
result = OrderProcessor.new(@order, payment_method: params[:payment_method]).call
if result.success?
redirect_to @order, notice: 'Sipariş onaylandı!'
else
@order.update!(status: 'payment_failed')
flash.now[:alert] = result.error
render :new, status: :unprocessable_entity
end
else
render :new, status: :unprocessable_entity
end
end
endService Object kalıbı basit bir kuralı izler: bir sınıf, bir sorumluluk, bir genel call metodu. Bir Result nesnesi döndürmek başarı ve başarısızlığı temiz biçimde ele almayı sağlar.
Soru 9: Rails'te Concern'leri açıklayın
Concern'ler Modeller veya Controller'lar arasında kod çıkarmayı ve paylaşmayı sağlar. Temiz bir dahil etme sözdizimi için ActiveSupport::Concern kullanırlar.
# app/models/concerns/sluggable.rb
# Slug üretmek için yeniden kullanılabilir Concern
module Sluggable
extend ActiveSupport::Concern
included do
# Dahil edildiğinde çalışan kod
before_validation :generate_slug, if: :should_generate_slug?
validates :slug, presence: true, uniqueness: true
end
# Sınıf metotları
class_methods do
def find_by_slug!(slug)
find_by!(slug: slug)
end
def sluggable_source(column = :title)
@sluggable_source = column
end
def sluggable_source_column
@sluggable_source || :title
end
end
# Örnek metotları
def to_param
slug
end
private
def should_generate_slug?
slug.blank? || send("#{self.class.sluggable_source_column}_changed?")
end
def generate_slug
source = send(self.class.sluggable_source_column)
return if source.blank?
base_slug = source.parameterize
self.slug = unique_slug(base_slug)
end
def unique_slug(base)
slug = base
counter = 1
while self.class.where(slug: slug).where.not(id: id).exists?
slug = "#{base}-#{counter}"
counter += 1
end
slug
end
end# app/models/article.rb
class Article < ApplicationRecord
include Sluggable
sluggable_source :title # Opsiyonel, varsayılan :title
end
# app/models/product.rb
class Product < ApplicationRecord
include Sluggable
sluggable_source :name
end# app/controllers/concerns/pagination.rb
# Controller'lar için Concern
module Pagination
extend ActiveSupport::Concern
included do
helper_method :page_param, :per_page_param
end
private
def paginate(relation)
relation.page(page_param).per(per_page_param)
end
def page_param
params[:page]&.to_i || 1
end
def per_page_param
[params[:per_page]&.to_i || 25, 100].min
end
endConcern'ler gerçekten paylaşılan kod için kullanışlıdır. Bir Modeli yalnızca "kısaltmak" için Concern oluşturmaktan kaçının: bu, karmaşıklığı azaltmadan gizler.
RSpec ile test
Soru 10: Rails'te RSpec testleri nasıl yapılandırılır?
RSpec, Rails için standart test çerçevesidir. İyi bir test yapısı Model spec'leri, Controller spec'leri, Service spec'leri ve entegrasyon testlerini içerir.
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
# FactoryBot ile fabrikalar
let(:user) { build(:user) }
let(:admin) { build(:user, :admin) }
describe 'validations' do
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
it 'e-posta formatını doğrular' do
user.email = 'invalid'
expect(user).not_to be_valid
expect(user.errors[:email]).to include('is invalid')
end
end
describe 'associations' do
it { is_expected.to have_many(:articles).dependent(:destroy) }
it { is_expected.to have_one(:profile) }
it { is_expected.to belong_to(:organization).optional }
end
describe '#full_name' do
it 'ad ve soyadı birleştirerek döndürür' do
user = build(:user, first_name: 'John', last_name: 'Doe')
expect(user.full_name).to eq('John Doe')
end
it 'eksik soyadını ele alır' do
user = build(:user, first_name: 'John', last_name: nil)
expect(user.full_name).to eq('John')
end
end
describe '.active' do
it 'yalnızca aktif kullanıcıları döndürür' do
active = create(:user, active: true)
inactive = create(:user, active: false)
expect(User.active).to include(active)
expect(User.active).not_to include(inactive)
end
end
end# spec/services/order_processor_spec.rb
require 'rails_helper'
RSpec.describe OrderProcessor do
let(:user) { create(:user) }
let(:product) { create(:product, stock_quantity: 10, price: 100) }
let(:order) { create(:order, user: user, items: [build(:order_item, product: product, quantity: 2)]) }
subject { described_class.new(order, payment_method: 'card') }
describe '#call' do
context 'sipariş geçerli olduğunda' do
before do
allow(PaymentGateway).to receive(:charge).and_return(
OpenStruct.new(success?: true, transaction_id: 'txn_123')
)
end
it 'siparişi başarıyla işler' do
result = subject.call
expect(result).to be_success
expect(order.reload.status).to eq('completed')
end
it 'ürün stoğunu azaltır' do
expect { subject.call }.to change { product.reload.stock_quantity }.by(-2)
end
it 'onay e-postası gönderir' do
expect { subject.call }
.to have_enqueued_mail(OrderMailer, :confirmation)
.with(order)
end
end
context 'ödeme başarısız olduğunda' do
before do
allow(PaymentGateway).to receive(:charge).and_return(
OpenStruct.new(success?: false, error: 'Card declined')
)
end
it 'başarısız sonuç döndürür' do
result = subject.call
expect(result).to be_failure
expect(result.error).to include('Card declined')
end
it 'sipariş durumunu güncellemez' do
expect { subject.call }.not_to change { order.reload.status }
end
end
end
end# spec/requests/api/v1/products_spec.rb
require 'rails_helper'
RSpec.describe 'API V1 Products', type: :request do
let(:user) { create(:user) }
let(:headers) { { 'Authorization' => "Bearer #{user.api_token}" } }
describe 'GET /api/v1/products' do
let!(:products) { create_list(:product, 3, :active) }
it 'ürün listesini döndürür' do
get '/api/v1/products', headers: headers
expect(response).to have_http_status(:ok)
expect(json_response['data'].size).to eq(3)
end
it 'kategoriye göre filtreler' do
category = create(:category)
categorized = create(:product, category: category)
get '/api/v1/products', params: { category_id: category.id }, headers: headers
expect(json_response['data'].map { |p| p['id'] }).to eq([categorized.id])
end
end
describe 'POST /api/v1/products' do
let(:valid_params) do
{ product: { name: 'Yeni Ürün', price: 99.99, category_id: create(:category).id } }
end
it 'yeni bir ürün oluşturur' do
expect {
post '/api/v1/products', params: valid_params, headers: headers
}.to change(Product, :count).by(1)
expect(response).to have_http_status(:created)
end
end
endİyi uygulamalar: veriler için let, metotlar/bağlamlar için describe, koşullar için context ve belirli iddialar için it kullanın. Her test tek bir şeyi test etmelidir.
Soru 11: FactoryBot ile fabrikaları nasıl kullanılır?
FactoryBot, test verilerini bildirimsel ve sürdürülebilir biçimde oluşturmaya olanak tanır. Fabrikalar statik fixture'ların yerini alır.
# spec/factories/users.rb
FactoryBot.define do
factory :user do
# Benzersizlik için sıralamalar
sequence(:email) { |n| "user#{n}@example.com" }
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
password { 'password123' }
confirmed_at { Time.current }
# Varyasyonlar için trait'ler
trait :admin do
role { 'admin' }
after(:create) do |user|
user.permissions.create!(name: 'admin_access')
end
end
trait :unconfirmed do
confirmed_at { nil }
end
trait :with_profile do
after(:create) do |user|
create(:profile, user: user)
end
end
trait :with_articles do
transient do
articles_count { 3 }
end
after(:create) do |user, evaluator|
create_list(:article, evaluator.articles_count, author: user)
end
end
# Miras alınan fabrika
factory :admin_user do
admin
with_profile
end
end
end# spec/factories/orders.rb
FactoryBot.define do
factory :order do
user
status { 'pending' }
trait :with_items do
transient do
items_count { 2 }
end
after(:create) do |order, evaluator|
create_list(:order_item, evaluator.items_count, order: order)
order.recalculate_total!
end
end
trait :completed do
status { 'completed' }
processed_at { Time.current }
with_items
end
trait :high_value do
after(:create) do |order|
create(:order_item, order: order, quantity: 10, unit_price: 500)
order.recalculate_total!
end
end
end
end# Testlerde kullanım
RSpec.describe OrderProcessor do
# build: kalıcı olmayan örnek
let(:user) { build(:user) }
# create: VT'ye kaydedilmiş
let(:order) { create(:order, :with_items, user: user) }
# create_list: birden fazla örnek
let(:products) { create_list(:product, 5) }
# Trait'leri birleştirme
let(:admin) { create(:user, :admin, :with_profile) }
# Nitelikleri geçersiz kılma
let(:expensive_order) { create(:order, :with_items, items_count: 10) }
# build_stubbed: daha hızlı, birim testleri için
let(:stubbed_user) { build_stubbed(:user) }
endKalıcılık gerekmediğinde create yerine build veya build_stubbed tercih edin: bu, testleri önemli ölçüde hızlandırır.
Arka plan işleri
Soru 12: Rails'te Active Job ve Sidekiq nasıl kullanılır?
Active Job, arka plan işleri için backend'den (Sidekiq, Resque vb.) bağımsız birleşik bir arayüz sunar. Sidekiq, Redis ile performansı sayesinde popüler tercihtir.
# app/jobs/process_order_job.rb
class ProcessOrderJob < ApplicationJob
queue_as :default
# Yeniden deneme yapılandırması
retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 10
discard_on ActiveJob::DeserializationError
# Sidekiq seçenekleri (Sidekiq backend ise)
sidekiq_options retry: 5, backtrace: true
def perform(order_id)
order = Order.find(order_id)
OrderProcessor.new(order).call
rescue ActiveRecord::RecordNotFound
# Sıraya alma ile yürütme arasında sipariş silinmiş
Rails.logger.warn("Order #{order_id} not found, skipping job")
end
end# app/jobs/batch_email_job.rb
class BatchEmailJob < ApplicationJob
queue_as :mailers
# Sidekiq Enterprise veya throttle gem ile hız sınırlama
sidekiq_options throttle: { threshold: 100, period: 1.minute }
def perform(user_ids, template_id)
template = EmailTemplate.find(template_id)
User.where(id: user_ids).find_each do |user|
UserMailer.custom_email(user, template).deliver_later
end
end
end# İşleri sıraya alma
# Anında
ProcessOrderJob.perform_later(order.id)
# Geciktirilmiş
ProcessOrderJob.set(wait: 5.minutes).perform_later(order.id)
# Belirli bir saatte
ProcessOrderJob.set(wait_until: Date.tomorrow.noon).perform_later(order.id)
# Belirli kuyruk
ProcessOrderJob.set(queue: :critical).perform_later(order.id)
# Senkron (test veya hata ayıklama için)
ProcessOrderJob.perform_now(order.id)# config/sidekiq.yml
:concurrency: 10
:queues:
- [critical, 3] # Yüksek öncelik, ağırlık 3
- [default, 2] # Orta öncelik, ağırlık 2
- [mailers, 1] # Düşük öncelik, ağırlık 1
- [low, 1]
:schedule:
cleanup_job:
cron: '0 3 * * *' # Her gün 03:00
class: CleanupJobActive Job backend'i soyutlar ancak belirli özelliklere (batch'ler, hız sınırlama) erişim genellikle seçilen backend'e bağımlılığı gerektirir.
Ruby on Rails mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
API geliştirme
Soru 13: Rails ile RESTful API nasıl oluşturulur?
Rails, API-only Controller'lar ve serializer'larla JSON API oluşturmayı kolaylaştırır. İyi bir API sürümlendirilmiş, belgelenmiş ve güvenlidir.
# app/controllers/api/v1/base_controller.rb
module Api
module V1
class BaseController < ActionController::API
include ActionController::HttpAuthentication::Token::ControllerMethods
before_action :authenticate_token!
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity
rescue_from ActionController::ParameterMissing, with: :bad_request
private
def authenticate_token!
authenticate_or_request_with_http_token do |token, options|
@current_user = User.find_by(api_token: token)
end
end
def current_user
@current_user
end
def not_found(exception)
render json: { error: 'Kaynak bulunamadı', details: exception.message },
status: :not_found
end
def unprocessable_entity(exception)
render json: { error: 'Doğrulama başarısız', details: exception.record.errors },
status: :unprocessable_entity
end
def bad_request(exception)
render json: { error: 'Geçersiz istek', details: exception.message },
status: :bad_request
end
end
end
end# app/controllers/api/v1/products_controller.rb
module Api
module V1
class ProductsController < BaseController
before_action :set_product, only: [:show, :update, :destroy]
def index
@products = Product.active
.includes(:category)
.page(params[:page])
.per(params[:per_page] || 20)
render json: {
data: ProductSerializer.new(@products).serializable_hash,
meta: pagination_meta(@products)
}
end
def show
render json: ProductSerializer.new(@product, include: [:category, :reviews])
end
def create
@product = Product.new(product_params)
@product.save!
render json: ProductSerializer.new(@product), status: :created
end
def update
@product.update!(product_params)
render json: ProductSerializer.new(@product)
end
def destroy
@product.destroy!
head :no_content
end
private
def set_product
@product = Product.find(params[:id])
end
def product_params
params.require(:product).permit(:name, :description, :price, :category_id)
end
def pagination_meta(collection)
{
current_page: collection.current_page,
total_pages: collection.total_pages,
total_count: collection.total_count
}
end
end
end
end# app/serializers/product_serializer.rb
# jsonapi-serializer gem ile
class ProductSerializer
include JSONAPI::Serializer
attributes :id, :name, :description, :price, :created_at
attribute :formatted_price do |product|
"$#{product.price.to_f.round(2)}"
end
belongs_to :category
has_many :reviews
link :self do |product|
Rails.application.routes.url_helpers.api_v1_product_url(product)
end
endAPI iyi uygulamaları: namespace ile sürümlendir, uygun HTTP kodlarını kullan, koleksiyonları sayfalandır ve net hata mesajları sun.
Soru 14: Rails'te JWT kimlik doğrulaması nasıl uygulanır?
JWT (JSON Web Tokens), API'ler için popüler bir durumsuz kimlik doğrulama yöntemidir. Token, kullanıcı kimliğini ve geçerliliğini kodlar.
# app/services/jwt_service.rb
class JwtService
SECRET_KEY = Rails.application.credentials.secret_key_base
ALGORITHM = 'HS256'.freeze
class << self
def encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
payload[:iat] = Time.current.to_i
JWT.encode(payload, SECRET_KEY, ALGORITHM)
end
def decode(token)
decoded = JWT.decode(token, SECRET_KEY, true, algorithm: ALGORITHM)
HashWithIndifferentAccess.new(decoded.first)
rescue JWT::ExpiredSignature
raise AuthenticationError, 'Token süresi doldu'
rescue JWT::DecodeError
raise AuthenticationError, 'Geçersiz token'
end
end
end# app/controllers/api/v1/auth_controller.rb
module Api
module V1
class AuthController < ActionController::API
def login
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
token = JwtService.encode(user_id: user.id)
render json: {
token: token,
user: UserSerializer.new(user),
expires_at: 24.hours.from_now
}
else
render json: { error: 'Geçersiz kimlik bilgileri' }, status: :unauthorized
end
end
def refresh
token = JwtService.encode(user_id: current_user.id)
render json: { token: token, expires_at: 24.hours.from_now }
end
end
end
end# app/controllers/concerns/jwt_authenticatable.rb
module JwtAuthenticatable
extend ActiveSupport::Concern
included do
before_action :authenticate_jwt!
end
private
def authenticate_jwt!
header = request.headers['Authorization']
token = header&.split(' ')&.last
raise AuthenticationError, 'Token eksik' unless token
decoded = JwtService.decode(token)
@current_user = User.find(decoded[:user_id])
rescue AuthenticationError => e
render json: { error: e.message }, status: :unauthorized
rescue ActiveRecord::RecordNotFound
render json: { error: 'Kullanıcı bulunamadı' }, status: :unauthorized
end
def current_user
@current_user
end
endÜretim için göz önünde bulundurun: refresh token'lar, çıkış sonrasında token'ları kara listeye alma ve kısa son kullanma süreleri. devise-jwt gibi gem'ler uygulamayı basitleştirir.
Önbellekleme ve performans
Soru 15: Rails'te önbelleklemeyi nasıl uygularız?
Rails çeşitli önbellekleme seviyeleri sunar: fragment caching, Russian Doll caching, low-level caching. Seçim kullanım durumuna bağlıdır.
# config/environments/production.rb
config.action_controller.perform_caching = true
config.cache_store = :redis_cache_store, {
url: ENV['REDIS_URL'],
namespace: 'myapp:cache',
expires_in: 1.day,
race_condition_ttl: 10.seconds
}<%# app/views/products/index.html.erb %>
<%# Otomatik önbellek anahtarıyla fragment caching %>
<% @products.each do |product| %>
<%# Ürünün updated_at'ine dayalı önbellek %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
<%# Russian Doll caching - iç içe önbellek %>
<% cache ['v1', @category] do %>
<h2><%= @category.name %></h2>
<% @category.products.each do |product| %>
<% cache ['v1', product] do %>
<%= render product %>
<% end %>
<% end %>
<% end %>
<%# Koşullu önbellek %>
<% cache_if current_user.nil?, @product do %>
<%= render @product %>
<% end %># app/models/product.rb
class Product < ApplicationRecord
# Russian Doll önbelleğini geçersiz kılmak için ebeveyne touch
belongs_to :category, touch: true
# Özel önbellek anahtarı
def cache_key_with_version
"#{super}/#{reviews.maximum(:updated_at)&.to_i}"
end
end# Servislerde low-level caching
class DashboardStatsService
def call
Rails.cache.fetch('dashboard:stats', expires_in: 15.minutes) do
{
total_users: User.count,
active_users: User.where('last_sign_in_at > ?', 30.days.ago).count,
total_orders: Order.completed.count,
revenue_mtd: Order.completed.where(created_at: Time.current.beginning_of_month..).sum(:total)
}
end
end
end
# Race condition korumalı önbellek
Rails.cache.fetch('popular_products', expires_in: 1.hour, race_condition_ttl: 10.seconds) do
Product.bestsellers.limit(10).to_a
end
# Açık geçersiz kılma
Rails.cache.delete('dashboard:stats')
Rails.cache.delete_matched('products:*')Russian Doll caching etkilidir çünkü yalnızca değiştirilen parçalar yeniden üretilir. Geçersizliği yaymak için ilişkilerde touch: true kullanın.
Soru 16: Bir Rails uygulamasının performansı nasıl optimize edilir?
Rails optimizasyonu birden çok yönü kapsar: VT sorguları, önbellekleme, asset'ler ve mimari. İzleme ile metodik bir yaklaşım esastır.
# Veritabanı optimizasyonu
# config/database.yml
production:
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
prepared_statements: true
advisory_locks: true
# app/models/order.rb
class Order < ApplicationRecord
# Sık sorgular için bileşik indeksler
# add_index :orders, [:user_id, :status, :created_at]
# Yalnızca gerekli sütunları seç
scope :summary, -> { select(:id, :status, :total, :created_at) }
# Büyük hacimler için toplu işleme
def self.process_pending
pending.find_each(batch_size: 1000) do |order|
ProcessOrderJob.perform_later(order.id)
end
end
# Tekrarlayan hesaplamalardan kaçınma
def self.revenue_by_month
completed
.group("DATE_TRUNC('month', created_at)")
.sum(:total)
end
end# Bellek optimizasyonu
# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
preload_app!
before_fork do
ActiveRecord::Base.connection_pool.disconnect!
end
on_worker_boot do
ActiveRecord::Base.establish_connection
end# rack-mini-profiler ile profil oluşturma
# Gemfile
group :development do
gem 'rack-mini-profiler'
gem 'memory_profiler'
gem 'stackprof'
end
# config/initializers/mini_profiler.rb
if defined?(Rack::MiniProfiler)
Rack::MiniProfiler.config.position = 'bottom-right'
Rack::MiniProfiler.config.start_hidden = true
end# Tembel yükleme ve sayfalama
class ProductsController < ApplicationController
def index
@products = Product.active
.includes(:category, :primary_image)
.page(params[:page])
.per(24)
# Sonraki sayfa için ön yükleme
if @products.next_page
Rails.cache.fetch("products:page:#{@products.next_page}", expires_in: 5.minutes) do
Product.active.page(@products.next_page).per(24).to_a
end
end
end
endTemel araçlar: profil oluşturmak için rack-mini-profiler, N+1 tespiti için bullet, üretim izleme için New Relic veya Scout.
Güvenlik
Soru 17: Rails'te güvenlik için en iyi uygulamalar nelerdir?
Rails, yaygın güvenlik açıklarına karşı varsayılan korumalar içerir. Bu korumaları anlamak ve doğru yapılandırmak çok önemlidir.
# CSRF koruması
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Varsayılan olarak etkin, geçersiz token'da exception fırlatır
protect_from_forgery with: :exception
# API'ler için :null_session kullanın
# protect_from_forgery with: :null_session
end
# View'larda token formlara otomatik olarak eklenir
# <%= form_with ... %> authenticity_token içerir
# AJAX istekleri için
# csrf_meta_tags değeriyle X-CSRF-Token header'ı ekleyin# SQL Injection önleme
# ✅ Enterpole edilmiş parametreler otomatik olarak escape edilir
User.where('email = ?', params[:email])
User.where(email: params[:email])
# ❌ TEHLİKE - Doğrudan interpolasyon
User.where("email = '#{params[:email]}'")
# ✅ Dinamik ORDER yan tümceleri için
ALLOWED_SORTS = %w[name created_at price].freeze
sort_column = ALLOWED_SORTS.include?(params[:sort]) ? params[:sort] : 'name'
Product.order(sort_column)# XSS koruması
# Rails view'larda HTML'i otomatik olarak escape eder
# ✅ Otomatik escape
<%= user.name %>
# ❌ Tehlikeli - escape edilmemiş içerik
<%== user.bio %>
<%= raw user.bio %>
<%= user.bio.html_safe %>
# ✅ Güvenli HTML için sanitize kullanın
<%= sanitize user.bio, tags: %w[p br strong em] %># Strong Parameters
class UsersController < ApplicationController
def update
@user.update!(user_params)
end
private
def user_params
# İzin verilen niteliklerin açık beyaz listesi
params.require(:user).permit(:name, :email, :avatar)
# Yalnızca yöneticiler için
if current_user.admin?
params.require(:user).permit(:name, :email, :role, :active)
else
params.require(:user).permit(:name, :email)
end
end
end# Güvenlik header'ları
# config/initializers/secure_headers.rb
Rails.application.config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '1; mode=block',
'X-Content-Type-Options' => 'nosniff',
'X-Download-Options' => 'noopen',
'X-Permitted-Cross-Domain-Policies' => 'none',
'Referrer-Policy' => 'strict-origin-when-cross-origin'
}
# Content Security Policy
Rails.application.config.content_security_policy do |policy|
policy.default_src :self
policy.script_src :self
policy.style_src :self, :unsafe_inline
policy.img_src :self, :data, 'https:'
endbrakeman (statik güvenlik analizi) ile düzenli denetim yapın ve bundle audit ile gem'leri güncel tutun.
Soru 18: Rails'te kimlik doğrulama ve yetkilendirme nasıl ele alınır?
Kimlik doğrulama kimliği doğrular, yetkilendirme izinleri kontrol eder. Devise auth'u, Pundit veya CanCanCan yetkilendirmeyi yönetir.
# Devise kurulumu
# app/models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:confirmable, :lockable, :trackable
enum role: { user: 0, moderator: 1, admin: 2 }
def admin?
role == 'admin'
end
end# Pundit politikaları
# app/policies/article_policy.rb
class ArticlePolicy < ApplicationPolicy
def index?
true
end
def show?
record.published? || owner_or_admin?
end
def create?
user.present?
end
def update?
owner_or_admin?
end
def destroy?
owner_or_admin?
end
def publish?
user&.admin? || user&.moderator?
end
# Koleksiyonlar için scope
class Scope < Scope
def resolve
if user&.admin?
scope.all
elsif user
scope.where(published: true).or(scope.where(author: user))
else
scope.where(published: true)
end
end
end
private
def owner_or_admin?
user&.admin? || record.author == user
end
end# Pundit ile controller
class ArticlesController < ApplicationController
include Pundit::Authorization
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
def index
@articles = policy_scope(Article).includes(:author).page(params[:page])
end
def show
@article = Article.find(params[:id])
authorize @article
end
def update
@article = Article.find(params[:id])
authorize @article
if @article.update(article_params)
redirect_to @article, notice: 'Makale güncellendi.'
else
render :edit, status: :unprocessable_entity
end
end
def publish
@article = Article.find(params[:id])
authorize @article
@article.update!(published: true, published_at: Time.current)
redirect_to @article, notice: 'Makale yayınlandı.'
end
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = "Bu eylemi gerçekleştirme yetkiniz yok."
redirect_back(fallback_location: root_path)
end
endPundit, CanCanCan'a göre daha açık ve test edilebilirdir. Her eylemin karşılık gelen bir politika metodu vardır ve scope'lar koleksiyonları otomatik olarak filtreler.
İleri düzey Rails
Soru 19: Rails'te Repository desenini açıklayın
Repository deseni veri erişim mantığını uygulamanın geri kalanından izole eder. Rails Active Record kullansa da (farklı bir desen), Repository karmaşık durumlarda yararlı olabilir.
# app/repositories/base_repository.rb
class BaseRepository
def initialize(model_class)
@model_class = model_class
end
def all
@model_class.all
end
def find(id)
@model_class.find(id)
end
def find_by(attributes)
@model_class.find_by(attributes)
end
def create(attributes)
@model_class.create(attributes)
end
def update(record, attributes)
record.update(attributes)
end
def delete(record)
record.destroy
end
end# app/repositories/product_repository.rb
class ProductRepository < BaseRepository
def initialize
super(Product)
end
def active
@model_class.where(active: true)
end
def in_category(category_id)
@model_class.where(category_id: category_id)
end
def search(query)
@model_class.where('name ILIKE ? OR description ILIKE ?',
"%#{query}%", "%#{query}%")
end
def with_stock
@model_class.where('stock_quantity > 0')
end
def bestsellers(limit: 10)
@model_class
.joins(:order_items)
.group(:id)
.order('COUNT(order_items.id) DESC')
.limit(limit)
end
def for_homepage
active
.with_stock
.includes(:category, :primary_image)
.order(featured: :desc, created_at: :desc)
.limit(12)
end
end# Bir serviste kullanım
class ProductSearchService
def initialize(repository: ProductRepository.new)
@repository = repository
end
def call(params)
products = @repository.active
products = products.in_category(params[:category]) if params[:category]
products = products.search(params[:query]) if params[:query].present?
products = products.with_stock if params[:in_stock]
products
end
end
# Mock'larla test etmeyi kolaylaştırır
RSpec.describe ProductSearchService do
let(:repository) { instance_double(ProductRepository) }
let(:service) { described_class.new(repository: repository) }
it 'kategoriye göre filtreler' do
products = double('products')
allow(repository).to receive(:active).and_return(products)
allow(products).to receive(:in_category).with(1).and_return(products)
service.call(category: 1)
expect(products).to have_received(:in_category).with(1)
end
endActive Record zaten harika bir desen olduğu için Repository Rails'te isteğe bağlıdır. Karmaşık sorgular veya depolama izolasyonu önemli olduğunda kullanın.
Soru 20: Rails'te CQRS deseni nasıl uygulanır?
CQRS (Command Query Responsibility Segregation) okuma ve yazma işlemlerini ayırır. Rails'te bu, query'ler ve command'lar için ayrı sınıflara dönüşür.
# app/commands/base_command.rb
class BaseCommand
include ActiveModel::Validations
def self.call(*args)
new(*args).call
end
def call
return failure(errors) unless valid?
execute
end
private
def execute
raise NotImplementedError
end
def success(data = nil)
CommandResult.success(data)
end
def failure(errors)
CommandResult.failure(errors)
end
end
CommandResult = Struct.new(:success, :data, :errors, keyword_init: true) do
def success? = success
def failure? = !success
def self.success(data)
new(success: true, data: data, errors: [])
end
def self.failure(errors)
new(success: false, data: nil, errors: Array(errors))
end
end# app/commands/orders/create_order_command.rb
module Orders
class CreateOrderCommand < BaseCommand
attr_reader :user, :items, :shipping_address
validates :user, presence: true
validates :items, presence: true
validate :validate_items_availability
def initialize(user:, items:, shipping_address:)
@user = user
@items = items
@shipping_address = shipping_address
end
private
def execute
order = nil
ActiveRecord::Base.transaction do
order = Order.create!(
user: user,
shipping_address: shipping_address,
status: 'pending'
)
items.each do |item|
order.items.create!(
product_id: item[:product_id],
quantity: item[:quantity],
unit_price: Product.find(item[:product_id]).price
)
end
order.calculate_total!
end
OrderCreatedEvent.broadcast(order)
success(order)
rescue ActiveRecord::RecordInvalid => e
failure(e.message)
end
def validate_items_availability
items.each do |item|
product = Product.find_by(id: item[:product_id])
unless product&.stock_quantity&.>= item[:quantity]
errors.add(:items, "Ürün #{item[:product_id]} mevcut değil")
end
end
end
end
end# app/queries/orders/user_orders_query.rb
module Orders
class UserOrdersQuery
def initialize(user, params = {})
@user = user
@params = params
end
def call
orders = @user.orders.includes(:items, items: :product)
orders = apply_status_filter(orders)
orders = apply_date_filter(orders)
orders = apply_sorting(orders)
orders.page(@params[:page]).per(@params[:per_page] || 20)
end
private
def apply_status_filter(orders)
return orders unless @params[:status]
orders.where(status: @params[:status])
end
def apply_date_filter(orders)
orders = orders.where('created_at >= ?', @params[:from]) if @params[:from]
orders = orders.where('created_at <= ?', @params[:to]) if @params[:to]
orders
end
def apply_sorting(orders)
case @params[:sort]
when 'oldest' then orders.order(created_at: :asc)
when 'total_desc' then orders.order(total: :desc)
else orders.order(created_at: :desc)
end
end
end
end# CQRS kullanan controller
class OrdersController < ApplicationController
def index
@orders = Orders::UserOrdersQuery.new(current_user, filter_params).call
end
def create
result = Orders::CreateOrderCommand.call(
user: current_user,
items: order_params[:items],
shipping_address: order_params[:shipping_address]
)
if result.success?
redirect_to result.data, notice: 'Sipariş oluşturuldu!'
else
flash.now[:alert] = result.errors.join(', ')
render :new, status: :unprocessable_entity
end
end
endCQRS, asimetrik okuma/yazma ihtiyaçları olan karmaşık uygulamalarda parlar. Basit CRUD için aşırı mühendisliktir.
Soru 21: Action Cable ile WebSocket nasıl ele alınır?
Action Cable, gerçek zamanlı çift yönlü iletişim için Rails'e WebSocket'leri entegre eder. Sunucular arası senkronizasyon için Redis kullanır.
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
# Oturum çerezi üzerinden
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
# API'ler için JWT üzerinden
elsif verified_user = verify_jwt_token
verified_user
else
reject_unauthorized_connection
end
end
def verify_jwt_token
token = request.params[:token]
return nil unless token
decoded = JwtService.decode(token)
User.find(decoded[:user_id])
rescue
nil
end
end
end# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
@room = ChatRoom.find(params[:room_id])
# İzinleri kontrol et
unless @room.accessible_by?(current_user)
reject
return
end
stream_for @room
# Diğerlerine varlığı bildir
broadcast_presence(:joined)
end
def unsubscribed
broadcast_presence(:left) if @room
end
def send_message(data)
message = @room.messages.create!(
user: current_user,
content: data['content']
)
# Tüm abonelere yayınla
ChatChannel.broadcast_to(@room, {
type: 'message',
message: MessageSerializer.new(message).as_json
})
end
def typing
ChatChannel.broadcast_to(@room, {
type: 'typing',
user: current_user.name
})
end
private
def broadcast_presence(action)
ChatChannel.broadcast_to(@room, {
type: 'presence',
action: action,
user: current_user.name,
online_count: @room.online_users_count
})
end
endimport consumer from "./consumer"
const chatChannel = consumer.subscriptions.create(
{ channel: "ChatChannel", room_id: roomId },
{
connected() {
console.log("Connected to chat")
},
disconnected() {
console.log("Disconnected from chat")
},
received(data) {
switch(data.type) {
case 'message':
this.appendMessage(data.message)
break
case 'typing':
this.showTypingIndicator(data.user)
break
case 'presence':
this.updatePresence(data)
break
}
},
sendMessage(content) {
this.perform('send_message', { content: content })
},
notifyTyping() {
this.perform('typing')
}
}
)Action Cable yeniden bağlanmaları ve senkronizasyonu otomatik olarak yönetir. Üretimde Redis'i adapter olarak yapılandırın ve eşzamanlı bağlantılara göre ölçeklendirin.
Soru 22: Rails'te multi-tenancy nasıl uygulanır?
Multi-tenancy, bir uygulamanın birden fazla izole müşteriye (tenant) hizmet vermesini sağlar. Üç ana yaklaşım: veritabanı, şema veya satır düzeyi.
# ActsAsTenant veya manuel ile satır düzeyi multitenancy
# app/models/concerns/tenant_scoped.rb
module TenantScoped
extend ActiveSupport::Concern
included do
belongs_to :tenant
# Geçerli tenant'a varsayılan scope
default_scope -> { where(tenant: Current.tenant) if Current.tenant }
# Tenant doğrulama
before_validation :set_tenant, on: :create
end
private
def set_tenant
self.tenant ||= Current.tenant
end
end
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :tenant, :user
end# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_current_tenant
private
def set_current_tenant
Current.tenant = resolve_tenant
Current.user = current_user
end
def resolve_tenant
# Alt alan adı üzerinden
if request.subdomain.present? && request.subdomain != 'www'
Tenant.find_by!(subdomain: request.subdomain)
# Header üzerinden (API'ler için)
elsif request.headers['X-Tenant-ID'].present?
Tenant.find(request.headers['X-Tenant-ID'])
# Kullanıcı üzerinden
elsif current_user
current_user.tenant
end
rescue ActiveRecord::RecordNotFound
redirect_to root_url(subdomain: 'www'), alert: 'Tenant bulunamadı'
end
end# app/models/project.rb
class Project < ApplicationRecord
include TenantScoped
has_many :tasks
belongs_to :owner, class_name: 'User'
end
# app/models/user.rb
class User < ApplicationRecord
include TenantScoped
has_many :projects, foreign_key: :owner_id
# Yöneticiler birden fazla tenant'a ait olabilir
has_many :tenant_memberships
has_many :accessible_tenants, through: :tenant_memberships, source: :tenant
end# Apartment gem ile şema düzeyinde (PostgreSQL)
# config/initializers/apartment.rb
Apartment.configure do |config|
config.excluded_models = %w[Tenant User]
config.tenant_names = -> { Tenant.pluck(:subdomain) }
end
# Kullanım
Apartment::Tenant.switch('acme') do
# Bu bloktaki tüm sorgular 'acme' şemasını kullanır
Project.all # SELECT * FROM acme.projects
endSatır düzeyi en basit olanıdır ancak sızıntılara karşı sürekli dikkat gerektirir. Şema düzeyi daha iyi izolasyon sunar ancak migration'ları karmaşıklaştırır. Güvenlik ve ölçeklenebilirlik ihtiyaçlarına göre seçim yapın.
Soru 23: Rails ile mikroservis mimarisi nasıl kurulur?
Rails, HTTP/gRPC veya mesaj kuyrukları üzerinden iletişimle mikroservis mimarisinin temeli olarak hizmet edebilir. Anahtar, sınırları iyi tanımlamaktır.
# HTTP servis istemcisi
# app/services/payment_service_client.rb
class PaymentServiceClient
include HTTParty
base_uri ENV.fetch('PAYMENT_SERVICE_URL')
def initialize
@options = {
headers: {
'Content-Type' => 'application/json',
'X-Service-Token' => ENV.fetch('SERVICE_TOKEN')
},
timeout: 10
}
end
def create_charge(amount:, currency:, source:, metadata: {})
response = self.class.post('/charges', @options.merge(
body: { amount: amount, currency: currency, source: source, metadata: metadata }.to_json
))
handle_response(response)
end
def get_charge(charge_id)
response = self.class.get("/charges/#{charge_id}", @options)
handle_response(response)
end
private
def handle_response(response)
case response.code
when 200..299
ServiceResult.success(response.parsed_response)
when 400..499
ServiceResult.failure(response.parsed_response['error'], code: response.code)
else
ServiceResult.failure('Servis kullanılamıyor', code: response.code)
end
rescue Net::OpenTimeout, Net::ReadTimeout
ServiceResult.failure('Servis zaman aşımı')
end
end# Sidekiq/Redis ile olay tabanlı iletişim
# app/events/order_events.rb
module OrderEvents
class Created
include Wisper::Publisher
def call(order)
broadcast(:order_created, order)
end
end
end
# app/listeners/inventory_listener.rb
class InventoryListener
def order_created(order)
order.items.each do |item|
InventoryServiceClient.new.reserve_stock(
product_id: item.product_id,
quantity: item.quantity,
reference: order.id
)
end
end
end
# config/initializers/wisper.rb
Wisper.subscribe(InventoryListener.new, async: true)
Wisper.subscribe(NotificationListener.new, async: true)# API Gateway deseni
# app/controllers/api/v1/gateway_controller.rb
module Api
module V1
class GatewayController < BaseController
# Birden fazla servisi birleştir
def dashboard
results = Parallel.map([:orders, :inventory, :analytics], in_threads: 3) do |service|
fetch_from_service(service)
end
render json: {
orders: results[0],
inventory: results[1],
analytics: results[2]
}
end
private
def fetch_from_service(service)
case service
when :orders
OrderServiceClient.new.recent_orders(limit: 5)
when :inventory
InventoryServiceClient.new.low_stock_alerts
when :analytics
AnalyticsServiceClient.new.daily_summary
end
rescue => e
{ error: "#{service} kullanılamıyor", message: e.message }
end
end
end
endRails mikroservisleri için: net API sözleşmeleri (OpenAPI) tanımlayın, devre kesiciler uygulayın (gem circuitbox) ve dağıtık izleme kullanın (gem opentelemetry).
Soru 24: Bir Rails uygulaması üretime nasıl deploy edilir?
Modern Rails dağıtımı container'ları veya PaaS'ı kullanır. Sağlam bir üretim yapılandırması asset'leri, veritabanını ve izlemeyi kapsar.
# config/environments/production.rb
Rails.application.configure do
config.cache_classes = true
config.eager_load = true
config.consider_all_requests_local = false
# Asset'ler
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
config.assets.compile = false
config.assets.digest = true
# Logging
config.log_level = ENV.fetch('LOG_LEVEL', 'info').to_sym
config.log_tags = [:request_id]
config.logger = ActiveSupport::Logger.new(STDOUT)
.tap { |logger| logger.formatter = Logger::Formatter.new }
.then { |logger| ActiveSupport::TaggedLogging.new(logger) }
# Önbellek
config.cache_store = :redis_cache_store, {
url: ENV['REDIS_URL'],
expires_in: 1.day
}
# SSL'i zorla
config.force_ssl = true
config.ssl_options = { hsts: { subdomains: true } }
# Action Mailer
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: ENV['SMTP_HOST'],
port: ENV['SMTP_PORT'],
user_name: ENV['SMTP_USER'],
password: ENV['SMTP_PASSWORD'],
authentication: :plain,
enable_starttls_auto: true
}
end# Dockerfile
FROM ruby:3.3-alpine AS builder
RUN apk add --no-cache build-base postgresql-dev nodejs yarn
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true && \
bundle config set --local without 'development test' && \
bundle install
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN bundle exec rails assets:precompile
# Üretim imajı
FROM ruby:3.3-alpine
RUN apk add --no-cache postgresql-client tzdata
WORKDIR /app
COPY /app /app
COPY /usr/local/bundle /usr/local/bundle
ENV RAILS_ENV=production
ENV RAILS_LOG_TO_STDOUT=true
EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]# docker-compose.production.yml
version: '3.8'
services:
web:
build: .
environment:
- DATABASE_URL=postgres://user:pass@db/app_production
- REDIS_URL=redis://redis:6379/0
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
depends_on:
- db
- redis
deploy:
replicas: 3
resources:
limits:
memory: 512M
sidekiq:
build: .
command: bundle exec sidekiq
environment:
- DATABASE_URL=postgres://user:pass@db/app_production
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
deploy:
replicas: 2
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:Üretim kontrol listesi: zorunlu SSL, ENV ile sırlar, sağlık kontrolleri, otomatik VT yedekleri, izleme (APM + log + metrik) ve yapılandırılmış uyarılar.
Soru 25: Rails 7+'ta bilinmesi gereken yeni özellikler nelerdir?
Rails 7+ önemli değişiklikler getirir: varsayılan Hotwire, import map'ler, geliştirilmiş şifreli credentials ve birçok optimizasyon.
# Hotwire - Turbo Frame'ler
# app/views/articles/index.html.erb
<%= turbo_frame_tag "articles" do %>
<% @articles.each do |article| %>
<%= turbo_frame_tag dom_id(article) do %>
<%= render article %>
<% end %>
<% end %>
<%= link_to "Daha fazla yükle", articles_path(page: @page + 1),
data: { turbo_frame: "articles" } %>
<% end %>
# Gerçek zamanlı güncellemeler için Turbo Stream'ler
# app/controllers/comments_controller.rb
def create
@comment = @article.comments.create!(comment_params.merge(user: current_user))
respond_to do |format|
format.turbo_stream
format.html { redirect_to @article }
end
end
# app/views/comments/create.turbo_stream.erb
<%= turbo_stream.append "comments", @comment %>
<%= turbo_stream.update "comments_count", @article.comments.count %>
<%= turbo_stream.replace "comment_form", partial: "comments/form", locals: { comment: Comment.new } %># Stimulus controller'ları
# app/javascript/controllers/search_controller.js
import { Controller } from "@hotwired/stimulus"
import { debounce } from "lodash-es"
export default class extends Controller {
static targets = ["input", "results"]
static values = { url: String }
connect() {
this.search = debounce(this.search.bind(this), 300)
}
async search() {
const query = this.inputTarget.value
if (query.length < 2) return
const response = await fetch(`${this.urlValue}?q=${encodeURIComponent(query)}`)
this.resultsTarget.innerHTML = await response.text()
}
}# Import Map (JavaScript bundler olmadan)
# config/importmap.rb
pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers"
# CDN'den pin'ler
pin "lodash-es", to: "https://ga.jspm.io/npm:lodash-es@4.17.21/lodash.js"# Active Record Encryption (Rails 7+)
# app/models/user.rb
class User < ApplicationRecord
encrypts :email, deterministic: true # Aramaya izin verir
encrypts :phone_number # Varsayılan olarak deterministik değil
encrypts :ssn, deterministic: true, downcase: true
end
# config/credentials.yml.enc
active_record_encryption:
primary_key: abc123...
deterministic_key: def456...
key_derivation_salt: ghi789...# Sorgu arayüzünde iyileştirmeler
# Rails 7.1+
# Asenkron sorgular
users = User.where(active: true).load_async
# Sorgu çalışırken işlemeye devam et
# Sonuçlara users.to_a ile erişin
# Common Table Expressions (CTE)
User.with(
recent_orders: Order.where('created_at > ?', 30.days.ago)
).joins('JOIN recent_orders ON recent_orders.user_id = users.id')
# Otomatik inverse_of tespiti
class Author < ApplicationRecord
has_many :books # inverse_of otomatik tespit edilir
end
# Varsayılan strict loading (N+1'i önler)
class ApplicationRecord < ActiveRecord::Base
self.strict_loading_by_default = true
endRails 7+ basitliği (varsayılan olarak Webpack yok) ve Hotwire ile HTML-over-the-wire'ı tercih eder. Bu yaklaşım, modern bir kullanıcı deneyimi sunarken JavaScript karmaşıklığını azaltır.
Sonuç
Ruby on Rails mülakatları, framework'ün tamamına hakimiyeti ve sözleşmelerinin anlaşılmasını değerlendirir. Hatırlanması gereken temel noktalar:
✅ Temeller: MVC, Active Record, migration'lar, doğrulamalar ve ilişkiler
✅ Mimari: Service Object'ler, Concern'ler, Query Object'ler ve CQRS desenleri
✅ Performans: N+1 sorguları, caching (fragment, Russian Doll, low-level), eager loading
✅ Test: RSpec, FactoryBot, request spec'ler ve test iyi uygulamaları
✅ Güvenlik: CSRF, SQL injection, XSS, Strong Parameters ve kimlik doğrulama/yetkilendirme
✅ API'ler: RESTful tasarım, JWT, serializer'lar ve sürümlendirme
✅ Üretim: arka plan işleri, WebSocket'ler, dağıtım ve izleme
Rails felsefesi (Convention over Configuration, DRY ve Rails Way) tüm mimari kararlara rehberlik eder. Bu prensiplere hakim olmak ve ne zaman onlardan ayrılınacağını bilmek sağlam bir uzmanlığı gösterir.
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Etiketler
Paylaş
İlgili makaleler

2026'da Rails API Modu: RESTful API, Serializasyon ve Mulakat Sorulari
Rails API-only modu ile production-ready RESTful API gelistirme: Alba ve jsonapi-serializer ile JSON serializasyonu, JWT authentication, hata yonetimi ve RSpec testleri.

ActiveRecord: Ruby on Rails'te N+1 Sorgu Sorunlarını Çözmek
Rails'te ActiveRecord ile N+1 sorgularını tespit etme ve çözme rehberi. includes, preload, eager_load ve otomatik tespit araçlarına hâkim olun.

Ruby on Rails 7: Reaktif Uygulamalar icin Hotwire ve Turbo
Rails 7'de Hotwire ve Turbo icin kapsamli rehber. Turbo Drive, Frames ve Streams ile JavaScript yazmadan reaktif uygulamalar gelistirme.