docs/developer/tutorial/file-uploads.mdx
In this tutorial, we'll add a logo image upload to our Brand model. Spree uses Rails Active Storage for handling file attachments, image processing, and storage.
First, let's add a logo attachment to the Brand model:
module Spree
class Brand < Spree::Base
has_one_attached :logo
# ... existing code ...
end
end
For the admin form to accept the file upload, we need to permit the logo attribute in the controller.
Update your brands controller:
module Spree
module Admin
class BrandsController < ResourceController
private
def permitted_resource_params
params.require(:brand).permit(
:name,
:description,
:logo
)
end
end
end
end
Now let's add the file upload field to the brand form. Spree provides a spree_file_field helper that handles everything including:
Update your brand form partial:
<div class="card mb-6">
<div class="card-header">
<h5 class="card-title"><%= Spree.t(:general_settings) %></h5>
</div>
<div class="card-body">
<%= f.spree_text_field :name, required: true %>
<%= f.spree_rich_text_area :description %>
</div>
</div>
<div class="card mb-6">
<div class="card-header">
<h5 class="card-title"><%= Spree.t(:logo) %></h5>
</div>
<div class="card-body">
<%= f.spree_file_field :logo, width: 300, height: 300 %>
</div>
</div>
To enable cropping functionality, add the crop: true option to the spree_file_field helper.
Let's show a thumbnail of the brand logo in the admin index view.
Update the table header partial:
<tr>
<th><%= Spree.t(:logo) %></th>
<th><%= Spree.t(:name) %></th>
<th></th>
</tr>
Update the table row partial to display the logo:
<tr id="<%= spree_dom_id brand %>" class="cursor-pointer" data-controller="row-link">
<td data-action="click->row-link#openLink" class="w-70">
<div class="d-flex align-items-center gap-2">
<% if brand.logo.attached? %>
<%= spree_image_tag brand.logo, width: 48, height: 48, class: 'rounded' %>
<% else %>
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 48px; height: 48px;">
<%= icon('photo-off', class: 'text-muted') %>
</div>
<% end %>
<span class="font-weight-bold"><%= brand.name %></span>
</div>
</td>
<td data-action="click->row-link#openLink" class="w-20">
<%= spree_date(brand.created_at) %>
</td>
<td class="actions w-10">
<%= link_to_edit(brand, data: { row_link_target: :link, turbo_frame: '_top' }) if can? :edit, brand %>
</td>
</tr>