aboutsummaryrefslogtreecommitdiffstats
path: root/lib/active_storage
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2017-07-21 16:51:04 -0500
committerGitHub <noreply@github.com>2017-07-21 16:51:04 -0500
commitf0d7ce9e767ffbf7307ed6efaa4a189ae3ea3c0a (patch)
tree545ce85fc61a1e6bcc75110428648dbf9d59a0ac /lib/active_storage
parent986a71d26868d296f4c619df85909d1073b6c91f (diff)
parent6ac4fec964e67cf3d7dfbf7726bff9b05aca522c (diff)
downloadrails-f0d7ce9e767ffbf7307ed6efaa4a189ae3ea3c0a.tar.gz
rails-f0d7ce9e767ffbf7307ed6efaa4a189ae3ea3c0a.tar.bz2
rails-f0d7ce9e767ffbf7307ed6efaa4a189ae3ea3c0a.zip
Merge pull request #63 from rails/variants
On-demand variants
Diffstat (limited to 'lib/active_storage')
-rw-r--r--lib/active_storage/blob.rb9
-rw-r--r--lib/active_storage/direct_uploads_controller.rb14
-rw-r--r--lib/active_storage/disk_controller.rb38
-rw-r--r--lib/active_storage/engine.rb67
-rw-r--r--lib/active_storage/routes.rb2
-rw-r--r--lib/active_storage/variant.rb35
-rw-r--r--lib/active_storage/variation.rb53
-rw-r--r--lib/active_storage/verified_key_with_expiration.rb2
8 files changed, 130 insertions, 90 deletions
diff --git a/lib/active_storage/blob.rb b/lib/active_storage/blob.rb
index 1a15361747..6bd3941cd8 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
@@ -31,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
@@ -40,6 +42,11 @@ class ActiveStorage::Blob < ActiveRecord::Base
ActiveStorage::Filename.new(self[:filename])
end
+ 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/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
diff --git a/lib/active_storage/engine.rb b/lib/active_storage/engine.rb
index 5f0b62809e..b32ae34516 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"
@@ -33,32 +22,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?
+ initializer "active_storage.verifiers" do
+ require "active_storage/verified_key_with_expiration"
+ require "active_storage/variation"
- require "yaml"
- require "erb"
+ config.after_initialize do |app|
+ ActiveStorage::VerifiedKeyWithExpiration.verifier = \
+ ActiveStorage::Variation.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
+ 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
- else
- raise "No storage service specified for current env (#{Rails.env}). " \
- "Add config.active_storage.service = :local into your config/environments/#{Rails.env}.rb."
+ 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
deleted file mode 100644
index 748427a776..0000000000
--- a/lib/active_storage/routes.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-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
diff --git a/lib/active_storage/variant.rb b/lib/active_storage/variant.rb
new file mode 100644
index 0000000000..435033f980
--- /dev/null
+++ b/lib/active_storage/variant.rb
@@ -0,0 +1,35 @@
+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
+
+ def initialize(blob, variation)
+ @blob, @variation = blob, variation
+ end
+
+ def processed
+ process unless service.exist?(key)
+ self
+ end
+
+ def key
+ "variants/#{blob.key}/#{variation.key}"
+ end
+
+ def url(expires_in: 5.minutes, disposition: :inline)
+ service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename
+ end
+
+
+ private
+ def process
+ service.upload key, transform(service.download(blob.key))
+ end
+
+ def transform(io)
+ 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/lib/active_storage/verified_key_with_expiration.rb b/lib/active_storage/verified_key_with_expiration.rb
index e429ee21ce..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)