carrierwave 1.3.4 → 3.1.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +278 -86
  3. data/lib/carrierwave/compatibility/paperclip.rb +4 -2
  4. data/lib/carrierwave/downloader/base.rb +101 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +68 -0
  6. data/lib/carrierwave/locale/en.yml +9 -6
  7. data/lib/carrierwave/mount.rb +53 -61
  8. data/lib/carrierwave/mounter.rb +169 -77
  9. data/lib/carrierwave/orm/activerecord.rb +23 -58
  10. data/lib/carrierwave/processing/mini_magick.rb +137 -123
  11. data/lib/carrierwave/processing/rmagick.rb +47 -20
  12. data/lib/carrierwave/processing/vips.rb +315 -0
  13. data/lib/carrierwave/processing.rb +1 -0
  14. data/lib/carrierwave/sanitized_file.rb +83 -70
  15. data/lib/carrierwave/storage/abstract.rb +5 -5
  16. data/lib/carrierwave/storage/file.rb +6 -5
  17. data/lib/carrierwave/storage/fog.rb +114 -62
  18. data/lib/carrierwave/storage.rb +1 -0
  19. data/lib/carrierwave/test/matchers.rb +11 -7
  20. data/lib/carrierwave/uploader/cache.rb +40 -24
  21. data/lib/carrierwave/uploader/callbacks.rb +1 -1
  22. data/lib/carrierwave/uploader/configuration.rb +42 -19
  23. data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  24. data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
  25. data/lib/carrierwave/uploader/dimension.rb +66 -0
  26. data/lib/carrierwave/uploader/download.rb +2 -123
  27. data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
  28. data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
  29. data/lib/carrierwave/uploader/file_size.rb +2 -2
  30. data/lib/carrierwave/uploader/mountable.rb +6 -0
  31. data/lib/carrierwave/uploader/processing.rb +45 -7
  32. data/lib/carrierwave/uploader/proxy.rb +17 -4
  33. data/lib/carrierwave/uploader/serialization.rb +1 -1
  34. data/lib/carrierwave/uploader/store.rb +73 -7
  35. data/lib/carrierwave/uploader/url.rb +7 -4
  36. data/lib/carrierwave/uploader/versions.rb +161 -111
  37. data/lib/carrierwave/uploader.rb +10 -17
  38. data/lib/carrierwave/utilities/file_name.rb +47 -0
  39. data/lib/carrierwave/utilities/uri.rb +14 -11
  40. data/lib/carrierwave/utilities.rb +1 -0
  41. data/lib/carrierwave/validations/active_model.rb +7 -9
  42. data/lib/carrierwave/version.rb +1 -1
  43. data/lib/carrierwave.rb +22 -17
  44. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -4
  45. data/lib/generators/uploader_generator.rb +3 -3
  46. metadata +94 -47
  47. data/lib/carrierwave/uploader/content_type_blacklist.rb +0 -48
  48. data/lib/carrierwave/uploader/content_type_whitelist.rb +0 -48
  49. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -51
  50. data/lib/carrierwave/uploader/extension_whitelist.rb +0 -52
@@ -18,7 +18,7 @@ module CarrierWave
18
18
  # Adds a processor callback which applies operations as a file is uploaded.
19
19
  # The argument may be the name of any method of the uploader, expressed as a symbol,
20
20
  # or a list of such methods, or a hash where the key is a method and the value is
21
- # an array of arguments to call the method with
21
+ # an array of arguments to call the method with. Also accepts an :if or :unless condition
22
22
  #
23
23
  # === Parameters
24
24
  #
@@ -31,6 +31,7 @@ module CarrierWave
31
31
  # process :sepiatone, :vignette
32
32
  # process :scale => [200, 200]
33
33
  # process :scale => [200, 200], :if => :image?
34
+ # process :scale => [200, 200], :unless => :disallowed_image_type?
34
35
  # process :sepiatone, :if => :image?
