Class: ActiveRecord::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/has_one_attached_deletable.rb

Class Method Summary collapse

Class Method Details

.has_one_attached_deletable(name, **options, &block) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/has_one_attached_deletable.rb', line 4

def self.has_one_attached_deletable(name, **options, &block)

  class_eval do
    attr_accessor :"#{name}_delete"
    attr_accessor :"#{name}_infos"

    before_validation { send(name).purge_later if send("#{name}_attachment").present? && send("#{name}_delete") == 'true' }

    # From Rails 7.1, after_commit callbacks run in the same order they were defined.
    # Prior to this version, they ran in reverse order.
    after_commit_callbacks_run_in_order = Rails
                                            .application
                                            .config
                                            .active_record
                                            .try(:run_after_transaction_callbacks_in_order_defined)
    if after_commit_callbacks_run_in_order
      has_one_attached name, **options, &block
      after_commit :"resize_#{name}", unless: Proc.new { |u| u.send("#{name}_infos").blank? }
    else
      after_commit :"resize_#{name}", unless: Proc.new { |u| u.send("#{name}_infos").blank? }
      has_one_attached name, **options, &block
    end

    define_method :"#{name}_delete=" do |value|
      instance_variable_set :"@#{name}_delete", value
    end

    define_method :"resize_#{name}" do
      return unless send(name).attached?


      params = JSON(send("#{name}_infos"))
      # reset the infos to prevent multiple resize if multiple save
      instance_variable_set :"@#{name}_infos", nil

      # Skip if any of the required params are missing
      return unless ['width', 'height', 'x', 'y'].all? { |k| params[k].present? }
      left = params['x'].round
      top = params['y'].round
      width = params['width'].round
      height = params['height'].round
      # Skip if width or height are not > 0
      return if width <= 0 || height <= 0
      rotation = params['rotate']
      transformations = {}
      if ActiveStorage.variant_processor == :mini_magick
        # From Rails 6, ImageProcessing with MiniMagick uses an 'auto-orient' by default to interpret the EXIF Orientation metadata.
        transformations[:'auto-orient'] = true
        # Handle rotation
        transformations[:rotate] = rotation if rotation.present?
        # Handle cropping
        transformations[:crop] = "#{width}x#{height}+#{left}+#{top}"
        # Finalize by repaging
        transformations.merge!({
          repage: true,
          :'+' => true
        })
      elsif ActiveStorage.variant_processor == :vips
        # Handle rotation
        transformations[:rotate] = rotation if rotation.present?
        # Handle cropping
        transformations[:crop] = [left, top, width, height]
      end

      variant = Rails::VERSION::MAJOR >= 6  ? send(name).variant(**transformations)
                                            : send(name).variant(combine_options: transformations)

      variant_url = variant.processed.url
      downloaded_image = URI.open(variant_url)
      attachable = { io: downloaded_image, filename: send(name).filename.to_s }

      if Rails::VERSION::MAJOR >= 6
        # Prevent double-update on the record, losing ActiveModel::Dirty data.
        # Based on Rails source code:
        # - https://github.com/rails/rails/blob/v6.0.2.2/activestorage/lib/active_storage/attached/model.rb
        # - https://github.com/rails/rails/blob/v6.0.2.2/activestorage/lib/active_storage/attached/one.rb
        attachment_change = ActiveStorage::Attached::Changes::CreateOne.new("#{name}", self, attachable)
        attachment_change.save
        attachment_change.upload
      else
        self.send(name).attach(**attachable)
      end
    end
  end
end