This skill should be used when the user asks about "file uploads", "Active Storage", "attachments", "has_one_attached", "has_many_attached", "image variants", "S3", "cloud storage", "direct uploads", "file processing", "image transformations", or needs guidance on handling file uploads in Rails applications.
/plugin marketplace add bastos/rails-plugin/plugin install bastos-ruby-on-rails@bastos/rails-pluginThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Comprehensive guide to file uploads and cloud storage in Rails.
rails active_storage:install
rails db:migrate
This creates:
active_storage_blobs - File metadataactive_storage_attachments - Polymorphic join tableactive_storage_variant_records - Cached variant infoclass User < ApplicationRecord
has_one_attached :avatar
end
class Article < ApplicationRecord
has_many_attached :images
end
class Document < ApplicationRecord
has_one_attached :file, service: :amazon
end
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
attachable.variant :thumb, resize_to_limit: [100, 100]
attachable.variant :medium, resize_to_limit: [300, 300]
end
end
# Controller
def create
@user = User.new(user_params)
@user.save
end
def user_params
params.require(:user).permit(:name, :avatar, images: [])
end
<%# Form %>
<%= form_with model: @user do |form| %>
<%= form.file_field :avatar %>
<%= form.file_field :images, multiple: true %>
<%= form.submit %>
<% end %>
# From file
user.avatar.attach(io: File.open("/path/to/photo.jpg"), filename: "photo.jpg")
# From uploaded file
user.avatar.attach(params[:avatar])
# From URL (downloaded)
user.avatar.attach(
io: URI.open("https://example.com/photo.jpg"),
filename: "downloaded.jpg",
content_type: "image/jpeg"
)
# From string content
user.avatar.attach(
io: StringIO.new(pdf_content),
filename: "document.pdf",
content_type: "application/pdf"
)
user.avatar.attached? # true/false
user.images.attached? # true/false
user.images.any? # true/false
user.images.count # number of attachments
<%# Direct URL %>
<%= url_for(@user.avatar) %>
<%# Download URL %>
<%= rails_blob_path(@user.avatar, disposition: "attachment") %>
<%# Image tag %>
<%= image_tag @user.avatar %>
<%# With fallback %>
<%= image_tag(@user.avatar.attached? ? @user.avatar : "default_avatar.png") %>
<%# On-the-fly transformation %>
<%= image_tag @user.avatar.variant(resize_to_limit: [100, 100]) %>
<%# Named variant %>
<%= image_tag @user.avatar.variant(:thumb) %>
<%# Complex transformations %>
<%= image_tag @user.avatar.variant(
resize_to_fill: [200, 200],
format: :webp,
saver: { quality: 80 }
) %>
| Method | Description |
|---|---|
resize_to_limit: [w, h] | Resize to fit within bounds |
resize_to_fill: [w, h] | Resize and crop to fill |
resize_to_fit: [w, h] | Resize to fit exactly |
resize_and_pad: [w, h] | Resize and pad to fill |
crop: "100x100+10+10" | Crop specific area |
rotate: 90 | Rotate degrees |
format: :webp | Convert format |
<%# PDF preview (first page) %>
<%= image_tag @document.file.preview(resize_to_limit: [200, 200]) %>
<%# Video preview (frame) %>
<%= image_tag @article.video.preview(resize_to_limit: [300, 200]) %>
<%# Include JS %>
<%= javascript_include_tag "activestorage" %>
<%# Form with direct upload %>
<%= form_with model: @user do |form| %>
<%= form.file_field :avatar, direct_upload: true %>
<% end %>
// Listen for upload events
addEventListener("direct-upload:initialize", event => {
const { target, detail } = event
const { id, file } = detail
// Show upload UI
})
addEventListener("direct-upload:progress", event => {
const { id, progress } = event.detail
// Update progress bar
})
addEventListener("direct-upload:error", event => {
const { id, error } = event.detail
// Handle error
})
addEventListener("direct-uploads:end", event => {
// All uploads complete
})
# config/storage.yml
local:
service: Disk
root: <%= Rails.root.join("storage") %>
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
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: my-app-<%= Rails.env %>
google:
service: GCS
project: my-project
credentials: <%= Rails.root.join("path/to/keyfile.json") %>
bucket: my-app-<%= Rails.env %>
azure:
service: AzureStorage
storage_account_name: myaccount
storage_access_key: <%= Rails.application.credentials.dig(:azure, :storage_access_key) %>
container: my-container
mirror:
service: Mirror
primary: amazon
mirrors: [ google, azure ]
# config/environments/development.rb
config.active_storage.service = :local
# config/environments/production.rb
config.active_storage.service = :amazon
# config/storage.yml
amazon_public:
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: my-public-bucket
public: true
class Article < ApplicationRecord
has_many_attached :images, service: :amazon_public
end
# Gemfile
gem "active_storage_validations"
# Model
class User < ApplicationRecord
has_one_attached :avatar
validates :avatar,
attached: true,
content_type: ["image/png", "image/jpeg"],
size: { less_than: 5.megabytes }
end
class Article < ApplicationRecord
has_many_attached :images
validates :images,
content_type: /\Aimage\/.*\z/,
size: { less_than: 10.megabytes },
limit: { max: 10 }
end
class User < ApplicationRecord
has_one_attached :avatar
validate :acceptable_avatar
private
def acceptable_avatar
return unless avatar.attached?
unless avatar.blob.byte_size <= 5.megabyte
errors.add(:avatar, "is too large (max 5MB)")
end
acceptable_types = ["image/jpeg", "image/png", "image/gif"]
unless acceptable_types.include?(avatar.content_type)
errors.add(:avatar, "must be JPEG, PNG, or GIF")
end
end
end
# Download content
binary = user.avatar.download
# Open as tempfile
user.avatar.open do |file|
# file is a Tempfile
ImageProcessor.process(file.path)
end
# Access metadata
user.avatar.blob.byte_size
user.avatar.blob.content_type
user.avatar.blob.filename
user.avatar.blob.created_at
# Remove single attachment
user.avatar.purge # Sync
user.avatar.purge_later # Async (recommended)
# Remove specific from many
user.images.find(attachment_id).purge_later
# Remove all
user.images.purge_later
# Avoid N+1 queries
@users = User.with_attached_avatar
@articles = Article.with_attached_images
# Named scope automatically created
User.with_attached_avatar.where(active: true)
# test/models/user_test.rb
require "test_helper"
class UserTest < ActiveSupport::TestCase
test "can attach avatar" do
user = users(:one)
user.avatar.attach(
io: file_fixture("avatar.png").open,
filename: "avatar.png",
content_type: "image/png"
)
assert user.avatar.attached?
end
end
# RSpec
RSpec.describe User, type: :model do
it "attaches avatar" do
user = create(:user)
user.avatar.attach(
io: File.open(Rails.root.join("spec/fixtures/files/avatar.png")),
filename: "avatar.png"
)
expect(user.avatar).to be_attached
end
end
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.