35
36
  #
36
37
  # def sepiatone
@@ -49,6 +50,10 @@ module CarrierWave
49
50
  # ...
50
51
  # end
51
52
  #
53
+ # def disallowed_image_type?
54
+ # ...
55
+ # end
56
+ #
52
57
  # end
53
58
  #
54
59
  def process(*args)
@@ -57,12 +62,20 @@ module CarrierWave
57
62
  hash.merge!(arg)
58
63
  end
59
64
 
60
- condition = new_processors.delete(:if)
65
+ condition_type = new_processors.keys.detect { |key| [:if, :unless].include?(key) }
66
+ condition = new_processors.delete(:if) || new_processors.delete(:unless)
61
67
  new_processors.each do |processor, processor_args|
62
- self.processors += [[processor, processor_args, condition]]
68
+ self.processors += [[processor, processor_args, condition, condition_type]]
69
+
70
+ if processor == :convert
71
+ # Treat :convert specially, since it should trigger the file extension change
72
+ force_extension processor_args
73
+ if condition
74
+ warn "Use of 'process convert: format' with conditionals has an issue and doesn't work correctly. See https://github.com/carrierwaveuploader/carrierwave/issues/2723 for details. "
75
+ end
76
+ end
63
77
  end
64
78
  end
65
-
66
79
  end # ClassMethods
67
80
 
68
81
  ##
@@ -72,19 +85,44 @@ module CarrierWave
72
85
  return unless enable_processing
73
86
 
74
87
  with_callbacks(:process, new_file) do
75
- self.class.processors.each do |method, args, condition|
76
- if(condition)
88
+ self.class.processors.each do |method, args, condition, condition_type|
89
+ if condition && condition_type == :if
77
90
  if condition.respond_to?(:call)
78
91
  next unless condition.call(self, :args => args, :method => method, :file => new_file)
79
92
  else
80
93
  next unless self.send(condition, new_file)
81
94
  end
95
+ elsif condition && condition_type == :unless
96
+ if condition.respond_to?(:call)
97
+ next if condition.call(self, :args => args, :method => method, :file => new_file)
98
+ elsif self.send(condition, new_file)
99
+ next
100
+ end
101
+ end
102
+
103
+ if args.is_a? Array
104
+ kwargs, args = args.partition { |arg| arg.is_a? Hash }
105
+ end
106
+
107
+ if kwargs.present?
108
+ kwargs = kwargs.reduce(:merge)
109
+ self.send(method, *args, **kwargs)
110
+ else
111
+ self.send(method, *args)
82
112
  end
83
- self.send(method, *args)
84
113
  end
85
114
  end
86
115
  end
87
116
 
117
+ private
118
+
119
+ def forcing_extension(filename)
120
+ if force_extension && filename
121
+ Pathname.new(filename).sub_ext(".#{force_extension.to_s.delete_prefix('.')}").to_s
122
+ else
123
+ filename
124
+ end
125
+ end
88
126
  end # Processing
89
127
  end # Uploader
90
128
  end # CarrierWave
@@ -23,14 +23,27 @@ module CarrierWave
23
23
  alias_method :path, :current_path
24
24
 
25
25
  ##
26
- # Returns a string that uniquely identifies the last stored file
26
+ # Returns a string that uniquely identifies the retrieved or last stored file
27
27
  #
28
28
  # === Returns
29
29
  #
30
30
  # [String] uniquely identifies a file
31
31
  #
32
32
  def identifier
33
- storage.try(:identifier)
33
+ @identifier || (file && storage.try(:identifier))
34
+ end
35
+
36
+ ##
37
+ # Returns a String which is to be used as a temporary value which gets assigned to the column.
38
+ # The purpose is to mark the column that it will be updated. Finally before the save it will be
39
+ # overwritten by the #identifier value, which is usually #filename.
40
+ #
41
+ # === Returns
42
+ #
43
+ # [String] a temporary_identifier, by default the value of #cache_name is used
44
+ #
45
+ def temporary_identifier
46
+ cache_name || @identifier
34
47
  end
