# 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. - Published: 2026-02-03 - Updated: 2026-04-27 - Author: Anthony Fillion-Maillet - Tags: ruby on rails, ruby, mulakat, active record, teknik mulakat - Reading time: 22 min --- 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. > **Mülakat ipucu** > > İş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. ```ruby # 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 ``` ```ruby # 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 ``` ```erb <%# app/views/articles/show.html.erb %> <%# View verileri HTML formatında gösterir %>

<%= @article.title %>

Yazan: <%= @article.author.name %> • <%= l @article.created_at, format: :long %>

<%= simple_format @article.body %>
<%# Yorumlar için partial %> <%= render @comments %>
``` 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. ```ruby # 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 ``` ```ruby # 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) end ``` Active 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. ```ruby # 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 ``` ```ruby # 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 ``` ```bash # 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, seed ``` Migration'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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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ğinde ``` Kural: 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. ```ruby # 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 ``` ```ruby # 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. ## 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. ```ruby # 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 ``` ```bash # 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. ```ruby # 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 ``` ```ruby # 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 end ``` Callback'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. ```ruby # 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 ``` ```ruby # 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 end ``` Service 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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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 end ``` Concern'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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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) } end ``` Kalı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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # İş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) ``` ```ruby # 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: CleanupJob ``` Active 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. ## 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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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 end ``` API 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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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. ```ruby # 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 } ``` ```erb <%# 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 %>

<%= @category.name %>

<% @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 %> ``` ```ruby # 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 ``` ```ruby # 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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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 end ``` Temel 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. ```ruby # 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 ``` ```ruby # 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) ``` ```ruby # 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] %> ``` ```ruby # 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 ``` ```ruby # 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:' end ``` `brakeman` (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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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 end ``` Pundit, 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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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 end ``` Active 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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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 end ``` CQRS, 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. ```ruby # 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 ``` ```ruby # 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 end ``` ```javascript // app/javascript/channels/chat_channel.js import 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. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # 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 end ``` Satı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. ```ruby # 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 ``` ```ruby # 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) ``` ```ruby # 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 end ``` Rails 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. ```ruby # 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 # 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 --from=builder /app /app COPY --from=builder /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"] ``` ```yaml # 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. ```ruby # 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 } %> ``` ```ruby # 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() } } ``` ```ruby # 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" ``` ```ruby # 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... ``` ```ruby # 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 end ``` Rails 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. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/tr/blog/ruby-on-rails/ruby-on-rails-interview-questions