diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2017-07-21 16:51:04 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-07-21 16:51:04 -0500 |
commit | f0d7ce9e767ffbf7307ed6efaa4a189ae3ea3c0a (patch) | |
tree | 545ce85fc61a1e6bcc75110428648dbf9d59a0ac /lib/active_storage | |
parent | 986a71d26868d296f4c619df85909d1073b6c91f (diff) | |
parent | 6ac4fec964e67cf3d7dfbf7726bff9b05aca522c (diff) | |
download | rails-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.rb | 9 | ||||
-rw-r--r-- | lib/active_storage/direct_uploads_controller.rb | 14 | ||||
-rw-r--r-- | lib/active_storage/disk_controller.rb | 38 | ||||
-rw-r--r-- | lib/active_storage/engine.rb | 67 | ||||
-rw-r--r-- | lib/active_storage/routes.rb | 2 | ||||
-rw-r--r-- | lib/active_storage/variant.rb | 35 | ||||
-rw-r--r-- | lib/active_storage/variation.rb | 53 | ||||
-rw-r--r-- | lib/active_storage/verified_key_with_expiration.rb | 2 |
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) |