35
48
 
36
49
  ##
@@ -40,8 +53,8 @@ module CarrierWave
40
53
  #
41
54
  # [String] contents of the file
42
55
  #
43
- def read
44
- file.try(:read)
56
+ def read(*args)
57
+ file.try(:read, *args)
45
58
  end
46
59
 
47
60
  ##
@@ -7,7 +7,7 @@ module CarrierWave
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  def serializable_hash(options = nil)
10
- {"url" => url}.merge Hash[versions.map { |name, version| [name, { "url" => version.url }] }]
10
+ {"url" => url}.merge Hash[versions.map { |name, version| [name.to_s, { "url" => version.url }] }]
11
11
  end
12
12
 
13
13
  def as_json(options=nil)
@@ -11,9 +11,23 @@ module CarrierWave
11
11
  prepend Module.new {
12
12
  def initialize(*)
13
13
  super
14
- @file, @filename, @cache_id = nil
14
+ @file, @filename, @cache_id, @identifier, @deduplication_index = nil
15
15
  end
16
16
  }
17
+
18
+ after :store, :show_warning_when_filename_is_unavailable
19
+
20
+ class_attribute :filename_safeguard_checked
21
+ end
22
+
23
+ module ClassMethods
24
+ private
25
+
26
+ def inherited(subclass)
27
+ # To perform the filename safeguard check once per a class
28
+ self.filename_safeguard_checked = false
29
+ super
30
+ end
17
31
  end
18
32
 
19
33
  ##
@@ -34,9 +48,26 @@ module CarrierWave
34
48
  @filename
35
49
  end
36
50
 
51
+ ##
52
+ # Returns a filename which doesn't conflict with already-stored files.
53
+ #
54
+ # === Returns
55
+ #
56
+ # [String] the filename with suffix added for deduplication
57
+ #
58
+ def deduplicated_filename
59
+ return unless filename
60
+ return filename unless @deduplication_index
61
+
62
+ parts = filename.split('.')
63
+ basename = parts.shift
64
+ basename.sub!(/ ?\(\d+\)\z/, '')
65
+ ([basename.to_s + (@deduplication_index > 1 ? "(#{@deduplication_index})" : '')] + parts).join('.')
66
+ end
67
+
37
68
  ##
38
69
  # Calculates the path where the file should be stored. If +for_file+ is given, it will be
39
- # used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed.
70
+ # used as the identifier, otherwise +CarrierWave::Uploader#identifier+ is assumed.
40
71
  #
41
72
  # === Parameters
42
73
  #
@@ -46,7 +77,7 @@ module CarrierWave
46
77
  #
47
78
  # [String] the store path
48
79
  #
49
- def store_path(for_file=filename)
80
+ def store_path(for_file=identifier)
50
81
  File.join([store_dir, full_filename(for_file)].compact)
51
82
  end
52
83
 
@@ -60,8 +91,8 @@ module CarrierWave
60
91
  # [new_file (File, IOString, Tempfile)] any kind of file object
61
92
  #
62
93
  def store!(new_file=nil)
63
- cache!(new_file) if new_file && ((@cache_id != parent_cache_id) || @cache_id.nil?)
64
- if !cache_only and @file and @cache_id
94
+ cache!(new_file) if new_file && !cached?
95
+ if !cache_only && @file && @cache_id
65
96
  with_callbacks(:store, new_file) do
66
97
  new_file = storage.store!(@file)
67
98
  if delete_tmp_file_after_storage
@@ -69,7 +100,9 @@ module CarrierWave
69
100
  cache_storage.delete_dir!(cache_path(nil))
70
101
  end
71
102
  @file = new_file
72
- @cache_id = nil
103
+ @identifier = storage.identifier
104
+ @original_filename = @cache_id = @deduplication_index = nil
105
+ @staged = false
73
106
  end
