active_shrine 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|