From 10d80c654863c309dfd6f7e5716f006d64c56cd1 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 9 Apr 2020 11:02:07 +0200 Subject: [PATCH] Support file uploads This allows submissions to store file uploads --- app/controllers/file_uploads_controller.rb | 8 +++++ app/controllers/submissions_controller.rb | 8 +++-- app/jobs/submission_append_job.rb | 8 +++++ app/models/submission.rb | 30 +++++++++++++------ config/initializers/host.rb | 1 + config/routes.rb | 3 ++ ...te_active_storage_tables.active_storage.rb | 27 +++++++++++++++++ ...00409001610_add_metadata_to_submissions.rb | 6 ++++ db/schema.rb | 30 +++++++++++++++++-- 9 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 app/controllers/file_uploads_controller.rb create mode 100644 app/jobs/submission_append_job.rb create mode 100644 config/initializers/host.rb create mode 100644 db/migrate/20200406221804_create_active_storage_tables.active_storage.rb create mode 100644 db/migrate/20200409001610_add_metadata_to_submissions.rb diff --git a/app/controllers/file_uploads_controller.rb b/app/controllers/file_uploads_controller.rb new file mode 100644 index 0000000..8ad47cc --- /dev/null +++ b/app/controllers/file_uploads_controller.rb @@ -0,0 +1,8 @@ +class FileUploadsController < ApplicationController + def show + @form = Form.find_by!(token: params[:form_id]) + @submission = @form.submissions.find(params[:submission_id]) + @file_upload = @submission.files_attachments.find(params[:id]) + redirect_to url_for(@file_upload) + end +end diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 66412fa..518a3eb 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -5,14 +5,18 @@ class SubmissionsController < ApplicationController def create @form = Form.find_by!(token: params[:form_id]) - @submission = @form.submissions.build(data: data_params) + # create a new submission object. we need a persisted submission to be able to process + # potential the data - to be able to create URLs to uploads which is added as link to the table + @submission = @form.submissions.create(remote_ip: request.remote_ip, referrer: request.referer) + # processes the submitted data and saves the submission + @submission.process_data(data_params) respond_to do |format| if @submission.save format.html { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? } format.json { render(json: { success: true, data: @submission.data }) } else - format.html + format.html { redirect_to(@form.thank_you_url) if @form.thank_you_url.present? } format.json { render(json: { error: @submission.errors }, status: 422) } end end diff --git a/app/jobs/submission_append_job.rb b/app/jobs/submission_append_job.rb new file mode 100644 index 0000000..fe55176 --- /dev/null +++ b/app/jobs/submission_append_job.rb @@ -0,0 +1,8 @@ +class SubmissionAppendJob < ApplicationJob + queue_as :default + + def perform(submission_id) + submission = Submission.find(submission_id) + submission.append_to_spreadsheet + end +end diff --git a/app/models/submission.rb b/app/models/submission.rb index 2118688..13f03d1 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -1,17 +1,20 @@ class Submission < ApplicationRecord belongs_to :form - after_create :append_to_spreadsheet - validates_presence_of :data + has_many :file_uploads, dependent: :destroy + has_many_attached :files - def data=(value) - sanitized_data = {} - value.each do |key, value| - sanitized_data[key] = submission_value(value) + validates_presence_of :data, if: :appended_at? + + def process_data(submitted_data) + processed_data = {} + submitted_data.each do |key, value| + processed_data[key] = submission_value_for(value) end - write_attribute(:data, sanitized_data) + update_attribute(:data, processed_data) + SubmissionAppendJob.perform_later(self.id) end - def submission_value(value) + def submission_value_for(value) case value when Array value.join(', ') @@ -20,7 +23,16 @@ class Submission < ApplicationRecord when 'tinyforms_now' Time.now.utc.to_formatted_s(:rfc822) when ActionDispatch::Http::UploadedFile - '' + # manually create the ActiveStorage attachment because we need the ID of the Attachment to create the URL + # first the file needs to be uplaoded then we can create an Attachment + # The CreateOne mainly handles the uplaod and the creation of the blob for us + # `files` is the name from `has_many_attached :files` + create_one = ActiveStorage::Attached::Changes::CreateOne.new('files', self, value) + create_one.upload + attachment = ActiveStorage::Attachment.new(record: self, name: 'files', blob: create_one.blob) + attachment.save + # return the URL that we use to show in the Spreadsheet + Rails.application.routes.url_helpers.file_upload_url(form_id: form, submission_id: self, id: attachment.id, host: DEFAULT_HOST) else value.to_s end diff --git a/config/initializers/host.rb b/config/initializers/host.rb new file mode 100644 index 0000000..e4d2eaa --- /dev/null +++ b/config/initializers/host.rb @@ -0,0 +1 @@ +DEFAULT_HOST = ENV['DEFAULT_HOST'] || 'localhost:3000' diff --git a/config/routes.rb b/config/routes.rb index 39fe148..e647883 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,10 @@ Rails.application.routes.draw do resources :forms do resources :submissions end + # short link for submission file uploads + get '/s/:form_id/:submission_id/:id' => 'file_uploads#show', as: :file_upload + # form post url to save new submissions post '/s/:form_id' => 'submissions#create', as: :submission get '/login' => 'sessions#new', as: :login diff --git a/db/migrate/20200406221804_create_active_storage_tables.active_storage.rb b/db/migrate/20200406221804_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..0b2ce25 --- /dev/null +++ b/db/migrate/20200406221804_create_active_storage_tables.active_storage.rb @@ -0,0 +1,27 @@ +# This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change + create_table :active_storage_blobs do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.bigint :byte_size, null: false + t.string :checksum, null: false + t.datetime :created_at, null: false + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false + t.references :blob, null: false + + t.datetime :created_at, null: false + + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end +end diff --git a/db/migrate/20200409001610_add_metadata_to_submissions.rb b/db/migrate/20200409001610_add_metadata_to_submissions.rb new file mode 100644 index 0000000..cb9faf4 --- /dev/null +++ b/db/migrate/20200409001610_add_metadata_to_submissions.rb @@ -0,0 +1,6 @@ +class AddMetadataToSubmissions < ActiveRecord::Migration[6.0] + def change + add_column :submissions, :remote_ip, :string + add_column :submissions, :referrer, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 359fb4f..4e875ea 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,18 +10,39 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_04_08_212150) do +ActiveRecord::Schema.define(version: 2020_04_09_001610) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + create_table "authentications", force: :cascade do |t| t.integer "user_id" - t.string "access_token" - t.string "refresh_token" t.datetime "expires_at" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.text "access_token_ciphertext" + t.text "refresh_token_ciphertext" end create_table "forms", force: :cascade do |t| @@ -40,6 +61,8 @@ ActiveRecord::Schema.define(version: 2020_04_08_212150) do t.datetime "appended_at" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.string "remote_ip" + t.string "referrer" end create_table "users", force: :cascade do |t| @@ -50,4 +73,5 @@ ActiveRecord::Schema.define(version: 2020_04_08_212150) do t.datetime "updated_at", precision: 6, null: false end + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" end