74
107
  end
75
108
  end
@@ -84,13 +117,46 @@ module CarrierWave
84
117
  def retrieve_from_store!(identifier)
85
118
  with_callbacks(:retrieve_from_store, identifier) do
86
119
  @file = storage.retrieve!(identifier)
120
+ @identifier = identifier
121
+ end
122
+ end
123
+
124
+ ##
125
+ # Look for an identifier which doesn't collide with the given already-stored identifiers.
126
+ # It is done by adding a index number as the suffix.
127
+ # For example, if there's 'image.jpg' and the @deduplication_index is set to 2,
128
+ # The stored file will be named as 'image(2).jpg'.
129
+ #
130
+ # === Parameters
131
+ #
132
+ # [current_identifiers (Array[String])] List of identifiers for already-stored files
133
+ #
134
+ def deduplicate(current_identifiers)
135
+ @deduplication_index = nil
136
+ return unless current_identifiers.include?(identifier)
137
+
138
+ (1..current_identifiers.size + 1).each do |i|
139
+ @deduplication_index = i
140
+ break unless current_identifiers.include?(identifier)
87
141
  end
88
142
  end
89
143
 
90
144
  private
91
145
 
92
146
  def full_filename(for_file)
93
- for_file
147
+ forcing_extension(for_file)
148
+ end
149
+
150
+ def show_warning_when_filename_is_unavailable(_)
151
+ return if self.class.filename_safeguard_checked
152
+ self.class.filename_safeguard_checked = true
153
+ return if filename
154
+
155
+ warn <<~MESSAGE
156
+ [WARNING] Your uploader's #filename method defined at #{method(:filename).source_location.join(':')} didn't return value after storing the file.
157
+ It's likely that the method is safeguarded with `if original_filename`, which were necessary for pre-3.x CarrierWave but is no longer needed.
158
+ Removing it is recommended, as it is known to cause issues depending on the use case: https://github.com/carrierwaveuploader/carrierwave/issues/2708
159
+ MESSAGE
94
160
  end
95
161
 
96
162
  def storage
@@ -15,12 +15,15 @@ module CarrierWave
15
15
  # [String] the location where this file is accessible via a url
16
16
  #
17
17
  def url(options = {})
18
- if file.respond_to?(:url) and not (tmp_url = file.url).blank?
19
- file.method(:url).arity == 0 ? tmp_url : file.url(options)
20
- elsif file.respond_to?(:path)
18
+ if file.respond_to?(:url)
19
+ tmp_url = file.method(:url).arity.zero? ? file.url : file.url(options)
20
+ return tmp_url if tmp_url.present?
21
+ end
22
+
23
+ if file.respond_to?(:path)
21
24
  path = encode_path(file.path.sub(File.expand_path(root), ''))
22
25
 
23
- if host = asset_host
26
+ if (host = asset_host)
24
27
  if host.respond_to? :call
25
28
  "#{host.call(file)}#{path}"
26
29
  else
@@ -1,6 +1,89 @@
1
+ require "active_support/core_ext/object/deep_dup"
2
+
1
3
  module CarrierWave
2
4
  module Uploader
3
5
  module Versions
