# Ruby on Rails Interview Questions: Top 25 in 2026 > The 25 most asked Ruby on Rails interview questions. MVC architecture, Active Record, migrations, RSpec testing, REST APIs with detailed answers and code examples. - Published: 2026-02-03 - Updated: 2026-03-31 - Author: Anthony Fillion-Maillet - Tags: ruby on rails, ruby, interview, active record, technical interview - Reading time: 22 min --- Ruby on Rails interviews assess mastery of Ruby's most popular framework, understanding of MVC architecture, Active Record ORM, and the ability to build robust web applications following the "Convention over Configuration" philosophy. This guide covers the 25 most asked questions, from Rails fundamentals to advanced production patterns. > **Interview Tip** > > Recruiters appreciate candidates who understand the Rails philosophy: "Convention over Configuration", DRY (Don't Repeat Yourself), and Rails Way patterns. Explaining why Rails makes certain architectural choices makes the difference. ## Ruby on Rails Fundamentals ### Question 1: Explain the MVC Pattern in Ruby on Rails The Model-View-Controller (MVC) pattern is the architectural core of Rails. It separates responsibilities into three distinct layers for better maintainability and testability. ```ruby # app/models/article.rb # The Model handles data and business logic class Article < ApplicationRecord # Data validations validates :title, presence: true, length: { minimum: 5 } validates :body, presence: true # Associations with other models belongs_to :author, class_name: 'User' has_many :comments, dependent: :destroy has_many :tags, through: :article_tags # Scopes for reusable queries scope :published, -> { where(published: true) } scope :recent, -> { order(created_at: :desc).limit(10) } # Lifecycle callbacks before_save :generate_slug private def generate_slug self.slug = title.parameterize if title_changed? end end ``` ```ruby # app/controllers/articles_controller.rb # The Controller receives requests and orchestrates the response 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: 'Article created successfully.' 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 %> <%# The View displays data in HTML format %>

<%= @article.title %>

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

