kithe 2.0.0.pre.alpha2 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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