
## Introduction

This guide will show you how to use Rabata.io as a storage backend for your Ruby on Rails application using ActiveStorage. ActiveStorage facilitates uploading files to cloud storage services and attaching those files to Active Record objects.

## Prerequisites

Before you begin, make sure you have:

- Ruby on Rails 7.2+ installed
- A Rabata.io account with access credentials
- A bucket created in your Rabata.io account

## Installation

### Create a New Rails Application

If you're starting from scratch, create a new Rails application:

```bash
$ rails new my-app --database=postgresql
$ cd my-app
```

### Add AWS SDK Gem

Add the AWS SDK for S3 to your Gemfile:

```ruby
# Gemfile
gem 'aws-sdk-s3', require: false
```

Then install the gem:

```bash
$ bundle install
```

### Install ActiveStorage

If you're using a new Rails 7.2+ application, ActiveStorage is included by default. Otherwise, you can install it:

```bash
$ rails active_storage:install
$ rails db:migrate
```

## Configuration

### Configure ActiveStorage to Use Rabata.io

Edit your `config/storage.yml` file to include the Rabata.io configuration:

```yaml
# config/storage.yml
amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:rabata, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:rabata, :secret_access_key) %>
  region: us-east-1
  bucket: <%= Rails.application.credentials.dig(:rabata, :bucket) %>
  endpoint: https://s3.us-east-1.rabata.io
  force_path_style: true
```

### Store Credentials Securely

Use Rails credentials to securely store your Rabata.io credentials:

```bash
$ EDITOR="code --wait" rails credentials:edit
```

Add your Rabata.io credentials to the opened file:

```yaml
rabata:
  access_key_id: YOUR_ACCESS_KEY
  secret_access_key: YOUR_SECRET_KEY
  bucket: your-bucket-name
```

### Configure Environment

Set ActiveStorage to use Rabata.io in your environment files:

```ruby
# config/environments/development.rb
config.active_storage.service = :amazon

# config/environments/production.rb
config.active_storage.service = :amazon
```

## Basic Usage

### Setting Up Models

Add ActiveStorage attachments to your models:

```ruby
# app/models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar
  has_many_attached :photos
end
```

### Handling File Uploads in Controllers

```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def update
    @user = User.find(params[:id])

    if @user.update(user_params)
      redirect_to @user, notice: 'User was successfully updated.'
    else
      render :edit
    end
  end

  private

  def user_params
    params.require(:user).permit(:name, :email, :avatar, photos: [])
  end
end
```

### Creating File Upload Fields in Forms

```erb
<%# app/views/users/_form.html.erb %>
<%= form_with(model: user) do |form| %>
  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div class="field">
    <%= form.label :avatar %>
    <%= form.file_field :avatar %>
  </div>

  <div class="field">
    <%= form.label :photos, 'Upload multiple photos' %>
    <%= form.file_field :photos, multiple: true %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```

### Displaying Attached Files

```erb
<%# app/views/users/show.html.erb %>
<h1><%= @user.name %></h1>

<% if @user.avatar.attached? %>
  <%= image_tag @user.avatar %>
<% end %>

<h2>Photos</h2>
<div class="photos">
  <% @user.photos.each do |photo| %>
    <%= image_tag photo %>
  <% end %>
</div>
```

### Generating URLs for Attachments

```ruby
# Generate a URL for an attachment
url_for(@user.avatar)

# Generate a URL for a variant (resized image)
url_for(@user.avatar.variant(resize_to_limit: [100, 100]))
```

## Advanced Usage

### Direct Uploads

Enable direct uploads to Rabata.io by including the ActiveStorage JavaScript in your application:

```javascript
// app/javascript/application.js
import * as ActiveStorage from "@rails/activestorage"
ActiveStorage.start()
```

And add the direct upload attribute to your file fields:

```erb
<%= form.file_field :avatar, direct_upload: true %>
```

### Image Variants

Process images with variants:

```ruby
# app/views/users/show.html.erb
<%= image_tag @user.avatar.variant(resize_to_limit: [100, 100]) %>
```

Make sure you have image processing gems installed:

```ruby
# Gemfile
gem 'image_processing', '~> 1.2'
```

### Purging Attachments

Remove attachments when no longer needed:

```ruby
# Remove a single attachment
@user.avatar.purge

# Remove all attachments
@user.photos.purge
```

## Testing

### Model Tests

```ruby
# test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  test "user can have an avatar" do
    user = User.new

    # Attach a file
    user.avatar.attach(
      io: File.open(Rails.root.join('test', 'fixtures', 'files', 'avatar.png')),
      filename: 'avatar.png',
      content_type: 'image/png'
    )

    assert user.avatar.attached?
  end
end
```

### System Tests

```ruby
# test/system/users_test.rb
require "application_system_test_case"

class UsersTest < ApplicationSystemTestCase
  test "uploading an avatar" do
    visit new_user_path

    fill_in "Name", with: "John Doe"
    attach_file "Avatar", Rails.root.join('test', 'fixtures', 'files', 'avatar.png')

    click_on "Create User"

    assert_text "User was successfully created"
    assert page.has_css?("img[src*='avatar.png']")
  end
end
```

### RSpec

If you're using RSpec, you can test attachments like this:

```ruby
# spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  it "can have an avatar attached" do
    user = User.new
    user.avatar.attach(
      io: File.open(Rails.root.join('spec', 'fixtures', 'files', 'avatar.png')),
      filename: 'avatar.png',
      content_type: 'image/png'
    )

    expect(user.avatar).to be_attached
  end
end
```

## Troubleshooting

### Common Issues

1. **Access Denied Errors**: Ensure your Rabata.io credentials are correct and the bucket exists.

2. **CORS Issues with Direct Uploads**: If you're using direct uploads, you need to configure CORS for your Rabata.io bucket.

### CORS Configuration for Direct Uploads

If you're using direct uploads, you need to configure CORS for your Rabata.io bucket:

```json
[
  {
    "AllowedHeaders": [
      "Authorization",
      "Content-Type",
      "Content-Length",
      "Content-MD5",
      "x-amz-content-sha256",
      "x-amz-date",
      "x-amz-security-token",
      "x-amz-user-agent"
    ],
    "AllowedMethods": [
      "GET",
      "PUT",
      "POST",
      "DELETE"
    ],
    "AllowedOrigins": [
      "https://your-app-domain.com"
    ],
    "ExposeHeaders": [
      "ETag"
    ],
    "MaxAgeSeconds": 3600
  }
]
```

## Production Considerations

### Performance Optimization

1. **Use Direct Uploads**: For large files, direct uploads can significantly improve performance.

2. **Image Processing**: Consider using background jobs for image processing to avoid blocking requests.

```ruby
# app/models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end
```

### Monitoring

Monitor your ActiveStorage usage to avoid unexpected costs:

```ruby
# Get total storage used
total_bytes = ActiveStorage::Blob.sum(:byte_size)
puts "Total storage used: #{total_bytes / 1.megabyte} MB"
```

### Security Best Practices

1. **Validate File Types**: Always validate uploaded file types to prevent security issues.

```ruby
# app/models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar

  validate :acceptable_avatar

  private

  def acceptable_avatar
    return unless avatar.attached?

    unless avatar.blob.content_type.in?(%w(image/jpeg image/png))
      errors.add(:avatar, 'must be a JPEG or PNG')
    end

    unless avatar.blob.byte_size <= 1.megabyte
      errors.add(:avatar, 'is too big (max is 1MB)')
    end
  end
end
```

2. **Use Signed URLs**: For private files, use signed URLs with expiration.

```ruby
# Generate a signed URL that expires in 30 minutes
@user.avatar.blob.service_url(expires_in: 30.minutes)
```

## Complete Example

Here's a complete example of a Rails application that uses Rabata.io for file storage:

```ruby
# app/models/document.rb
class Document < ApplicationRecord
  has_one_attached :file

  validates :title, presence: true
  validate :acceptable_file

  private

  def acceptable_file
    return unless file.attached?

    unless file.blob.byte_size <= 10.megabytes
      errors.add(:file, 'is too big (max is 10MB)')
    end
  end
end

# app/controllers/documents_controller.rb
class DocumentsController < ApplicationController
  def index
    @documents = Document.all
  end

  def show
    @document = Document.find(params[:id])
  end

  def new
    @document = Document.new
  end

  def create
    @document = Document.new(document_params)

    if @document.save
      redirect_to @document, notice: 'Document was successfully created.'
    else
      render :new
    end
  end

  def destroy
    @document = Document.find(params[:id])
    @document.destroy

    redirect_to documents_path, notice: 'Document was successfully destroyed.'
  end

  private

  def document_params
    params.require(:document).permit(:title, :file)
  end
end
```

```erb
<%# app/views/documents/_form.html.erb %>
<%= form_with(model: document) do |form| %>
  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.label :file %>
    <%= form.file_field :file, direct_upload: true %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

<%# app/views/documents/show.html.erb %>
<h1><%= @document.title %></h1>

<% if @document.file.attached? %>
  <div>
    <% if @document.file.content_type.include?('image') %>
      <%= image_tag @document.file %>
    <% else %>
      <p>
        <%= link_to "Download #{@document.file.filename}", rails_blob_path(@document.file, disposition: "attachment") %>
      </p>
    <% end %>
  </div>
<% end %>
```

<div class="docs-article__actions">
  <a href="/docs/quickstart/ruby/" class="btn btn--secondary">Ruby Quickstart</a>
  <a href="/docs/quickstart/python/" class="btn btn--secondary">Python Quickstart</a>
</div>
