From 1966c188cfb06b39a47082e2f6c6e33a43668ae5 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 11 Jul 2017 18:53:17 +0200 Subject: Very incomplete first stab --- Gemfile | 1 + Gemfile.lock | 2 + .../controllers/variants_controller.rb | 22 ++++++++ lib/active_storage/engine.rb | 59 +++++++++++++-------- lib/active_storage/routes.rb | 1 + lib/active_storage/variant.rb | 52 ++++++++++++++++++ lib/active_storage/verified_key_with_expiration.rb | 2 +- test/fixtures/files/racecar.jpg | Bin 0 -> 1124062 bytes test/test_helper.rb | 3 ++ test/variation_test.rb | 16 ++++++ 10 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 lib/active_storage/controllers/variants_controller.rb create mode 100644 lib/active_storage/variant.rb create mode 100644 test/fixtures/files/racecar.jpg create mode 100644 test/variation_test.rb diff --git a/Gemfile b/Gemfile index a757a5c793..c4ecf50bbe 100644 --- a/Gemfile +++ b/Gemfile @@ -10,3 +10,4 @@ gem 'httparty' gem 'aws-sdk', '~> 2', require: false gem 'google-cloud-storage', require: false +gem 'mini_magick' diff --git a/Gemfile.lock b/Gemfile.lock index 7e4c6f78f2..8d0c4c7937 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -99,6 +99,7 @@ GEM mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) + mini_magick (4.8.0) mini_portile2 (2.1.0) minitest (5.10.2) multi_json (1.12.1) @@ -143,6 +144,7 @@ DEPENDENCIES byebug google-cloud-storage httparty + mini_magick rake sqlite3 diff --git a/lib/active_storage/controllers/variants_controller.rb b/lib/active_storage/controllers/variants_controller.rb new file mode 100644 index 0000000000..24cee16e80 --- /dev/null +++ b/lib/active_storage/controllers/variants_controller.rb @@ -0,0 +1,22 @@ +require "action_controller" +require "active_storage/blob" + +class ActiveStorage::Controllers::VariantsController < ActionController::Base + def show + if blob_key = decode_verified_key + variant = ActiveStorage::Variant.lookup(blob_key: blob_key, variation_key: params[:variation_key]) + redirect_to variant.url + else + head :not_found + end + end + + private + def decode_verified_key + ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_key]) + end + + def disposition_param + params[:disposition].presence_in(%w( inline attachment )) || 'inline' + end +end diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index c251f522c6..8918b179e0 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -33,29 +33,42 @@ module ActiveStorage end end - config.after_initialize do |app| - if config_choice = app.config.active_storage.service - config_file = Pathname.new(Rails.root.join("config/storage_services.yml")) - raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? - - require "yaml" - require "erb" - - configs = - begin - YAML.load(ERB.new(config_file.read).result) || {} - rescue Psych::SyntaxError => e - raise "YAML syntax error occurred while parsing #{config_file}. " \ - "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ - "Error: #{e.message}" - end - - ActiveStorage::Blob.service = - begin - ActiveStorage::Service.configure config_choice, configs - rescue => e - raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace - end + initializer "active_storage.verifiers" do + require "active_storage/verified_key_with_expiration" + require "active_storage/variant" + + config.after_initialize do |app| + ActiveStorage::VerifiedKeyWithExpiration.verifier = \ + ActiveStorage::Variant.verifier = \ + Rails.application.message_verifier('ActiveStorage') + end + end + + initializer "active_storage.services" do + config.after_initialize do |app| + if config_choice = app.config.active_storage.service + config_file = Pathname.new(Rails.root.join("config/storage_services.yml")) + raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? + + require "yaml" + require "erb" + + configs = + begin + YAML.load(ERB.new(config_file.read).result) || {} + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{config_file}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + end + + ActiveStorage::Blob.service = + begin + ActiveStorage::Service.configure config_choice, configs + rescue => e + raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace + end + end end end end diff --git a/lib/active_storage/routes.rb b/lib/active_storage/routes.rb index 748427a776..fade234ad3 100644 --- a/lib/active_storage/routes.rb +++ b/lib/active_storage/routes.rb @@ -1,2 +1,3 @@ get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob +get "/rails/active_storage/variants/:encoded_key/:encoded_transformation/*filename" => "active_storage/controllers/variants#show", as: :rails_blob_variant post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb new file mode 100644 index 0000000000..9b9dad43da --- /dev/null +++ b/lib/active_storage/variant.rb @@ -0,0 +1,52 @@ +require "active_storage/blob" +require "mini_magick" + +class ActiveStorage::Variant + class_attribute :verifier + + attr_reader :blob, :variation + delegate :service, to: :blob + + def self.lookup(blob_key:, variation_key:) + new ActiveStorage::Blob.find_by!(key: blob_key), variation: verifier.verify(variation_key) + end + + def self.encode_key(variation) + verifier.generate(variation) + end + + def initialize(blob, variation:) + @blob, @variation = blob, variation + end + + def url(expires_in: 5.minutes, disposition: :inline) + perform unless exist? + service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename + end + + def key + verifier.generate(variation) + end + + private + def perform + upload_variant transform(download_blob) + end + + def download_blob + service.download(blob.key) + end + + def upload_variant(variation) + service.upload key, variation + end + + def transform(io) + # FIXME: Actually do a variant based on the variation + File.open MiniMagick::Image.read(io).resize("500x500").path + end + + def exist? + service.exist?(key) + end +end diff --git a/lib/active_storage/verified_key_with_expiration.rb b/lib/active_storage/verified_key_with_expiration.rb index 8708106735..4a46483db5 100644 --- a/lib/active_storage/verified_key_with_expiration.rb +++ b/lib/active_storage/verified_key_with_expiration.rb @@ -1,5 +1,5 @@ class ActiveStorage::VerifiedKeyWithExpiration - class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveStorage') : nil + class_attribute :verifier class << self def encode(key, expires_in: nil) diff --git a/test/fixtures/files/racecar.jpg b/test/fixtures/files/racecar.jpg new file mode 100644 index 0000000000..934b4caa22 Binary files /dev/null and b/test/fixtures/files/racecar.jpg differ diff --git a/test/test_helper.rb b/test/test_helper.rb index 03593b12c7..878ce8391c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -24,6 +24,9 @@ ActiveStorage::Service.logger = ActiveSupport::Logger.new(STDOUT) require "active_storage/verified_key_with_expiration" ActiveStorage::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") +require "active_storage/variant" +ActiveStorage::Variant.verifier = ActiveSupport::MessageVerifier.new("Testing") + class ActiveSupport::TestCase private def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") diff --git a/test/variation_test.rb b/test/variation_test.rb new file mode 100644 index 0000000000..3b05095292 --- /dev/null +++ b/test/variation_test.rb @@ -0,0 +1,16 @@ +require "test_helper" +require "database/setup" +require "active_storage/variant" + +class ActiveStorage::VariationTest < ActiveSupport::TestCase + test "square variation" do + blob = ActiveStorage::Blob.create_after_upload! \ + io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)), filename: "racecar.jpg", content_type: "image/jpeg" + + variation_key = ActiveStorage::Variant.encode_key(resize: "500x500") + + variant = ActiveStorage::Variant.lookup(blob_key: blob.key, variation_key: variation_key) + + assert_match /racecar.jpg/, variant.url + end +end -- cgit v1.2.3 From 710957b20a24d0c62219cb7cc229c52905d74b3d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:04:54 -0500 Subject: Double confetti --- Gemfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Gemfile b/Gemfile index adc1e320e2..953b85ccfe 100644 --- a/Gemfile +++ b/Gemfile @@ -18,8 +18,6 @@ gem "httparty" gem "aws-sdk", "~> 2", require: false gem "google-cloud-storage", "~> 1.3", require: false -gem 'aws-sdk', '~> 2', require: false -gem 'google-cloud-storage', require: false gem 'mini_magick' gem "rubocop", require: false -- cgit v1.2.3 From 66d94ed78dced24cdd186e8bb5877cc6d43f5da8 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:05:03 -0500 Subject: Easier access to the variant of a blob --- lib/active_storage/blob.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/active_storage/blob.rb b/lib/active_storage/blob.rb index 1a15361747..a9d9b8771c 100644 --- a/lib/active_storage/blob.rb +++ b/lib/active_storage/blob.rb @@ -1,6 +1,7 @@ require "active_storage/service" require "active_storage/filename" require "active_storage/purge_job" +require "active_storage/variant" # Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at class ActiveStorage::Blob < ActiveRecord::Base @@ -40,6 +41,10 @@ class ActiveStorage::Blob < ActiveRecord::Base ActiveStorage::Filename.new(self[:filename]) end + def variant(variation) + ActiveStorage::Variant.new(self, variation: variation) + end + def url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: filename end -- cgit v1.2.3 From 5dbe5eaeb84b470624f1a2785315ddd4f7b1a4e3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:05:18 -0500 Subject: Follow AR like naming of factory method --- lib/active_storage/controllers/variants_controller.rb | 2 +- lib/active_storage/variant.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/controllers/variants_controller.rb b/lib/active_storage/controllers/variants_controller.rb index 24cee16e80..094f94e706 100644 --- a/lib/active_storage/controllers/variants_controller.rb +++ b/lib/active_storage/controllers/variants_controller.rb @@ -4,7 +4,7 @@ require "active_storage/blob" class ActiveStorage::Controllers::VariantsController < ActionController::Base def show if blob_key = decode_verified_key - variant = ActiveStorage::Variant.lookup(blob_key: blob_key, variation_key: params[:variation_key]) + variant = ActiveStorage::Variant.find_or_create_by(blob_key: blob_key, variation_key: params[:variation_key]) redirect_to variant.url else head :not_found diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 9b9dad43da..f005454b00 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -7,7 +7,7 @@ class ActiveStorage::Variant attr_reader :blob, :variation delegate :service, to: :blob - def self.lookup(blob_key:, variation_key:) + def self.find_or_create_by(blob_key:, variation_key:) new ActiveStorage::Blob.find_by!(key: blob_key), variation: verifier.verify(variation_key) end -- cgit v1.2.3 From 76395e3c1b997da7b3853b1b3e94b712b1a29ecf Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:05:40 -0500 Subject: Do real transformations in a safe way --- lib/active_storage/variant.rb | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index f005454b00..62262c7790 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -1,9 +1,15 @@ require "active_storage/blob" +require "active_support/core_ext/object/inclusion" require "mini_magick" class ActiveStorage::Variant class_attribute :verifier + ALLOWED_TRANSFORMATIONS = %i( + resize rotate format flip fill monochrome orient quality roll scale sharpen shave shear size thumbnail + transparent transpose transverse trim background bordercolor compress crop + ) + attr_reader :blob, :variation delegate :service, to: :blob @@ -42,11 +48,21 @@ class ActiveStorage::Variant end def transform(io) - # FIXME: Actually do a variant based on the variation - File.open MiniMagick::Image.read(io).resize("500x500").path + File.open \ + MiniMagick::Image.read(io).tap { |transforming_image| + variation.each do |(method, argument)| + if method = allowed_transformational_method(method.to_sym) + if argument.present? + transforming_image.public_send(method, argument) + else + transforming_image.public_send(method) + end + end + end + }.path end - def exist? - service.exist?(key) + def allowed_transformational_method(method) + method.presence_in(ALLOWED_TRANSFORMATIONS) end end -- cgit v1.2.3 From f1523ab39e38bdc031c3bfb61ed4b6decd23ffcd Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:06:00 -0500 Subject: Use a unique blob variant key for storage --- lib/active_storage/variant.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 62262c7790..3053f44211 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -27,7 +27,7 @@ class ActiveStorage::Variant def url(expires_in: 5.minutes, disposition: :inline) perform unless exist? - service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename + service.url blob_variant_key, expires_in: expires_in, disposition: disposition, filename: blob.filename end def key @@ -35,6 +35,10 @@ class ActiveStorage::Variant end private + def exist? + service.exist?(blob_variant_key) + end + def perform upload_variant transform(download_blob) end @@ -44,7 +48,11 @@ class ActiveStorage::Variant end def upload_variant(variation) - service.upload key, variation + service.upload blob_variant_key, variation + end + + def blob_variant_key + "variants/#{blob.key}/#{key}" end def transform(io) -- cgit v1.2.3 From dda013050fe559e2e9432ae836c1239eac48fbce Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 14:06:08 -0500 Subject: Use the direct accessor --- test/variation_test.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/variation_test.rb b/test/variation_test.rb index 3b05095292..bc47244468 100644 --- a/test/variation_test.rb +++ b/test/variation_test.rb @@ -7,9 +7,7 @@ class ActiveStorage::VariationTest < ActiveSupport::TestCase blob = ActiveStorage::Blob.create_after_upload! \ io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)), filename: "racecar.jpg", content_type: "image/jpeg" - variation_key = ActiveStorage::Variant.encode_key(resize: "500x500") - - variant = ActiveStorage::Variant.lookup(blob_key: blob.key, variation_key: variation_key) + variant = blob.variant(resize: "100x100") assert_match /racecar.jpg/, variant.url end -- cgit v1.2.3 From 1a9026b485b9b1da0f34c526d4c901406074c508 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:33:31 -0500 Subject: Extract routes.rb to engine location for auto configuration --- config/routes.rb | 13 +++++++++++++ lib/active_storage/engine.rb | 11 ----------- lib/active_storage/routes.rb | 3 --- test/test_helper.rb | 11 ----------- 4 files changed, 13 insertions(+), 25 deletions(-) create mode 100644 config/routes.rb delete mode 100644 lib/active_storage/routes.rb diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000000..9057eadc8a --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,13 @@ +Rails.application.routes.draw do + get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob + get "/rails/active_storage/variants/:encoded_blob_key/:encoded_variant_key/*filename" => "active_storage/variants#show", as: :rails_blob_variant + post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads + + resolve 'ActiveStorage::Variant' do |variant| + encoded_blob_key = ActiveStorage::VerifiedKeyWithExpiration.encode(variant.blob.key) + encoded_variant_key = ActiveStorage::Variant.encode_key(variant.variation) + filename = variant.blob.filename + + route_for(:rails_blob_variant, encoded_blob_key, encoded_variant_key, filename) + end +end diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index 8918b179e0..b04925a9fd 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -14,17 +14,6 @@ module ActiveStorage end end - initializer "active_storage.routes" do - require "active_storage/disk_controller" - require "active_storage/direct_uploads_controller" - - config.after_initialize do |app| - app.routes.prepend do - eval(File.read(File.expand_path("../routes.rb", __FILE__))) - end - end - end - initializer "active_storage.attached" do require "active_storage/attached" diff --git a/lib/active_storage/routes.rb b/lib/active_storage/routes.rb deleted file mode 100644 index fade234ad3..0000000000 --- a/lib/active_storage/routes.rb +++ /dev/null @@ -1,3 +0,0 @@ -get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob -get "/rails/active_storage/variants/:encoded_key/:encoded_transformation/*filename" => "active_storage/controllers/variants#show", as: :rails_blob_variant -post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads diff --git a/test/test_helper.rb b/test/test_helper.rb index 878ce8391c..6081fc1bcf 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -34,17 +34,6 @@ class ActiveSupport::TestCase end end -require "action_controller" -require "action_controller/test_case" - -class ActionController::TestCase - Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| - routes.draw do - eval(File.read(File.expand_path("../../lib/active_storage/routes.rb", __FILE__))) - end - end -end - require "active_storage/attached" ActiveRecord::Base.send :extend, ActiveStorage::Attached::Macros -- cgit v1.2.3 From 1c85eecee02ebf0c0148de807fbb1a9e9573af8a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:34:13 -0500 Subject: Move controllers to default engine location for auto loading --- Rakefile | 1 + app/controllers/active_storage/disk_controller.rb | 38 ++++++++++++++++++++++ .../active_storage/variants_controller.rb | 22 +++++++++++++ lib/active_storage/direct_uploads_controller.rb | 14 -------- lib/active_storage/disk_controller.rb | 38 ---------------------- 5 files changed, 61 insertions(+), 52 deletions(-) create mode 100644 app/controllers/active_storage/disk_controller.rb create mode 100644 app/controllers/active_storage/variants_controller.rb delete mode 100644 lib/active_storage/direct_uploads_controller.rb delete mode 100644 lib/active_storage/disk_controller.rb diff --git a/Rakefile b/Rakefile index f0baf50163..a41e07f373 100644 --- a/Rakefile +++ b/Rakefile @@ -3,6 +3,7 @@ require "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new do |test| + test.libs << "app/controllers" test.libs << "test" test.test_files = FileList["test/**/*_test.rb"] test.warning = false diff --git a/app/controllers/active_storage/disk_controller.rb b/app/controllers/active_storage/disk_controller.rb new file mode 100644 index 0000000000..16a295d00d --- /dev/null +++ b/app/controllers/active_storage/disk_controller.rb @@ -0,0 +1,38 @@ +require "action_controller" +require "active_storage/blob" +require "active_storage/verified_key_with_expiration" + +require "active_support/core_ext/object/inclusion" + +# This controller is a wrapper around local file downloading. It allows you to +# make abstraction of the URL generation logic and to serve files with expiry +# if you are using the +Disk+ service. +# +# By default, mounting the Active Storage engine inside your application will +# define a +/rails/blobs/:encoded_key/*filename+ route that will reference this +# controller's +show+ action and will be used to serve local files. +# +# A URL for an attachment can be generated through its +#url+ method, that +# will use the aforementioned route. +class ActiveStorage::DiskController < ActionController::Base + def show + if key = decode_verified_key + blob = ActiveStorage::Blob.find_by!(key: key) + + if stale?(etag: blob.checksum) + send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param + end + else + head :not_found + end + end + + private + def decode_verified_key + ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_key]) + end + + def disposition_param + params[:disposition].presence_in(%w( inline attachment )) || "inline" + end +end diff --git a/app/controllers/active_storage/variants_controller.rb b/app/controllers/active_storage/variants_controller.rb new file mode 100644 index 0000000000..05685dca17 --- /dev/null +++ b/app/controllers/active_storage/variants_controller.rb @@ -0,0 +1,22 @@ +class ActiveStorage::VariantsController < ActionController::Base + def show + if blob_key = decode_verified_blob_key + redirect_to processed_variant_for(blob_key).url(disposition: disposition_param) + else + head :not_found + end + end + + private + def decode_verified_blob_key + ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_blob_key]) + end + + def processed_variant_for(blob_key) + ActiveStorage::Variant.find_or_process_by!(blob_key: blob_key, encoded_variant_key: params[:encoded_variant_key]) + end + + def disposition_param + params[:disposition].presence_in(%w( inline attachment )) || 'inline' + end +end diff --git a/lib/active_storage/direct_uploads_controller.rb b/lib/active_storage/direct_uploads_controller.rb deleted file mode 100644 index 99ff27f903..0000000000 --- a/lib/active_storage/direct_uploads_controller.rb +++ /dev/null @@ -1,14 +0,0 @@ -require "action_controller" -require "active_storage/blob" - -class ActiveStorage::DirectUploadsController < ActionController::Base - def create - blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args) - render json: { url: blob.url_for_direct_upload, sgid: blob.to_sgid.to_param } - end - - private - def blob_args - params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, :metadata).to_h.symbolize_keys - end -end diff --git a/lib/active_storage/disk_controller.rb b/lib/active_storage/disk_controller.rb deleted file mode 100644 index 16a295d00d..0000000000 --- a/lib/active_storage/disk_controller.rb +++ /dev/null @@ -1,38 +0,0 @@ -require "action_controller" -require "active_storage/blob" -require "active_storage/verified_key_with_expiration" - -require "active_support/core_ext/object/inclusion" - -# This controller is a wrapper around local file downloading. It allows you to -# make abstraction of the URL generation logic and to serve files with expiry -# if you are using the +Disk+ service. -# -# By default, mounting the Active Storage engine inside your application will -# define a +/rails/blobs/:encoded_key/*filename+ route that will reference this -# controller's +show+ action and will be used to serve local files. -# -# A URL for an attachment can be generated through its +#url+ method, that -# will use the aforementioned route. -class ActiveStorage::DiskController < ActionController::Base - def show - if key = decode_verified_key - blob = ActiveStorage::Blob.find_by!(key: key) - - if stale?(etag: blob.checksum) - send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param - end - else - head :not_found - end - end - - private - def decode_verified_key - ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_key]) - end - - def disposition_param - params[:disposition].presence_in(%w( inline attachment )) || "inline" - end -end -- cgit v1.2.3 From 6c2cef21ce67f83bff45ce76c0370b03be11451f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:34:32 -0500 Subject: Fix-up variants controller --- .../active_storage/direct_uploads_controller.rb | 11 +++++++++++ .../controllers/variants_controller.rb | 22 ---------------------- 2 files changed, 11 insertions(+), 22 deletions(-) create mode 100644 app/controllers/active_storage/direct_uploads_controller.rb delete mode 100644 lib/active_storage/controllers/variants_controller.rb diff --git a/app/controllers/active_storage/direct_uploads_controller.rb b/app/controllers/active_storage/direct_uploads_controller.rb new file mode 100644 index 0000000000..dccd864e8d --- /dev/null +++ b/app/controllers/active_storage/direct_uploads_controller.rb @@ -0,0 +1,11 @@ +class ActiveStorage::DirectUploadsController < ActionController::Base + def create + blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args) + render json: { url: blob.url_for_direct_upload, sgid: blob.to_sgid.to_param } + end + + private + def blob_args + params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, :metadata).to_h.symbolize_keys + end +end diff --git a/lib/active_storage/controllers/variants_controller.rb b/lib/active_storage/controllers/variants_controller.rb deleted file mode 100644 index 094f94e706..0000000000 --- a/lib/active_storage/controllers/variants_controller.rb +++ /dev/null @@ -1,22 +0,0 @@ -require "action_controller" -require "active_storage/blob" - -class ActiveStorage::Controllers::VariantsController < ActionController::Base - def show - if blob_key = decode_verified_key - variant = ActiveStorage::Variant.find_or_create_by(blob_key: blob_key, variation_key: params[:variation_key]) - redirect_to variant.url - else - head :not_found - end - end - - private - def decode_verified_key - ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_key]) - end - - def disposition_param - params[:disposition].presence_in(%w( inline attachment )) || 'inline' - end -end -- cgit v1.2.3 From af999681122bf583b6644974ba2033453935fd6d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:35:15 -0500 Subject: Make processing an explicit step --- lib/active_storage/variant.rb | 12 ++++++++---- test/variation_test.rb | 4 +--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 3053f44211..a07735eb57 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -13,8 +13,8 @@ class ActiveStorage::Variant attr_reader :blob, :variation delegate :service, to: :blob - def self.find_or_create_by(blob_key:, variation_key:) - new ActiveStorage::Blob.find_by!(key: blob_key), variation: verifier.verify(variation_key) + def self.find_or_process_by!(blob_key:, encoded_variant_key:) + new(ActiveStorage::Blob.find_by!(key: blob_key), variation: verifier.verify(encoded_variant_key)).processed end def self.encode_key(variation) @@ -25,8 +25,12 @@ class ActiveStorage::Variant @blob, @variation = blob, variation end + def processed + process unless exist? + self + end + def url(expires_in: 5.minutes, disposition: :inline) - perform unless exist? service.url blob_variant_key, expires_in: expires_in, disposition: disposition, filename: blob.filename end @@ -39,7 +43,7 @@ class ActiveStorage::Variant service.exist?(blob_variant_key) end - def perform + def process upload_variant transform(download_blob) end diff --git a/test/variation_test.rb b/test/variation_test.rb index bc47244468..8e569f908c 100644 --- a/test/variation_test.rb +++ b/test/variation_test.rb @@ -7,8 +7,6 @@ class ActiveStorage::VariationTest < ActiveSupport::TestCase blob = ActiveStorage::Blob.create_after_upload! \ io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)), filename: "racecar.jpg", content_type: "image/jpeg" - variant = blob.variant(resize: "100x100") - - assert_match /racecar.jpg/, variant.url + assert_match /racecar.jpg/, blob.variant(resize: "100x100").processed.url end end -- cgit v1.2.3 From a968e3c3c75df3f209275d31eb0bd4ed6effd51e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:35:23 -0500 Subject: Consistent naming --- lib/active_storage/variant.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index a07735eb57..658fb2f5bd 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -51,8 +51,8 @@ class ActiveStorage::Variant service.download(blob.key) end - def upload_variant(variation) - service.upload blob_variant_key, variation + def upload_variant(variant) + service.upload blob_variant_key, variant end def blob_variant_key -- cgit v1.2.3 From beb60b9c3a3f1f51d10fa800b967402d79ffcf28 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 20 Jul 2017 17:35:41 -0500 Subject: True is the same as no arguments --- lib/active_storage/variant.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 658fb2f5bd..7fcd3924f4 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -64,10 +64,11 @@ class ActiveStorage::Variant MiniMagick::Image.read(io).tap { |transforming_image| variation.each do |(method, argument)| if method = allowed_transformational_method(method.to_sym) - if argument.present? - transforming_image.public_send(method, argument) - else + if argument.blank? || argument == true transforming_image.public_send(method) + else + # FIXME: Consider whitelisting allowed arguments as well? + transforming_image.public_send(method, argument) end end end -- cgit v1.2.3 From cbe89319de0d2eda8ee53ab7c9d3bc92751deeb4 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 12:35:00 -0500 Subject: Better naming --- lib/active_storage/variant.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 7fcd3924f4..4145ee644d 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -26,7 +26,7 @@ class ActiveStorage::Variant end def processed - process unless exist? + process unless processed? self end @@ -38,8 +38,9 @@ class ActiveStorage::Variant verifier.generate(variation) end + private - def exist? + def processed? service.exist?(blob_variant_key) end -- cgit v1.2.3 From 438d5cc48e92345f000f51bc0780b543b3087846 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:49:48 -0500 Subject: Accept that this is a full-Rails engine --- Gemfile.lock | 50 +++++++++++++++++++++++++++++++++++++++++++++----- activestorage.gemspec | 5 +---- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8fe836eb70..e18acab95b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,16 @@ GIT remote: https://github.com/rails/rails.git revision: 5c16dd35a23f75038baf1527143ee44accf081ff specs: + actioncable (5.2.0.alpha) + actionpack (= 5.2.0.alpha) + nio4r (~> 2.0) + websocket-driver (~> 0.6.1) + actionmailer (5.2.0.alpha) + actionpack (= 5.2.0.alpha) + actionview (= 5.2.0.alpha) + activejob (= 5.2.0.alpha) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) actionpack (5.2.0.alpha) actionview (= 5.2.0.alpha) activesupport (= 5.2.0.alpha) @@ -29,15 +39,30 @@ GIT i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) + rails (5.2.0.alpha) + actioncable (= 5.2.0.alpha) + actionmailer (= 5.2.0.alpha) + actionpack (= 5.2.0.alpha) + actionview (= 5.2.0.alpha) + activejob (= 5.2.0.alpha) + activemodel (= 5.2.0.alpha) + activerecord (= 5.2.0.alpha) + activesupport (= 5.2.0.alpha) + bundler (>= 1.3.0) + railties (= 5.2.0.alpha) + sprockets-rails (>= 2.0.0) + railties (5.2.0.alpha) + actionpack (= 5.2.0.alpha) + activesupport (= 5.2.0.alpha) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) PATH remote: . specs: activestorage (0.1) - actionpack (>= 5.2.0.alpha) - activejob (>= 5.2.0.alpha) - activerecord (>= 5.2.0.alpha) - activesupport (>= 5.2.0.alpha) + rails (>= 5.2.0.alpha) GEM remote: https://rubygems.org/ @@ -101,7 +126,10 @@ GEM multi_json (~> 1.10) loofah (2.0.3) nokogiri (>= 1.5.9) + mail (2.6.5) + mime-types (>= 1.16, < 4) memoist (0.16.0) + method_source (0.8.2) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) @@ -111,6 +139,7 @@ GEM multi_json (1.12.1) multi_xml (0.6.0) multipart-post (2.0.0) + nio4r (2.1.0) nokogiri (1.8.0) mini_portile2 (~> 2.2.0) os (0.9.6) @@ -148,12 +177,23 @@ GEM faraday (~> 0.9) jwt (~> 1.5) multi_json (~> 1.10) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.0) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) sqlite3 (1.3.13) + thor (0.19.4) thread_safe (0.3.6) tzinfo (1.2.3) thread_safe (~> 0.1) uber (0.1.0) unicode-display_width (1.3.0) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) PLATFORMS ruby @@ -175,4 +215,4 @@ DEPENDENCIES sqlite3 BUNDLED WITH - 1.15.1 + 1.15.2 diff --git a/activestorage.gemspec b/activestorage.gemspec index 884d3287e6..9546b60783 100644 --- a/activestorage.gemspec +++ b/activestorage.gemspec @@ -9,10 +9,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 2.3.0" - s.add_dependency "activesupport", ">= 5.2.0.alpha" - s.add_dependency "activerecord", ">= 5.2.0.alpha" - s.add_dependency "actionpack", ">= 5.2.0.alpha" - s.add_dependency "activejob", ">= 5.2.0.alpha" + s.add_dependency "rails", ">= 5.2.0.alpha" s.add_development_dependency "bundler", "~> 1.15" -- cgit v1.2.3 From 9ac31a3c8a7bf996ef2614a2dc83c1d345c78b35 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:50:19 -0500 Subject: Mount routes on the engine --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 9057eadc8a..ad54b178fe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,4 @@ -Rails.application.routes.draw do +ActiveStorage::Engine.routes.draw do get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob get "/rails/active_storage/variants/:encoded_blob_key/:encoded_variant_key/*filename" => "active_storage/variants#show", as: :rails_blob_variant post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads -- cgit v1.2.3 From 0c47740d858cb04c7165bce411584c3b05d155b6 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:50:36 -0500 Subject: Hacky way to mount routes for engine controller tests --- test/test_helper.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/test_helper.rb b/test/test_helper.rb index 6081fc1bcf..af379ad35a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -34,6 +34,17 @@ class ActiveSupport::TestCase end end +require "action_controller" +require "action_controller/test_case" +class ActionController::TestCase + Routes = ActionDispatch::Routing::RouteSet.new.tap do |routes| + routes.draw do + # FIXME: Hacky way to avoid having to instantiate the real engine + eval(File.readlines(File.expand_path("../../config/routes.rb", __FILE__)).slice(1..-2).join("\n")) + end + end +end + require "active_storage/attached" ActiveRecord::Base.send :extend, ActiveStorage::Attached::Macros -- cgit v1.2.3 From 7f4111185ca6286adbe0ffc1346d416c5fa7dfd3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:51:31 -0500 Subject: Extract variation value object --- lib/active_storage/blob.rb | 8 +++-- lib/active_storage/engine.rb | 2 +- lib/active_storage/variant.rb | 66 ++++++++--------------------------------- lib/active_storage/variation.rb | 53 +++++++++++++++++++++++++++++++++ test/test_helper.rb | 5 ++-- test/variation_test.rb | 15 +++++++--- 6 files changed, 84 insertions(+), 65 deletions(-) create mode 100644 lib/active_storage/variation.rb diff --git a/lib/active_storage/blob.rb b/lib/active_storage/blob.rb index a9d9b8771c..6bd3941cd8 100644 --- a/lib/active_storage/blob.rb +++ b/lib/active_storage/blob.rb @@ -32,8 +32,9 @@ class ActiveStorage::Blob < ActiveRecord::Base end end - # We can't wait until the record is first saved to have a key for it + def key + # We can't wait until the record is first saved to have a key for it self[:key] ||= self.class.generate_unique_secure_token end @@ -41,10 +42,11 @@ class ActiveStorage::Blob < ActiveRecord::Base ActiveStorage::Filename.new(self[:filename]) end - def variant(variation) - ActiveStorage::Variant.new(self, variation: variation) + def variant(transformations) + ActiveStorage::Variant.new(self, ActiveStorage::Variation.new(transformations)) end + def url(expires_in: 5.minutes, disposition: :inline) service.url key, expires_in: expires_in, disposition: disposition, filename: filename end diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index b04925a9fd..11227a4e04 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -28,7 +28,7 @@ module ActiveStorage config.after_initialize do |app| ActiveStorage::VerifiedKeyWithExpiration.verifier = \ - ActiveStorage::Variant.verifier = \ + ActiveStorage::Variation.verifier = \ Rails.application.message_verifier('ActiveStorage') end end diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 4145ee644d..8be51eba92 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -1,82 +1,40 @@ require "active_storage/blob" -require "active_support/core_ext/object/inclusion" require "mini_magick" class ActiveStorage::Variant - class_attribute :verifier - - ALLOWED_TRANSFORMATIONS = %i( - resize rotate format flip fill monochrome orient quality roll scale sharpen shave shear size thumbnail - transparent transpose transverse trim background bordercolor compress crop - ) - attr_reader :blob, :variation delegate :service, to: :blob - def self.find_or_process_by!(blob_key:, encoded_variant_key:) - new(ActiveStorage::Blob.find_by!(key: blob_key), variation: verifier.verify(encoded_variant_key)).processed - end - - def self.encode_key(variation) - verifier.generate(variation) + class << self + def find_or_process_by!(blob_key:, variation_key:) + new(ActiveStorage::Blob.find_by!(key: blob_key), variation: ActiveStorage::Variation.decode(variation_key)).processed + end end - def initialize(blob, variation:) + def initialize(blob, variation) @blob, @variation = blob, variation end def processed - process unless processed? + process unless service.exist?(key) self end - def url(expires_in: 5.minutes, disposition: :inline) - service.url blob_variant_key, expires_in: expires_in, disposition: disposition, filename: blob.filename + def key + "variants/#{blob.key}/#{variation.key}" end - def key - verifier.generate(variation) + def url(expires_in: 5.minutes, disposition: :inline) + service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename end private - def processed? - service.exist?(blob_variant_key) - end - def process - upload_variant transform(download_blob) - end - - def download_blob - service.download(blob.key) - end - - def upload_variant(variant) - service.upload blob_variant_key, variant - end - - def blob_variant_key - "variants/#{blob.key}/#{key}" + service.upload key, transform(service.download(blob.key)) end def transform(io) - File.open \ - MiniMagick::Image.read(io).tap { |transforming_image| - variation.each do |(method, argument)| - if method = allowed_transformational_method(method.to_sym) - if argument.blank? || argument == true - transforming_image.public_send(method) - else - # FIXME: Consider whitelisting allowed arguments as well? - transforming_image.public_send(method, argument) - end - end - end - }.path - end - - def allowed_transformational_method(method) - method.presence_in(ALLOWED_TRANSFORMATIONS) + File.open MiniMagick::Image.read(io).tap { |image| variation.transform(image) }.path end end diff --git a/lib/active_storage/variation.rb b/lib/active_storage/variation.rb new file mode 100644 index 0000000000..abff288ac1 --- /dev/null +++ b/lib/active_storage/variation.rb @@ -0,0 +1,53 @@ +require "active_support/core_ext/object/inclusion" + +# A set of transformations that can be applied to a blob to create a variant. +class ActiveStorage::Variation + class_attribute :verifier + + ALLOWED_TRANSFORMATIONS = %i( + resize rotate format flip fill monochrome orient quality roll scale sharpen shave shear size thumbnail + transparent transpose transverse trim background bordercolor compress crop + ) + + attr_reader :transformations + + class << self + def decode(key) + new verifier.verify(key) + end + + def encode(transformations) + verifier.generate(transformations) + end + end + + def initialize(transformations) + @transformations = transformations + end + + def transform(image) + transformations.each do |(method, argument)| + next unless eligible_transformation?(method) + + if eligible_argument?(argument) + image.public_send(method, argument) + else + image.public_send(method) + end + end + end + + def key + self.class.encode(transformations) + end + + private + def eligible_transformation?(method) + method.to_sym.in?(ALLOWED_TRANSFORMATIONS) + end + + # FIXME: Consider whitelisting allowed arguments as well? + def eligible_argument?(argument) + argument.present? && argument != true + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index af379ad35a..e98b6e0afc 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -15,7 +15,6 @@ rescue Errno::ENOENT {} end - require "active_storage/service/disk_service" require "tmpdir" ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: Dir.mktmpdir("active_storage_tests")) @@ -24,8 +23,8 @@ ActiveStorage::Service.logger = ActiveSupport::Logger.new(STDOUT) require "active_storage/verified_key_with_expiration" ActiveStorage::VerifiedKeyWithExpiration.verifier = ActiveSupport::MessageVerifier.new("Testing") -require "active_storage/variant" -ActiveStorage::Variant.verifier = ActiveSupport::MessageVerifier.new("Testing") +require "active_storage/variation" +ActiveStorage::Variation.verifier = ActiveSupport::MessageVerifier.new("Testing") class ActiveSupport::TestCase private diff --git a/test/variation_test.rb b/test/variation_test.rb index 8e569f908c..d138682005 100644 --- a/test/variation_test.rb +++ b/test/variation_test.rb @@ -3,10 +3,17 @@ require "database/setup" require "active_storage/variant" class ActiveStorage::VariationTest < ActiveSupport::TestCase - test "square variation" do - blob = ActiveStorage::Blob.create_after_upload! \ - io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)), filename: "racecar.jpg", content_type: "image/jpeg" + setup do + @blob = ActiveStorage::Blob.create_after_upload! \ + filename: "racecar.jpg", content_type: "image/jpeg", + io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)) + end + + test "resized variation" do + assert_match /racecar.jpg/, @blob.variant(resize: "100x100").processed.url + end - assert_match /racecar.jpg/, blob.variant(resize: "100x100").processed.url + test "resized and monochrome variation" do + assert_match /racecar.jpg/, @blob.variant(resize: "100x100", monochrome: true).processed.url end end -- cgit v1.2.3 From c6952638818213a2a475437bc93e60b4386ac02a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:52:09 -0500 Subject: Precise naming --- test/variant_test.rb | 19 +++++++++++++++++++ test/variation_test.rb | 19 ------------------- 2 files changed, 19 insertions(+), 19 deletions(-) create mode 100644 test/variant_test.rb delete mode 100644 test/variation_test.rb diff --git a/test/variant_test.rb b/test/variant_test.rb new file mode 100644 index 0000000000..bc3a1fef90 --- /dev/null +++ b/test/variant_test.rb @@ -0,0 +1,19 @@ +require "test_helper" +require "database/setup" +require "active_storage/variant" + +class ActiveStorage::VariantTest < ActiveSupport::TestCase + setup do + @blob = ActiveStorage::Blob.create_after_upload! \ + filename: "racecar.jpg", content_type: "image/jpeg", + io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)) + end + + test "resized variation" do + assert_match /racecar.jpg/, @blob.variant(resize: "100x100").processed.url + end + + test "resized and monochrome variation" do + assert_match /racecar.jpg/, @blob.variant(resize: "100x100", monochrome: true).processed.url + end +end diff --git a/test/variation_test.rb b/test/variation_test.rb deleted file mode 100644 index d138682005..0000000000 --- a/test/variation_test.rb +++ /dev/null @@ -1,19 +0,0 @@ -require "test_helper" -require "database/setup" -require "active_storage/variant" - -class ActiveStorage::VariationTest < ActiveSupport::TestCase - setup do - @blob = ActiveStorage::Blob.create_after_upload! \ - filename: "racecar.jpg", content_type: "image/jpeg", - io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)) - end - - test "resized variation" do - assert_match /racecar.jpg/, @blob.variant(resize: "100x100").processed.url - end - - test "resized and monochrome variation" do - assert_match /racecar.jpg/, @blob.variant(resize: "100x100", monochrome: true).processed.url - end -end -- cgit v1.2.3 From 67606dcdf52ae7f83e42a9872fdc545b02f227a2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 15:52:21 -0500 Subject: Over-indented --- test/variant_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/variant_test.rb b/test/variant_test.rb index bc3a1fef90..ee3bc5c0e5 100644 --- a/test/variant_test.rb +++ b/test/variant_test.rb @@ -5,8 +5,8 @@ require "active_storage/variant" class ActiveStorage::VariantTest < ActiveSupport::TestCase setup do @blob = ActiveStorage::Blob.create_after_upload! \ - filename: "racecar.jpg", content_type: "image/jpeg", - io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)) + filename: "racecar.jpg", content_type: "image/jpeg", + io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)) end test "resized variation" do -- cgit v1.2.3 From 796f8330ad441e93590a57521ef8fb80a030fb66 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:12:29 -0500 Subject: Fix and test VariantsController --- .../active_storage/variants_controller.rb | 5 ++++- config/routes.rb | 10 ++++----- lib/active_storage/variant.rb | 6 ------ test/controllers/variants_controller.rb | 25 ++++++++++++++++++++++ test/test_helper.rb | 2 ++ 5 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 test/controllers/variants_controller.rb diff --git a/app/controllers/active_storage/variants_controller.rb b/app/controllers/active_storage/variants_controller.rb index 05685dca17..dde7e1458f 100644 --- a/app/controllers/active_storage/variants_controller.rb +++ b/app/controllers/active_storage/variants_controller.rb @@ -13,7 +13,10 @@ class ActiveStorage::VariantsController < ActionController::Base end def processed_variant_for(blob_key) - ActiveStorage::Variant.find_or_process_by!(blob_key: blob_key, encoded_variant_key: params[:encoded_variant_key]) + ActiveStorage::Variant.new( + ActiveStorage::Blob.find_by!(key: blob_key), + ActiveStorage::Variation.decode(params[:variation_key]) + ).processed end def disposition_param diff --git a/config/routes.rb b/config/routes.rb index ad54b178fe..bd0787180a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,13 +1,13 @@ ActiveStorage::Engine.routes.draw do get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob - get "/rails/active_storage/variants/:encoded_blob_key/:encoded_variant_key/*filename" => "active_storage/variants#show", as: :rails_blob_variant + get "/rails/active_storage/variants/:encoded_blob_key/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variant post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads resolve 'ActiveStorage::Variant' do |variant| - encoded_blob_key = ActiveStorage::VerifiedKeyWithExpiration.encode(variant.blob.key) - encoded_variant_key = ActiveStorage::Variant.encode_key(variant.variation) - filename = variant.blob.filename + encoded_blob_key = ActiveStorage::VerifiedKeyWithExpiration.encode(variant.blob.key) + variantion_key = ActiveStorage::Variation.encode(variant.variation) + filename = variant.blob.filename - route_for(:rails_blob_variant, encoded_blob_key, encoded_variant_key, filename) + route_for(:rails_blob_variant, encoded_blob_key, variantion_key, filename) end end diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index 8be51eba92..ba2604eccf 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -5,12 +5,6 @@ class ActiveStorage::Variant attr_reader :blob, :variation delegate :service, to: :blob - class << self - def find_or_process_by!(blob_key:, variation_key:) - new(ActiveStorage::Blob.find_by!(key: blob_key), variation: ActiveStorage::Variation.decode(variation_key)).processed - end - end - def initialize(blob, variation) @blob, @variation = blob, variation end diff --git a/test/controllers/variants_controller.rb b/test/controllers/variants_controller.rb new file mode 100644 index 0000000000..132d93a3cf --- /dev/null +++ b/test/controllers/variants_controller.rb @@ -0,0 +1,25 @@ +require "test_helper" +require "database/setup" + +require "active_storage/variants_controller" +require "active_storage/verified_key_with_expiration" + +class ActiveStorage::VariantsControllerTest < ActionController::TestCase + setup do + @blob = ActiveStorage::Blob.create_after_upload! \ + filename: "racecar.jpg", content_type: "image/jpeg", + io: File.open(File.expand_path("../../fixtures/files/racecar.jpg", __FILE__)) + + @routes = Routes + @controller = ActiveStorage::VariantsController.new + end + + test "showing variant inline" do + get :show, params: { + filename: @blob.filename, + encoded_blob_key: ActiveStorage::VerifiedKeyWithExpiration.encode(@blob.key, expires_in: 5.minutes), + variation_key: ActiveStorage::Variation.encode(resize: "100x100") } + + assert_redirected_to /racecar.jpg\?disposition=inline/ + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index e98b6e0afc..7aa7b50bf3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,3 +1,5 @@ +$LOAD_PATH << File.expand_path("../../app/controllers", __FILE__) + require "bundler/setup" require "active_support" require "active_support/test_case" -- cgit v1.2.3 From dd3eced57622f256891ce97cdd0cf1feabef40c2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:24:39 -0500 Subject: Proper require --- lib/active_storage/engine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb index 11227a4e04..b32ae34516 100644 --- a/lib/active_storage/engine.rb +++ b/lib/active_storage/engine.rb @@ -24,7 +24,7 @@ module ActiveStorage initializer "active_storage.verifiers" do require "active_storage/verified_key_with_expiration" - require "active_storage/variant" + require "active_storage/variation" config.after_initialize do |app| ActiveStorage::VerifiedKeyWithExpiration.verifier = \ -- cgit v1.2.3 From c231a73b892e1fd2d4ae2e939fe36bee0238f919 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:25:01 -0500 Subject: Provide directed URL as well as resolving --- config/routes.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index bd0787180a..c376493199 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,13 +1,16 @@ ActiveStorage::Engine.routes.draw do get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob - get "/rails/active_storage/variants/:encoded_blob_key/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variant post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads - resolve 'ActiveStorage::Variant' do |variant| + get "/rails/active_storage/variants/:encoded_blob_key/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation + + direct :rails_variant do |variant| encoded_blob_key = ActiveStorage::VerifiedKeyWithExpiration.encode(variant.blob.key) - variantion_key = ActiveStorage::Variation.encode(variant.variation) + variation_key = variant.variation.key filename = variant.blob.filename - route_for(:rails_blob_variant, encoded_blob_key, variantion_key, filename) + route_for(:rails_blob_variation, encoded_blob_key, variation_key, filename) end + + resolve 'ActiveStorage::Variant' { |variant| route_for(:rails_variant, variant) } end -- cgit v1.2.3 From 39f9ef122dc011d08e0b8d76cad0112c2fa3665f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:25:11 -0500 Subject: Actually we just want them mounted straight --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index c376493199..ec4d954e81 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,4 @@ -ActiveStorage::Engine.routes.draw do +Rails.application.routes.draw do get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_blob post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads -- cgit v1.2.3 From b6fd579a7e97f1a3aee27d22e12784f7a6155799 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:26:34 -0500 Subject: Fix parens after inline block --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index ec4d954e81..d25f2c82f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,5 +12,5 @@ Rails.application.routes.draw do route_for(:rails_blob_variation, encoded_blob_key, variation_key, filename) end - resolve 'ActiveStorage::Variant' { |variant| route_for(:rails_variant, variant) } + resolve('ActiveStorage::Variant') { |variant| route_for(:rails_variant, variant) } end -- cgit v1.2.3 From 08d84e225cca6772aa54dfb7123120fe1070ea30 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:34:18 -0500 Subject: Extract test helper for image blob fixtures --- test/controllers/variants_controller.rb | 6 ++---- test/test_helper.rb | 6 ++++++ test/variant_test.rb | 4 +--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/test/controllers/variants_controller.rb b/test/controllers/variants_controller.rb index 132d93a3cf..22f5ec1454 100644 --- a/test/controllers/variants_controller.rb +++ b/test/controllers/variants_controller.rb @@ -6,12 +6,10 @@ require "active_storage/verified_key_with_expiration" class ActiveStorage::VariantsControllerTest < ActionController::TestCase setup do - @blob = ActiveStorage::Blob.create_after_upload! \ - filename: "racecar.jpg", content_type: "image/jpeg", - io: File.open(File.expand_path("../../fixtures/files/racecar.jpg", __FILE__)) - @routes = Routes @controller = ActiveStorage::VariantsController.new + + @blob = create_image_blob filename: "racecar.jpg" end test "showing variant inline" do diff --git a/test/test_helper.rb b/test/test_helper.rb index 7aa7b50bf3..69ba76b9c4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -33,6 +33,12 @@ class ActiveSupport::TestCase def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain") ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type end + + def create_image_blob(filename: "racecar.jpg", content_type: "image/jpeg") + ActiveStorage::Blob.create_after_upload! \ + io: File.open(File.expand_path("../fixtures/files/#{filename}", __FILE__)), + filename: filename, content_type: content_type + end end require "action_controller" diff --git a/test/variant_test.rb b/test/variant_test.rb index ee3bc5c0e5..0368960fbf 100644 --- a/test/variant_test.rb +++ b/test/variant_test.rb @@ -4,9 +4,7 @@ require "active_storage/variant" class ActiveStorage::VariantTest < ActiveSupport::TestCase setup do - @blob = ActiveStorage::Blob.create_after_upload! \ - filename: "racecar.jpg", content_type: "image/jpeg", - io: File.open(File.expand_path("../fixtures/files/racecar.jpg", __FILE__)) + @blob = create_image_blob filename: "racecar.jpg" end test "resized variation" do -- cgit v1.2.3 From fa33ec9e7decde0fc0ba3e2bd2b7fc9f06908065 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:34:28 -0500 Subject: Anemic intro --- lib/active_storage/variant.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb index ba2604eccf..435033f980 100644 --- a/lib/active_storage/variant.rb +++ b/lib/active_storage/variant.rb @@ -1,6 +1,7 @@ require "active_storage/blob" require "mini_magick" +# Image blobs can have variants that are the result of a set of transformations applied to the original. class ActiveStorage::Variant attr_reader :blob, :variation delegate :service, to: :blob -- cgit v1.2.3 From e9cf92cc399a169ec47496da198cfb984856000d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:44:10 -0500 Subject: Test actual transformations --- test/fixtures/files/racecar-100x100-monochrome.jpg | Bin 0 -> 27586 bytes test/fixtures/files/racecar-100x100.jpg | Bin 0 -> 29446 bytes test/variant_test.rb | 17 +++++++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/files/racecar-100x100-monochrome.jpg create mode 100644 test/fixtures/files/racecar-100x100.jpg diff --git a/test/fixtures/files/racecar-100x100-monochrome.jpg b/test/fixtures/files/racecar-100x100-monochrome.jpg new file mode 100644 index 0000000000..39e683747e Binary files /dev/null and b/test/fixtures/files/racecar-100x100-monochrome.jpg differ diff --git a/test/fixtures/files/racecar-100x100.jpg b/test/fixtures/files/racecar-100x100.jpg new file mode 100644 index 0000000000..2a515a4912 Binary files /dev/null and b/test/fixtures/files/racecar-100x100.jpg differ diff --git a/test/variant_test.rb b/test/variant_test.rb index 0368960fbf..e41842a80c 100644 --- a/test/variant_test.rb +++ b/test/variant_test.rb @@ -8,10 +8,23 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase end test "resized variation" do - assert_match /racecar.jpg/, @blob.variant(resize: "100x100").processed.url + variant = @blob.variant(resize: "100x100").processed + + assert_match /racecar.jpg/, variant.url + assert_same_image "racecar-100x100.jpg", variant end test "resized and monochrome variation" do - assert_match /racecar.jpg/, @blob.variant(resize: "100x100", monochrome: true).processed.url + variant = @blob.variant(resize: "100x100", monochrome: true).processed + + assert_match /racecar.jpg/, variant.url + assert_same_image "racecar-100x100-monochrome.jpg", variant end + + private + def assert_same_image(fixture_filename, variant) + assert_equal \ + File.binread(File.expand_path("../fixtures/files/#{fixture_filename}", __FILE__)), + File.binread(variant.service.send(:path_for, variant.key)) + end end -- cgit v1.2.3 From f3b092a6e6f6d873e14ebe1e612028ef7ac15e4a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:45:55 -0500 Subject: Test actual transformation via controller too --- test/controllers/variants_controller.rb | 1 + test/test_helper.rb | 6 ++++++ test/variant_test.rb | 7 ------- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/controllers/variants_controller.rb b/test/controllers/variants_controller.rb index 22f5ec1454..6753584d4d 100644 --- a/test/controllers/variants_controller.rb +++ b/test/controllers/variants_controller.rb @@ -19,5 +19,6 @@ class ActiveStorage::VariantsControllerTest < ActionController::TestCase variation_key: ActiveStorage::Variation.encode(resize: "100x100") } assert_redirected_to /racecar.jpg\?disposition=inline/ + assert_same_image "racecar-100x100.jpg", @blob.variant(resize: "100x100") end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 69ba76b9c4..20b22049b3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -39,6 +39,12 @@ class ActiveSupport::TestCase io: File.open(File.expand_path("../fixtures/files/#{filename}", __FILE__)), filename: filename, content_type: content_type end + + def assert_same_image(fixture_filename, variant) + assert_equal \ + File.binread(File.expand_path("../fixtures/files/#{fixture_filename}", __FILE__)), + File.binread(variant.service.send(:path_for, variant.key)) + end end require "action_controller" diff --git a/test/variant_test.rb b/test/variant_test.rb index e41842a80c..5294b87135 100644 --- a/test/variant_test.rb +++ b/test/variant_test.rb @@ -20,11 +20,4 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase assert_match /racecar.jpg/, variant.url assert_same_image "racecar-100x100-monochrome.jpg", variant end - - private - def assert_same_image(fixture_filename, variant) - assert_equal \ - File.binread(File.expand_path("../fixtures/files/#{fixture_filename}", __FILE__)), - File.binread(variant.service.send(:path_for, variant.key)) - end end -- cgit v1.2.3 From 2e9ff80e50fee0df6ea47d4d43a27c4505985b29 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:49:00 -0500 Subject: Quick example of variants --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 625b960624..48a33f8b3d 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,13 @@ class MessagesController < ApplicationController end ``` +Variation of image attachment: + +```erb +<%# Hitting the variant URL will lazy transform the original blob and then redirect to its new service location %> +<%= image_tag url_for(user.avatar.variant(resize: "100x100")) %> +``` + ## Installation 1. Add `require "active_storage"` to config/application.rb, after `require "rails/all"` line. -- cgit v1.2.3 From 6ac4fec964e67cf3d7dfbf7726bff9b05aca522c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 21 Jul 2017 16:50:23 -0500 Subject: Mention need for mini_magick with variants --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 69166664f6..b56999cae7 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Variation of image attachment: 3. Run `rails activestorage:install` to create needed directories, migrations, and configuration. 4. Configure the storage service in `config/environments/*` with `config.active_storage.service = :local` that references the services configured in `config/storage_services.yml`. +5. Optional: Add `gem "mini_magick"` to your Gemfile if you want to use variants. ## Todos -- cgit v1.2.3