shrine 3.0.0.alpha → 3.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of shrine might be problematic. Click here for more details.

Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -2
  3. data/README.md +1 -1
  4. data/doc/creating_persistence_plugins.md +20 -63
  5. data/doc/plugins/activerecord.md +13 -106
  6. data/doc/plugins/add_metadata.md +31 -9
  7. data/doc/plugins/atomic_helpers.md +33 -9
  8. data/doc/plugins/cached_attachment_data.md +3 -3
  9. data/doc/plugins/data_uri.md +16 -19
  10. data/doc/plugins/default_storage.md +32 -8
  11. data/doc/plugins/default_url.md +21 -9
  12. data/doc/plugins/derivation_endpoint.md +48 -0
  13. data/doc/plugins/derivatives.md +74 -63
  14. data/doc/plugins/entity.md +27 -2
  15. data/doc/plugins/included.md +8 -7
  16. data/doc/plugins/metadata_attributes.md +20 -5
  17. data/doc/plugins/model.md +22 -2
  18. data/doc/plugins/persistence.md +89 -0
  19. data/doc/plugins/remote_url.md +41 -45
  20. data/doc/plugins/remove_attachment.md +23 -4
  21. data/doc/plugins/remove_invalid.md +3 -4
  22. data/doc/plugins/restore_cached_data.md +3 -1
  23. data/doc/plugins/sequel.md +13 -105
  24. data/doc/plugins/signature.md +3 -3
  25. data/lib/shrine/attachment.rb +11 -1
  26. data/lib/shrine/plugins/_persistence.rb +69 -0
  27. data/lib/shrine/plugins/activerecord.rb +31 -81
  28. data/lib/shrine/plugins/atomic_helpers.rb +13 -3
  29. data/lib/shrine/plugins/backgrounding.rb +8 -8
  30. data/lib/shrine/plugins/cached_attachment_data.rb +2 -6
  31. data/lib/shrine/plugins/data_uri.rb +2 -6
  32. data/lib/shrine/plugins/default_storage.rb +28 -2
  33. data/lib/shrine/plugins/derivation_endpoint.rb +3 -9
  34. data/lib/shrine/plugins/derivatives.rb +26 -17
  35. data/lib/shrine/plugins/entity.rb +24 -16
  36. data/lib/shrine/plugins/included.rb +1 -0
  37. data/lib/shrine/plugins/infer_extension.rb +2 -0
  38. data/lib/shrine/plugins/metadata_attributes.rb +18 -8
  39. data/lib/shrine/plugins/model.rb +35 -14
  40. data/lib/shrine/plugins/remote_url.rb +2 -6
  41. data/lib/shrine/plugins/remove_attachment.rb +2 -6
  42. data/lib/shrine/plugins/remove_invalid.rb +10 -6
  43. data/lib/shrine/plugins/sequel.rb +31 -78
  44. data/lib/shrine/plugins/upload_options.rb +2 -2
  45. data/lib/shrine/plugins/validation.rb +1 -11
  46. data/lib/shrine/version.rb +1 -1
  47. metadata +4 -2
@@ -11,7 +11,7 @@ class Shrine
11
11
  # ## Promotion
12
12
  #
13
13
  # attacher.promote_block do
14
- # Attachment::PromoteJob.perform_async(record, name, data)
14
+ # Attachment::PromoteJob.perform_async(record, name, file_data)
15
15
  # end
16
16
  #
17
17
  # attacher.assign(io)
@@ -24,8 +24,8 @@ class Shrine
24
24
  # The promote worker can be implemented like this:
25
25
  #
26
26
  # class Attachment::PromoteJob
27
- # def perform(record, name, data)
28
- # attacher = Shrine::Attacher.retrieve(model: record, name: name, data: data)
27
+ # def perform(record, name, file_data)
28
+ # attacher = Shrine::Attacher.retrieve(model: record, name: name, file: file_data)
29
29
  # attacher.atomic_promote
30
30
  # end
31
31
  # end
@@ -65,7 +65,7 @@ class Shrine
65
65
  # You can also register promotion and deletion hooks globally:
66
66
  #
67
67
  # Shrine::Attacher.promote_block do
68
- # Attachment::PromoteJob.perform_async(record, name, data)
68
+ # Attachment::PromoteJob.perform_async(record, name, file_data)
69
69
  # end
70
70
  #
71
71
  # Shrine::Attacher.destroy_block do
@@ -83,7 +83,7 @@ class Shrine
83
83
  # Attachment::PromoteJob.perform_async(
84
84
  # attacher.record,
85
85
  # attacher.name,
86
- # attacher.data,
86
+ # attacher.file_data,
87
87
  # )
88
88
  # end
89
89
  def promote_block(&block)
@@ -94,7 +94,7 @@ class Shrine
94
94
  # Registers a global deletion block.
95
95
  #
96
96
  # Shrine::Attacher.destroy_block do |attacher|
97
- # Attachment::DeleteJob.perform_async(attacher.data)
97
+ # Attachment::DestroyJob.perform_async(attacher.data)
98
98
  # end
99
99
  def destroy_block(&block)
100
100
  shrine_class.opts[:backgrounding][:destroy_block] = block if block
@@ -116,7 +116,7 @@ class Shrine
116
116
  # Attachment::PromoteJob.perform_async(
117
117
  # attacher.record,
118
118
  # attacher.name
119
- # attacher.data,
119
+ # attacher.file_data,
120
120
  # )
121
121
  # end
122
122
  def promote_block(&block)
@@ -127,7 +127,7 @@ class Shrine
127
127
  # Registers an instance-level deletion hook.
128
128
  #
129
129
  # attacher.destroy_block do |attacher|
130
- # Attachment::DeleteJob.perform_async(attacher.data)
130
+ # Attachment::DestroyJob.perform_async(attacher.data)
131
131
  # end
132
132
  def destroy_block(&block)
133
133
  @destroy_block = block if block
@@ -7,12 +7,8 @@ class Shrine
7
7
  # [doc/plugins/cached_attachment_data.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/cached_attachment_data.md
8
8
  module CachedAttachmentData
9
9
  module AttachmentMethods
10
- def included(klass)
11
- super
12
-
13
- return unless options[:type] == :model
14
-
15
- name = attachment_name
10
+ def define_model_methods(name)
11
+ super if defined?(super)
16
12
 
17
13
  define_method :"cached_#{name}_data" do
18
14
  send(:"#{name}_attacher").cached_data
@@ -39,12 +39,8 @@ class Shrine
39
39
  end
40
40
 
41
41
  module AttachmentMethods
42
- def included(klass)
43
- super
44
-
45
- return unless options[:type] == :model
46
-
47
- name = attachment_name
42
+ def define_model_methods(name)
43
+ super if defined?(super)
48
44
 
49
45
  define_method :"#{name}_data_uri=" do |uri|
50
46
  send(:"#{name}_attacher").assign_data_uri(uri)
@@ -11,6 +11,22 @@ class Shrine
11
11
  uploader.opts[:default_storage].merge!(opts)
12
12
  end
13
13
 
14
+ module AttacherClassMethods
15
+ def default_cache(value = nil, &block)
16
+ default_storage.merge!(cache: value || block)
17
+ end
18
+
19
+ def default_store(value = nil, &block)
20
+ default_storage.merge!(store: value || block)
21
+ end
22
+
23
+ private
24
+
25
+ def default_storage
26
+ shrine_class.opts[:default_storage]
27
+ end
28
+ end
29
+
14
30
  module AttacherMethods
15
31
  def initialize(**options)
16
32
  super(**shrine_class.opts[:default_storage], **options)
@@ -18,7 +34,12 @@ class Shrine
18
34
 
19
35
  def cache_key
20
36
  if @cache.respond_to?(:call)
21
- @cache.call(record, name)
37
+ if @cache.arity == 2
38
+ Shrine.deprecation("Passing record & name argument to default storage block is deprecated and will be removed in Shrine 4. Use a block without arguments instead.")
39
+ @cache.call(record, name)
40
+ else
41
+ instance_exec(&@cache)
42
+ end
22
43
  else
23
44
  @cache
24
45
  end
@@ -26,7 +47,12 @@ class Shrine
26
47
 
27
48
  def store_key
28
49
  if @store.respond_to?(:call)
29
- @store.call(record, name)
50
+ if @store.arity == 2
51
+ Shrine.deprecation("Passing record & name argument to default storage block is deprecated and will be removed in Shrine 4. Use a block without arguments instead.")
52
+ @store.call(record, name)
53
+ else
54
+ instance_exec(&@store)
55
+ end
30
56
  else
31
57
  @store
32
58
  end
@@ -15,8 +15,8 @@ class Shrine
15
15
  module DerivationEndpoint
16
16
  LOG_SUBSCRIBER = -> (event) do
17
17
  Shrine.logger.info "Derivation (#{event.duration}ms) – #{{
18
- name: event[:name],
19
- args: event[:args],
18
+ name: event[:derivation].name,
19
+ args: event[:derivation].args,
20
20
  uploader: event[:uploader],
21
21
  }.inspect}"
22
22
  end
@@ -600,13 +600,7 @@ class Shrine
600
600
  def instrument_derivation(&block)
601
601
  return yield unless shrine_class.respond_to?(:instrument)
602
602
 
603
- shrine_class.instrument(
604
- :derivation,
605
- name: derivation.name,
606
- args: derivation.args,
607
- derivation: derivation,
608
- &block
609
- )
603
+ shrine_class.instrument(:derivation, derivation: derivation, &block)
610
604
  end
611
605
 
612
606
  # Massages the derivation result, ensuring it's opened in binary mode,
@@ -29,13 +29,21 @@ class Shrine
29
29
  end
30
30
 
31
31
  module AttachmentMethods
32
- def initialize(name, **options)
33
- super
32
+ def define_entity_methods(name)
33
+ super if defined?(super)
34
34
 
35
35
  define_method(:"#{name}_derivatives") do |*args|
36
36
  send(:"#{name}_attacher").get_derivatives(*args)
37
37
  end
38
38
  end
39
+
40
+ def define_model_methods(name)
41
+ super if defined?(super)
42
+
43
+ define_method(:"#{name}_derivatives!") do |*args|
44
+ send(:"#{name}_attacher").create_derivatives(*args)
45
+ end
46
+ end
39
47
  end
40
48
 
41
49
  module AttacherClassMethods
@@ -45,7 +53,12 @@ class Shrine
45
53
  # # ...
46
54
  # end
47
55
  def derivatives_processor(name, &block)
48
- shrine_class.opts[:derivatives][:processors][name.to_sym] = block
56
+ if block
57
+ shrine_class.opts[:derivatives][:processors][name.to_sym] = block
58
+ else
59
+ shrine_class.opts[:derivatives][:processors][name.to_sym] or
60
+ fail Error, "derivatives processor #{name.inspect} not registered"
61
+ end
49
62
  end
50
63
 
51
64
  # Specifies default storage to which derivatives will be uploaded.
@@ -60,9 +73,11 @@ class Shrine
60
73
  # end
61
74
  # end
62
75
  def derivatives_storage(storage_key = nil, &block)
63
- fail ArgumentError, "storage key or block needs to be provided" unless storage_key || block
64
-
65
- shrine_class.opts[:derivatives][:storage] = storage_key || block
76
+ if storage_key || block
77
+ shrine_class.opts[:derivatives][:storage] = storage_key || block
78
+ else
79
+ shrine_class.opts[:derivatives][:storage]
80
+ end
66
81
  end
67
82
  end
68
83
 
@@ -160,9 +175,9 @@ class Shrine
160
175
  # end
161
176
  #
162
177
  # attacher.create_derivatives(:my_processor)
163
- def create_derivatives(processor_name, **options)
164
- files = process_derivatives(processor_name)
165
- add_derivatives(files, **options)
178
+ def create_derivatives(*args)
179
+ files = process_derivatives(*args)
180
+ add_derivatives(files)
166
181
  end
167
182
 
168
183
  # Uploads given hash of files and adds uploaded files to the
@@ -238,7 +253,7 @@ class Shrine
238
253
  # attacher.process_derivatives(:thumbnails)
239
254
  # #=> { small: #<File:...>, medium: #<File:...>, large: #<File:...> }
240
255
  def process_derivatives(processor_name, source = nil, **options)
241
- processor = derivatives_processor(processor_name)
256
+ processor = self.class.derivatives_processor(processor_name)
242
257
  fetch_source = source ? source.method(:tap) : file!.method(:download)
243
258
  result = nil
244
259
 
@@ -458,12 +473,6 @@ class Shrine
458
473
  )
459
474
  end
460
475
 
461
- # Retrieves derivatives processor with specified name.
462
- def derivatives_processor(name)
463
- shrine_class.opts[:derivatives][:processors][name.to_sym] or
464
- fail Error, "derivatives processor #{name.inspect} not registered"
465
- end
466
-
467
476
  # Returns symbolized array or single key.
468
477
  def derivative_path(path)
469
478
  path = path.map { |key| key.is_a?(String) ? key.to_sym : key }
@@ -473,7 +482,7 @@ class Shrine
473
482
 
474
483
  # Storage to which derivatives will be uploaded to by default.
475
484
  def derivative_storage(path)
476
- storage = shrine_class.opts[:derivatives][:storage]
485
+ storage = self.class.derivatives_storage
477
486
  storage = instance_exec(path, &storage) if storage.respond_to?(:call)
478
487
  storage
479
488
  end
@@ -11,9 +11,18 @@ class Shrine
11
11
  end
12
12
 
13
13
  module AttachmentMethods
14
+ attr_reader :type
15
+
16
+ def initialize(name, type: :entity, **options)
17
+ super(name, **options)
18
+ @type = type
19
+
20
+ define_entity_methods(name)
21
+ end
22
+
14
23
  # Defines `#<name>`, `#<name>_url`, and `#<name>_attacher` methods.
15
- def initialize(name, **options)
16
- super
24
+ def define_entity_methods(name)
25
+ super if defined?(super)
17
26
 
18
27
  attachment = self
19
28
 
@@ -36,7 +45,9 @@ class Shrine
36
45
  # Creates an instance of the corresponding Attacher subclass. It's not
37
46
  # memoized because the entity object could be frozen.
38
47
  def attacher(record, options)
39
- shrine_class::Attacher.from_entity(record, @name, @options.merge(options))
48
+ attacher = record.class.send(:"#{@name}_attacher", options)
49
+ attacher.load_entity(record, @name)
50
+ attacher
40
51
  end
41
52
  end
42
53
 
@@ -47,9 +58,9 @@ class Shrine
47
58
  #
48
59
  # attacher = Attacher.from_entity(photo, :image)
49
60
  # attacher.file #=> #<Shrine::UploadedFile>
50
- def from_entity(record, name, type: :entity, **options)
61
+ def from_entity(record, name, **options)
51
62
  attacher = new(**options)
52
- attacher.load_entity(record, name, type: type)
63
+ attacher.load_entity(record, name)
53
64
  attacher
54
65
  end
55
66
  end
@@ -59,14 +70,18 @@ class Shrine
59
70
 
60
71
  # Saves record and name and initializes attachment from the entity
61
72
  # attribute. Called from `Attacher.from_entity`.
62
- def load_entity(record, name, type: :entity)
73
+ def load_entity(record, name)
74
+ set_entity(record, name)
75
+ read
76
+ end
77
+
78
+ # Sets record and name without loading the attachment from the entity
79
+ # attribute.
80
+ def set_entity(record, name)
63
81
  @record = record
64
82
  @name = name.to_sym
65
- @type = type
66
83
 
67
84
  @context.merge!(record: record, name: name)
68
-
69
- read
70
85
  end
71
86
 
72
87
  # Overwrites the current attachment with the one from model attribute.
@@ -113,13 +128,6 @@ class Shrine
113
128
  def read_attribute
114
129
  record.public_send(attribute)
115
130
  end
116
-
117
- # Returns whether the attacher has been loaded from an entity instance.
118
- def entity?
119
- type == :entity
120
- end
121
-
122
- attr_reader :type
123
131
  end
124
132
  end
125
133
 
@@ -14,6 +14,7 @@ class Shrine
14
14
  module AttachmentMethods
15
15
  def included(klass)
16
16
  super
17
+
17
18
  klass.instance_exec(@name, &shrine_class.opts[:included][:block])
18
19
  end
19
20
  end
@@ -53,6 +53,8 @@ class Shrine
53
53
  end
54
54
 
55
55
  module InstanceMethods
56
+ private
57
+
56
58
  def basic_location(io, metadata:)
57
59
  location = Pathname(super)
58
60
 
@@ -10,27 +10,37 @@ class Shrine
10
10
  uploader.plugin :entity
11
11
  end
12
12
 
13
- def self.configure(uploader, mappings = {})
14
- uploader.opts[:metadata_attributes] ||= { mappings: {} }
15
- uploader.opts[:metadata_attributes][:mappings].merge!(mappings)
13
+ def self.configure(uploader, **opts)
14
+ uploader.opts[:metadata_attributes] ||= {}
15
+ uploader.opts[:metadata_attributes].merge!(opts)
16
16
  end
17
17
 
18
18
  module AttacherClassMethods
19
- def metadata_attributes(mappings)
20
- shrine_class.opts[:metadata_attributes][:mappings].merge!(mappings)
19
+ def metadata_attributes(mappings = nil)
20
+ if mappings
21
+ shrine_class.opts[:metadata_attributes].merge!(mappings)
22
+ else
23
+ shrine_class.opts[:metadata_attributes]
24
+ end
21
25
  end
22
26
  end
23
27
 
24
28
  module AttacherMethods
25
29
  def column_values
26
- values = super
30
+ super.merge(metadata_attributes)
31
+ end
32
+
33
+ private
34
+
35
+ def metadata_attributes
36
+ values = {}
27
37
 
28
- shrine_class.opts[:metadata_attributes][:mappings].each do |source, destination|
38
+ self.class.metadata_attributes.each do |source, destination|
29
39
  metadata_attribute = destination.is_a?(Symbol) ? :"#{name}_#{destination}" : :"#{destination}"
30
40
 
31
41
  next unless record.respond_to?(metadata_attribute)
32
42
 
33
- values[metadata_attribute] = file && file.metadata[source.to_s]
43
+ values[metadata_attribute] = file&.metadata[source.to_s]
34
44
  end
35
45
 
36
46
  values
@@ -26,19 +26,31 @@ class Shrine
26
26
  super(name, type: :model, **options)
27
27
  end
28
28
 
29
- # We define the setter dynamically on inclusion to allow other plugins
30
- # to still have time to override attachment type on inclusion.
29
+ # We define model methods only on inclusion, if the attachment type is
30
+ # still "model". This gives other plugins the ability to force
31
+ # attachment type to "entity" for certain classes, and we can skip
32
+ # defining model methods in this case.
31
33
  def included(klass)
32
34
  super
33
35
 
34
- return unless options[:type] == :model
36
+ return unless type == :model
35
37
 
36
- name = attachment_name
38
+ define_model_methods(@name)
39
+ end
40
+
41
+ # Defines attachment setter and enhances the copy constructor.
42
+ def define_model_methods(name)
43
+ super if defined?(super)
37
44
 
38
45
  define_method :"#{name}=" do |value|
39
46
  send(:"#{name}_attacher").model_assign(value)
40
47
  end
41
48
 
49
+ define_method :"#{name}_changed?" do
50
+ send(:"#{name}_attacher").changed?
51
+ end
52
+
53
+ # The copy constructor that's called on #dup and #clone.
42
54
  define_method :initialize_copy do |other|
43
55
  super(other)
44
56
  instance_variable_set(:"@#{name}_attacher", instance_variable_get(:"@#{name}_attacher")&.dup)
@@ -49,14 +61,15 @@ class Shrine
49
61
 
50
62
  # Memoizes the attacher instance into an instance variable.
51
63
  def attacher(record, options)
52
- return super unless @options[:type] == :model
64
+ return super unless type == :model
53
65
 
54
- name = attachment_name
66
+ if !record.instance_variable_get(:"@#{@name}_attacher") || options.any?
67
+ attacher = super
68
+ attacher.set_model(record, @name)
55
69
 
56
- if !record.instance_variable_get(:"@#{name}_attacher") || options.any?
57
- record.instance_variable_set(:"@#{name}_attacher", super)
70
+ record.instance_variable_set(:"@#{@name}_attacher", attacher)
58
71
  else
59
- record.instance_variable_get(:"@#{name}_attacher")
72
+ record.instance_variable_get(:"@#{@name}_attacher")
60
73
  end
61
74
  end
62
75
  end
@@ -68,9 +81,9 @@ class Shrine
68
81
  #
69
82
  # attacher = Attacher.from_model(photo, :image)
70
83
  # attacher.file #=> #<Shrine::UploadedFile>
71
- def from_model(record, name, type: :model, **options)
84
+ def from_model(record, name, **options)
72
85
  attacher = new(**options)
73
- attacher.load_model(record, name, type: type)
86
+ attacher.load_model(record, name)
74
87
  attacher
75
88
  end
76
89
  end
@@ -83,8 +96,16 @@ class Shrine
83
96
 
84
97
  # Saves record and name and initializes attachment from the model
85
98
  # attribute. Called from `Attacher.from_model`.
86
- def load_model(record, name, type: :model)
87
- load_entity(record, name, type: type)
99
+ def load_model(record, name)
100
+ set_model(record, name)
101
+ read
102
+ end
103
+
104
+ # Saves record and name without loading attachment from the model
105
+ # attribute.
106
+ def set_model(record, name)
107
+ set_entity(record, name)
108
+ @model = true
88
109
  end
89
110
 
90
111
  # Called by the attachment attribute setter on the model.
@@ -127,7 +148,7 @@ class Shrine
127
148
  # This allows users to still use the attacher with an entity instance
128
149
  # or without any record instance.
129
150
  def model?
130
- type == :model
151
+ instance_variable_defined?(:@model)
131
152
  end
132
153
  end
133
154
  end
@@ -43,12 +43,8 @@ class Shrine
43
43
  end
44
44
 
45
45
  module AttachmentMethods
46
- def included(klass)
47
- super
48
-
49
- return unless options[:type] == :model
50
-
51
- name = attachment_name
46
+ def define_model_methods(name)
47
+ super if defined?(super)
52
48
 
53
49
  define_method :"#{name}_remote_url=" do |url|
54
50
  send(:"#{name}_attacher").assign_remote_url(url)
@@ -7,12 +7,8 @@ class Shrine
7
7
  # [doc/plugins/remove_attachment.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/remove_attachment.md
8
8
  module RemoveAttachment
9
9
  module AttachmentMethods
10
- def included(klass)
11
- super
12
-
13
- return unless options[:type] == :model
14
-
15
- name = attachment_name
10
+ def define_model_methods(name)
11
+ super if defined?(super)
16
12
 
17
13
  define_method :"remove_#{name}=" do |value|
18
14
  send(:"#{name}_attacher").remove = value
@@ -11,14 +11,18 @@ class Shrine
11
11
  end
12
12
 
13
13
  module AttacherMethods
14
- def validate(*)
14
+ def change(*)
15
15
  super
16
16
  ensure
17
- if errors.any? && changed?
18
- destroy(background: true)
19
- set @previous.file
20
- remove_instance_variable(:@previous)
21
- end
17
+ revert_change if errors.any?
18
+ end
19
+
20
+ private
21
+
22
+ def revert_change
23
+ destroy(background: true)
24
+ set @previous.file
25
+ remove_instance_variable(:@previous)
22
26
  end
23
27
  end
24
28
  end