activestorage 6.0.3.4 → 6.1.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activestorage might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +144 -162
- data/MIT-LICENSE +1 -1
- data/README.md +35 -3
- data/app/controllers/active_storage/base_controller.rb +11 -0
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -0
- data/app/controllers/active_storage/{blobs_controller.rb → blobs/redirect_controller.rb} +2 -2
- data/app/controllers/active_storage/disk_controller.rb +8 -20
- data/app/controllers/active_storage/representations/proxy_controller.rb +19 -0
- data/app/controllers/active_storage/{representations_controller.rb → representations/redirect_controller.rb} +2 -2
- data/app/controllers/concerns/active_storage/file_server.rb +18 -0
- data/app/controllers/concerns/active_storage/set_blob.rb +1 -1
- data/app/controllers/concerns/active_storage/set_current.rb +2 -2
- data/app/controllers/concerns/active_storage/set_headers.rb +12 -0
- data/app/jobs/active_storage/mirror_job.rb +15 -0
- data/app/models/active_storage/attachment.rb +18 -10
- data/app/models/active_storage/blob.rb +114 -59
- data/app/models/active_storage/blob/analyzable.rb +6 -2
- data/app/models/active_storage/blob/identifiable.rb +7 -6
- data/app/models/active_storage/blob/representable.rb +34 -4
- data/app/models/active_storage/preview.rb +31 -10
- data/app/models/active_storage/record.rb +7 -0
- data/app/models/active_storage/variant.rb +28 -41
- data/app/models/active_storage/variant_record.rb +8 -0
- data/app/models/active_storage/variant_with_record.rb +54 -0
- data/app/models/active_storage/variation.rb +25 -20
- data/config/routes.rb +58 -8
- data/db/migrate/20170806125915_create_active_storage_tables.rb +14 -5
- data/db/update_migrate/20190112182829_add_service_name_to_active_storage_blobs.rb +17 -0
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +11 -0
- data/lib/active_storage.rb +5 -2
- data/lib/active_storage/analyzer.rb +6 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +3 -0
- data/lib/active_storage/analyzer/null_analyzer.rb +4 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +14 -3
- data/lib/active_storage/attached/changes/create_many.rb +1 -0
- data/lib/active_storage/attached/changes/create_one.rb +17 -4
- data/lib/active_storage/attached/many.rb +4 -3
- data/lib/active_storage/attached/model.rb +59 -12
- data/lib/active_storage/attached/one.rb +4 -3
- data/lib/active_storage/engine.rb +25 -27
- data/lib/active_storage/gem_version.rb +3 -3
- data/lib/active_storage/log_subscriber.rb +6 -0
- data/lib/active_storage/previewer.rb +3 -2
- data/lib/active_storage/previewer/mupdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/poppler_pdf_previewer.rb +3 -3
- data/lib/active_storage/previewer/video_previewer.rb +2 -2
- data/lib/active_storage/service.rb +36 -7
- data/lib/active_storage/service/azure_storage_service.rb +40 -35
- data/lib/active_storage/service/configurator.rb +3 -1
- data/lib/active_storage/service/disk_service.rb +36 -31
- data/lib/active_storage/service/gcs_service.rb +18 -16
- data/lib/active_storage/service/mirror_service.rb +31 -7
- data/lib/active_storage/service/registry.rb +32 -0
- data/lib/active_storage/service/s3_service.rb +53 -23
- data/lib/active_storage/transformers/image_processing_transformer.rb +13 -7
- data/lib/active_storage/transformers/transformer.rb +0 -3
- metadata +57 -21
- data/db/update_migrate/20180723000244_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.rb +0 -9
- data/lib/active_storage/downloading.rb +0 -47
- data/lib/active_storage/transformers/mini_magick_transformer.rb +0 -38
    
        data/config/routes.rb
    CHANGED
    
    | @@ -2,9 +2,13 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            Rails.application.routes.draw do
         | 
| 4 4 | 
             
              scope ActiveStorage.routes_prefix do
         | 
| 5 | 
            -
                get "/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
         | 
| 5 | 
            +
                get "/blobs/redirect/:signed_id/*filename" => "active_storage/blobs/redirect#show", as: :rails_service_blob
         | 
| 6 | 
            +
                get "/blobs/proxy/:signed_id/*filename" => "active_storage/blobs/proxy#show", as: :rails_service_blob_proxy
         | 
| 7 | 
            +
                get "/blobs/:signed_id/*filename" => "active_storage/blobs/redirect#show"
         | 
| 6 8 |  | 
| 7 | 
            -
                get "/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation
         | 
| 9 | 
            +
                get "/representations/redirect/:signed_blob_id/:variation_key/*filename" => "active_storage/representations/redirect#show", as: :rails_blob_representation
         | 
| 10 | 
            +
                get "/representations/proxy/:signed_blob_id/:variation_key/*filename" => "active_storage/representations/proxy#show", as: :rails_blob_representation_proxy
         | 
| 11 | 
            +
                get "/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations/redirect#show"
         | 
| 8 12 |  | 
| 9 13 | 
             
                get  "/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
         | 
| 10 14 | 
             
                put  "/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
         | 
| @@ -19,14 +23,60 @@ Rails.application.routes.draw do | |
| 19 23 | 
             
                route_for(:rails_blob_representation, signed_blob_id, variation_key, filename, options)
         | 
| 20 24 | 
             
              end
         | 
| 21 25 |  | 
| 22 | 
            -
              resolve("ActiveStorage::Variant") { |variant, options| route_for( | 
| 23 | 
            -
              resolve("ActiveStorage:: | 
| 24 | 
            -
             | 
| 26 | 
            +
              resolve("ActiveStorage::Variant") { |variant, options| route_for(ActiveStorage.resolve_model_to_route, variant, options) }
         | 
| 27 | 
            +
              resolve("ActiveStorage::VariantWithRecord") { |variant, options| route_for(ActiveStorage.resolve_model_to_route, variant, options) }
         | 
| 28 | 
            +
              resolve("ActiveStorage::Preview") { |preview, options| route_for(ActiveStorage.resolve_model_to_route, preview, options) }
         | 
| 25 29 |  | 
| 26 30 | 
             
              direct :rails_blob do |blob, options|
         | 
| 27 31 | 
             
                route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
         | 
| 28 32 | 
             
              end
         | 
| 29 33 |  | 
| 30 | 
            -
              resolve("ActiveStorage::Blob")       { |blob, options| route_for( | 
| 31 | 
            -
              resolve("ActiveStorage::Attachment") { |attachment, options| route_for( | 
| 32 | 
            -
             | 
| 34 | 
            +
              resolve("ActiveStorage::Blob")       { |blob, options| route_for(ActiveStorage.resolve_model_to_route, blob, options) }
         | 
| 35 | 
            +
              resolve("ActiveStorage::Attachment") { |attachment, options| route_for(ActiveStorage.resolve_model_to_route, attachment.blob, options) }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              direct :rails_storage_proxy do |model, options|
         | 
| 38 | 
            +
                if model.respond_to?(:signed_id)
         | 
| 39 | 
            +
                  route_for(
         | 
| 40 | 
            +
                    :rails_service_blob_proxy,
         | 
| 41 | 
            +
                    model.signed_id,
         | 
| 42 | 
            +
                    model.filename,
         | 
| 43 | 
            +
                    options
         | 
| 44 | 
            +
                  )
         | 
| 45 | 
            +
                else
         | 
| 46 | 
            +
                  signed_blob_id = model.blob.signed_id
         | 
| 47 | 
            +
                  variation_key  = model.variation.key
         | 
| 48 | 
            +
                  filename       = model.blob.filename
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  route_for(
         | 
| 51 | 
            +
                    :rails_blob_representation_proxy,
         | 
| 52 | 
            +
                    signed_blob_id,
         | 
| 53 | 
            +
                    variation_key,
         | 
| 54 | 
            +
                    filename,
         | 
| 55 | 
            +
                    options
         | 
| 56 | 
            +
                  )
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              direct :rails_storage_redirect do |model, options|
         | 
| 61 | 
            +
                if model.respond_to?(:signed_id)
         | 
| 62 | 
            +
                  route_for(
         | 
| 63 | 
            +
                    :rails_service_blob,
         | 
| 64 | 
            +
                    model.signed_id,
         | 
| 65 | 
            +
                    model.filename,
         | 
| 66 | 
            +
                    options
         | 
| 67 | 
            +
                  )
         | 
| 68 | 
            +
                else
         | 
| 69 | 
            +
                  signed_blob_id = model.blob.signed_id
         | 
| 70 | 
            +
                  variation_key  = model.variation.key
         | 
| 71 | 
            +
                  filename       = model.blob.filename
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  route_for(
         | 
| 74 | 
            +
                    :rails_blob_representation,
         | 
| 75 | 
            +
                    signed_blob_id,
         | 
| 76 | 
            +
                    variation_key,
         | 
| 77 | 
            +
                    filename,
         | 
| 78 | 
            +
                    options
         | 
| 79 | 
            +
                  )
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
            end if ActiveStorage.draw_routes
         | 
| @@ -1,13 +1,14 @@ | |
| 1 1 | 
             
            class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
         | 
| 2 2 | 
             
              def change
         | 
| 3 3 | 
             
                create_table :active_storage_blobs do |t|
         | 
| 4 | 
            -
                  t.string   :key, | 
| 5 | 
            -
                  t.string   :filename, | 
| 4 | 
            +
                  t.string   :key,          null: false
         | 
| 5 | 
            +
                  t.string   :filename,     null: false
         | 
| 6 6 | 
             
                  t.string   :content_type
         | 
| 7 7 | 
             
                  t.text     :metadata
         | 
| 8 | 
            -
                  t. | 
| 9 | 
            -
                  t. | 
| 10 | 
            -
                  t. | 
| 8 | 
            +
                  t.string   :service_name, null: false
         | 
| 9 | 
            +
                  t.bigint   :byte_size,    null: false
         | 
| 10 | 
            +
                  t.string   :checksum,     null: false
         | 
| 11 | 
            +
                  t.datetime :created_at,   null: false
         | 
| 11 12 |  | 
| 12 13 | 
             
                  t.index [ :key ], unique: true
         | 
| 13 14 | 
             
                end
         | 
| @@ -22,5 +23,13 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2] | |
| 22 23 | 
             
                  t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
         | 
| 23 24 | 
             
                  t.foreign_key :active_storage_blobs, column: :blob_id
         | 
| 24 25 | 
             
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                create_table :active_storage_variant_records do |t|
         | 
| 28 | 
            +
                  t.belongs_to :blob, null: false, index: false
         | 
| 29 | 
            +
                  t.string :variation_digest, null: false
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
         | 
| 32 | 
            +
                  t.foreign_key :active_storage_blobs, column: :blob_id
         | 
| 33 | 
            +
                end
         | 
| 25 34 | 
             
              end
         | 
| 26 35 | 
             
            end
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
         | 
| 2 | 
            +
              def up
         | 
| 3 | 
            +
                unless column_exists?(:active_storage_blobs, :service_name)
         | 
| 4 | 
            +
                  add_column :active_storage_blobs, :service_name, :string
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  if configured_service = ActiveStorage::Blob.service.name
         | 
| 7 | 
            +
                    ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  change_column :active_storage_blobs, :service_name, :string, null: false
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def down
         | 
| 15 | 
            +
                remove_column :active_storage_blobs, :service_name
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                create_table :active_storage_variant_records do |t|
         | 
| 4 | 
            +
                  t.belongs_to :blob, null: false, index: false
         | 
| 5 | 
            +
                  t.string :variation_digest, null: false
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
         | 
| 8 | 
            +
                  t.foreign_key :active_storage_blobs, column: :blob_id
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
    
        data/lib/active_storage.rb
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            #--
         | 
| 4 | 
            -
            # Copyright (c) 2017- | 
| 4 | 
            +
            # Copyright (c) 2017-2020 David Heinemeier Hansson, Basecamp
         | 
| 5 5 | 
             
            #
         | 
| 6 6 | 
             
            # Permission is hereby granted, free of charge, to any person obtaining
         | 
| 7 7 | 
             
            # a copy of this software and associated documentation files (the
         | 
| @@ -53,6 +53,7 @@ module ActiveStorage | |
| 53 53 | 
             
              mattr_accessor :paths, default: {}
         | 
| 54 54 |  | 
| 55 55 | 
             
              mattr_accessor :variable_content_types,           default: []
         | 
| 56 | 
            +
              mattr_accessor :web_image_content_types,          default: []
         | 
| 56 57 | 
             
              mattr_accessor :binary_content_type,              default: "application/octet-stream"
         | 
| 57 58 | 
             
              mattr_accessor :content_types_to_serve_as_binary, default: []
         | 
| 58 59 | 
             
              mattr_accessor :content_types_allowed_inline,     default: []
         | 
| @@ -60,14 +61,16 @@ module ActiveStorage | |
| 60 61 | 
             
              mattr_accessor :service_urls_expire_in, default: 5.minutes
         | 
| 61 62 |  | 
| 62 63 | 
             
              mattr_accessor :routes_prefix, default: "/rails/active_storage"
         | 
| 64 | 
            +
              mattr_accessor :draw_routes, default: true
         | 
| 65 | 
            +
              mattr_accessor :resolve_model_to_route, default: :rails_storage_redirect
         | 
| 63 66 |  | 
| 64 67 | 
             
              mattr_accessor :replace_on_assign_to_many, default: false
         | 
| 68 | 
            +
              mattr_accessor :track_variants, default: false
         | 
| 65 69 |  | 
| 66 70 | 
             
              module Transformers
         | 
| 67 71 | 
             
                extend ActiveSupport::Autoload
         | 
| 68 72 |  | 
| 69 73 | 
             
                autoload :Transformer
         | 
| 70 74 | 
             
                autoload :ImageProcessingTransformer
         | 
| 71 | 
            -
                autoload :MiniMagickTransformer
         | 
| 72 75 | 
             
              end
         | 
| 73 76 | 
             
            end
         | 
| @@ -12,6 +12,12 @@ module ActiveStorage | |
| 12 12 | 
             
                  false
         | 
| 13 13 | 
             
                end
         | 
| 14 14 |  | 
| 15 | 
            +
                # Implement this method in concrete subclasses. It will determine if blob analysis
         | 
| 16 | 
            +
                # should be done in a job or performed inline. By default, analysis is enqueued in a job.
         | 
| 17 | 
            +
                def self.analyze_later?
         | 
| 18 | 
            +
                  true
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 15 21 | 
             
                def initialize(blob)
         | 
| 16 22 | 
             
                  @blob = blob
         | 
| 17 23 | 
             
                end
         | 
| @@ -43,6 +43,9 @@ module ActiveStorage | |
| 43 43 | 
             
                  rescue LoadError
         | 
| 44 44 | 
             
                    logger.info "Skipping image analysis because the mini_magick gem isn't installed"
         | 
| 45 45 | 
             
                    {}
         | 
| 46 | 
            +
                  rescue MiniMagick::Error => error
         | 
| 47 | 
            +
                    logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
         | 
| 48 | 
            +
                    {}
         | 
| 46 49 | 
             
                  end
         | 
| 47 50 |  | 
| 48 51 | 
             
                  def rotated_image?(image)
         | 
| @@ -44,7 +44,8 @@ module ActiveStorage | |
| 44 44 | 
             
                  end
         | 
| 45 45 |  | 
| 46 46 | 
             
                  def duration
         | 
| 47 | 
            -
                     | 
| 47 | 
            +
                    duration = video_stream["duration"] || container["duration"]
         | 
| 48 | 
            +
                    Float(duration) if duration
         | 
| 48 49 | 
             
                  end
         | 
| 49 50 |  | 
| 50 51 | 
             
                  def angle
         | 
| @@ -98,12 +99,22 @@ module ActiveStorage | |
| 98 99 | 
             
                    probe["streams"] || []
         | 
| 99 100 | 
             
                  end
         | 
| 100 101 |  | 
| 102 | 
            +
                  def container
         | 
| 103 | 
            +
                    probe["format"] || {}
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 101 106 | 
             
                  def probe
         | 
| 102 | 
            -
                    download_blob_to_tempfile { |file| probe_from(file) }
         | 
| 107 | 
            +
                    @probe ||= download_blob_to_tempfile { |file| probe_from(file) }
         | 
| 103 108 | 
             
                  end
         | 
| 104 109 |  | 
| 105 110 | 
             
                  def probe_from(file)
         | 
| 106 | 
            -
                    IO.popen([ ffprobe_path, | 
| 111 | 
            +
                    IO.popen([ ffprobe_path,
         | 
| 112 | 
            +
                      "-print_format", "json",
         | 
| 113 | 
            +
                      "-show_streams",
         | 
| 114 | 
            +
                      "-show_format",
         | 
| 115 | 
            +
                      "-v", "error",
         | 
| 116 | 
            +
                      file.path
         | 
| 117 | 
            +
                    ]) do |output|
         | 
| 107 118 | 
             
                      JSON.parse(output.read)
         | 
| 108 119 | 
             
                    end
         | 
| 109 120 | 
             
                  rescue Errno::ENOENT
         | 
| @@ -9,6 +9,7 @@ module ActiveStorage | |
| 9 9 |  | 
| 10 10 | 
             
                def initialize(name, record, attachable)
         | 
| 11 11 | 
             
                  @name, @record, @attachable = name, record, attachable
         | 
| 12 | 
            +
                  blob.identify_without_saving
         | 
| 12 13 | 
             
                end
         | 
| 13 14 |  | 
| 14 15 | 
             
                def attachment
         | 
| @@ -53,17 +54,29 @@ module ActiveStorage | |
| 53 54 | 
             
                    when ActiveStorage::Blob
         | 
| 54 55 | 
             
                      attachable
         | 
| 55 56 | 
             
                    when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
         | 
| 56 | 
            -
                      ActiveStorage::Blob.build_after_unfurling | 
| 57 | 
            +
                      ActiveStorage::Blob.build_after_unfurling(
         | 
| 57 58 | 
             
                        io: attachable.open,
         | 
| 58 59 | 
             
                        filename: attachable.original_filename,
         | 
| 59 | 
            -
                        content_type: attachable.content_type
         | 
| 60 | 
            +
                        content_type: attachable.content_type,
         | 
| 61 | 
            +
                        record: record,
         | 
| 62 | 
            +
                        service_name: attachment_service_name
         | 
| 63 | 
            +
                      )
         | 
| 60 64 | 
             
                    when Hash
         | 
| 61 | 
            -
                      ActiveStorage::Blob.build_after_unfurling( | 
| 65 | 
            +
                      ActiveStorage::Blob.build_after_unfurling(
         | 
| 66 | 
            +
                        **attachable.reverse_merge(
         | 
| 67 | 
            +
                          record: record,
         | 
| 68 | 
            +
                          service_name: attachment_service_name
         | 
| 69 | 
            +
                        ).symbolize_keys
         | 
| 70 | 
            +
                      )
         | 
| 62 71 | 
             
                    when String
         | 
| 63 | 
            -
                      ActiveStorage::Blob.find_signed(attachable)
         | 
| 72 | 
            +
                      ActiveStorage::Blob.find_signed!(attachable, record: record)
         | 
| 64 73 | 
             
                    else
         | 
| 65 74 | 
             
                      raise ArgumentError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
         | 
| 66 75 | 
             
                    end
         | 
| 67 76 | 
             
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  def attachment_service_name
         | 
| 79 | 
            +
                    record.attachment_reflections[name].options[:service_name]
         | 
| 80 | 
            +
                  end
         | 
| 68 81 | 
             
              end
         | 
| 69 82 | 
             
            end
         | 
| @@ -29,15 +29,16 @@ module ActiveStorage | |
| 29 29 | 
             
                #   document.images.attach([ first_blob, second_blob ])
         | 
| 30 30 | 
             
                def attach(*attachables)
         | 
| 31 31 | 
             
                  if record.persisted? && !record.changed?
         | 
| 32 | 
            -
                    record. | 
| 32 | 
            +
                    record.public_send("#{name}=", blobs + attachables.flatten)
         | 
| 33 | 
            +
                    record.save
         | 
| 33 34 | 
             
                  else
         | 
| 34 35 | 
             
                    record.public_send("#{name}=", (change&.attachables || blobs) + attachables.flatten)
         | 
| 35 36 | 
             
                  end
         | 
| 36 37 | 
             
                end
         | 
| 37 38 |  | 
| 38 | 
            -
                # Returns true if any attachments  | 
| 39 | 
            +
                # Returns true if any attachments have been made.
         | 
| 39 40 | 
             
                #
         | 
| 40 | 
            -
                #   class Gallery <  | 
| 41 | 
            +
                #   class Gallery < ApplicationRecord
         | 
| 41 42 | 
             
                #     has_many_attached :photos
         | 
| 42 43 | 
             
                #   end
         | 
| 43 44 | 
             
                #
         | 
| @@ -1,5 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            require "active_support/core_ext/object/try"
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            module ActiveStorage
         | 
| 4 6 | 
             
              # Provides the class-level DSL for declaring an Active Record model's attachments.
         | 
| 5 7 | 
             
              module Attached::Model
         | 
| @@ -8,7 +10,7 @@ module ActiveStorage | |
| 8 10 | 
             
                class_methods do
         | 
| 9 11 | 
             
                  # Specifies the relation between a single attachment and the model.
         | 
| 10 12 | 
             
                  #
         | 
| 11 | 
            -
                  #   class User <  | 
| 13 | 
            +
                  #   class User < ApplicationRecord
         | 
| 12 14 | 
             
                  #     has_one_attached :avatar
         | 
| 13 15 | 
             
                  #   end
         | 
| 14 16 | 
             
                  #
         | 
| @@ -30,10 +32,22 @@ module ActiveStorage | |
| 30 32 | 
             
                  #
         | 
| 31 33 | 
             
                  # If the +:dependent+ option isn't set, the attachment will be purged
         | 
| 32 34 | 
             
                  # (i.e. destroyed) whenever the record is destroyed.
         | 
| 33 | 
            -
                   | 
| 35 | 
            +
                  #
         | 
| 36 | 
            +
                  # If you need the attachment to use a service which differs from the globally configured one,
         | 
| 37 | 
            +
                  # pass the +:service+ option. For instance:
         | 
| 38 | 
            +
                  #
         | 
| 39 | 
            +
                  #   class User < ActiveRecord::Base
         | 
| 40 | 
            +
                  #     has_one_attached :avatar, service: :s3
         | 
| 41 | 
            +
                  #   end
         | 
| 42 | 
            +
                  #
         | 
| 43 | 
            +
                  def has_one_attached(name, dependent: :purge_later, service: nil)
         | 
| 44 | 
            +
                    validate_service_configuration(name, service)
         | 
| 45 | 
            +
             | 
| 34 46 | 
             
                    generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
         | 
| 47 | 
            +
                      # frozen_string_literal: true
         | 
| 35 48 | 
             
                      def #{name}
         | 
| 36 | 
            -
                        @ | 
| 49 | 
            +
                        @active_storage_attached ||= {}
         | 
| 50 | 
            +
                        @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::One.new("#{name}", self)
         | 
| 37 51 | 
             
                      end
         | 
| 38 52 |  | 
| 39 53 | 
             
                      def #{name}=(attachable)
         | 
| @@ -55,16 +69,19 @@ module ActiveStorage | |
| 55 69 |  | 
| 56 70 | 
             
                    after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
         | 
| 57 71 |  | 
| 58 | 
            -
                    ActiveRecord::Reflection. | 
| 59 | 
            -
                       | 
| 72 | 
            +
                    reflection = ActiveRecord::Reflection.create(
         | 
| 73 | 
            +
                      :has_one_attached,
         | 
| 60 74 | 
             
                      name,
         | 
| 61 | 
            -
                       | 
| 75 | 
            +
                      nil,
         | 
| 76 | 
            +
                      { dependent: dependent, service_name: service },
         | 
| 77 | 
            +
                      self
         | 
| 62 78 | 
             
                    )
         | 
| 79 | 
            +
                    ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
         | 
| 63 80 | 
             
                  end
         | 
| 64 81 |  | 
| 65 82 | 
             
                  # Specifies the relation between multiple attachments and the model.
         | 
| 66 83 | 
             
                  #
         | 
| 67 | 
            -
                  #   class Gallery <  | 
| 84 | 
            +
                  #   class Gallery < ApplicationRecord
         | 
| 68 85 | 
             
                  #     has_many_attached :photos
         | 
| 69 86 | 
             
                  #   end
         | 
| 70 87 | 
             
                  #
         | 
| @@ -86,10 +103,22 @@ module ActiveStorage | |
| 86 103 | 
             
                  #
         | 
| 87 104 | 
             
                  # If the +:dependent+ option isn't set, all the attachments will be purged
         | 
| 88 105 | 
             
                  # (i.e. destroyed) whenever the record is destroyed.
         | 
| 89 | 
            -
                   | 
| 106 | 
            +
                  #
         | 
| 107 | 
            +
                  # If you need the attachment to use a service which differs from the globally configured one,
         | 
| 108 | 
            +
                  # pass the +:service+ option. For instance:
         | 
| 109 | 
            +
                  #
         | 
| 110 | 
            +
                  #   class Gallery < ActiveRecord::Base
         | 
| 111 | 
            +
                  #     has_many_attached :photos, service: :s3
         | 
| 112 | 
            +
                  #   end
         | 
| 113 | 
            +
                  #
         | 
| 114 | 
            +
                  def has_many_attached(name, dependent: :purge_later, service: nil)
         | 
| 115 | 
            +
                    validate_service_configuration(name, service)
         | 
| 116 | 
            +
             | 
| 90 117 | 
             
                    generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
         | 
| 118 | 
            +
                      # frozen_string_literal: true
         | 
| 91 119 | 
             
                      def #{name}
         | 
| 92 | 
            -
                        @ | 
| 120 | 
            +
                        @active_storage_attached ||= {}
         | 
| 121 | 
            +
                        @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::Many.new("#{name}", self)
         | 
| 93 122 | 
             
                      end
         | 
| 94 123 |  | 
| 95 124 | 
             
                      def #{name}=(attachables)
         | 
| @@ -128,12 +157,24 @@ module ActiveStorage | |
| 128 157 |  | 
| 129 158 | 
             
                    after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
         | 
| 130 159 |  | 
| 131 | 
            -
                    ActiveRecord::Reflection. | 
| 132 | 
            -
                       | 
| 160 | 
            +
                    reflection = ActiveRecord::Reflection.create(
         | 
| 161 | 
            +
                      :has_many_attached,
         | 
| 133 162 | 
             
                      name,
         | 
| 134 | 
            -
                       | 
| 163 | 
            +
                      nil,
         | 
| 164 | 
            +
                      { dependent: dependent, service_name: service },
         | 
| 165 | 
            +
                      self
         | 
| 135 166 | 
             
                    )
         | 
| 167 | 
            +
                    ActiveRecord::Reflection.add_attachment_reflection(self, name, reflection)
         | 
| 136 168 | 
             
                  end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                  private
         | 
| 171 | 
            +
                    def validate_service_configuration(association_name, service)
         | 
| 172 | 
            +
                      if service.present?
         | 
| 173 | 
            +
                        ActiveStorage::Blob.services.fetch(service) do
         | 
| 174 | 
            +
                          raise ArgumentError, "Cannot configure service :#{service} for #{name}##{association_name}"
         | 
| 175 | 
            +
                        end
         | 
| 176 | 
            +
                      end
         | 
| 177 | 
            +
                    end
         | 
| 137 178 | 
             
                end
         | 
| 138 179 |  | 
| 139 180 | 
             
                def attachment_changes #:nodoc:
         | 
| @@ -144,6 +185,12 @@ module ActiveStorage | |
| 144 185 | 
             
                  super || attachment_changes.any?
         | 
| 145 186 | 
             
                end
         | 
| 146 187 |  | 
| 188 | 
            +
                def initialize_dup(*) #:nodoc:
         | 
| 189 | 
            +
                  super
         | 
| 190 | 
            +
                  @active_storage_attached = nil
         | 
| 191 | 
            +
                  @attachment_changes = nil
         | 
| 192 | 
            +
                end
         | 
| 193 | 
            +
             | 
| 147 194 | 
             
                def reload(*) #:nodoc:
         | 
| 148 195 | 
             
                  super.tap { @attachment_changes = nil }
         | 
| 149 196 | 
             
                end
         |