ruby
67 lines · 3 tabs
Maya Patel
Jan 2026
3 tabs
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :posts do
resources :comments, only: [:index, :create]
end
resources :users, only: [:show, :update]
end
namespace :v2 do
resources :posts do
resources :comments
member do
post :publish
post :archive
end
end
resources :users
resources :profiles
end
# Default to latest version
root to: redirect('/api/v2')
end
end
module Api
module V1
class PostsController < ApplicationController
def index
posts = Post.published.includes(:author).page(params[:page])
# Add deprecation warning
response.headers['X-API-Deprecation'] = 'This API version is deprecated. Please migrate to /api/v2'
response.headers['X-API-Sunset'] = '2024-12-31'
render json: posts, each_serializer: V1::PostSerializer
end
end
end
end
module Api
module V2
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :content, :excerpt, :status,
:published_at, :created_at, :updated_at,
:metadata
belongs_to :author, serializer: V2::UserSerializer
has_many :tags
# V2 includes additional computed fields
def metadata
{
word_count: object.body.split.size,
read_time: (object.body.split.size / 200.0).ceil,
featured: object.featured?,
trending: object.trending_score > 10
}
end
# V2 uses different field name
def content
object.body
end
end
end
end
3 files · ruby
Explain with highlit
API versioning allows backward-incompatible changes without breaking existing clients. I version via URL path (/api/v1/posts) for simplicity and explicit version selection. Each version namespace has its own controllers and serializers. Routes use namespace blocks to organize versions. When creating v2, I duplicate v1 controllers and modify only what changes, sharing models between versions. For minor changes, I use optional params or conditional logic within a version. Deprecation headers warn clients about sunset timelines. I maintain at least two versions simultaneously during transitions. This strategy balances stability for existing clients with evolution for new features.
Related snips
ruby
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
rails
hotwire
turbo
by codesnips
4 tabs
ruby
class Post < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
scope :draft, -> { where(published_at: nil) }
ActiveRecord scopes for reusable query logic
rails
activerecord
patterns
by Alex Kumar
1 tab
ruby
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
jwt
authentication
api
by Kai Nakamura
2 tabs
ruby
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
rails
caching
http-caching
by Alex Kumar
1 tab
ruby
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
rails
turbo
hotwire
by codesnips
4 tabs
ruby
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
rails
performance
streaming
by codesnips
3 tabs
Share this code
Here's the card — post it anywhere.