app/models/my_app_scope/file.rb
module MyAppScope
class File < ::Spree::Asset
has_attached_file :attachment,
url: '/spree/products/:id/:basename.:extension',
path: ':rails_root/public/spree/products/:id/:basename.:extension'
do_not_validate_attachment_file_type :attachment
def download_url
s3_object.presigned_url('get', expires_in: 30)
end
end
end
app/models/decorators/solidus/product.rb
module Decorators
module Product
extend ActiveSupport::Concern
included do
has_many :files, as: :viewable,
dependent: :destroy,
class_name: 'MyAppScope::File'
end
end
end
Spree::Product.include(Decorators::Product)
app/models/decorators/solidus/user.rb
module Decorators
module Solidus
module User
extend ActiveSupport::Concern
included do
has_many :products,
-> { unscope(:order).distinct },
through: :orders
has_many :files,
-> { distinct },
through: :products
end
end
end
end
Spree::User.include(Decorators::Solidus::User)
lib/spree/permission_sets/file_ability.rb
module Spree
module PermissionSets
class FileAbility < PermissionSets::Base
def activate!
can :download,
MyAppScope::File,
id: files_ids
end
private
def files_ids
@files_ids ||= user.file_ids
end
end
end
end
app/controllers/my_app_scope/files_controller.rb
module MyAppScope
class FilesController < Spree::StoreController
def download
authorize!(:download, file)
redirect_to file.download_url
end
private
def file
@file ||= MyAppScope::File.find(params[:id])
end
end
end
config/routes.rb
Rails.application.routes.draw do
scope module: 'my_app_scope' do
get 'download/:id', to: 'files#download', as: :download
end
end
<%- product.files.each do |file| %>
<li><%= link_to file.attachment_file_name, download_path(file), target: '_blank' %></li>
<% end %>
config/routes.rb
Rails.application.routes.draw do
...
end
Spree::Core::Engine.routes.draw do
namespace :admin do
resources :products do
resources :files
end
end
end
app/overrides/spree/admin/shared/_product_tabs/add_files_tab.html.erb.deface
<!-- insert_bottom "[data-hook=admin_product_tabs]"-->
<%= content_tag :li, class: ('active' if current == 'Files') do %>
<%= link_to 'Files', admin_product_files_path(@product) %>
<% end %>
app/views/spree/admin/files/index.html.erb
<%= render partial: 'spree/admin/shared/product_tabs', locals: { current: 'Files' } %>
<% admin_breadcrumb('Files') %>
<div id="new_file">
<%= render 'new', product: @product %>
</div>
<table class="index" id="files-table" data-hook="files_table">
<thead>
<tr data-hook="files_header">
<th>File Name</th>
<th>File Type</th>
<th>Download Url</th>
<th class="actions"></th>
</tr>
</thead>
<tbody>
<%= render partial: 'file', collection: @product.files, as: :file, locals: { product: @product } %>
</tbody>
</table>
app/views/spree/admin/files/_file.html.erb
<%- return unless file.id %>
<tr id="<%= spree_dom_id file %>" data-hook="files_row">
<td><%= file.attachment_file_name %></td>
<td><%= file.attachment_content_type %></td>
<td><%= link_to 'Download', file.download_url %></td>
<td class="actions">
<%= link_to_delete file,
name: 'Delete File',
url: admin_product_file_path(@product, file),
method: :delete,
no_text: true %>
</td>
</tr>
app/views/spree/admin/files/_new.html.erb
<%= form_with model: product.files.build, url: admin_product_files_path, html: { multipart: true } do |f| %>
<fieldset data-hook="new_file">
<legend align="center"><%= t('spree.new_file') %></legend>
<%= f.label :attachment %><br>
<%= f.file_field :attachment %>
<%= f.hidden_field :viewable_id %>
<div class="form-buttons filter-actions actions" data-hook="buttons">
<%= button_tag 'Upload', class: 'btn btn-primary' %>
<%= link_to t('spree.actions.cancel'), admin_product_files_url(@product), id: 'cancel_link', class: 'button' %>
</div>
</fieldset>
<% end %>
app/views/admin/files/create.js.erb
$('#my_app_scope_file_attachment').val('')
$('#files-table > tbody').append('<%= j render "file", file: @file %>')
app/controllers/spree/admin/files_controller.rb
module Spree
module Admin
class FilesController < ResourceController
before_action :load_data
create.before :set_viewable
private
def load_data
@product = Spree::Product.friendly.find(params[:product_id])
end
def model_class
MyAppScope::File
end
def set_viewable
@file.viewable_type = 'Spree::Product'
@file.viewable_id = file_params[:viewable_id]
@file.attachment = file_params[:attachment]
@file.public = file_params[:public]
end
def file_params
params.require(:my_app_scope_file)
end
end
end
end