amazon:
service: S3
access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
region: us-east-1
bucket: <%= Rails.application.credentials.dig(:aws, :bucket) %>
class User < ApplicationRecord
has_one_attached :avatar
validates :avatar, content_type: ['image/png', 'image/jpg', 'image/jpeg'],
size: { less_than: 5.megabytes }
end
module Api
module V1
class AvatarsController < BaseController
before_action :authenticate_user!
def create
current_user.avatar.attach(params[:avatar])
if current_user.avatar.attached?
render json: {
url: url_for(current_user.avatar),
thumb_url: url_for(current_user.avatar.variant(resize_to_limit: [200, 200]))
}, status: :created
else
render json: { errors: current_user.errors }, status: :unprocessable_entity
end
end
def destroy
current_user.avatar.purge
render json: { message: 'Avatar deleted' }, status: :ok
end
end
end
end
ActiveStorage provides a unified interface for uploading files to cloud storage services like S3, GCS, or Azure Storage. I configure storage services in config/storage.yml and attach files to models using has_one_attached or has_many_attached macros. Files are stored with metadata in the database while blobs live in the configured storage service. Variants support on-the-fly image transformations using ImageProcessing gem. Direct uploads from browser to S3 bypass the Rails app entirely, improving performance and reducing server load. For APIs, I generate presigned URLs for clients to upload directly, then create the ActiveStorage attachment after successful upload. Proper security includes validating content types and file sizes before processing.
Related snips
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
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
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
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
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
<form data-controller="query-sync" data-action="change->query-sync#apply">
<select name="status" class="rounded border p-2">
<option value="">Any</option>
<option value="open">Open</option>
<option value="closed">Closed</option>
</select>
Filter UI that syncs query params via Stimulus (no front-end router)
Share this code
Here's the card — post it anywhere.