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.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +42 -14
- data/Rakefile +2 -8
- data/config.ru +9 -0
- data/lib/.DS_Store +0 -0
- data/lib/active_shrine/attached/base.rb +24 -0
- data/lib/active_shrine/attached/changes/create_many.rb +45 -0
- data/lib/active_shrine/attached/changes/create_one.rb +51 -0
- data/lib/active_shrine/attached/changes/create_one_of_many.rb +17 -0
- data/lib/active_shrine/attached/changes/delete_many.rb +28 -0
- data/lib/active_shrine/attached/changes/delete_one.rb +24 -0
- data/lib/active_shrine/attached/changes/detach_many.rb +24 -0
- data/lib/active_shrine/attached/changes/detach_one.rb +31 -0
- data/lib/active_shrine/attached/changes/purge_many.rb +34 -0
- data/lib/active_shrine/attached/changes/purge_one.rb +34 -0
- data/lib/active_shrine/attached/changes.rb +24 -0
- data/lib/active_shrine/attached/many.rb +78 -0
- data/lib/active_shrine/attached/one.rb +90 -0
- data/lib/active_shrine/attached.rb +14 -0
- data/lib/active_shrine/attachment.rb +111 -0
- data/lib/active_shrine/job/destroy_shrine_attachment.rb +18 -0
- data/lib/active_shrine/job/promote_shrine_attachment.rb +21 -0
- data/lib/active_shrine/job.rb +12 -0
- data/lib/active_shrine/model.rb +200 -0
- data/lib/active_shrine/railtie.rb +11 -0
- data/lib/active_shrine/reflection.rb +75 -0
- data/lib/active_shrine/version.rb +5 -0
- data/lib/active_shrine.rb +18 -0
- data/lib/generators/.DS_Store +0 -0
- data/lib/generators/active_shrine/install/install_generator.rb +29 -0
- data/lib/generators/active_shrine/install/templates/app/jobs/destroy_shrine_attachment_job.rb +5 -0
- data/lib/generators/active_shrine/install/templates/app/jobs/promote_shrine_attachment_job.rb +5 -0
- data/lib/generators/active_shrine/install/templates/config/initializers/shrine.rb +51 -0
- data/lib/generators/active_shrine/install/templates/db/migrate/create_active_shrine_attachments.rb +28 -0
- data/sig/active_shrine.rbs +4 -0
- metadata +115 -47
- data/MIT-LICENSE +0 -20
- data/app/assets/config/api_base_manifest.js +0 -1
- data/app/assets/stylesheets/api_base/application.css +0 -15
- data/app/controllers/api_base/application_controller.rb +0 -6
- data/app/helpers/api_base/application_helper.rb +0 -6
- data/app/jobs/api_base/application_job.rb +0 -6
- data/app/mailers/api_base/application_mailer.rb +0 -8
- data/app/models/api_base/api_log.rb +0 -108
- data/app/models/api_base/application_record.rb +0 -7
- data/app/views/layouts/api_base/application.html.erb +0 -15
- data/config/routes.rb +0 -4
- data/db/migrate/20220612165032_create_api_logs.rb +0 -22
- data/lib/api_base/behaviours/get_json.rb +0 -26
- data/lib/api_base/behaviours/post_json.rb +0 -27
- data/lib/api_base/behaviours/shared.rb +0 -51
- data/lib/api_base/concerns/filterer.rb +0 -47
- data/lib/api_base/concerns/traceable.rb +0 -25
- data/lib/api_base/connection.rb +0 -24
- data/lib/api_base/endpoint.rb +0 -65
- data/lib/api_base/engine.rb +0 -7
- data/lib/api_base/errors/api_error.rb +0 -8
- data/lib/api_base/errors/processing_error.rb +0 -8
- data/lib/api_base/service.rb +0 -17
- data/lib/api_base/version.rb +0 -5
- data/lib/api_base.rb +0 -14
- 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,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,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,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,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
|