active_shrine 0.1.0 → 0.2.0

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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.rspec +1 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +42 -14
  9. data/Rakefile +2 -8
  10. data/config.ru +9 -0
  11. data/lib/.DS_Store +0 -0
  12. data/lib/active_shrine/attached/base.rb +24 -0
  13. data/lib/active_shrine/attached/changes/create_many.rb +45 -0
  14. data/lib/active_shrine/attached/changes/create_one.rb +51 -0
  15. data/lib/active_shrine/attached/changes/create_one_of_many.rb +17 -0
  16. data/lib/active_shrine/attached/changes/delete_many.rb +28 -0
  17. data/lib/active_shrine/attached/changes/delete_one.rb +24 -0
  18. data/lib/active_shrine/attached/changes/detach_many.rb +24 -0
  19. data/lib/active_shrine/attached/changes/detach_one.rb +31 -0
  20. data/lib/active_shrine/attached/changes/purge_many.rb +34 -0
  21. data/lib/active_shrine/attached/changes/purge_one.rb +34 -0
  22. data/lib/active_shrine/attached/changes.rb +24 -0
  23. data/lib/active_shrine/attached/many.rb +78 -0
  24. data/lib/active_shrine/attached/one.rb +90 -0
  25. data/lib/active_shrine/attached.rb +14 -0
  26. data/lib/active_shrine/attachment.rb +111 -0
  27. data/lib/active_shrine/job/destroy_shrine_attachment.rb +18 -0
  28. data/lib/active_shrine/job/promote_shrine_attachment.rb +21 -0
  29. data/lib/active_shrine/job.rb +12 -0
  30. data/lib/active_shrine/model.rb +200 -0
  31. data/lib/active_shrine/railtie.rb +11 -0
  32. data/lib/active_shrine/reflection.rb +75 -0
  33. data/lib/active_shrine/version.rb +5 -0
  34. data/lib/active_shrine.rb +18 -0
  35. data/lib/generators/.DS_Store +0 -0
  36. data/lib/generators/active_shrine/install/install_generator.rb +29 -0
  37. data/lib/generators/active_shrine/install/templates/app/jobs/destroy_shrine_attachment_job.rb +5 -0
  38. data/lib/generators/active_shrine/install/templates/app/jobs/promote_shrine_attachment_job.rb +5 -0
  39. data/lib/generators/active_shrine/install/templates/config/initializers/shrine.rb +51 -0
  40. data/lib/generators/active_shrine/install/templates/db/migrate/create_active_shrine_attachments.rb +28 -0
  41. data/sig/active_shrine.rbs +4 -0
  42. metadata +115 -47
  43. data/MIT-LICENSE +0 -20
  44. data/app/assets/config/api_base_manifest.js +0 -1
  45. data/app/assets/stylesheets/api_base/application.css +0 -15
  46. data/app/controllers/api_base/application_controller.rb +0 -6
  47. data/app/helpers/api_base/application_helper.rb +0 -6
  48. data/app/jobs/api_base/application_job.rb +0 -6
  49. data/app/mailers/api_base/application_mailer.rb +0 -8
  50. data/app/models/api_base/api_log.rb +0 -108
  51. data/app/models/api_base/application_record.rb +0 -7
  52. data/app/views/layouts/api_base/application.html.erb +0 -15
  53. data/config/routes.rb +0 -4
  54. data/db/migrate/20220612165032_create_api_logs.rb +0 -22
  55. data/lib/api_base/behaviours/get_json.rb +0 -26
  56. data/lib/api_base/behaviours/post_json.rb +0 -27
  57. data/lib/api_base/behaviours/shared.rb +0 -51
  58. data/lib/api_base/concerns/filterer.rb +0 -47
  59. data/lib/api_base/concerns/traceable.rb +0 -25
  60. data/lib/api_base/connection.rb +0 -24
  61. data/lib/api_base/endpoint.rb +0 -65
  62. data/lib/api_base/engine.rb +0 -7
  63. data/lib/api_base/errors/api_error.rb +0 -8
  64. data/lib/api_base/errors/processing_error.rb +0 -8
  65. data/lib/api_base/service.rb +0 -17
  66. data/lib/api_base/version.rb +0 -5
  67. data/lib/api_base.rb +0 -14
  68. 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, ActiveShrine 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, ActiveShrine 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.2.0"
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