6
+ class Builder
7
+ def initialize(name)
8
+ @name = name
9
+ @options = {}
10
+ @blocks = []
11
+ @klass = nil
12
+ end
13
+
14
+ def configure(options, &block)
15
+ @options.merge!(options)
16
+ @blocks << block if block
17
+ @klass = nil
18
+ end
19
+
20
+ def build(superclass)
21
+ return @klass if @klass
22
+ @klass = Class.new(superclass)
23
+ superclass.const_set("VersionUploader#{@name.to_s.camelize}", @klass)
24
+
25
+ @klass.version_names += [@name]
26
+ @klass.versions = {}
27
+ @klass.processors = []
28
+ @klass.version_options = @options
29
+ @klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
30
+ # Define the enable_processing method for versions so they get the
31
+ # value from the parent class unless explicitly overwritten
32
+ def self.enable_processing(value=nil)
33
+ self.enable_processing = value if value
34
+ if defined?(@enable_processing) && !@enable_processing.nil?
35
+ @enable_processing
36
+ else
37
+ superclass.enable_processing
38
+ end
39
+ end
40
+
41
+ # Regardless of what is set in the parent uploader, do not enforce the
42
+ # move_to_cache config option on versions because it moves the original
43
+ # file to the version's target file.
44
+ #
45
+ # If you want to enforce this setting on versions, override this method
46
+ # in each version:
47
+ #
48
+ # version :thumb do
49
+ # def move_to_cache
50
+ # true
51
+ # end
52
+ # end
53
+ #
54
+ def move_to_cache
55
+ false
56
+ end
57
+
58
+ # Need to rely on the parent version's identifier, as versions don't have its own one.
59
+ def identifier
60
+ parent_version.identifier
61
+ end
62
+ RUBY
63
+ @blocks.each { |block| @klass.class_eval(&block) }
64
+ @klass
65
+ end
66
+
67
+ def deep_dup
68
+ other = dup
69
+ other.instance_variable_set(:@blocks, @blocks.dup)
70
+ other
71
+ end
72
+
73
+ def method_missing(name, *args)
74
+ super
75
+ rescue NoMethodError => e
76
+ raise e.exception <<~ERROR
77
+ #{e.message}
78
+ If you're trying to configure a version, do it inside a block like `version(:thumb) { self.#{name} #{args.map(&:inspect).join(', ')} }`.
79
+ ERROR
80
+ end
81
+
82
+ def respond_to_missing?(*)
83
+ super
84
+ end
85
+ end
86
+
4
87
  extend ActiveSupport::Concern
5
88
 
6
89
  include CarrierWave::Uploader::Callbacks
@@ -11,9 +94,8 @@ module CarrierWave
11
94
  self.versions = {}
12
95
  self.version_names = []
13
96
 
14
- attr_accessor :parent_cache_id, :parent_version
97
+ attr_accessor :parent_version
15
98
 
16
- after :cache, :assign_parent_cache_id
17
99
  after :cache, :cache_versions!
18
100
  after :store, :store_versions!
19
101
  after :remove, :remove_versions!
@@ -51,80 +133,33 @@ module CarrierWave
51
133
  # process :scale => [200, 200]
52
134
  # end
53
135
  #
136
+ # version :square, :unless => :invalid_image_type? do
137
+ # process :scale => [100, 100]
138
+ # end
139
+ #
54
140
  # end
55
141
  #
56
142
  def version(name, options = {}, &block)
57
143
  name = name.to_sym
58
- build_version(name, options)
144
+ versions[name] ||= Builder.new(name)
145
+ versions[name].configure(options, &block)
59
146
 