<%= simple_format @article.body %>
<%# Partial for comments %> <%= render @comments %>
``` The typical flow: the request arrives at the Router, which dispatches to the appropriate Controller. The Controller interacts with the Model to retrieve or modify data, then passes this data to the View for HTML rendering. ### Question 2: What is Active Record and How Does Rails ORM Work? Active Record is Rails' ORM (Object-Relational Mapping) that implements the Active Record pattern. Each Model class represents a database table, and each instance represents a row. ```ruby # app/models/user.rb # Active Record automatically maps columns to attributes class User < ApplicationRecord # The 'users' table is automatically associated # Columns: id, email, name, created_at, updated_at has_secure_password # BCrypt for password has_many :articles, foreign_key: :author_id has_one :profile, dependent: :destroy has_and_belongs_to_many :roles # Validations validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: URI::MailTo::EMAIL_REGEXP } # Callbacks before_save :normalize_email # Class methods for queries def self.admins joins(:roles).where(roles: { name: 'admin' }) end private def normalize_email self.email = email.downcase.strip end end ``` ```ruby # Active Record query examples # Rails console or within a service # Creation user = User.create!(email: 'dev@example.com', name: 'Alice', password: 'secret123') # Reading with conditions active_users = User.where(active: true).order(:name) user = User.find_by(email: 'dev@example.com') # Chained queries (lazy evaluation) recent_admins = User.admins .where('created_at > ?', 1.month.ago) .includes(:profile) .limit(10) # N+1 prevention with eager loading articles = Article.includes(:author, :comments).published # Update user.update!(name: 'Alice Martin') # Transactions User.transaction do user.debit_balance!(100) recipient.credit_balance!(100) Payment.create!(from: user, to: recipient, amount: 100) end ``` Active Record converts Ruby methods into optimized SQL queries. Methods like `where`, `joins`, `includes` are lazy - the query is only executed when iterating or calling `to_a`. ### Question 3: Explain the Rails Migration System Migrations allow versioning database schema with Ruby. They are reversible and enable controlled evolution of data structure. ```ruby # db/migrate/20260203100000_create_products.rb # Migration to create a table 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 and updated_at automatic end # Indexes for performance add_index :products, :name add_index :products, [:category_id, :active] end end ``` ```ruby # db/migrate/20260203110000_add_slug_to_products.rb # Migration to modify an existing table class AddSlugToProducts < ActiveRecord::Migration[7.1] def change add_column :products, :slug, :string add_index :products, :slug, unique: true # Fill existing slugs reversible do |dir| dir.up do Product.find_each do |product| product.update_column(:slug, product.name.parameterize) end end end # Make non-nullable after filling change_column_null :products, :slug, false end end ``` ```bash # Essential migration commands rails db:migrate # Run pending migrations rails db:rollback # Undo last migration rails db:rollback STEP=3 # Undo last 3 migrations rails db:migrate:status # See migration status rails db:seed # Run db/seeds.rb rails db:reset # Drop, create, migrate, seed ``` Migrations must be reversible. The `change` method is smart and can automatically reverse common operations. For complex cases, use `up` and `down` separately. ## Advanced Active Record ### Question 4: How to Optimize N+1 Queries in Rails? The N+1 problem occurs when an initial query is followed by N additional queries to load associations. Rails provides several eager loading methods to solve this issue. ```ruby # app/controllers/orders_controller.rb class OrdersController < ApplicationController def index # ❌ N+1 PROBLEM: 1 query + N queries per order # @orders = Order.all # In the view: order.user.name generates a query per order # ✅ SOLUTION with includes (eager loading) @orders = Order.includes(:user, :items) .where(status: 'completed') .order(created_at: :desc) # Generates only 3 queries total end def show # includes: loads associations separately (2-3 queries) @order = Order.includes(items: :product).find(params[:id]) # preload: forces separate loading @order = Order.preload(:items, :user).find(params[:id]) # eager_load: forces LEFT OUTER JOIN (1 query) @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 # Scope with default includes scope :with_details, -> { includes(:user, items: :product) } # Counter cache to avoid COUNT queries # Requires: add_column :users, :orders_count, :integer, default: 0 belongs_to :user, counter_cache: true end ``` ```ruby # N+1 detection with Bullet gem (development) # config/environments/development.rb config.after_initialize do Bullet.enable = true Bullet.alert = true Bullet.bullet_logger = true Bullet.rails_logger = true end # Bullet will show alerts when: # - An N+1 query is detected # - Unnecessary eager loading is present # - A counter cache should be used ``` The rule: use `includes` by default (Rails chooses the optimal strategy), `preload` when forcing separate queries, `eager_load` when filtering on associations. ### Question 5: Explain Scopes and Query Objects in Rails Scopes encapsulate reusable query conditions. For complex queries, Query Objects offer better organization and testability. ```ruby # app/models/product.rb class Product < ApplicationRecord # Simple scopes scope :active, -> { where(active: true) } scope :in_stock, -> { where('stock_quantity > 0') } scope :featured, -> { where(featured: true) } # Scopes with parameters scope :cheaper_than, ->(price) { where('price < ?', price) } scope :in_category, ->(category) { where(category: category) } # Chainable scopes scope :available, -> { active.in_stock } # Scope with joins scope :with_recent_orders, -> { joins(:order_items) .where('order_items.created_at > ?', 30.days.ago) .distinct } # Scope with subquery scope :bestsellers, -> { where(id: OrderItem.group(:product_id) .order('COUNT(*) DESC') .limit(10) .select(:product_id)) } end ``` ```ruby # app/queries/products_search_query.rb # Query Object for complex searches 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 # Usage in controller @products = ProductsSearchQuery.new(Product.active).call(params) ``` Scopes are perfect for simple, reusable conditions. Query Objects suit complex searches with multiple optional filters and composition logic. ## Routing and Controllers ### Question 6: How Does RESTful Routing Work in Rails? Rails encourages RESTful routes that map HTTP verbs to CRUD actions. The router translates URLs into specific controller calls. ```ruby # config/routes.rb Rails.application.routes.draw do # Standard RESTful routes (7 actions) resources :articles do # Nested routes resources :comments, only: [:create, :destroy] # Member routes (act on an instance) member do post :publish delete :archive end # Collection routes (act on the collection) collection do get :drafts get :search end end # API routes with namespace namespace :api do namespace :v1 do resources :products, only: [:index, :show, :create, :update] do resources :reviews, shallow: true end end end # Custom route get 'dashboard', to: 'dashboard#index' # Route constraints constraints(SubdomainConstraint.new) do resources :admin_settings end # Root route root 'home#index' end ``` ```bash # rails routes - Shows all generated routes # # 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 ``` Generated route helpers (`article_path(@article)`, `new_article_path`) allow referencing URLs dynamically and maintainably. ### Question 7: Explain Callbacks and Filters in Controllers Callbacks (before_action, after_action, around_action) allow executing code before, after, or around controller actions. ```ruby # app/controllers/application_controller.rb class ApplicationController < ActionController::Base # CSRF protection enabled by default protect_from_forgery with: :exception # Global callback for authentication before_action :authenticate_user! # Global error handling rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActionController::ParameterMissing, with: :bad_request private def not_found render json: { error: 'Resource not found' }, 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 # Callbacks with options before_action :require_admin before_action :set_product, only: [:show, :edit, :update, :destroy] after_action :log_activity, only: [:create, :update, :destroy] # Conditional 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: 'Product created.' else render :new, status: :unprocessable_entity end end def update if @product.update(product_params) redirect_to [:admin, @product], notice: 'Product updated.' 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 ``` Callbacks execute in declaration order. Use `skip_before_action` in subclasses to disable inherited callbacks. Avoid callbacks with too much business logic - prefer Service Objects. ## Services and Architecture ### Question 8: How to Implement Service Objects in Rails? Service Objects encapsulate complex business logic that belongs neither in Models nor Controllers. They improve testability and follow the single responsibility principle. ```ruby # app/services/order_processor.rb # Service Object with standardized interface class OrderProcessor def initialize(order, payment_method:) @order = order @payment_method = payment_method end def call return failure('Order already processed') 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("Payment failed: #{e.message}") rescue InsufficientStockError => e failure("Stock insufficient: #{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: "Order ##{@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: 'Order confirmed!' 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 ``` The Service Object pattern follows a simple convention: one class, one responsibility, one public `call` method. Returning a Result object enables clean success and failure handling. ### Question 9: Explain Concerns in Rails Concerns allow extracting and sharing code between Models or Controllers. They use `ActiveSupport::Concern` for clean inclusion syntax. ```ruby # app/models/concerns/sluggable.rb # Reusable Concern for generating slugs module Sluggable extend ActiveSupport::Concern included do # Code executed on inclusion before_validation :generate_slug, if: :should_generate_slug? validates :slug, presence: true, uniqueness: true end # Class methods 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 # Instance methods 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 # Optional, :title by default end # app/models/product.rb class Product < ApplicationRecord include Sluggable sluggable_source :name end ``` ```ruby # app/controllers/concerns/pagination.rb # Concern for controllers 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 ``` Concerns are useful for truly shared code. Avoid creating Concerns just to "shorten" a Model - that hides complexity without reducing it. ## Testing with RSpec ### Question 10: How to Structure RSpec Tests in Rails? RSpec is the standard testing framework for Rails. A good test structure includes Model specs, Controller specs, Service specs, and integration tests. ```ruby # spec/models/user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do # Factories with FactoryBot 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 'validates email format' 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 'returns first and last name combined' do user = build(:user, first_name: 'John', last_name: 'Doe') expect(user.full_name).to eq('John Doe') end it 'handles missing last name' do user = build(:user, first_name: 'John', last_name: nil) expect(user.full_name).to eq('John') end end describe '.active' do it 'returns only active users' 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 'when order is valid' do before do allow(PaymentGateway).to receive(:charge).and_return( OpenStruct.new(success?: true, transaction_id: 'txn_123') ) end it 'processes the order successfully' do result = subject.call expect(result).to be_success expect(order.reload.status).to eq('completed') end it 'decrements product stock' do expect { subject.call }.to change { product.reload.stock_quantity }.by(-2) end it 'sends confirmation email' do expect { subject.call } .to have_enqueued_mail(OrderMailer, :confirmation) .with(order) end end context 'when payment fails' do before do allow(PaymentGateway).to receive(:charge).and_return( OpenStruct.new(success?: false, error: 'Card declined') ) end it 'returns failure result' do result = subject.call expect(result).to be_failure expect(result.error).to include('Card declined') end it 'does not update order status' 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 'returns list of products' do get '/api/v1/products', headers: headers expect(response).to have_http_status(:ok) expect(json_response['data'].size).to eq(3) end it 'filters by category' 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: 'New Product', price: 99.99, category_id: create(:category).id } } end it 'creates a new product' 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 ``` Best practices: use `let` for data, `describe` for methods/contexts, `context` for conditions, and `it` for specific assertions. One test should test one thing. ### Question 11: How to Use Factories with FactoryBot? FactoryBot enables creating test data declaratively and maintainably. Factories replace static fixtures. ```ruby # spec/factories/users.rb FactoryBot.define do factory :user do # Sequences for uniqueness 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 } # Traits for variations 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 # Inherited factory 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 # Usage in tests RSpec.describe OrderProcessor do # build: non-persisted instance let(:user) { build(:user) } # create: persisted to DB let(:order) { create(:order, :with_items, user: user) } # create_list: multiple instances let(:products) { create_list(:product, 5) } # Combining traits let(:admin) { create(:user, :admin, :with_profile) } # Override attributes let(:expensive_order) { create(:order, :with_items, items_count: 10) } # build_stubbed: faster, for unit tests let(:stubbed_user) { build_stubbed(:user) } end ``` Prefer `build` or `build_stubbed` over `create` when persistence isn't necessary - this significantly speeds up tests. ## Background Jobs ### Question 12: How to Use Active Job and Sidekiq in Rails? Active Job provides a unified interface for background jobs, regardless of backend (Sidekiq, Resque, etc.). Sidekiq is the popular choice for its performance with Redis. ```ruby # app/jobs/process_order_job.rb class ProcessOrderJob < ApplicationJob queue_as :default # Retry configuration retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 10 discard_on ActiveJob::DeserializationError # Sidekiq options (if Sidekiq backend) sidekiq_options retry: 5, backtrace: true def perform(order_id) order = Order.find(order_id) OrderProcessor.new(order).call rescue ActiveRecord::RecordNotFound # Order deleted between enqueue and execution 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 # Rate limiting with Sidekiq Enterprise or throttle gem 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 # Enqueuing jobs # Immediate ProcessOrderJob.perform_later(order.id) # Delayed ProcessOrderJob.set(wait: 5.minutes).perform_later(order.id) # At specific time ProcessOrderJob.set(wait_until: Date.tomorrow.noon).perform_later(order.id) # Specific queue ProcessOrderJob.set(queue: :critical).perform_later(order.id) # Synchronous (for tests or debugging) ProcessOrderJob.perform_now(order.id) ``` ```ruby # config/sidekiq.yml :concurrency: 10 :queues: - [critical, 3] # High priority, weight 3 - [default, 2] # Medium priority, weight 2 - [mailers, 1] # Low priority, weight 1 - [low, 1] :schedule: cleanup_job: cron: '0 3 * * *' # Every day at 3am class: CleanupJob ``` Active Job abstracts the backend, but accessing specific features (batches, rate limiting) often requires coupling to the chosen backend. ## API Development ### Question 13: How to Build a RESTful API with Rails? Rails facilitates building JSON APIs with API-only Controllers and serializers. A good API is versioned, documented, and secure. ```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: 'Resource not found', details: exception.message }, status: :not_found end def unprocessable_entity(exception) render json: { error: 'Validation failed', details: exception.record.errors }, status: :unprocessable_entity end def bad_request(exception) render json: { error: 'Bad request', 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 # With jsonapi-serializer gem 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 best practices: version via namespace, use appropriate HTTP codes, paginate collections, and provide clear error messages. ### Question 14: How to Implement JWT Authentication in Rails? JWT (JSON Web Tokens) is a popular stateless authentication method for APIs. The token encodes user identity and validity. ```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 has expired' rescue JWT::DecodeError raise AuthenticationError, 'Invalid 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: 'Invalid credentials' }, 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, 'Missing token' 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: 'User not found' }, status: :unauthorized end def current_user @current_user end end ``` For production, consider: refresh tokens, token blacklisting for logout, and short expiration times. Gems like `devise-jwt` simplify implementation. ## Caching and Performance ### Question 15: How to Implement Caching in Rails? Rails offers several caching levels: fragment caching, Russian Doll caching, low-level caching. The choice depends on the use case. ```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 %> <%# Fragment caching with automatic cache key %> <% @products.each do |product| %> <%# Cache based on product's updated_at %> <% cache product do %> <%= render product %> <% end %> <% end %> <%# Russian Doll caching - nested cache %> <% cache ['v1', @category] do %>

