Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Code: Using Spree::Asset to add Files to Spree::Product

Config

File Model

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

Product Decorator

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)

User Decorator

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)

Files PermissionSet

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

Frontend

Files Controller

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

Files Routes

config/routes.rb

Rails.application.routes.draw do
  scope module: 'my_app_scope' do
    get 'download/:id', to: 'files#download', as: :download
  end
end

Listing Product Files

<%- product.files.each do |file| %>
  <li><%= link_to file.attachment_file_name, download_path(file), target: '_blank' %></li>
<% end %>

Backend

Adding Route to Solidus Engine

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

Product Tab Deface

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 %>

Product Files Index

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>

Product File Item

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>

Product File New Form

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 %>

Product File Create JS

app/views/admin/files/create.js.erb

$('#my_app_scope_file_attachment').val('')
$('#files-table > tbody').append('<%= j render "file", file: @file %>')

Product Files Controller

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment