From aaf841518866b34d769d9a951a389d1eef70d6e7 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 5 Jul 2017 15:18:50 +0200 Subject: [PATCH] Add attachments --- README.md | 17 +------------- lib/active_vault/attachment.rb | 27 ++++++++++++++++++++++ lib/active_vault/attachments.rb | 30 +++++++++++++++++++++++++ lib/active_vault/migration.rb | 27 ++++++++++++++++------ lib/active_vault/railtie.rb | 8 +++++++ test/attachments_test.rb | 27 ++++++++++++++++++++++ test/database/create_users_migration.rb | 7 ++++++ test/database/setup.rb | 4 +++- test/test_helper.rb | 10 ++++++++- 9 files changed, 132 insertions(+), 25 deletions(-) create mode 100644 lib/active_vault/attachment.rb create mode 100644 lib/active_vault/attachments.rb create mode 100644 test/attachments_test.rb create mode 100644 test/database/create_users_migration.rb diff --git a/README.md b/README.md index 5160acda56..a72c79948f 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,11 @@ ```ruby class Person < ApplicationRecord - has_one :avatar -end - -class Avatar < ApplicationRecord - belongs_to :person - belongs_to :image, class_name: 'ActiveVault::Blob' - - has_file :image + has_file :avatar end avatar.image.url(expires_in: 5.minutes) - -class ActiveVault::DownloadsController < ActionController::Base - def show - head :ok, ActiveVault::Blob.locate(params[:id]).download_headers - end -end - - class AvatarsController < ApplicationController def create # @avatar = Avatar.create \ diff --git a/lib/active_vault/attachment.rb b/lib/active_vault/attachment.rb new file mode 100644 index 0000000000..eb108e9cbb --- /dev/null +++ b/lib/active_vault/attachment.rb @@ -0,0 +1,27 @@ +require "active_vault/blob" +require "global_id" +require "active_support/core_ext/module/delegation" + +# Schema: id, record_gid, blob_id, created_at +class ActiveVault::Attachment < ActiveRecord::Base + self.table_name = "active_vault_attachments" + + belongs_to :blob, class_name: "ActiveVault::Blob" + + delegate_missing_to :blob + + def record + @record ||= GlobalID::Locator.locate(record_gid) + end + + def record=(record) + @record = record + self.record_gid = record&.to_gid + end + + def purge + blob.purge + destroy + record.public_send "#{name}=", nil + end +end diff --git a/lib/active_vault/attachments.rb b/lib/active_vault/attachments.rb new file mode 100644 index 0000000000..c66c142650 --- /dev/null +++ b/lib/active_vault/attachments.rb @@ -0,0 +1,30 @@ +require "active_vault/attachment" +require "action_dispatch/http/upload" + +module ActiveVault::Attachments + def has_file(name) + define_method(name) do + (@active_vault_attachments ||= {})[name] ||= + ActiveVault::Attachment.find_by(record_gid: to_gid.to_s, name: name)&.tap { |a| a.record = self } + end + + define_method(:"#{name}=") do |attachable| + case attachable + when ActiveVault::Blob + blob = attachable + when ActionDispatch::Http::UploadedFile + blob = ActiveVault::Blob.create_after_upload! \ + io: attachable.open, + filename: attachable.original_filename, + content_type: attachable.content_type + when Hash + blob = ActiveVault::Blob.create_after_upload!(attachable) + when NilClass + blob = nil + end + + (@active_vault_attachments ||= {})[name] = blob ? + ActiveVault::Attachment.create!(record_gid: to_gid.to_s, name: name, blob: blob)&.tap { |a| a.record = self } : nil + end + end +end diff --git a/lib/active_vault/migration.rb b/lib/active_vault/migration.rb index b3c66428ce..985d26d1b9 100644 --- a/lib/active_vault/migration.rb +++ b/lib/active_vault/migration.rb @@ -1,15 +1,28 @@ -class ActiveVault::CreateBlobs < ActiveRecord::Migration[5.1] +class ActiveVault::CreateTables < ActiveRecord::Migration[5.1] def change - t.string :key - t.string :filename - t.string :content_type - t.text :metadata create_table :active_vault_blobs do |t| + t.string :key + t.string :filename + t.string :content_type + t.text :metadata t.integer :byte_size - t.string :checksum - t.time :created_at + t.string :checksum + t.time :created_at t.index [ :key ], unique: true end + + create_table :active_vault_attachments do |t| + t.string :name + t.string :record_gid + t.integer :blob_id + + t.time :created_at + + t.index :record_gid + t.index :blob_id + t.index [ :record_gid, :name ] + t.index [ :record_gid, :blob_id ], unique: true + end end end diff --git a/lib/active_vault/railtie.rb b/lib/active_vault/railtie.rb index c254f4c77c..f1c5740aa5 100644 --- a/lib/active_vault/railtie.rb +++ b/lib/active_vault/railtie.rb @@ -15,5 +15,13 @@ module ActiveVault end end end + + initializer "action_file.attachments" do + require "active_vault/attachments" + + ActiveSupport.on_load(:active_record) do + extend ActiveVault::Attachments + end + end end end diff --git a/test/attachments_test.rb b/test/attachments_test.rb new file mode 100644 index 0000000000..970804b68f --- /dev/null +++ b/test/attachments_test.rb @@ -0,0 +1,27 @@ +require "test_helper" +require "database/setup" +require "active_vault/blob" + +# ActiveRecord::Base.logger = Logger.new(STDOUT) + +class User < ActiveRecord::Base + has_file :avatar +end + +class ActiveVault::AttachmentsTest < ActiveSupport::TestCase + setup { @user = User.create!(name: "DHH") } + + test "create attachment from existing blob" do + @user.avatar = create_blob filename: "funky.jpg" + assert_equal "funky.jpg", @user.avatar.filename.to_s + end + + test "purge attached blob" do + @user.avatar = create_blob filename: "funky.jpg" + avatar_key = @user.avatar.key + + @user.avatar.purge + assert_nil @user.avatar + assert_not ActiveVault::Blob.site.exist?(avatar_key) + end +end diff --git a/test/database/create_users_migration.rb b/test/database/create_users_migration.rb new file mode 100644 index 0000000000..38dcdc129b --- /dev/null +++ b/test/database/create_users_migration.rb @@ -0,0 +1,7 @@ +class ActiveVault::CreateUsers < ActiveRecord::Migration[5.1] + def change + create_table :users do |t| + t.string :name + end + end +end diff --git a/test/database/setup.rb b/test/database/setup.rb index bc6e8b9ec1..7373d72237 100644 --- a/test/database/setup.rb +++ b/test/database/setup.rb @@ -1,4 +1,6 @@ require "active_vault/migration" +require_relative "create_users_migration" ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') -ActiveVault::CreateBlobs.migrate(:up) +ActiveVault::CreateTables.migrate(:up) +ActiveVault::CreateUsers.migrate(:up) diff --git a/test/test_helper.rb b/test/test_helper.rb index 96ef58b73f..29bd31e62f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -17,4 +17,12 @@ class ActiveSupport::TestCase def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") ActiveVault::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type end -end \ No newline at end of file +end + + +require "active_vault/attachments" +ActiveRecord::Base.send :extend, ActiveVault::Attachments + +require "global_id" +GlobalID.app = "ActiveVaultExampleApp" +ActiveRecord::Base.send :include, GlobalID::Identification