60
- versions[name].class_eval(&block) if block
61
- versions[name]
62
- end
147
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
148
+ def #{name}
149
+ versions[:#{name}]
150
+ end
151
+ RUBY
63
152
 
64
- def recursively_apply_block_to_versions(&block)
65
- versions.each do |name, version|
66
- version.class_eval(&block)
67
- version.recursively_apply_block_to_versions(&block)
68
- end
153
+ versions[name]
69
154
  end
70
155
 
71
156
  private
72
157
 
73
- def build_version(name, options)
74
- if !versions.has_key?(name)
75
- uploader = Class.new(self)
76
- const_set("Uploader#{uploader.object_id}".tr('-', '_'), uploader)
77
- uploader.version_names += [name]
78
- uploader.versions = {}
79
- uploader.processors = []
80
- uploader.version_options = options
81
-
82
- uploader.class_eval <<-RUBY, __FILE__, __LINE__ + 1
83
- # Define the enable_processing method for versions so they get the
84
- # value from the parent class unless explicitly overwritten
85
- def self.enable_processing(value=nil)
86
- self.enable_processing = value if value
87
- if defined?(@enable_processing) && !@enable_processing.nil?
88
- @enable_processing
89
- else
90
- superclass.enable_processing
91
- end
92
- end
93
-
94
- # Regardless of what is set in the parent uploader, do not enforce the
95
- # move_to_cache config option on versions because it moves the original
96
- # file to the version's target file.
97
- #
98
- # If you want to enforce this setting on versions, override this method
99
- # in each version:
100
- #
101
- # version :thumb do
102
- # def move_to_cache
103
- # true
104
- # end
105
- # end
106
- #
107
- def move_to_cache
108
- false
109
- end
110
- RUBY
111
-
112
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
113
- def #{name}
114
- versions[:#{name}]
115
- end
116
- RUBY
117
- else
118
- uploader = Class.new(versions[name])
119
- const_set("Uploader#{uploader.object_id}".tr('-', '_'), uploader)
120
- uploader.processors = []
121
- uploader.version_options = uploader.version_options.merge(options)
122
- end
123
-
124
- # Add the current version hash to class attribute :versions
125
- self.versions = versions.merge(name => uploader)
158
+ def inherited(subclass)
159
+ # To prevent subclass version changes affecting superclass versions
160
+ subclass.versions = versions.deep_dup
161
+ super
126
162
  end
127
-
128
163
  end # ClassMethods
129
164
 
130
165
  ##
@@ -138,7 +173,7 @@ module CarrierWave
138
173
  return @versions if @versions
139
174
  @versions = {}
140
175
  self.class.versions.each do |name, version|
141
- @versions[name] = version.new(model, mounted_as)
176
+ @versions[name] = version.build(self.class).new(model, mounted_as)
142
177
  @versions[name].parent_version = self
143
178
  end
144
179
  @versions
@@ -161,24 +196,45 @@ module CarrierWave
161
196
  #
162
197
  # === Returns
163
198
  #
164
- # [Boolean] True when the version exists according to its :if condition
199
+ # [Boolean] True when the version satisfy its :if or :unless condition
165
200
  #
166
- def version_exists?(name)
201
+ def version_active?(name)
167
202
  name = name.to_sym
168
203
 
169
- return false unless self.class.versions.has_key?(name)
204
+ return false unless versions.has_key?(name)
170
205
 
171
- condition = self.class.versions[name].version_options[:if]
172
- if(condition)
173
- if(condition.respond_to?(:call))
174
- condition.call(self, :version => name, :file => file)
206
+ if_condition = versions[name].class.version_options[:if]
207
+ unless_condition = versions[name].class.version_options[:unless]
208
+
209
+ if if_condition
210
+ if if_condition.respond_to?(:call)
211
+ if_condition.call(self, :version => name, :file => file)
212
+ else
213
+ send(if_condition, file)
214
+ end
215
+ elsif unless_condition
216
+ if unless_condition.respond_to?(:call)
217
+ !unless_condition.call(self, :version => name, :file => file)
175
218
  else
176
- send(condition, file)
219
+ !send(unless_condition, file)
177
220
  end
178
221
  else
179
222
  true
180
223
  end
181
224
  end
225
+ alias_method :version_exists?, :version_active?
226
+ CarrierWave.deprecator.deprecate_methods(self, version_exists?: :version_active?)
227
+
228
+ ##
229
+ # Copies the parent's cache_id when caching a version file.
230
+ # This behavior is not essential but it makes easier to understand
231
+ # that the cached files are generated by the single upload attempt.
232
+ #
233
+ def cache!(*args)
234
+ self.cache_id = parent_version.cache_id if parent_version
235
+
236
+ super
237
+ end
182
238
 
183
239
  ##
184
240
  # When given a version name as a parameter, will return the url for that version
@@ -220,45 +276,47 @@ module CarrierWave
220
276
  # Recreate versions and reprocess them. This can be used to recreate
221
277
  # versions if their parameters somehow have changed.
222
278
  #
223
- def recreate_versions!(*versions)
224
- # Some files could possibly not be stored on the local disk. This
225
- # doesn't play nicely with processing. Make sure that we're only
226
- # processing a cached file
227
- #
228
- # The call to store! will trigger the necessary callbacks to both
229
- # process this version and all sub-versions
230
- if versions.any?
231
- file = sanitized_file if !cached?
232
- store_versions!(file, versions)
233
- else
234
- cache! if !cached?
235
- store!
279
+ def recreate_versions!(*names)
280
+ # As well as specified versions, we need to reprocess versions
281
+ # that are the source of another version.
282
+
283
+ self.cache_id = CarrierWave.generate_cache_id
284
+ derived_versions.each_value do |v|
285
+ v.cache!(file) if names.empty? || !(v.descendant_version_names & names).empty?
286
+ end
287
+ active_versions.each do |name, v|
288
+ v.store! if names.empty? || names.include?(name)
236
289
  end
290
+ ensure
291
+ @cache_id = nil
237
292
  end
238
293
 
239
- private
240
- def assign_parent_cache_id(file)
241
- active_versions.each do |name, uploader|
242
- uploader.parent_cache_id = @cache_id
294
+ protected
295
+
296
+ def descendant_version_names
297
+ [version_name] + derived_versions.flat_map do |name, version|
298
+ version.descendant_version_names
243
299
  end
244
300
  end
245
301
 
246
302
  def active_versions
247
303
  versions.select do |name, uploader|
248
- version_exists?(name)
304
+ version_active?(name)
249
305
  end
250
306
  end
251
307
 
252
- def dependent_versions
308
+ private
309
+
310
+ def derived_versions
253
311
  active_versions.reject do |name, v|
254
312
  v.class.version_options[:from_version]
255
- end.to_a + sibling_versions.select do |name, v|
313
+ end.merge(active_sibling_versions.select do |name, v|
256
314
  v.class.version_options[:from_version] == self.class.version_names.last
257
- end.to_a
315
+ end)
258
316
  end
259
317
 
260
- def sibling_versions
261
- parent_version.try(:versions) || []
318
+ def active_sibling_versions
319
+ parent_version&.active_versions || {}
262
320
  end
263
321
 
264
322
  def full_filename(for_file)
@@ -270,31 +328,23 @@ module CarrierWave
270
328
  end
271
329
 
272
330
  def cache_versions!(new_file)
273
- dependent_versions.each do |name, v|
274
- v.send(:cache_id=, @cache_id)
275
- v.cache!(new_file)
276
- end
331
+ derived_versions.each_value { |v| v.cache!(new_file) }
277
332
  end
278
333
 
279
- def store_versions!(new_file, versions=nil)
280
- if versions
281
- active = Hash[active_versions]
282
- versions.each { |v| active[v].try(:store!, new_file) } unless active.empty?
283
- else
284
- active_versions.each { |name, v| v.store!(new_file) }
285
- end
334
+ def store_versions!(new_file)
335
+ active_versions.each_value { |v| v.store!(new_file) }
286
336
  end
287
337
 
288
338
  def remove_versions!
289
- versions.each { |name, v| v.remove! }
339
+ versions.each_value { |v| v.remove! }
290
340
  end
291
341
 
292
342
  def retrieve_versions_from_cache!(cache_name)
293
- active_versions.each { |name, v| v.retrieve_from_cache!(cache_name) }
343
+ active_versions.each_value { |v| v.retrieve_from_cache!(cache_name) }
294
344
  end
295
345
 
296
346
  def retrieve_versions_from_store!(identifier)
297
- active_versions.each { |name, v| v.retrieve_from_store!(identifier) }
347
+ active_versions.each_value { |v| v.retrieve_from_store!(identifier) }
298
348
  end
299
349
 
300
350
  end # Versions