kithe 1.1.2 → 2.0.0.pre.alpha1
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/app/jobs/kithe/asset_delete_job.rb +5 -2
- data/app/jobs/kithe/asset_promote_job.rb +12 -2
- data/app/models/kithe/asset.rb +14 -6
- data/app/models/kithe/asset/derivative_updater.rb +2 -3
- data/app/uploaders/kithe/asset_uploader.rb +24 -21
- data/app/uploaders/kithe/derivative_uploader.rb +7 -3
- data/lib/kithe/version.rb +1 -1
- data/lib/shrine/plugins/kithe_accept_remote_url.rb +36 -9
- data/lib/shrine/plugins/kithe_multi_cache.rb +3 -6
- data/lib/shrine/plugins/kithe_promotion_callbacks.rb +82 -0
- data/lib/shrine/plugins/kithe_promotion_directives.rb +111 -0
- data/spec/dummy/log/development.log +1 -0
- data/spec/dummy/log/test.log +51669 -0
- data/spec/models/kithe/asset/asset_create_derivatives_spec.rb +7 -3
- data/spec/models/kithe/asset/asset_promotion_hooks_spec.rb +179 -12
- data/spec/models/kithe/asset_spec.rb +3 -2
- data/spec/models/kithe/derivative_spec.rb +4 -4
- data/spec/shrine/kithe_multi_cache_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/test_support/shrine_spec_support.rb +2 -1
- metadata +9 -8
- data/lib/shrine/plugins/kithe_promotion_hooks.rb +0 -138
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2821832cedc85d2e7cf3b503dfdbbfb8786b92eb9797067362534e76dc926390
|
4
|
+
data.tar.gz: 71af9ce59d1a87339503b832da9d9fc4b2622850e2d554116fa3c024236c36c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc7d9197215d95a0386dac5fe4ccef822b92c3459afbfb898f6a3a7f48b20245200c97fef3ea7659778e7d29962f26a631ba47e33860a364dccc58bb0e9c0a17
|
7
|
+
data.tar.gz: e93788031c2a37533a20f6fcaba47d88cea0c1a4a3f0657a5cadff7e6711e376bf49cb7eab6ea2139edc1a330fc125731f4eecf170178198f0ba244f80680b1e
|
@@ -1,7 +1,10 @@
|
|
1
1
|
module Kithe
|
2
2
|
class AssetDeleteJob < Job
|
3
|
-
def perform(data)
|
4
|
-
|
3
|
+
def perform(attacher_class, data)
|
4
|
+
attacher_class = Object.const_get(attacher_class)
|
5
|
+
|
6
|
+
attacher = attacher_class.from_data(data)
|
7
|
+
attacher.destroy
|
5
8
|
end
|
6
9
|
end
|
7
10
|
end
|
@@ -1,7 +1,17 @@
|
|
1
1
|
module Kithe
|
2
2
|
class AssetPromoteJob < Job
|
3
|
-
|
4
|
-
|
3
|
+
# note we add a `promotion_directives` arg on the end, differing from standard
|
4
|
+
# shrine. It is a hash.
|
5
|
+
def perform(attacher_class, record_class, record_id, name, file_data, promotion_directives)
|
6
|
+
attacher_class = Object.const_get(attacher_class)
|
7
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
8
|
+
|
9
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
10
|
+
attacher.set_promotion_directives(promotion_directives)
|
11
|
+
|
12
|
+
attacher.atomic_promote
|
13
|
+
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
14
|
+
# attachment has changed or record has been deleted, nothing to do
|
5
15
|
end
|
6
16
|
end
|
7
17
|
end
|
data/app/models/kithe/asset.rb
CHANGED
@@ -19,7 +19,7 @@ class Kithe::Asset < Kithe::Model
|
|
19
19
|
|
20
20
|
# TODO we may need a way for local app to provide custom uploader class.
|
21
21
|
# or just override at ./kithe/asset_uploader.rb locally?
|
22
|
-
include Kithe::AssetUploader::Attachment
|
22
|
+
include Kithe::AssetUploader::Attachment(:file)
|
23
23
|
|
24
24
|
# for convenience, let's delegate some things to shrine parts
|
25
25
|
delegate :content_type, :original_filename, :size, :height, :width, :page_count,
|
@@ -42,8 +42,11 @@ class Kithe::Asset < Kithe::Model
|
|
42
42
|
# to happen only after asset is promoted, like derivatives.
|
43
43
|
define_model_callbacks :promotion
|
44
44
|
|
45
|
+
before_promotion :refresh_metadata_before_promotion
|
45
46
|
after_promotion :schedule_derivatives
|
46
47
|
|
48
|
+
|
49
|
+
|
47
50
|
# Establish a derivative definition that will be used to create a derivative
|
48
51
|
# when #create_derivatives is called, for instance automatically after promotion.
|
49
52
|
#
|
@@ -183,15 +186,15 @@ class Kithe::Asset < Kithe::Model
|
|
183
186
|
|
184
187
|
# Runs the shrine promotion step, that we normally have in backgrounding, manually
|
185
188
|
# and in foreground. You might use this if a promotion failed and you need to re-run it,
|
186
|
-
# perhaps in bulk. It's also useful in tests.
|
189
|
+
# perhaps in bulk. It's also useful in tests. Also persists, using shrine `atomic_promote`.
|
187
190
|
#
|
188
191
|
# This will no-op unless the attached file is stored in cache -- that is, it
|
189
192
|
# will no-op if the file has already been promoted. In this way it matches ordinary
|
190
193
|
# shrine promotion. (Do we need an option to force promotion anyway?)
|
191
194
|
#
|
192
|
-
# Note that calling `file_attacher.promote` on it's own won't do
|
193
|
-
#
|
194
|
-
def promote(action: :store, context
|
195
|
+
# Note that calling `file_attacher.promote` or `atomic_promote` on it's own won't do
|
196
|
+
# quite the same things.
|
197
|
+
def promote(action: :store, **context)
|
195
198
|
return unless file_attacher.cached?
|
196
199
|
|
197
200
|
context = {
|
@@ -199,7 +202,7 @@ class Kithe::Asset < Kithe::Model
|
|
199
202
|
record: self
|
200
203
|
}.merge(context)
|
201
204
|
|
202
|
-
file_attacher.
|
205
|
+
file_attacher.atomic_promote(**context)
|
203
206
|
end
|
204
207
|
|
205
208
|
# The derivative creator sets metadata when it's created all derivatives
|
@@ -269,6 +272,11 @@ class Kithe::Asset < Kithe::Model
|
|
269
272
|
end
|
270
273
|
end
|
271
274
|
|
275
|
+
# Called by before_promotion hook
|
276
|
+
def refresh_metadata_before_promotion
|
277
|
+
file.refresh_metadata!(promoting: true)
|
278
|
+
end
|
279
|
+
|
272
280
|
# Meant to be called in after_save hook, looks at activerecord dirty tracking in order
|
273
281
|
# to removes all derivatives if the asset sha512 has changed
|
274
282
|
def remove_invalid_derivatives
|
@@ -51,8 +51,7 @@ class Kithe::Asset::DerivativeUpdater
|
|
51
51
|
|
52
52
|
# add our derivative key to context when uploading, so Kithe::DerivativeUploader can
|
53
53
|
# use it if needed.
|
54
|
-
uploaded_file = uploader.upload(io, record: deriv, metadata: metadata
|
55
|
-
|
54
|
+
uploaded_file = uploader.upload(io, record: deriv, metadata: metadata, kithe_derivative_key: key)
|
56
55
|
optimistically_save_derivative(uploaded_file: uploaded_file, derivative: deriv)
|
57
56
|
end
|
58
57
|
|
@@ -63,7 +62,7 @@ class Kithe::Asset::DerivativeUpdater
|
|
63
62
|
# This method calls itself recursively to do that. Gives up after max_optimistic_tries,
|
64
63
|
# at which point it'll just raise the constraint violation exception.
|
65
64
|
def optimistically_save_derivative(uploaded_file:, derivative:, tries: 0)
|
66
|
-
derivative.file_attacher.
|
65
|
+
derivative.file_attacher.change(uploaded_file)
|
67
66
|
save_deriv_ensuring_unchanged_asset(derivative)
|
68
67
|
rescue ActiveRecord::RecordNotUnique => e
|
69
68
|
if tries < max_optimistic_tries
|
@@ -30,6 +30,10 @@ module Kithe
|
|
30
30
|
# so it can be submitted again.
|
31
31
|
plugin :cached_attachment_data
|
32
32
|
|
33
|
+
# Used in a before_promotion hook to have our metadata extraction happen on
|
34
|
+
# promotion, possibly in the background.
|
35
|
+
plugin :refresh_metadata
|
36
|
+
|
33
37
|
# Marcel analyzer is pure-ruby and fast. It's from Basecamp and is what
|
34
38
|
# ActiveStorage uses. It is very similar to :mimemagic (and uses mimemagic
|
35
39
|
# under the hood), but mimemagic seems not to be maintained with up to date
|
@@ -50,9 +54,10 @@ module Kithe
|
|
50
54
|
end
|
51
55
|
|
52
56
|
# Will save height and width to metadata for image types. (Won't for non-image types)
|
53
|
-
|
57
|
+
# ignore errors (often due to storing a non-image file), consistent with shrine 2.x behavior.
|
58
|
+
plugin :store_dimensions, on_error: :ignore
|
54
59
|
|
55
|
-
# promotion and deletion will be in background.
|
60
|
+
# promotion and deletion will (sometimes) be in background.
|
56
61
|
plugin :backgrounding
|
57
62
|
|
58
63
|
# Useful in case consumers want it, and doesn't harm anything to be available.
|
@@ -63,16 +68,13 @@ module Kithe
|
|
63
68
|
# feature can be used to make promotion not happen at all, or happen in foreground.
|
64
69
|
# asset.file_attacher.set_promotion_directives(promote: false)
|
65
70
|
# asset.file_attacher.set_promotion_directives(promote: "inline")
|
66
|
-
Attacher.
|
67
|
-
Kithe::TimingPromotionDirective.new(key: :promote, directives:
|
71
|
+
Attacher.promote_block do
|
72
|
+
Kithe::TimingPromotionDirective.new(key: :promote, directives: self.promotion_directives) do |directive|
|
68
73
|
if directive.inline?
|
69
|
-
|
70
|
-
# since backgrounding mechanism still reloads a new instance, sorry.
|
71
|
-
#Kithe::AssetPromoteJob.perform_now(data)
|
72
|
-
self.class.promote(data)
|
74
|
+
promote
|
73
75
|
elsif directive.background?
|
74
|
-
# What shrine normally expects for backgrounding
|
75
|
-
Kithe::AssetPromoteJob.perform_later(
|
76
|
+
# What shrine normally expects for backgrounding, plus promotion_directives
|
77
|
+
Kithe::AssetPromoteJob.perform_later(self.class.name, record.class.name, record.id, name.to_s, file_data, self.promotion_directives)
|
76
78
|
end
|
77
79
|
end
|
78
80
|
end
|
@@ -80,22 +82,19 @@ module Kithe
|
|
80
82
|
# Delete using shrine backgrounding, but can be effected
|
81
83
|
# by promotion_directives[:delete], similar to promotion above.
|
82
84
|
# Yeah, not really a "promotion" directive, oh well.
|
83
|
-
Attacher.
|
84
|
-
Kithe::TimingPromotionDirective.new(key: :delete, directives:
|
85
|
+
Attacher.destroy_block do
|
86
|
+
Kithe::TimingPromotionDirective.new(key: :delete, directives: self.promotion_directives) do |directive|
|
85
87
|
if directive.inline?
|
86
|
-
|
88
|
+
destroy
|
87
89
|
elsif directive.background?
|
88
90
|
# What shrine normally expects for backgrounding
|
89
|
-
Kithe::AssetDeleteJob.perform_later(data)
|
91
|
+
Kithe::AssetDeleteJob.perform_later(self.class.name, data)
|
90
92
|
end
|
91
93
|
end
|
92
94
|
end
|
93
95
|
|
94
96
|
plugin :add_metadata
|
95
97
|
|
96
|
-
# So we can assign a hash representing cached file
|
97
|
-
plugin :parsed_json
|
98
|
-
|
99
98
|
# Makes files stored as /asset/#{asset_pk}/#{random_uuid}.#{original_suffix}
|
100
99
|
plugin :kithe_storage_location
|
101
100
|
|
@@ -113,7 +112,7 @@ module Kithe
|
|
113
112
|
# checksums until then anyway.
|
114
113
|
plugin :signature
|
115
114
|
add_metadata do |io, context|
|
116
|
-
if context[:action]
|
115
|
+
if context[:action] != :cache
|
117
116
|
{
|
118
117
|
md5: calculate_signature(io, :md5),
|
119
118
|
sha1: calculate_signature(io, :sha1),
|
@@ -123,8 +122,12 @@ module Kithe
|
|
123
122
|
end
|
124
123
|
metadata_method :md5, :sha1, :sha512
|
125
124
|
|
126
|
-
|
127
|
-
#
|
128
|
-
|
125
|
+
|
126
|
+
# Gives us (set_)promotion_directives methods on our attacher to
|
127
|
+
# house lifecycle directives, about whether promotion, deletion,
|
128
|
+
# derivatives happen in foreground, background, or not at all.
|
129
|
+
plugin :kithe_promotion_directives
|
130
|
+
|
131
|
+
plugin :kithe_promotion_callbacks
|
129
132
|
end
|
130
133
|
end
|
@@ -9,7 +9,10 @@ module Kithe
|
|
9
9
|
plugin :activerecord
|
10
10
|
|
11
11
|
plugin :determine_mime_type, analyzer: :marcel
|
12
|
-
|
12
|
+
|
13
|
+
# ignore error, often from storing a non-image file which can't have dimensions
|
14
|
+
# extracted. behavior consistent with shrine 2.x.
|
15
|
+
plugin :store_dimensions, on_error: :ignore
|
13
16
|
|
14
17
|
# Useful in case consumers want it, and doesn't harm anything to be available.
|
15
18
|
# https://github.com/shrinerb/shrine/blob/master/doc/plugins/rack_response.md
|
@@ -32,10 +35,11 @@ module Kithe
|
|
32
35
|
def extract_metadata(io, context = {})
|
33
36
|
result = super
|
34
37
|
|
35
|
-
if context
|
38
|
+
if context[:kithe_derivative_key] &&
|
36
39
|
context[:record]
|
37
40
|
extension = MiniMime.lookup_by_content_type(result["mime_type"] || "")&.extension
|
38
|
-
result["filename"] = "#{context[:record].asset.friendlier_id}_#{context[:
|
41
|
+
result["filename"] = "#{context[:record].asset.friendlier_id}_#{context[:kithe_derivative_key]}.#{extension}"
|
42
|
+
result["kithe_derivative_key"] = context[:kithe_derivative_key]
|
39
43
|
end
|
40
44
|
|
41
45
|
return result
|
data/lib/kithe/version.rb
CHANGED
@@ -8,7 +8,19 @@ class Shrine
|
|
8
8
|
# It also supports specifying custom request headers for those remote URLs, to
|
9
9
|
# support OAuth2 Authorization headers, with browse-everything or similar.
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# model.attachment_column = {
|
12
|
+
# "storate" => "remote_url",
|
13
|
+
# "id" => "http://example.com/image.jpg",
|
14
|
+
# "headers" => {
|
15
|
+
# "Authorization" => "something"
|
16
|
+
# }
|
17
|
+
# }
|
18
|
+
#
|
19
|
+
# If you provide the (optional) "headers" key, they will wind up stored with
|
20
|
+
# file data in "metadata" hash, as "remote_url_headers". And they will be
|
21
|
+
# used with HTTP request to fetch the URL given, when promoting.
|
22
|
+
#
|
23
|
+
# The implementation uses the shrine-url storage, registering it as storage with key "remote_url";
|
12
24
|
# our custom kithe_multi_cache plugin to allow this alternate storage to be set as
|
13
25
|
# cache even though it's not main cache; and a #promote override suggested by
|
14
26
|
# Janko@shrine to get request headers to be supported.
|
@@ -18,14 +30,14 @@ class Shrine
|
|
18
30
|
# FUTURE. Need to whitelist allowed URLs/hostnames. :( A pain cause it'll
|
19
31
|
# be different for different apps, so we need to allow uploader customization?
|
20
32
|
class KitheAcceptRemoteUrl
|
33
|
+
STORAGE_KEY = :remote_url
|
34
|
+
METADATA_KEY = "remote_url_headers"
|
35
|
+
|
21
36
|
def self.configure(uploader, options = {})
|
22
37
|
# Ensure remote_url is registered as a storage
|
23
|
-
# Note, using downloader: :net_http so it can be tested with WebMock, would be
|
24
|
-
# better not to have to do that.
|
25
|
-
# https://github.com/shrinerb/shrine-url/issues/5
|
26
38
|
#
|
27
|
-
# Lazy default to make it easier to specify other.
|
28
|
-
uploader.storages[
|
39
|
+
# Lazy default to make it easier to specify other if an app wants to.
|
40
|
+
uploader.storages[STORAGE_KEY] ||= Shrine::Storage::Url.new
|
29
41
|
end
|
30
42
|
|
31
43
|
def self.load_dependencies(uploader, *)
|
@@ -34,17 +46,32 @@ class Shrine
|
|
34
46
|
uploader.plugin :kithe_multi_cache, additional_cache: :remote_url
|
35
47
|
end
|
36
48
|
|
49
|
+
module FileMethods
|
50
|
+
attr_reader :url_fetch_headers
|
51
|
+
def initialize(data)
|
52
|
+
# passed in as headers top-level key, but any but whitelisted keys actually
|
53
|
+
# end up being thrown out by shrine, and too hard to change that, so
|
54
|
+
# we'll copy it to 'metadata'
|
55
|
+
if data["storage"].to_s == STORAGE_KEY.to_s && data["headers"]
|
56
|
+
data["metadata"] ||= {}
|
57
|
+
data["metadata"][METADATA_KEY] = data["headers"]
|
58
|
+
end
|
59
|
+
super
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
37
63
|
module AttacherMethods
|
64
|
+
|
38
65
|
# Override to use 'headers' key in UploadedFile data for making remote request,
|
39
66
|
# when remote_url is being supplied.
|
40
|
-
def promote(
|
41
|
-
if
|
67
|
+
def promote(storage: store_key, **options)
|
68
|
+
if file.storage_key.to_s == STORAGE_KEY.to_s && file.data.dig("metadata", METADATA_KEY)
|
42
69
|
# instead of having Shrine "open" the file itself, we'll "open" it ourselves, so
|
43
70
|
# we can add supplied headers. Due to the beauty of design of `down` and `shrine-url`,
|
44
71
|
# and lazy opening, they'll end up using what we already opened. This approach
|
45
72
|
# suggested by Janko.
|
46
73
|
# https://groups.google.com/d/msg/ruby-shrine/SbeGujDa_k8/PeSGwpl9BAAJ
|
47
|
-
|
74
|
+
file.open(headers: file.data.dig("metadata", METADATA_KEY))
|
48
75
|
end
|
49
76
|
super
|
50
77
|
end
|
@@ -24,20 +24,17 @@ class Shrine
|
|
24
24
|
# And the data can be saved, and the remote url (shrine-url) file will be promoted as usual,
|
25
25
|
# even though it's not registered as the cache storage.
|
26
26
|
#
|
27
|
-
# NOTE: This implementation can be made a lot simpler once this PR is in a shrine release:
|
28
|
-
# https://github.com/shrinerb/shrine/pull/319
|
29
|
-
# https://github.com/shrinerb/shrine/commit/88c23d54814568b04987680f00b6b36f421c8d81
|
30
27
|
module KitheMultiCache
|
31
28
|
def self.configure(uploader, options = {})
|
32
|
-
uploader.opts[:kithe_multi_cache_keys] = Array(options[:additional_cache]).collect(&:
|
29
|
+
uploader.opts[:kithe_multi_cache_keys] = Array(options[:additional_cache]).collect(&:to_sym)
|
33
30
|
end
|
34
31
|
|
35
32
|
# override #cache to lazily extend with our custom module. Kinda hacky,
|
36
33
|
# but couldn't think of any other way to only extend the "cache" uploader,
|
37
34
|
# and not the "store" uploader.
|
38
35
|
module AttacherMethods
|
39
|
-
def cached?(file =
|
40
|
-
super || (file && shrine_class.opts[:kithe_multi_cache_keys].include?(file.storage_key))
|
36
|
+
def cached?(file = self.file)
|
37
|
+
super || (file && shrine_class.opts[:kithe_multi_cache_keys].include?(file.storage_key.to_sym))
|
41
38
|
end
|
42
39
|
end
|
43
40
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
class Shrine
|
2
|
+
module Plugins
|
3
|
+
# We want ActiveSupport-style callbacks around "promotion" -- the shrine process of finalizing
|
4
|
+
# a file by moving it from 'cache' storage to 'store' storage.
|
5
|
+
#
|
6
|
+
# We want to suport after_promotion hooks, and before_promotion hooks, the before_promotion hooks
|
7
|
+
# should be able to cancel promotion. (A convenient way to do validation even with backgrounding promotion,
|
8
|
+
# although you'd want to record the validation fail somewhere)
|
9
|
+
#
|
10
|
+
# For now, the actual hooks are registered in the `Asset` activerecord model. This works because
|
11
|
+
# our Asset model only has ONE shrine attachment, it is backwards compatible with kithe 1.
|
12
|
+
# It might make more sense to have the callbacks on the Uploader itself, in the future though.
|
13
|
+
#
|
14
|
+
# We want to be able to register these callbacks, and have them invoked regardless of how
|
15
|
+
# promotion happens -- inline; in a background job; or even explicitly calling Asset#promote
|
16
|
+
#
|
17
|
+
# ## Weird implementation
|
18
|
+
#
|
19
|
+
# It's a bit hard to get this to happen in shrine architecture. We end up needing to assume
|
20
|
+
# activerecord and wrap the activerecord_after_save method (for inline promotion). And then also
|
21
|
+
# overwrite atomic_promote to get background promotion and other cases.
|
22
|
+
#
|
23
|
+
# Because getting this right required some shuffling around of where the wrapping happened, it
|
24
|
+
# was convenient and avoided confusion to isolate wrapping in a class method that can be used
|
25
|
+
# anywhere, and only depends on args passed in, no implicit state anywhere.
|
26
|
+
class KithePromotionCallbacks
|
27
|
+
# promotion logic differs somewhat in different modes of use (bg or inline promotion),
|
28
|
+
# so we extract the wrapping logic here. Exactly what the logic wrapped is can
|
29
|
+
# differ.
|
30
|
+
#
|
31
|
+
# Kithe::PromotionCallbacks.with_promotion_callbacks(record) do
|
32
|
+
# promotion_logic # sometimes `super`
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
def self.with_promotion_callbacks(model)
|
36
|
+
# If callbacks haven't been skipped, and we have a model that implements
|
37
|
+
# callbacks, wrap yield in callbacks.
|
38
|
+
#
|
39
|
+
# Otherwise, just do it.
|
40
|
+
if ( !model.file_attacher.promotion_directives["skip_callbacks"] &&
|
41
|
+
model &&
|
42
|
+
model.class.respond_to?(:_promotion_callbacks) )
|
43
|
+
model.run_callbacks(:promotion) do
|
44
|
+
yield
|
45
|
+
end
|
46
|
+
else
|
47
|
+
yield
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module AttacherMethods
|
52
|
+
# For INLINE promotion, we need to wrap this one in callbacks, in order to be
|
53
|
+
# wrapping enough to a) be able to cancel in `before`, and b) have `after`
|
54
|
+
# actually be running after promotion is complete and persisted.
|
55
|
+
#
|
56
|
+
# But we only want to do it here for 'inline' promotion mode. For 'false'
|
57
|
+
# disabled promotion, we don't want to run callbacks at all; and for 'background'
|
58
|
+
# this is too early, we want callbacks to run in bg job, not here. AND only
|
59
|
+
# if we're actually promoting, otherwise we don't want to run callbacks!
|
60
|
+
def activerecord_after_save
|
61
|
+
if self.promotion_directives["promote"] == "inline" && promote?
|
62
|
+
Shrine::Plugins::KithePromotionCallbacks.with_promotion_callbacks(record) do
|
63
|
+
super
|
64
|
+
end
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Wrapping atomic_promote in callbacks gets background promotion, since the shrine pattern
|
71
|
+
# for background job for promotion uses atomic_promote. It also gets any 'manual' use of atomic
|
72
|
+
# promote, such as from our Asset#promote method.
|
73
|
+
def atomic_promote(*args, **kwargs)
|
74
|
+
Shrine::Plugins::KithePromotionCallbacks.with_promotion_callbacks(record) do
|
75
|
+
super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
register_plugin(:kithe_promotion_callbacks, KithePromotionCallbacks)
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
class Shrine
|
2
|
+
module Plugins
|
3
|
+
# This adds some features around shrine promotion that we found useful for dealing
|
4
|
+
# with backgrounding promotion.
|
5
|
+
#
|
6
|
+
# By default the kithe setup:
|
7
|
+
#
|
8
|
+
# * Does "promotion" in background job -- metadata extraction, and moving file from 'cache' to 'store'
|
9
|
+
# * Runs ActiveSupport-style callbacks around promotion (before_promotion, after_promotion)
|
10
|
+
# * Uses these callbacks to make sure we extract metadata on promotion (which shrine doesn't do by default)
|
11
|
+
# * Uses these callbacks to trigger our custom create_derivatives code *after* promotion
|
12
|
+
#
|
13
|
+
# There are times you want to customize these life cycle actions, either disabling them, or switching
|
14
|
+
# them from a background job to happen inline in the foreground. Some use cases for this are: 1) in
|
15
|
+
# automated testing; 2) when you are running a batch job (eg batch import), you might want to
|
16
|
+
# disable some expensive things per-record to instead do them all in batch at the end, or
|
17
|
+
# run them inline to keep from clogging up your bg job queue, and have better 'backpressure'.
|
18
|
+
#
|
19
|
+
# We provide something we call "promotion directives" to let you customize these. You can set
|
20
|
+
# them on a shrine Attacher; or on a Kithe `Asset` model individually, or globally on the class.
|
21
|
+
#
|
22
|
+
# ## Directives
|
23
|
+
#
|
24
|
+
# * `promote`: default `:background`; set to `:inline` to do promotion inline instead of a background
|
25
|
+
# job, or `false` to make promotion not happen automatically at all.
|
26
|
+
#
|
27
|
+
# * `skip_callbacks`: default `false`, set to `true` to disable our custom promotion callbacks
|
28
|
+
# entirely, including disabling our default callbacks such as derivative creation and
|
29
|
+
# promotion metadata extraction.
|
30
|
+
#
|
31
|
+
# * `create_derivatives`: default `background` (create a separate bg job). Also can be `false`
|
32
|
+
# to disable, or `inline` to create derivatives 'inline' when the after_promotion hook
|
33
|
+
# occurs -- which could already be in a bg job depending on `promote` directive!
|
34
|
+
#
|
35
|
+
# * `delete`: should _deletion_ of shrine attachment happen in a bg job? Default `:background`,
|
36
|
+
# can also be `false` (can't think of a good use case), or `:inline`.
|
37
|
+
#
|
38
|
+
# # Examples of setting
|
39
|
+
#
|
40
|
+
# ## Globally on Kithe::Asset
|
41
|
+
#
|
42
|
+
# Useful for batch processing or changing test defaults.
|
43
|
+
#
|
44
|
+
# Kithe::Asset.promotion_directives = { promote: :inline, create_derivatives: false }
|
45
|
+
#
|
46
|
+
# ## On a Kithe::Asset individual model
|
47
|
+
#
|
48
|
+
# asset = Kithe:Assst.new
|
49
|
+
# asset.set_promotion_directives(create_derivatives: :inline)
|
50
|
+
#
|
51
|
+
# (Aggregates on top of whatever was set at class level of previously set with `Asset#set_promotion_directives)`,
|
52
|
+
# does not replace previously settings but merges into them!
|
53
|
+
#
|
54
|
+
# ## Directly on a shrine attacher
|
55
|
+
#
|
56
|
+
# some_asset.file = some_assignable_file
|
57
|
+
# some_asset.file_attacher.set_promotion_directives(skip_callbacks: true)
|
58
|
+
# some_asset.save!
|
59
|
+
#
|
60
|
+
# (Aggregates on top of whatever was already set, merges into it, does not replace!)
|
61
|
+
#
|
62
|
+
# ## Checking current settings
|
63
|
+
#
|
64
|
+
# some_asset.promotion_directives
|
65
|
+
#
|
66
|
+
# or
|
67
|
+
#
|
68
|
+
# some_asset.file_attacher.promotion_directives
|
69
|
+
#
|
70
|
+
class KithePromotionDirectives
|
71
|
+
# whitelist of allowed promotion_directive keys, so we can raise on typos but still
|
72
|
+
# be extensible. Also serves as some documentation of what directives available.
|
73
|
+
class_attribute :allowed_promotion_directives,
|
74
|
+
instance_writer: false,
|
75
|
+
default: [:promote, :skip_callbacks, :create_derivatives, :delete]
|
76
|
+
|
77
|
+
module AttacherMethods
|
78
|
+
|
79
|
+
# Set one or more promotion directives, stored context[:promotion_directives], that
|
80
|
+
# will be serialized and restored to context for bg promotion. The values are intended
|
81
|
+
# to be simple strings or other json-serializable primitives.
|
82
|
+
#
|
83
|
+
# set_promotion_directives will merge it's results into existing promotion directives,
|
84
|
+
# existing keys will remain. So you can set multiple directives with multiple
|
85
|
+
# calls to set_promotion_directives, or pass multiple keys to one calls.
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# some_model.file_attacher.set_promotion_directives(skip_callbacks: true)
|
89
|
+
# some_model.save!
|
90
|
+
def set_promotion_directives(hash)
|
91
|
+
# ActiveJob sometimes has trouble if there are symbols in there, somewhat
|
92
|
+
# unpredictably.
|
93
|
+
hash = hash.collect { |k, v| [k.to_s, v === Symbol ? v.to_s : v.to_s]}.to_h
|
94
|
+
|
95
|
+
unrecognized = hash.keys.collect(&:to_sym) - KithePromotionDirectives.allowed_promotion_directives
|
96
|
+
unless unrecognized.length == 0
|
97
|
+
raise ArgumentError.new("Unrecognized promotion directive key: #{unrecognized.join('')}")
|
98
|
+
end
|
99
|
+
|
100
|
+
promotion_directives.merge!(hash)
|
101
|
+
end
|
102
|
+
|
103
|
+
# context[:promotion_directives], lazily initializing to hash for convenience.
|
104
|
+
def promotion_directives
|
105
|
+
context[:promotion_directives] ||= {}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
register_plugin(:kithe_promotion_directives, KithePromotionDirectives)
|
110
|
+
end
|
111
|
+
end
|