kithe 2.0.0.pre.alpha2 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -4
  3. data/app/indexing/kithe/indexable/record_index_updater.rb +1 -1
  4. data/app/jobs/kithe/create_derivatives_job.rb +2 -2
  5. data/app/models/kithe/asset.rb +82 -154
  6. data/app/models/kithe/asset/derivative_creator.rb +32 -62
  7. data/app/models/kithe/asset/derivative_definition.rb +12 -13
  8. data/app/models/kithe/asset/set_shrine_uploader.rb +64 -0
  9. data/app/models/kithe/collection.rb +0 -6
  10. data/app/models/kithe/model.rb +0 -21
  11. data/app/models/kithe/work.rb +0 -5
  12. data/app/uploaders/kithe/asset_uploader.rb +15 -78
  13. data/lib/kithe.rb +22 -20
  14. data/{app/models → lib}/kithe/config_base.rb +6 -1
  15. data/lib/kithe/engine.rb +14 -3
  16. data/lib/kithe/indexable_settings.rb +1 -1
  17. data/lib/kithe/patch_fx.rb +39 -0
  18. data/lib/kithe/version.rb +4 -1
  19. data/lib/shrine/plugins/kithe_checksum_signatures.rb +41 -0
  20. data/lib/shrine/plugins/kithe_controllable_backgrounding.rb +53 -0
  21. data/lib/shrine/plugins/kithe_derivative_definitions.rb +101 -0
  22. data/lib/shrine/plugins/kithe_derivatives.rb +54 -0
  23. data/lib/shrine/plugins/kithe_determine_mime_type.rb +39 -0
  24. data/lib/shrine/plugins/kithe_persisted_derivatives.rb +161 -0
  25. data/lib/shrine/plugins/kithe_promotion_callbacks.rb +4 -0
  26. data/lib/shrine/plugins/kithe_promotion_directives.rb +33 -3
  27. data/lib/shrine/plugins/kithe_storage_location.rb +53 -4
  28. data/lib/tasks/kithe_tasks.rake +22 -15
  29. data/spec/dummy/app/models/plain_active_record.rb +3 -0
  30. data/spec/dummy/config/database.yml +6 -0
  31. data/spec/dummy/db/schema.rb +102 -0
  32. data/spec/dummy/log/development.log +3616 -0
  33. data/spec/dummy/log/test.log +86464 -0
  34. data/spec/dummy/tmp/development_secret.txt +1 -1
  35. data/spec/indexing/indexable_spec.rb +1 -1
  36. data/spec/models/kithe/asset/asset_derivatives_spec.rb +137 -0
  37. data/spec/models/kithe/asset/asset_promotion_hooks_spec.rb +26 -5
  38. data/spec/models/kithe/asset/set_shrine_uploader_spec.rb +39 -0
  39. data/spec/models/kithe/asset_spec.rb +9 -59
  40. data/spec/models/kithe/model_spec.rb +0 -32
  41. data/spec/models/kithe_spec.rb +10 -0
  42. data/spec/shrine/kithe_accept_remote_url_spec.rb +49 -0
  43. data/spec/shrine/kithe_checksum_signatures_spec.rb +63 -0
  44. data/spec/shrine/kithe_derivative_definitions_spec.rb +303 -0
  45. data/spec/shrine/kithe_persisted_derivatives_spec.rb +424 -0
  46. data/spec/shrine/kithe_storage_location_spec.rb +43 -15
  47. data/spec/spec_helper.rb +0 -19
  48. data/spec/test_support/images/3x3_pixel.jpg +0 -0
  49. data/spec/test_support/shrine_spec_support.rb +2 -1
  50. metadata +60 -36
  51. data/app/models/kithe/asset/derivative_updater.rb +0 -119
  52. data/app/models/kithe/derivative.rb +0 -15
  53. data/app/uploaders/kithe/derivative_uploader.rb +0 -48
  54. data/spec/dummy/db/structure.sql +0 -309
  55. data/spec/models/kithe/asset/asset_create_derivatives_spec.rb +0 -320
  56. data/spec/models/kithe/derivative_spec.rb +0 -168
@@ -1,3 +1,6 @@
1
1
  module Kithe
2
- VERSION = '2.0.0-alpha2'
2
+ # not sure why rubygems turned our alphas into 2.0.0.pre.alpha1, inserting
3
+ # "pre". We need to do same thing with betas to get version orderings
4
+ # appropriate.
5
+ VERSION = '2.0.2'
3
6
  end
@@ -0,0 +1,41 @@
1
+ class Shrine
2
+ module Plugins
3
+ # Using the shrine signature and add_metadata plugins, ensure that the shrine standard
4
+ # digest/checksum signatures are recorded in metadata.
5
+ #
6
+ # This plugin is NOT included in Kithe::AssetUploader by default, include it in your
7
+ # local uploader if desired.
8
+ #
9
+ # We want to store md5 and sha1 checksums (legacy compat), as well as
10
+ # sha512 (more recent digital preservation recommendation: https://ocfl.io/draft/spec/#digests)
11
+ #
12
+ # We only calculate them only on promotion action (not cache action), to avoid needlessly
13
+ # expensive double-computation, and because for direct uploads/backgrounding, we haven't
14
+ # actually gotten the file in our hands to compute checksums until then anyway.
15
+ #
16
+ # the add_metadata plugin's `metadata_method` is used to make md5, sha1, and sha512 methods
17
+ # available on the Attacher. (They also end up delegated from the Asset model)
18
+ class KitheChecksumSignatures
19
+ def self.load_dependencies(uploader, *)
20
+ uploader.plugin :add_metadata
21
+ uploader.plugin :signature
22
+ end
23
+
24
+ def self.configure(uploader, opts = {})
25
+ uploader.class_eval do
26
+ add_metadata do |io, derivative:nil, **context|
27
+ if context[:action] != :cache && derivative.nil?
28
+ {
29
+ md5: calculate_signature(io, :md5),
30
+ sha1: calculate_signature(io, :sha1),
31
+ sha512: calculate_signature(io, :sha512)
32
+ }
33
+ end
34
+ end
35
+ metadata_method :md5, :sha1, :sha512
36
+ end
37
+ end
38
+ end
39
+ register_plugin(:kithe_checksum_signatures, KitheChecksumSignatures)
40
+ end
41
+ end
@@ -0,0 +1,53 @@
1
+ class Shrine
2
+ module Plugins
3
+
4
+ # Set up shrine `backgrounding`, where promotion and deletion can happen in a background job.
5
+ #
6
+ # https://shrinerb.com/docs/getting-started#backgrounding
7
+ # https://shrinerb.com/docs/plugins/backgrounding
8
+ #
9
+ # By default, kithe does promotion and deletion in kithe-provided ActiveJob classes.
10
+ #
11
+ # But this plugin implements code to let you use kithe_promotion_directives to make them happen
12
+ # inline instead, or disable them.
13
+ #
14
+ # asset.file_attacher.set_promotion_directives(promote: false)
15
+ # asset.file_attacher.set_promotion_directives(promote: :inline)
16
+ # asset.file_attacher.set_promotion_directives(promote: "inline")
17
+ #
18
+ # asset.file_attacher.set_promotion_directives(delete: :inline)
19
+ class KitheControllableBackgrounding
20
+ def self.load_dependencies(uploader, *)
21
+ uploader.plugin :backgrounding
22
+ uploader.plugin :kithe_promotion_directives
23
+ end
24
+
25
+ def self.configure(uploader, options = {})
26
+
27
+ # promote using shrine backgrounding, but can be effected by promotion_directives[:promote]
28
+ uploader::Attacher.promote_block do
29
+ Kithe::TimingPromotionDirective.new(key: :promote, directives: self.promotion_directives) do |directive|
30
+ if directive.inline?
31
+ promote
32
+ elsif directive.background?
33
+ # What shrine normally expects for backgrounding, plus promotion_directives
34
+ Kithe::AssetPromoteJob.perform_later(self.class.name, record.class.name, record.id, name.to_s, file_data, self.promotion_directives)
35
+ end
36
+ end
37
+ end
38
+
39
+ uploader::Attacher.destroy_block do
40
+ Kithe::TimingPromotionDirective.new(key: :delete, directives: self.promotion_directives) do |directive|
41
+ if directive.inline?
42
+ destroy
43
+ elsif directive.background?
44
+ # What shrine normally expects for backgrounding
45
+ Kithe::AssetDeleteJob.perform_later(self.class.name, data)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ register_plugin(:kithe_controllable_backgrounding, KitheControllableBackgrounding)
52
+ end
53
+ end
@@ -0,0 +1,101 @@
1
+ class Shrine
2
+ module Plugins
3
+ class KitheDerivativeDefinitions
4
+ def self.configure(uploader, *opts)
5
+ # use Rails class_attribute to conveniently have a class-level place
6
+ # to store our derivative definitions that are inheritable and overrideable.
7
+ # We store it on the Attacher class, because that's where shrine
8
+ # puts derivative processor definitions, so seems appropriate.
9
+ uploader::Attacher.class_attribute :kithe_derivative_definitions, instance_writer: false, default: []
10
+
11
+ # Register our derivative processor, that will create our registered derivatives,
12
+ # with our custom options.
13
+ uploader::Attacher.derivatives(:kithe_derivatives) do |original, **options|
14
+ Kithe::Asset::DerivativeCreator.new(self.class.kithe_derivative_definitions,
15
+ source_io: original,
16
+ shrine_attacher: self,
17
+ only: options[:only],
18
+ except: options[:except],
19
+ lazy: options[:lazy]
20
+ ).call
21
+ end
22
+ end
23
+
24
+ module AttacherClassMethods
25
+ # Establish a derivative definition that will be used to create a derivative
26
+ # when #create_derivatives is called, for instance automatically after promotion.
27
+ #
28
+ # The most basic definition consists of a derivative key, and a ruby block that
29
+ # takes the original file, transforms it, and returns a ruby File or other
30
+ # (shrine-compatible) IO-like object. It will usually be done inside a custom Asset
31
+ # class definition.
32
+ #
33
+ # class Asset < Kithe::Asset
34
+ # define_derivative :thumbnail do |original_file|
35
+ # end
36
+ # end
37
+ #
38
+ # The original_file passed in will be a ruby File object that is already open for reading. If
39
+ # you need a local file path for your transformation, just use `original_file.path`.
40
+ #
41
+ # The return value can be any IO-like object. If it is a ruby File or Tempfile,
42
+ # that temporary file will be deleted for you after the derivative has been created. If you
43
+ # have to make any intermediate files, you are responsible for cleaning them up. Ruby stdlib
44
+ # Tempfile and Dir.mktmpdir may be useful.
45
+ #
46
+ # If in order to do your transformation you need additional information about the original,
47
+ # just add a `record:` keyword argument to your block, and the Asset object will be passed in:
48
+ #
49
+ # define_derivative :thumbnail do |original_file, record:|
50
+ # record.width, record.height, record.content_type # etc
51
+ # end
52
+ #
53
+ # Derivatives are normally uploaded to the Shrine storage labeled :kithe_derivatives,
54
+ # but a definition can specify an alternate Shrine storage id. (specified shrine storage key
55
+ # is applied on derivative creation; if you change it with existing derivatives, they should
56
+ # remain, and be accessible, where they were created; there is no built-in solution at present
57
+ # for moving them).
58
+ #
59
+ # define_derivative :thumbnail, storage_key: :my_thumb_storage do |original| # ...
60
+ #
61
+ # You can also set `default_create: false` if you want a particular definition not to be
62
+ # included in a no-arg `asset.create_derivatives` that is normally triggered on asset creation.
63
+ #
64
+ # And you can set content_type to either a specific type like `image/jpeg` (or array of such) or a general type
65
+ # like `image`, if you want to define a derivative generation routine for only certain types.
66
+ # If multiple blocks for the same key are defined, with different content_type restrictions,
67
+ # the most specific one will be used. That is, for a JPG, `image/jpeg` beats `image` beats no restriction.
68
+ def define_derivative(key, content_type: nil, default_create: true, &block)
69
+ # Make sure we dup the array to handle sub-classes on class_attribute
70
+ self.kithe_derivative_definitions = self.kithe_derivative_definitions.dup.push(
71
+ Kithe::Asset::DerivativeDefinition.new(
72
+ key: key,
73
+ content_type: content_type,
74
+ default_create: default_create,
75
+ proc: block
76
+ )
77
+ ).freeze
78
+ end
79
+
80
+ # Returns all derivative keys registered with a definition, as array of strings
81
+ def defined_derivative_keys
82
+ self.kithe_derivative_definitions.collect(&:key).uniq.collect(&:to_s)
83
+ end
84
+
85
+ # If you have a subclass that has inherited derivative definitions, you can
86
+ # remove them -- only by key, will remove any definitions with that key regardless
87
+ # of content_type restrictions.
88
+ #
89
+ # This could be considered rather bad OO design, you might want to consider
90
+ # a different class hieararchy where you don't have to do this. But it's here.
91
+ def remove_derivative_definition!(*keys)
92
+ keys = keys.collect(&:to_sym)
93
+ self.kithe_derivative_definitions = self.kithe_derivative_definitions.reject do |defn|
94
+ keys.include?(defn.key.to_sym)
95
+ end.freeze
96
+ end
97
+ end
98
+ end
99
+ register_plugin(:kithe_derivative_definitions, KitheDerivativeDefinitions)
100
+ end
101
+ end
@@ -0,0 +1,54 @@
1
+ require 'mini_mime'
2
+
3
+ class Shrine
4
+ module Plugins
5
+ # Includes the Shrine `derivatives` plugin with some configuration, and
6
+ # extra features. The metadata for shrine derivatives is stored in the same
7
+ # JSON as the main file.
8
+ #
9
+ # * default kithe storage location of :kithe_derivatives
10
+ #
11
+ # * nice metadata["filename"] for derivatives, instead of default shrine fairly
12
+ # random (filename ends up used by default in content-disposition headers when delivered)
13
+ #
14
+ # * Includes kithe_persisted_derivatives with #add_persisted_derivatives
15
+ # and #create_persisted_derivatives methods for concurrency-safe
16
+ # derivative persisting.
17
+ #
18
+ # ## Shrine derivatives references
19
+ #
20
+ # https://shrinerb.com/docs/plugins/derivatives
21
+ # https://shrinerb.com/docs/processing
22
+ class KitheDerivatives
23
+ def self.load_dependencies(uploader, *)
24
+ uploader.plugin :derivatives, storage: -> (derivative) do
25
+ # default derivatives storage to
26
+ :kithe_derivatives
27
+ end
28
+
29
+ uploader.plugin :kithe_persisted_derivatives
30
+ uploader.plugin :kithe_derivative_definitions
31
+ end
32
+
33
+ module InstanceMethods
34
+
35
+ # Override to fix "filename" metadata to be something reasonable, regardless
36
+ # of what if anything was the filename of the IO being attached. shrine S3 will
37
+ # insist on setting a default content-disposition with this filename.
38
+ def extract_metadata(io, derivative:nil, **context)
39
+ result = super
40
+
41
+ if derivative && context[:record] && result["mime_type"]
42
+ extension = MiniMime.lookup_by_content_type(result["mime_type"] || "")&.extension || "bin"
43
+ result["filename"] = "#{context[:record].friendlier_id}_#{derivative}.#{extension}"
44
+ end
45
+
46
+ result
47
+ end
48
+ end
49
+
50
+ end
51
+ register_plugin(:kithe_derivatives, KitheDerivatives)
52
+ end
53
+ end
54
+
@@ -0,0 +1,39 @@
1
+ class Shrine
2
+ module Plugins
3
+ # Custom kithe logic for determining mime type, using the shrine mime_type plugin.
4
+ #
5
+ # We start out using the `marcel` analyzer.
6
+ # Marcel analyzer is pure-ruby and fast. It's from Basecamp and is what
7
+ # ActiveStorage uses. It is very similar to :mimemagic (and uses mimemagic
8
+ # under the hood), but mimemagic seems not to be maintained with up to date
9
+ # magic db? https://github.com/minad/mimemagic/pull/66
10
+ #
11
+ # But marcel is not able to catch some of our MP3s as audio/mpeg. The
12
+ # `mediainfo` CLI is, and is one of the tools Harvard FITS uses.
13
+ # If marcel came up blank, AND we are configured to use mediainfo CLI
14
+ # (which by default we will be if it's available), we will try
15
+ # shelling out to mediainfo command line.
16
+ #
17
+ # https://github.com/MediaArea/MediaInfo
18
+ #
19
+ # Ensure that if mime-type can't be otherwise determined, it is assigned
20
+ # "application/octet-stream", basically the type for generic binary.
21
+ class KitheDetermineMimeType
22
+ def self.load_dependencies(uploader, *)
23
+ uploader.plugin :determine_mime_type, analyzer: -> (io, analyzers) do
24
+ mime_type = analyzers[:marcel].call(io)
25
+
26
+
27
+ if Kithe.use_mediainfo && mime_type == "application/octet-stream" || mime_type.blank?
28
+ mime_type = Kithe::MediainfoAnalyzer.new.call(io)
29
+ end
30
+
31
+ mime_type = "application/octet-stream" if mime_type.blank?
32
+
33
+ mime_type
34
+ end
35
+ end
36
+ end
37
+ register_plugin(:kithe_determine_mime_type, KitheDetermineMimeType)
38
+ end
39
+ end
@@ -0,0 +1,161 @@
1
+ class Shrine
2
+ module Plugins
3
+ # Some convenience methods for adding/changing derivatives in
4
+ # concurrency-safe manner:
5
+ #
6
+ # * Won't make a change if the underlying original has changed
7
+ # so doesn't match the one you wanted to remove.
8
+ # * Won't over-write changes made concurrently in the db by other processes
9
+ # * Will always make sure to clean up any temporary files on all error
10
+ # and falure conditions.
11
+ #
12
+ # Shrine has some building blocks for this, which we use, but it's a bit tricky
13
+ # to put them together to be generically reliable, as we think we've done here.
14
+ #
15
+ # All these methods will cause your Asset model to be saved, because of how
16
+ # the shrine atomic helpers work. So these methods will by default raise
17
+ # a TypeError if your Asset model has any unrelated outstanding changes,
18
+ # but you can tell it to save anyway with `allow_other_changes: true`.
19
+ #
20
+ # ## Shrine references:
21
+ #
22
+ # https://shrinerb.com/docs/plugins/derivatives
23
+ # https://shrinerb.com/docs/processing
24
+ class KithePersistedDerivatives
25
+ module AttacherMethods
26
+ # Like the shrine `add_derivatives` method, but also *persists* the
27
+ # derivatives (saves to db), in a realiably concurrency-safe way.
28
+ #
29
+ # Generally can take any options that shrine `add_derivatives`
30
+ # can take, including custom `storage` or `metadata` arguments.
31
+ #
32
+ # Like shrine add_derivatives, it will assume the files passed in are
33
+ # temporary, and delete them for you. If you want to disable this behavior:
34
+ #
35
+ # attacher.add_persisted_derivatives({key: io}, delete: false)
36
+ #
37
+ # In some cases the derivatives can't be persisted because the underlying
38
+ # database has changed such that they would not be applicable. In those
39
+ # cases `false` will be return value, otherwise returns the new derivatives
40
+ # just as shrine `add_derivatives`
41
+ #
42
+ # Because the concurrent-safe persistence method will save the associated model --
43
+ # and save without ActiveRecord validation -- it is not safe to
44
+ # add_persisted_derivatives on a model with other unsaved changes. The
45
+ # method will by default refuse to do so, throwing a TypeError. If you'd
46
+ # like to force it, pass `allow_other_changes: true` as an argument.
47
+ #
48
+ # Also takes care of deleting any replaced derivative files, that are no longer
49
+ # referenced by the model. Shrine by default does not do this:
50
+ # https://github.com/shrinerb/shrine/issues/468
51
+ #
52
+ # All deletions are inline. In general this could be a fairly expensive operation,
53
+ # it can be wise to do it in a bg job.
54
+ def add_persisted_derivatives(local_files, **options)
55
+ other_changes_allowed = !!options.delete(:allow_other_changes)
56
+ if record && !other_changes_allowed && record.changed?
57
+ raise TypeError.new("Can't safely add_persisted_derivatives on model with unsaved changes. Pass `allow_other_changes: true` to force.")
58
+ end
59
+
60
+ existing_derivative_files = nil
61
+
62
+ # upload to storage
63
+ new_derivatives = upload_derivatives(local_files, **options)
64
+
65
+ begin
66
+ atomic_persist do |reloaded_attacher|
67
+ # record so we can delete any replaced ones...
68
+ existing_derivative_files = map_derivative(reloaded_attacher.derivatives).collect { |path, file| file }
69
+
70
+ # make sure we don't override derivatives created in other jobs, by
71
+ # first using the current up-to-date derivatives from db,
72
+ # then merging our changes in on top.
73
+ set_derivatives(reloaded_attacher.derivatives)
74
+ merge_derivatives(new_derivatives)
75
+ end
76
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound => e
77
+ # underlying file has changed or model has been deleted, inappropriate
78
+ # to add the derivatives, we can just silently drop them, but clean
79
+ # up after ourselves.
80
+ delete_derivatives(local_files) unless options[:delete] == false
81
+ delete_derivatives(new_derivatives)
82
+
83
+ return false
84
+ rescue StandardError => e
85
+ # unexpected error, clean up our files and re-raise
86
+ delete_derivatives(local_files) unless options[:delete] == false
87
+ delete_derivatives(new_derivatives)
88
+ raise e
89
+ end
90
+
91
+ # Take care of deleting from storage any derivatives that were replaced.
92
+ current_derivative_files = map_derivative(derivatives).collect { |path, file| file }
93
+ replaced_files = existing_derivative_files - current_derivative_files
94
+ delete_derivatives(replaced_files)
95
+
96
+ new_derivatives
97
+ end
98
+
99
+ # Like the shrine `create_derivatives` method, but persists the created derivatives
100
+ # to the database in a concurrency-safe way.
101
+ #
102
+ # Can take all options that shrine `create_derivatives` can take, including custom
103
+ # processors, custom storage key, and arbitrary custom processor arguments.
104
+ #
105
+ # asset.file_attacher.create_persisted_derivatives
106
+ # asset.file_attacher.create_persisted_derivatives(storage: :custom_key)
107
+ # asset.file_attacher.create_persisted_derivatives(:kithe_derivatives)
108
+ # asset.file_attacher.create_persisted_derivatives(:kithe_derivatives, some_arg: "value")
109
+ # asset.file_attacher.create_persisted_derivatives(:kithe_derivatives, alternate_source_file)
110
+ #
111
+ # Also has an `allow_other_changes` argument, see #add_persisted_derivatives.
112
+ def create_persisted_derivatives(*args, storage: nil, allow_other_changes: false, **options)
113
+ return false unless file
114
+
115
+ local_files = process_derivatives(*args, **options)
116
+ add_persisted_derivatives(local_files, storage: storage, allow_other_changes: allow_other_changes)
117
+ end
118
+
119
+ # Kind of like built-in Shrine #remove_derivatives, but also takes care of
120
+ # persisting AND deleting the removed derivative file from storage --
121
+ # all in concurrency-safe way, including not making sure to overwrite
122
+ # any unrelated derivatives someone else was adding.
123
+ #
124
+ # Can take the same sorts of path arguments as Shrine derivative #remove_derivatives
125
+ #
126
+ # asset.file_attacher.remove_persisted_derivatives(:small_thumb)
127
+ # asset.file_attacher.remove_persisted_derivatives(:small_thumb, :large_thumb)
128
+ # asset.file_attacher.remove_persisted_derivatives(:small_thumb, :large_thumb, allow_other_changes: true)
129
+ def remove_persisted_derivatives(*paths, **options)
130
+ return if paths.empty?
131
+
132
+ other_changes_allowed = !!options.delete(:allow_other_changes)
133
+ if record && !other_changes_allowed && record.changed?
134
+ raise TypeError.new("Can't safely add_persisted_derivatives on model with unsaved changes. Pass `allow_other_changes: true` to force.")
135
+ end
136
+
137
+ removed_derivatives = nil
138
+ atomic_persist do |reloaded_attacher|
139
+ set_derivatives(reloaded_attacher.derivatives)
140
+ removed_derivatives = remove_derivatives(*paths, delete: false)
141
+ end
142
+
143
+ if removed_derivatives
144
+ map_derivative(removed_derivatives) do |_, derivative|
145
+ derivative.delete if derivative
146
+ end
147
+ end
148
+
149
+ removed_derivatives
150
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
151
+ # original was already deleted or changed, the derivatives wer'e trying to delete.
152
+ # It should be fine to do nothing, the process that deleted or changed
153
+ # the model should already have deleted all these derivatives.
154
+ # But we'll return false as a signel.
155
+ return false
156
+ end
157
+ end
158
+ end
159
+ register_plugin(:kithe_persisted_derivatives, KithePersistedDerivatives)
160
+ end
161
+ end