active_shrine 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.rspec +1 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +84 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +42 -14
  8. data/Rakefile +2 -8
  9. data/config.ru +9 -0
  10. data/lib/.DS_Store +0 -0
  11. data/lib/active_shrine/attached/base.rb +24 -0
  12. data/lib/active_shrine/attached/changes/create_many.rb +45 -0
  13. data/lib/active_shrine/attached/changes/create_one.rb +51 -0
  14. data/lib/active_shrine/attached/changes/create_one_of_many.rb +17 -0
  15. data/lib/active_shrine/attached/changes/delete_many.rb +28 -0
  16. data/lib/active_shrine/attached/changes/delete_one.rb +24 -0
  17. data/lib/active_shrine/attached/changes/detach_many.rb +24 -0
  18. data/lib/active_shrine/attached/changes/detach_one.rb +31 -0
  19. data/lib/active_shrine/attached/changes/purge_many.rb +34 -0
  20. data/lib/active_shrine/attached/changes/purge_one.rb +34 -0
  21. data/lib/active_shrine/attached/changes.rb +24 -0
  22. data/lib/active_shrine/attached/many.rb +78 -0
  23. data/lib/active_shrine/attached/one.rb +90 -0
  24. data/lib/active_shrine/attached.rb +14 -0
  25. data/lib/active_shrine/attachment.rb +111 -0
  26. data/lib/active_shrine/job/destroy_shrine_attachment.rb +18 -0
  27. data/lib/active_shrine/job/promote_shrine_attachment.rb +21 -0
  28. data/lib/active_shrine/job.rb +12 -0
  29. data/lib/active_shrine/model.rb +200 -0
  30. data/lib/active_shrine/railtie.rb +11 -0
  31. data/lib/active_shrine/reflection.rb +75 -0
  32. data/lib/active_shrine/version.rb +5 -0
  33. data/lib/active_shrine.rb +18 -0
  34. data/lib/generators/.DS_Store +0 -0
  35. data/lib/generators/active_shrine/install/install_generator.rb +29 -0
  36. data/lib/generators/active_shrine/install/templates/app/jobs/destroy_shrine_attachment_job.rb +5 -0
  37. data/lib/generators/active_shrine/install/templates/app/jobs/promote_shrine_attachment_job.rb +5 -0
  38. data/lib/generators/active_shrine/install/templates/config/initializers/shrine.rb +51 -0
  39. data/lib/generators/active_shrine/install/templates/db/migrate/create_active_shrine_attachments.rb +28 -0
  40. data/sig/active_shrine.rbs +4 -0
  41. metadata +120 -47
  42. data/MIT-LICENSE +0 -20
  43. data/app/assets/config/api_base_manifest.js +0 -1
  44. data/app/assets/stylesheets/api_base/application.css +0 -15
  45. data/app/controllers/api_base/application_controller.rb +0 -6
  46. data/app/helpers/api_base/application_helper.rb +0 -6
  47. data/app/jobs/api_base/application_job.rb +0 -6
  48. data/app/mailers/api_base/application_mailer.rb +0 -8
  49. data/app/models/api_base/api_log.rb +0 -108
  50. data/app/models/api_base/application_record.rb +0 -7
  51. data/app/views/layouts/api_base/application.html.erb +0 -15
  52. data/config/routes.rb +0 -4
  53. data/db/migrate/20220612165032_create_api_logs.rb +0 -22
  54. data/lib/api_base/behaviours/get_json.rb +0 -26
  55. data/lib/api_base/behaviours/post_json.rb +0 -27
  56. data/lib/api_base/behaviours/shared.rb +0 -51
  57. data/lib/api_base/concerns/filterer.rb +0 -47
  58. data/lib/api_base/concerns/traceable.rb +0 -25
  59. data/lib/api_base/connection.rb +0 -24
  60. data/lib/api_base/endpoint.rb +0 -65
  61. data/lib/api_base/engine.rb +0 -7
  62. data/lib/api_base/errors/api_error.rb +0 -8
  63. data/lib/api_base/errors/processing_error.rb +0 -8
  64. data/lib/api_base/service.rb +0 -17
  65. data/lib/api_base/version.rb +0 -5
  66. data/lib/api_base.rb +0 -14
  67. data/lib/tasks/api_base_tasks.rake +0 -5
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveShrine
4
+ module Attached
5
+ # = ActiveShrine\Attached\One
6
+ #
7
+ # Representation of a single attachment to a model.
8
+ class One < Base
9
+ ##
10
+ # :method: purge
11
+ #
12
+ # Directly purges the attachment (i.e. destroys the blob and
13
+ # attachment and deletes the file on the service).
14
+ delegate :purge, to: :purge_one
15
+
16
+ ##
17
+ # :method: purge_later
18
+ #
19
+ # Purges the attachment through the queuing system.
20
+ delegate :purge_later, to: :purge_one
21
+
22
+ ##
23
+ # :method: detach
24
+ #
25
+ # Deletes the attachment without purging it, leaving its blob in place.
26
+ delegate :detach, to: :detach_one
27
+
28
+ delegate_missing_to :attachment, allow_nil: true
29
+
30
+ # Returns the associated attachment record.
31
+ #
32
+ # You don't have to call this method to access the attachment's methods as
33
+ # they are all available at the model level.
34
+ def attachment
35
+ change.present? ? change.attachment : record.public_send(:"#{name}_attachment")
36
+ end
37
+
38
+ # Returns +true+ if an attachment is not attached.
39
+ #
40
+ # class User < ApplicationRecord
41
+ # has_one_attached :avatar
42
+ # end
43
+ #
44
+ # User.new.avatar.blank? # => true
45
+ def blank?
46
+ !attached?
47
+ end
48
+
49
+ # Attaches an +attachable+ to the record.
50
+ #
51
+ # If the record is persisted and unchanged, the attachment is saved to
52
+ # the database immediately. Otherwise, it'll be saved to the DB when the
53
+ # record is next saved.
54
+ #
55
+ # person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
56
+ # person.avatar.attach(params[:signed_id]) # Signed reference to attachment
57
+ #
58
+ # See https://shrinerb.com/docs/attacher#attaching for more
59
+ def attach(attachable)
60
+ record.public_send(:"#{name}=", attachable)
61
+ if record.persisted? && !record.changed?
62
+ return if !record.save
63
+ end
64
+
65
+ record.public_send(:"#{name}")
66
+ end
67
+
68
+ # Returns +true+ if an attachment has been made.
69
+ #
70
+ # class User < ApplicationRecord
71
+ # has_one_attached :avatar
72
+ # end
73
+ #
74
+ # User.new.avatar.attached? # => false
75
+ def attached?
76
+ attachment.present?
77
+ end
78
+
79
+ private
80
+
81
+ def purge_one
82
+ Attached::Changes::PurgeOne.new(name, record, attachment)
83
+ end
84
+
85
+ def detach_one
86
+ Attached::Changes::DetachOne.new(name, record, attachment)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveShrine
4
+ module Attached # :nodoc:
5
+ extend ActiveSupport::Autoload
6
+
7
+ eager_autoload do
8
+ autoload :Base
9
+ autoload :Changes
10
+ autoload :Many
11
+ autoload :One
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shrine"
4
+
5
+ # == Schema Information
6
+ #
7
+ # Table name: active_shrine_attachments
8
+ #
9
+ # id :bigint not null, primary key
10
+ # file_data :jsonb not null
11
+ # metadata :jsonb not null
12
+ # name :string not null
13
+ # record_type :string
14
+ # type :string default("ActiveShrine::Attachment"), not null
15
+ # created_at :datetime not null
16
+ # updated_at :datetime not null
17
+ # record_id :bigint
18
+ #
19
+ # Indexes
20
+ #
21
+ # active_shrine_attachments_on_file_data (file_data) USING gin
22
+ # active_shrine_attachments_on_metadata (metadata) USING gin
23
+ # active_shrine_attachments_on_name (name)
24
+ # active_shrine_attachments_on_record (record_type,record_id)
25
+ #
26
+ module ActiveShrine
27
+ class Attachment < ActiveRecord::Base
28
+ include Shrine::Attachment(:file)
29
+ include ActiveModel::Serializers::JSON
30
+
31
+ self.table_name = "active_shrine_attachments"
32
+
33
+ belongs_to :record, polymorphic: true, optional: true
34
+
35
+ validates :name, presence: true
36
+ validates :file_data, presence: true
37
+
38
+ before_save :maybe_store_record
39
+
40
+ def url
41
+ file_url
42
+ end
43
+
44
+ def content_type
45
+ file.mime_type
46
+ end
47
+
48
+ def filename
49
+ file.original_filename
50
+ end
51
+
52
+ def extension
53
+ file.extension
54
+ end
55
+
56
+ def representable?
57
+ %r{image/.*}.match? content_type
58
+ end
59
+
60
+ def signed_id
61
+ # add the id to ensure uniqueness
62
+ value = ({id:, file: file.to_json} if file.present?) || {}
63
+ Rails.application.message_verifier(:active_shrine_attachment).generate value
64
+ end
65
+
66
+ def file=(value)
67
+ # It is the same file. we are good to go.
68
+ return if value == signed_id
69
+
70
+ if value.is_a?(String)
71
+ # it is an already uploaded file. either
72
+ # - via direct upload so the form is sending us a json hash to set
73
+ # - or was set because a previous submission failed, so the form is sending us the signed_id
74
+ begin
75
+ # attempt to parse as a json hash
76
+ value = JSON.parse value
77
+ rescue JSON::ParserError
78
+ # this is not a valid json hash, let's check if it is a valid signed_id
79
+ unsigned = Rails.application.message_verifier(:active_shrine_attachment).verify value
80
+ value = JSON.parse unsigned["file"]
81
+ end
82
+ end
83
+
84
+ super(value)
85
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
86
+ errors.add(:file, "is invalid")
87
+ end
88
+
89
+ def purge
90
+ file_attacher.destroy_block { destroy } if file_attacher.respond_to?(:destroy_block)
91
+ destroy
92
+ end
93
+
94
+ def purge_later
95
+ file_attacher.destroy_background
96
+ file_attacher.instance_variable_set :@file, nil # prevent shrine from attempting to destroy the file again
97
+ destroy
98
+ rescue NoMethodError
99
+ raise NotImplementedError, ("You need to enable Shrine backgrounding to use purge_later: " \
100
+ "https://shrinerb.com/docs/plugins/backgrounding")
101
+ end
102
+
103
+ private
104
+
105
+ def maybe_store_record
106
+ return unless record.present?
107
+
108
+ metadata.merge! record_type:, record_id:
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shrine"
4
+
5
+ module ActiveShrine
6
+ module Job
7
+ module DestroyShrineAttachment
8
+ private
9
+
10
+ def perform(attacher_class, data)
11
+ attacher_class = attacher_class.constantize
12
+
13
+ attacher = attacher_class.from_data(data)
14
+ attacher.destroy
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shrine"
4
+
5
+ module ActiveShrine
6
+ module Job
7
+ module PromoteShrineAttachment
8
+ private
9
+
10
+ def perform(attacher_class, record_class, record_id, name, file_data)
11
+ attacher_class = attacher_class.constantize
12
+ record = record_class.constantize.find(record_id)
13
+
14
+ attacher = attacher_class.retrieve(model: record, name:, file: file_data)
15
+ attacher.atomic_promote
16
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
17
+ # attachment has changed or record has been deleted, nothing to do
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveShrine
4
+ module Job # :nodoc:
5
+ extend ActiveSupport::Autoload
6
+
7
+ eager_autoload do
8
+ autoload :PromoteShrineAttachment
9
+ autoload :DestroyShrineAttachment
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveShrine
4
+ # Provides the class-level DSL for declaring an Active Record model's attachments.
5
+ module Model
6
+ extend ActiveSupport::Concern
7
+ include ActiveShrine::Reflection::ActiveRecordExtensions
8
+
9
+ ##
10
+ # :method: *_attachment
11
+ #
12
+ # Returns the attachment for the +has_one_attached+.
13
+ #
14
+ # User.last.avatar_attachment
15
+
16
+ ##
17
+ # :method: *_attachments
18
+ #
19
+ # Returns the attachments for the +has_many_attached+.
20
+ #
21
+ # Gallery.last.photos_attachments
22
+
23
+ ##
24
+ # :method: with_attached_*
25
+ #
26
+ # Includes the attachments in your query to avoid N+1 queries.
27
+ #
28
+ # User.with_attached_avatar
29
+ #
30
+ # Use the plural form for +has_many_attached+:
31
+ #
32
+ # Gallery.with_attached_photos
33
+
34
+ class_methods do
35
+ # Specifies the relation between a single attachment and the model.
36
+ #
37
+ # class User < ApplicationRecord
38
+ # has_one_attached :avatar
39
+ # end
40
+ #
41
+ # There is no column defined on the model side, Active Storage takes
42
+ # care of the mapping between your records and the attachment.
43
+ #
44
+ # To avoid N+1 queries, you can include the attachments in your query like so:
45
+ #
46
+ # User.with_attached_avatar
47
+ #
48
+ # Under the covers, this relationship is implemented as a +has_one+ association to a
49
+ # ActiveShrine::Attachment record. These associations are available as +avatar_attachment+.
50
+ # But you shouldn't need to work with these associations directly in most circumstances.
51
+ #
52
+ # The system has been designed to having you go through the One
53
+ # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+.
54
+ #
55
+ # If the +:dependent+ option isn't set, the attachment will be destroyed
56
+ # (i.e. deleted from the database and file storage) whenever the record is destroyed.
57
+ #
58
+ # If you need to enable +strict_loading+ to prevent lazy loading of attachment,
59
+ # pass the +:strict_loading+ option. You can do:
60
+ #
61
+ # class User < ApplicationRecord
62
+ # has_one_attached :avatar, strict_loading: true
63
+ # end
64
+ #
65
+ # Note: ActiveShrine relies on polymorphic associations, which in turn store class names in the database.
66
+ # When renaming classes that use <tt>has_many</tt>, make sure to also update the class names in the
67
+ # <tt>active_shrine_attachments.record_type</tt> polymorphic type column of
68
+ # the corresponding rows.
69
+ def has_one_attached(name, class_name: "::ActiveShrine::Attachment", dependent: :destroy, strict_loading: false)
70
+ generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
71
+ # frozen_string_literal: true
72
+ def #{name}
73
+ @active_shrine_attached ||= {}
74
+ @active_shrine_attached[:#{name}] ||= Attached::One.new("#{name}", self)
75
+ end
76
+
77
+ def #{name}=(attachable)
78
+ shrine_attachment_changes["#{name}"] =
79
+ if attachable.presence.nil?
80
+ Attached::Changes::DeleteOne.new("#{name}", self)
81
+ else
82
+ Attached::Changes::CreateOne.new("#{name}", self, attachable)
83
+ end
84
+ end
85
+ CODE
86
+
87
+ has_one(:"#{name}_attachment", -> { where(name:) }, class_name:, as: :record, inverse_of: :record,
88
+ dependent:, strict_loading:)
89
+
90
+ scope :"with_attached_#{name}", -> { includes(:"#{name}_attachment") }
91
+
92
+ after_save { shrine_attachment_changes[name.to_s]&.save }
93
+
94
+ after_commit(on: %i[create update]) { shrine_attachment_changes.delete(name.to_s) }
95
+
96
+ reflection = ActiveRecord::Reflection.create(
97
+ :has_one_attached,
98
+ name,
99
+ nil,
100
+ {dependent:, source: :active_shrine},
101
+ self
102
+ )
103
+ yield reflection if block_given?
104
+ ActiveRecord::Reflection.add_shrine_attachment_reflection(self, name, reflection)
105
+ end
106
+
107
+ # Specifies the relation between multiple attachments and the model.
108
+ #
109
+ # class Gallery < ApplicationRecord
110
+ # has_many_attached :photos
111
+ # end
112
+ #
113
+ # There are no columns defined on the model side, Active Storage takes
114
+ # care of the mapping between your records and the attachments.
115
+ #
116
+ # To avoid N+1 queries, you can include the attachments in your query like so:
117
+ #
118
+ # Gallery.where(user: Current.user).with_attached_photos
119
+ #
120
+ # Under the covers, this relationship is implemented as a +has_many+ association to a
121
+ # ActiveShrine::Attachment record. These associations are available as +photos_attachments+.
122
+ # But you shouldn't need to work with these associations directly in most circumstances.
123
+ #
124
+ # The system has been designed to having you go through the Many
125
+ # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+.
126
+ #
127
+ # If the +:dependent+ option isn't set, all the attachments will be destroyed
128
+ # (i.e. deleted from the database and file storage) whenever the record is destroyed.
129
+ #
130
+ # If you need to enable +strict_loading+ to prevent lazy loading of attachment,
131
+ # pass the +:strict_loading+ option. You can do:
132
+ #
133
+ # class Gallery < ApplicationRecord
134
+ # has_many_attached :photos, strict_loading: true
135
+ # end
136
+ #
137
+ # Note: ActiveShrine relies on polymorphic associations, which in turn store class names in the database.
138
+ # When renaming classes that use <tt>has_many</tt>, make sure to also update the class names in the
139
+ # <tt>active_shrine_attachments.record_type</tt> polymorphic type column of
140
+ # the corresponding rows.
141
+ def has_many_attached(name, class_name: "::ActiveShrine::Attachment", dependent: :destroy, strict_loading: false)
142
+ generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
143
+ # frozen_string_literal: true
144
+ def #{name}
145
+ @active_shrine_attached ||= {}
146
+ @active_shrine_attached[:#{name}] ||= Attached::Many.new("#{name}", self)
147
+ end
148
+
149
+ def #{name}=(attachables)
150
+ attachables = Array(attachables).compact_blank
151
+ pending_uploads = shrine_attachment_changes["#{name}"].try(:pending_uploads)
152
+
153
+ shrine_attachment_changes["#{name}"] = if attachables.none?
154
+ Attached::Changes::DeleteMany.new("#{name}", self)
155
+ else
156
+ Attached::Changes::CreateMany.new("#{name}", self, attachables, pending_uploads: pending_uploads)
157
+ end
158
+ end
159
+ CODE
160
+
161
+ has_many(:"#{name}_attachments", -> { where(name:) }, class_name:, as: :record, inverse_of: :record,
162
+ dependent:, strict_loading:)
163
+
164
+ scope :"with_attached_#{name}", -> { includes(:"#{name}_attachments") }
165
+
166
+ after_save { shrine_attachment_changes[name.to_s]&.save }
167
+
168
+ after_commit(on: %i[create update]) { shrine_attachment_changes.delete(name.to_s) }
169
+
170
+ reflection = ActiveRecord::Reflection.create(
171
+ :has_many_attached,
172
+ name,
173
+ nil,
174
+ {dependent:, source: :active_shrine},
175
+ self
176
+ )
177
+ yield reflection if block_given?
178
+ ActiveRecord::Reflection.add_shrine_attachment_reflection(self, name, reflection)
179
+ end
180
+ end
181
+
182
+ def shrine_attachment_changes # :nodoc:
183
+ @shrine_attachment_changes ||= {}
184
+ end
185
+
186
+ def changed_for_autosave? # :nodoc:
187
+ super || shrine_attachment_changes.any?
188
+ end
189
+
190
+ def initialize_dup(*) # :nodoc:
191
+ super
192
+ @active_shrine_attached = nil
193
+ @shrine_attachment_changes = nil
194
+ end
195
+
196
+ def reload(*) # :nodoc:
197
+ super.tap { @shrine_attachment_changes = nil }
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,11 @@
1
+ require "rails"
2
+
3
+ module ActiveShrine
4
+ class Railtie < ::Rails::Railtie
5
+ initializer "active_shrine.active_record" do
6
+ ActiveSupport.on_load(:active_record) do
7
+ ActiveRecord::Reflection.singleton_class.prepend(ActiveShrine::Reflection::ReflectionExtension)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveShrine
4
+ module Reflection
5
+ class HasAttachedReflection < ActiveRecord::Reflection::MacroReflection # :nodoc:
6
+ # def variant(name, transformations)
7
+ # named_variants[name] = NamedVariant.new(transformations)
8
+ # end
9
+
10
+ # def named_variants
11
+ # @named_variants ||= {}
12
+ # end
13
+ end
14
+
15
+ # Holds all the metadata about a has_one_attached attachment as it was
16
+ # specified in the Active Record class.
17
+ class HasOneAttachedReflection < HasAttachedReflection # :nodoc:
18
+ def macro
19
+ :has_one_attached
20
+ end
21
+ end
22
+
23
+ # Holds all the metadata about a has_many_attached attachment as it was
24
+ # specified in the Active Record class.
25
+ class HasManyAttachedReflection < HasAttachedReflection # :nodoc:
26
+ def macro
27
+ :has_many_attached
28
+ end
29
+ end
30
+
31
+ module ReflectionExtension # :nodoc:
32
+ def add_shrine_attachment_reflection(model, name, reflection)
33
+ model.shrine_attachment_reflections[name.to_s] = reflection
34
+ end
35
+
36
+ private
37
+
38
+ def reflection_class_for(macro)
39
+ case macro
40
+ when :has_one_attached
41
+ HasOneAttachedReflection
42
+ when :has_many_attached
43
+ HasManyAttachedReflection
44
+ else
45
+ super
46
+ end
47
+ end
48
+ end
49
+
50
+ module ActiveRecordExtensions
51
+ extend ActiveSupport::Concern
52
+
53
+ module ClassMethods
54
+ def shrine_attachment_reflections
55
+ @shrine_attachment_reflections ||= {}
56
+ end
57
+
58
+ # Returns an array of reflection objects for all the attachments in the
59
+ # class.
60
+ def reflect_on_all_attachments
61
+ shrine_attachment_reflections.values
62
+ end
63
+
64
+ # Returns the reflection object for the named +attachment+.
65
+ #
66
+ # User.reflect_on_attachment(:avatar)
67
+ # # => the avatar reflection
68
+ #
69
+ def reflect_on_attachment(attachment)
70
+ shrine_attachment_reflections[attachment.to_s]
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveShrine
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "active_shrine/version"
4
+ require_relative "active_shrine/railtie"
5
+
6
+ module ActiveShrine
7
+ extend ActiveSupport::Autoload
8
+
9
+ class Error < StandardError; end
10
+
11
+ eager_autoload do
12
+ autoload :Attached
13
+ autoload :Attachment
14
+ autoload :Job
15
+ autoload :Model
16
+ autoload :Reflection
17
+ end
18
+ end
Binary file
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ # require "rails/generators/active_record/migration"
5
+
6
+ module ActiveShrine
7
+ class InstallGenerator < Rails::Generators::Base
8
+ include Rails::Generators::Migration
9
+
10
+ source_root File.expand_path("templates", __dir__)
11
+
12
+ desc "Install ActiveShrine"
13
+
14
+ def start
15
+ directory "app"
16
+ directory "config"
17
+ migration_template "db/migrate/create_active_shrine_attachments.rb", "db/migrate/create_active_shrine_attachments.rb"
18
+
19
+ Bundler.with_unbundled_env do
20
+ run "bundle add fastimage"
21
+ end
22
+ end
23
+
24
+ def self.next_migration_number(dirname)
25
+ next_migration_number = current_migration_number(dirname) + 1
26
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), format("%.14d", next_migration_number)].max
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DestroyShrineAttachmentJob < ApplicationJob
4
+ include ActiveShrine::Job::DestroyShrineAttachment
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PromoteShrineAttachmentJob < ApplicationJob
4
+ include ActiveShrine::Job::PromoteShrineAttachment
5
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Be sure to restart your server when you modify this file.
4
+
5
+ require "shrine"
6
+ require "shrine/storage/file_system"
7
+
8
+ Shrine.logger = Rails.logger
9
+
10
+ # upload files to the public dir
11
+ Shrine.storages = {
12
+ cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # temporary
13
+ store: Shrine::Storage::FileSystem.new("public", prefix: "uploads") # permanent
14
+ }
15
+
16
+ Shrine.plugin :activerecord
17
+ Shrine.plugin :determine_mime_type, analyzer: lambda { |io, analyzers|
18
+ mime_type = analyzers[:marcel].call(io)
19
+ mime_type = analyzers[:file].call(io) if mime_type == "application/octet-stream" || mime_type.nil?
20
+ mime_type = analyzers[:mime_types].call(io) if mime_type == "text/plain"
21
+ mime_type
22
+ }
23
+ Shrine.plugin :instrumentation
24
+ Shrine.plugin :infer_extension, force: true
25
+ Shrine.plugin :store_dimensions
26
+ Shrine.plugin :pretty_location
27
+ Shrine.plugin :refresh_metadata
28
+
29
+ Shrine.plugin :backgrounding
30
+
31
+ Shrine::Attacher.promote_block do
32
+ if PromoteShrineAttachmentJob.respond_to? :perform_async
33
+ # sidekiq
34
+ PromoteShrineAttachmentJob.perform_async(self.class.name, record.class.name, record.id, name.to_s, file_data)
35
+ else
36
+ # activejob
37
+ PromoteShrineAttachmentJob.perform_later(self.class.name, record.class.name, record.id, name.to_s, file_data)
38
+ end
39
+ end
40
+
41
+ Shrine::Attacher.destroy_block do
42
+ if PromoteShrineAttachmentJob.respond_to? :perform_async
43
+ # sidekiq
44
+ DestroyShrineAttachmentJob.perform_async(self.class.name, data)
45
+ else
46
+ # activejob
47
+ DestroyShrineAttachmentJob.perform_later(self.class.name, data)
48
+ end
49
+ end
50
+
51
+ Shrine.plugin :upload_endpoint, url: true