<%= @category.name %>

<% @category.products.each do |product| %> <% cache ['v1', product] do %> <%= render product %> <% end %> <% end %> <% end %> <%# Conditional cache %> <% cache_if current_user.nil?, @product do %> <%= render @product %> <% end %> ``` ```ruby # app/models/product.rb class Product < ApplicationRecord # Touch parent to invalidate Russian Doll cache belongs_to :category, touch: true # Custom cache key def cache_key_with_version "#{super}/#{reviews.maximum(:updated_at)&.to_i}" end end ``` ```ruby # Low-level caching in services 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 # Cache with race condition protection Rails.cache.fetch('popular_products', expires_in: 1.hour, race_condition_ttl: 10.seconds) do Product.bestsellers.limit(10).to_a end # Explicit invalidation Rails.cache.delete('dashboard:stats') Rails.cache.delete_matched('products:*') ``` Russian Doll caching is effective because only modified fragments are regenerated. Use `touch: true` on associations to propagate invalidation. ### Question 16: How to Optimize Rails Application Performance? Rails optimization covers multiple aspects: DB queries, caching, assets, and architecture. A methodical approach with monitoring is essential. ```ruby # Database optimization # config/database.yml production: pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> prepared_statements: true advisory_locks: true # app/models/order.rb class Order < ApplicationRecord # Composite indexes for frequent queries # add_index :orders, [:user_id, :status, :created_at] # Select only needed columns scope :summary, -> { select(:id, :status, :total, :created_at) } # Batch processing for large volumes def self.process_pending pending.find_each(batch_size: 1000) do |order| ProcessOrderJob.perform_later(order.id) end end # Avoid repetitive calculations def self.revenue_by_month completed .group("DATE_TRUNC('month', created_at)") .sum(:total) end end ``` ```ruby # Memory optimization # 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 # Profiling with rack-mini-profiler # 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 # Lazy loading and pagination class ProductsController < ApplicationController def index @products = Product.active .includes(:category, :primary_image) .page(params[:page]) .per(24) # Prefetch for next page 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 ``` Essential tools: `rack-mini-profiler` for profiling, `bullet` for N+1 detection, `New Relic` or `Scout` for production monitoring. ## Security ### Question 17: What Are Rails Security Best Practices? Rails includes default protections against common vulnerabilities. Understanding and correctly configuring these protections is crucial. ```ruby # CSRF Protection # app/controllers/application_controller.rb class ApplicationController < ActionController::Base # Enabled by default, raises exception if token invalid protect_from_forgery with: :exception # For APIs, use :null_session # protect_from_forgery with: :null_session end # In views, the token is automatically included in forms # <%= form_with ... %> includes authenticity_token # For AJAX requests # Add X-CSRF-Token header with csrf_meta_tags value ``` ```ruby # SQL Injection Prevention # ✅ Interpolated parameters automatically escaped User.where('email = ?', params[:email]) User.where(email: params[:email]) # ❌ DANGER - Direct interpolation User.where("email = '#{params[:email]}'") # ✅ For dynamic ORDER clauses ALLOWED_SORTS = %w[name created_at price].freeze sort_column = ALLOWED_SORTS.include?(params[:sort]) ? params[:sort] : 'name' Product.order(sort_column) ``` ```ruby # XSS Protection # Rails automatically escapes HTML in views # ✅ Automatically escaped <%= user.name %> # ❌ Dangerous - unescaped content <%== user.bio %> <%= raw user.bio %> <%= user.bio.html_safe %> # ✅ For safe HTML, use sanitize <%= 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 # Explicit whitelist of allowed attributes params.require(:user).permit(:name, :email, :avatar) # For admins only if current_user.admin? params.require(:user).permit(:name, :email, :role, :active) else params.require(:user).permit(:name, :email) end end end ``` ```ruby # Secure Headers # 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 ``` Regularly audit with `brakeman` (static security analysis) and keep gems updated with `bundle audit`. ### Question 18: How to Handle Authentication and Authorization in Rails? Authentication verifies identity, authorization controls permissions. Devise handles auth, Pundit or CanCanCan handle authorization. ```ruby # Devise setup # 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 policies # 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 # Scope for collections 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 # Controller with Pundit 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: 'Article updated.' 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: 'Article published.' end rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private def user_not_authorized flash[:alert] = "You are not authorized to perform this action." redirect_back(fallback_location: root_path) end end ``` Pundit is more explicit and testable than CanCanCan. Each action has a corresponding policy method, and scopes automatically filter collections. ## Advanced Rails ### Question 19: Explain the Repository Pattern in Rails The Repository pattern isolates data access logic from the rest of the application. While Rails uses Active Record (a different pattern), Repository can be useful for complex cases. ```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 # Usage in a service 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 # Facilitates testing with mocks RSpec.describe ProductSearchService do let(:repository) { instance_double(ProductRepository) } let(:service) { described_class.new(repository: repository) } it 'filters by category' 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 ``` Repository is optional in Rails since Active Record is already an excellent pattern. Use it for complex queries or when storage isolation is important. ### Question 20: How to Implement the CQRS Pattern in Rails? CQRS (Command Query Responsibility Segregation) separates read and write operations. In Rails, this translates to distinct classes for queries and commands. ```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, "Product #{item[:product_id]} not available") 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 # Controller using CQRS 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: 'Order created!' else flash.now[:alert] = result.errors.join(', ') render :new, status: :unprocessable_entity end end end ``` CQRS shines for complex applications with asymmetric read/write needs. For simple CRUD, it's over-engineering. ### Question 21: How to Handle WebSockets with Action Cable? Action Cable integrates WebSockets into Rails for bidirectional real-time communication. It uses Redis for synchronization between servers. ```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 # Via session cookie if verified_user = User.find_by(id: cookies.encrypted[:user_id]) verified_user # Via JWT for APIs 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]) # Check permissions unless @room.accessible_by?(current_user) reject return end stream_for @room # Notify others of presence 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'] ) # Broadcast to all subscribers 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 automatically handles reconnections and synchronization. In production, configure Redis as adapter and scale according to concurrent connections. ### Question 22: How to Implement Multi-tenancy in Rails? Multi-tenancy allows an application to serve multiple isolated clients (tenants). Three main approaches: database-level, schema-level, or row-level. ```ruby # Row-level multitenancy with ActsAsTenant or manual # app/models/concerns/tenant_scoped.rb module TenantScoped extend ActiveSupport::Concern included do belongs_to :tenant # Default scope to current tenant default_scope -> { where(tenant: Current.tenant) if Current.tenant } # Tenant validation 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 # Via subdomain if request.subdomain.present? && request.subdomain != 'www' Tenant.find_by!(subdomain: request.subdomain) # Via header (for APIs) elsif request.headers['X-Tenant-ID'].present? Tenant.find(request.headers['X-Tenant-ID']) # Via user elsif current_user current_user.tenant end rescue ActiveRecord::RecordNotFound redirect_to root_url(subdomain: 'www'), alert: 'Tenant not found' 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 # Admins can belong to multiple tenants has_many :tenant_memberships has_many :accessible_tenants, through: :tenant_memberships, source: :tenant end ``` ```ruby # Schema-level with Apartment gem (PostgreSQL) # config/initializers/apartment.rb Apartment.configure do |config| config.excluded_models = %w[Tenant User] config.tenant_names = -> { Tenant.pluck(:subdomain) } end # Usage Apartment::Tenant.switch('acme') do # All queries in this block use the 'acme' schema Project.all # SELECT * FROM acme.projects end ``` Row-level is simplest but requires constant attention to leaks. Schema-level offers better isolation but complicates migrations. Choose based on security and scalability needs. ### Question 23: How to Set Up a Microservices Architecture with Rails? Rails can serve as a base for microservices architecture with communication via HTTP/gRPC or message queues. The key is defining boundaries well. ```ruby # HTTP service client # 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('Service unavailable', code: response.code) end rescue Net::OpenTimeout, Net::ReadTimeout ServiceResult.failure('Service timeout') end end ``` ```ruby # Event-driven communication with Sidekiq/Redis # 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 pattern # app/controllers/api/v1/gateway_controller.rb module Api module V1 class GatewayController < BaseController # Aggregate multiple services 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} unavailable", message: e.message } end end end end ``` For Rails microservices: define clear API contracts (OpenAPI), implement circuit breakers (gem `circuitbox`), and use distributed tracing (gem `opentelemetry`). ### Question 24: How to Deploy a Rails Application to Production? Modern Rails deployment uses containers or PaaS. A robust production configuration covers assets, database, and monitoring. ```ruby # config/environments/production.rb Rails.application.configure do config.cache_classes = true config.eager_load = true config.consider_all_requests_local = false # Assets 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) } # Cache config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'], expires_in: 1.day } # Force SSL 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 # Production image 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: ``` Production checklist: mandatory SSL, secrets via ENV, health checks, automated DB backups, monitoring (APM + logs + metrics), and configured alerting. ### Question 25: What Are the New Features in Rails 7+ to Know? Rails 7+ brings significant changes: Hotwire by default, import maps, improved encrypted credentials, and many optimizations. ```ruby # Hotwire - Turbo Frames # 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 "Load more", articles_path(page: @page + 1), data: { turbo_frame: "articles" } %> <% end %> # Turbo Streams for real-time updates # 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 controllers # 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 Maps (without JavaScript bundler) # 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" # Pins from CDN 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 # Allows searches encrypts :phone_number # Non-deterministic by default 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 # Query interface improvements # Rails 7.1+ # Async queries users = User.where(active: true).load_async # Continue processing while query runs # Access results with users.to_a # 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') # Automatic inverse_of detection class Author < ApplicationRecord has_many :books # inverse_of automatically detected end # Strict loading by default (avoids N+1) class ApplicationRecord < ActiveRecord::Base self.strict_loading_by_default = true end ``` Rails 7+ favors simplicity (no Webpack by default) and HTML-over-the-wire with Hotwire. This approach reduces JavaScript complexity while offering a modern user experience. ## Conclusion Ruby on Rails interviews assess mastery of the complete framework and understanding of its conventions. Key takeaways: ✅ **Fundamentals**: MVC, Active Record, migrations, validations, and associations ✅ **Architecture**: Service Objects, Concerns, Query Objects, and CQRS patterns ✅ **Performance**: N+1 queries, caching (fragment, Russian Doll, low-level), eager loading ✅ **Testing**: RSpec, FactoryBot, request specs, and testing best practices ✅ **Security**: CSRF, SQL injection, XSS, Strong Parameters, and authentication/authorization ✅ **APIs**: RESTful design, JWT, serializers, and versioning ✅ **Production**: Background jobs, WebSockets, deployment, and monitoring The Rails philosophy - Convention over Configuration, DRY, and Rails Way - guides all architectural decisions. Mastering these principles and knowing when to deviate demonstrates solid expertise. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/ruby-on-rails/ruby-on-rails-interview-questions