mini_paperclip 0.1.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/{ruby.yml → ci.yml} +4 -1
- data/.gitignore +1 -0
- data/Gemfile +1 -7
- data/README.md +5 -3
- data/gemfiles/development.gemfile +7 -0
- data/gemfiles/rails_52.gemfile +1 -6
- data/gemfiles/rails_60.gemfile +1 -6
- data/gemfiles/rails_head.gemfile +5 -0
- data/lib/mini_paperclip.rb +10 -8
- data/lib/mini_paperclip/attachment.rb +119 -68
- data/lib/mini_paperclip/class_methods.rb +7 -1
- data/lib/mini_paperclip/config.rb +2 -1
- data/lib/mini_paperclip/interpolator.rb +15 -9
- data/lib/mini_paperclip/shoulda/matchers/validate_attachment_geometry_matcher.rb +1 -0
- data/lib/mini_paperclip/storage/base.rb +9 -9
- data/lib/mini_paperclip/storage/filesystem.rb +17 -10
- data/lib/mini_paperclip/storage/s3.rb +34 -16
- data/lib/mini_paperclip/validators/file_size_validator.rb +2 -2
- data/lib/mini_paperclip/validators/geometry_validator.rb +15 -11
- data/lib/mini_paperclip/version.rb +1 -1
- data/mini_paperclip.gemspec +1 -0
- metadata +20 -6
- data/gemfiles/rails_52.gemfile.lock +0 -98
- data/gemfiles/rails_60.gemfile.lock +0 -98
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d115f886793d57d62835f40e27768ff04588d3e1f17e7fb430b11f2b146e2ed
|
4
|
+
data.tar.gz: 6d1557f279f9bf007b90d06272e7e19a1829f38b1c0f6a4ce079c06e410fca58
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b8c590434edbc1bac00c85028f44d8de3ff67a001643a0613718af5f60bcd06862d012867f3137cb758a48b4db6d8db699df80e3b9838f64dd082e105ddbbf7
|
7
|
+
data.tar.gz: 04524d26fb7e8f96e631c82d1ecce996505b66d60d4c47d0016f48e66ec6cc427eb7a28a7241afa4c894ecb08e4afe92c466bd7d15b4a34d3e5e269dfcc06665
|
@@ -36,5 +36,8 @@ jobs:
|
|
36
36
|
ruby-version: ${{ matrix.ruby }}
|
37
37
|
- name: Install dependencies
|
38
38
|
run: bundle install --gemfile=${{ matrix.gemfile }}
|
39
|
-
- name: Run tests
|
39
|
+
- name: Run tests and Print coverage
|
40
|
+
env:
|
41
|
+
COVERAGE: 1
|
42
|
+
LOGLEVEL: info
|
40
43
|
run: bundle exec --gemfile=${{ matrix.gemfile }} rake
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -2,11 +2,5 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in mini_paperclip.gemspec
|
4
4
|
gemspec
|
5
|
-
|
6
|
-
gem "rake", "~> 12.0"
|
7
|
-
gem "rspec", "~> 3.0"
|
8
|
-
gem "rack-test"
|
9
|
-
gem "webmock"
|
10
|
-
gem "tapp"
|
11
5
|
gem "activerecord"
|
12
|
-
|
6
|
+
eval_gemfile 'gemfiles/development.gemfile'
|
data/README.md
CHANGED
@@ -71,6 +71,7 @@ MiniPaperclip.config.tap do |config|
|
|
71
71
|
config.s3_acl # s3 object acl
|
72
72
|
config.s3_cache_control # Set this value to Cache-Control header when put-object
|
73
73
|
config.interpolates # minimum templates using by `String#gsub!`
|
74
|
+
config.keep_old_files # Delete old attached file if it's false (default false)
|
74
75
|
config.read_timeout # timeout when attachment set url
|
75
76
|
config.logger # You can set logger object.
|
76
77
|
end
|
@@ -104,7 +105,7 @@ end
|
|
104
105
|
|
105
106
|
Interpolate is a simple template system like this.
|
106
107
|
|
107
|
-
template: `:class/:
|
108
|
+
template: `:class/:attachment/:id/:hash.:extension`
|
108
109
|
result: `books/images/1234/abcdef1234567.png`
|
109
110
|
|
110
111
|
You can check default interpolates.
|
@@ -116,8 +117,9 @@ p MiniPaperclip.config.interpolaters
|
|
116
117
|
You can add any interpolate key and process.
|
117
118
|
|
118
119
|
```
|
119
|
-
MiniPaperclip.config.interpolates[
|
120
|
-
|
120
|
+
MiniPaperclip.config.interpolates[':custom_style'] = -> (style) {
|
121
|
+
# This block is called by the scope in the instance of the Interpolator
|
122
|
+
# You can also call `attachment` and `config` in this block
|
121
123
|
}
|
122
124
|
```
|
123
125
|
|
data/gemfiles/rails_52.gemfile
CHANGED
data/gemfiles/rails_60.gemfile
CHANGED
data/lib/mini_paperclip.rb
CHANGED
@@ -8,6 +8,7 @@ require "active_support/number_helper"
|
|
8
8
|
require "mini_magick"
|
9
9
|
require "mimemagic"
|
10
10
|
require "aws-sdk-s3"
|
11
|
+
require "image_size"
|
11
12
|
|
12
13
|
require "mini_paperclip/attachment"
|
13
14
|
require "mini_paperclip/class_methods"
|
@@ -23,16 +24,17 @@ module MiniPaperclip
|
|
23
24
|
@config ||= Config.new(
|
24
25
|
# defaults
|
25
26
|
interpolates: {
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
':class' => ->(_) { class_result },
|
28
|
+
':attachment' => ->(_) { attachment_result },
|
29
|
+
':hash' => ->(style) { hash_key(style) },
|
30
|
+
':extension' => ->(_) { extension },
|
31
|
+
':id' => ->(_) { @attachment.record.id },
|
32
|
+
':updated_at' => ->(_) { attachment.updated_at&.to_i },
|
33
|
+
':style' => ->(style) { style }
|
33
34
|
},
|
34
|
-
hash_data: ":class/:
|
35
|
+
hash_data: ":class/:attachment/:id/:style/:updated_at",
|
35
36
|
url_missing_path: ":attachment/:style/missing.png",
|
37
|
+
keep_old_files: false,
|
36
38
|
read_timeout: 60,
|
37
39
|
logger: Logger.new($stdout),
|
38
40
|
)
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module MiniPaperclip
|
4
4
|
class Attachment
|
5
|
-
|
5
|
+
UnsupportedError = Class.new(StandardError)
|
6
6
|
|
7
7
|
attr_reader :record, :attachment_name, :config, :storage,
|
8
8
|
:waiting_write_file, :meta_content_type
|
@@ -15,7 +15,7 @@ module MiniPaperclip
|
|
15
15
|
@meta_content_type = nil
|
16
16
|
@dirty = false
|
17
17
|
@storage = Storage.const_get(@config.storage.to_s.camelcase)
|
18
|
-
.new(
|
18
|
+
.new(self, @config)
|
19
19
|
end
|
20
20
|
|
21
21
|
def original_filename
|
@@ -31,7 +31,7 @@ module MiniPaperclip
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def updated_at
|
34
|
-
@record.read_attribute("#{@attachment_name}_updated_at")
|
34
|
+
@record.read_attribute("#{@attachment_name}_updated_at")
|
35
35
|
end
|
36
36
|
|
37
37
|
def file?
|
@@ -61,94 +61,51 @@ module MiniPaperclip
|
|
61
61
|
@waiting_write_file = nil
|
62
62
|
@meta_content_type = nil
|
63
63
|
|
64
|
+
if present?
|
65
|
+
push_delete_files
|
66
|
+
end
|
67
|
+
|
64
68
|
if file.nil?
|
65
|
-
|
66
|
-
@record.write_attribute("#{@attachment_name}_file_name", nil)
|
67
|
-
@record.write_attribute("#{@attachment_name}_content_type", nil)
|
68
|
-
@record.write_attribute("#{@attachment_name}_file_size", nil)
|
69
|
-
@record.write_attribute("#{@attachment_name}_updated_at", nil)
|
69
|
+
assign_nil
|
70
70
|
elsif file.instance_of?(Attachment)
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
@waiting_copy_attachment = file
|
71
|
+
if file.present?
|
72
|
+
assign_attachment(file)
|
73
|
+
else
|
74
|
+
assign_nil
|
75
|
+
end
|
77
76
|
elsif file.respond_to?(:original_filename)
|
78
|
-
|
79
|
-
@record.write_attribute("#{@attachment_name}_file_name", file.original_filename)
|
80
|
-
@record.write_attribute("#{@attachment_name}_content_type", strict_content_type(file.to_io))
|
81
|
-
@record.write_attribute("#{@attachment_name}_file_size", file.size)
|
82
|
-
@record.write_attribute("#{@attachment_name}_updated_at", Time.current)
|
83
|
-
@waiting_write_file = build_tempfile(file.tap(&:rewind))
|
84
|
-
@meta_content_type = file.content_type
|
77
|
+
assign_uploaded_file(file)
|
85
78
|
elsif file.respond_to?(:path)
|
86
|
-
|
87
|
-
@record.write_attribute("#{@attachment_name}_file_name", File.basename(file.path))
|
88
|
-
@record.write_attribute("#{@attachment_name}_content_type", strict_content_type(file))
|
89
|
-
@record.write_attribute("#{@attachment_name}_file_size", file.size)
|
90
|
-
@record.write_attribute("#{@attachment_name}_updated_at", Time.current)
|
91
|
-
@waiting_write_file = build_tempfile(file.tap(&:rewind))
|
79
|
+
assign_file(file)
|
92
80
|
elsif file.instance_of?(String)
|
93
81
|
if file.empty?
|
94
82
|
# do nothing
|
95
83
|
elsif file.start_with?('http')
|
96
|
-
|
97
|
-
open_uri_option = {
|
98
|
-
read_timeout: MiniPaperclip.config.read_timeout || 60
|
99
|
-
}
|
100
|
-
uri = URI.parse(file)
|
101
|
-
uri.open(open_uri_option) do |io|
|
102
|
-
@record.write_attribute("#{@attachment_name}_file_name", File.basename(uri.path))
|
103
|
-
@record.write_attribute("#{@attachment_name}_content_type", strict_content_type(io))
|
104
|
-
@record.write_attribute("#{@attachment_name}_file_size", io.size)
|
105
|
-
@record.write_attribute("#{@attachment_name}_updated_at", Time.current)
|
106
|
-
@waiting_write_file = build_tempfile(io.tap(&:rewind))
|
107
|
-
@meta_content_type = io.meta["content-type"]
|
108
|
-
end
|
84
|
+
assign_http(file)
|
109
85
|
elsif file.start_with?('data:')
|
110
|
-
|
111
|
-
match_data = file.match(/\Adata:([-\w]+\/[-\w\+\.]+)?;base64,(.*)/m)
|
112
|
-
if match_data.nil?
|
113
|
-
raise UnsupporedError, "attachment for \"#{file[0..100]}\" is not supported"
|
114
|
-
end
|
115
|
-
raw = Base64.decode64(match_data[2])
|
116
|
-
@record.write_attribute("#{@attachment_name}_file_name", nil)
|
117
|
-
@record.write_attribute("#{@attachment_name}_content_type", strict_content_type(StringIO.new(raw)))
|
118
|
-
@record.write_attribute("#{@attachment_name}_file_size", raw.bytesize)
|
119
|
-
@record.write_attribute("#{@attachment_name}_updated_at", Time.current)
|
120
|
-
@waiting_write_file = build_tempfile(StringIO.new(raw))
|
121
|
-
@meta_content_type = match_data[1]
|
86
|
+
assign_data_uri(file)
|
122
87
|
else
|
123
|
-
raise
|
88
|
+
raise UnsupportedError, "attachment for \"#{file[0..100]}\" is not supported"
|
124
89
|
end
|
125
90
|
else
|
126
|
-
raise
|
91
|
+
raise UnsupportedError, "attachment for #{file.class} is not supported"
|
127
92
|
end
|
128
93
|
end
|
129
94
|
|
130
95
|
def process_and_store
|
131
96
|
return unless file?
|
132
|
-
|
133
|
-
if @waiting_copy_attachment
|
134
|
-
debug("start attachment copy")
|
135
|
-
@storage.copy(:original, @waiting_copy_attachment)
|
136
|
-
@config.styles&.each do |style, size_arg|
|
137
|
-
@storage.copy(style, @waiting_copy_attachment)
|
138
|
-
end
|
139
|
-
@waiting_copy_attachment = nil
|
140
|
-
return
|
141
|
-
end
|
142
|
-
|
143
|
-
return if @waiting_write_file.nil?
|
97
|
+
return unless @waiting_write_file
|
144
98
|
|
145
99
|
begin
|
146
100
|
debug("start attachment styles process")
|
147
101
|
@storage.write(:original, @waiting_write_file)
|
148
102
|
@config.styles&.each do |style, size_arg|
|
149
103
|
Tempfile.create([style.to_s, File.extname(@waiting_write_file.path)]) do |temp|
|
104
|
+
temp.binmode
|
150
105
|
MiniMagick::Tool::Convert.new do |convert|
|
151
106
|
convert << @waiting_write_file.path
|
107
|
+
convert.coalesce if animated?
|
108
|
+
convert.auto_orient
|
152
109
|
if size_arg.end_with?('#')
|
153
110
|
# crop option
|
154
111
|
convert.resize("#{size_arg[0..-2]}^")
|
@@ -157,20 +114,114 @@ module MiniPaperclip
|
|
157
114
|
else
|
158
115
|
convert.resize(size_arg)
|
159
116
|
end
|
117
|
+
convert.layers("optimize") if animated?
|
160
118
|
convert << temp.path
|
161
119
|
end
|
162
120
|
@storage.write(style, temp)
|
163
121
|
end
|
164
122
|
end
|
123
|
+
|
124
|
+
# should delete after write for copy
|
125
|
+
if !@config.keep_old_files
|
126
|
+
do_delete_files
|
127
|
+
end
|
128
|
+
|
165
129
|
ensure
|
166
|
-
@waiting_write_file.close!
|
130
|
+
if @waiting_write_file.respond_to?(:close!)
|
131
|
+
@waiting_write_file.close!
|
132
|
+
elsif @waiting_write_file.respond_to?(:close)
|
133
|
+
@waiting_write_file.close
|
134
|
+
end
|
167
135
|
end
|
168
136
|
@waiting_write_file = nil
|
169
137
|
end
|
170
138
|
|
139
|
+
def push_delete_files
|
140
|
+
@storage.push_delete_file(:original)
|
141
|
+
@config.styles&.each_key do |style|
|
142
|
+
@storage.push_delete_file(style)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def do_delete_files
|
147
|
+
@storage.do_delete_files
|
148
|
+
end
|
149
|
+
|
150
|
+
def animated?
|
151
|
+
content_type == 'image/gif'
|
152
|
+
end
|
153
|
+
|
171
154
|
private
|
172
155
|
|
156
|
+
def assign_nil
|
157
|
+
# clear
|
158
|
+
@record.write_attribute("#{@attachment_name}_file_name", nil)
|
159
|
+
@record.write_attribute("#{@attachment_name}_content_type", nil)
|
160
|
+
@record.write_attribute("#{@attachment_name}_file_size", nil)
|
161
|
+
@record.write_attribute("#{@attachment_name}_updated_at", nil)
|
162
|
+
end
|
163
|
+
|
164
|
+
def assign_attachment(attachment)
|
165
|
+
# copy
|
166
|
+
@waiting_write_file = attachment.storage.open(:original)
|
167
|
+
@record.write_attribute("#{@attachment_name}_file_name", attachment.original_filename)
|
168
|
+
@record.write_attribute("#{@attachment_name}_content_type", attachment.content_type)
|
169
|
+
@record.write_attribute("#{@attachment_name}_file_size", attachment.size)
|
170
|
+
@record.write_attribute("#{@attachment_name}_updated_at", Time.current)
|
171
|
+
end
|
172
|
+
|
173
|
+
def assign_uploaded_file(file)
|
174
|
+
# e.g. ActionDispatch::Http::UploadedFile
|
175
|
+
@record.write_attribute("#{@attachment_name}_file_name", file.original_filename)
|
176
|
+
@record.write_attribute("#{@attachment_name}_content_type", strict_content_type(file.to_io))
|
177
|
+
@record.write_attribute("#{@attachment_name}_file_size", file.size)
|
178
|
+
@record.write_attribute("#{@attachment_name}_updated_at", Time.current)
|
179
|
+
@waiting_write_file = build_tempfile(file.tap(&:rewind))
|
180
|
+
@meta_content_type = file.content_type
|
181
|
+
end
|
182
|
+
|
183
|
+
def assign_file(file)
|
184
|
+
# e.g. File
|
185
|
+
@record.write_attribute("#{@attachment_name}_file_name", File.basename(file.path))
|
186
|
+
@record.write_attribute("#{@attachment_name}_content_type", strict_content_type(file))
|
187
|
+
@record.write_attribute("#{@attachment_name}_file_size", file.size)
|
188
|
+
@record.write_attribute("#{@attachment_name}_updated_at", Time.current)
|
189
|
+
@waiting_write_file = build_tempfile(file.tap(&:rewind))
|
190
|
+
end
|
191
|
+
|
192
|
+
def assign_http(url)
|
193
|
+
# download from url
|
194
|
+
open_uri_option = {
|
195
|
+
read_timeout: MiniPaperclip.config.read_timeout || 60
|
196
|
+
}
|
197
|
+
uri = URI.parse(url)
|
198
|
+
uri.open(open_uri_option) do |io|
|
199
|
+
@record.write_attribute("#{@attachment_name}_file_name", File.basename(uri.path))
|
200
|
+
@record.write_attribute("#{@attachment_name}_content_type", strict_content_type(io))
|
201
|
+
@record.write_attribute("#{@attachment_name}_file_size", io.size)
|
202
|
+
@record.write_attribute("#{@attachment_name}_updated_at", Time.current)
|
203
|
+
@waiting_write_file = build_tempfile(io.tap(&:rewind))
|
204
|
+
@meta_content_type = io.meta["content-type"]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def assign_data_uri(data_uri)
|
209
|
+
# data-uri
|
210
|
+
match_data = data_uri.match(/\Adata:([-\w]+\/[-\w\+\.]+)?;base64,(.*)/m)
|
211
|
+
if match_data.nil?
|
212
|
+
raise UnsupportedError, "attachment for \"#{data_uri[0..100]}\" is not supported"
|
213
|
+
end
|
214
|
+
raw = Base64.decode64(match_data[2])
|
215
|
+
@record.write_attribute("#{@attachment_name}_file_name", nil)
|
216
|
+
@record.write_attribute("#{@attachment_name}_content_type", strict_content_type(StringIO.new(raw)))
|
217
|
+
@record.write_attribute("#{@attachment_name}_file_size", raw.bytesize)
|
218
|
+
@record.write_attribute("#{@attachment_name}_updated_at", Time.current)
|
219
|
+
@waiting_write_file = build_tempfile(StringIO.new(raw))
|
220
|
+
@meta_content_type = match_data[1]
|
221
|
+
end
|
222
|
+
|
173
223
|
def strict_content_type(io)
|
224
|
+
io.rewind
|
174
225
|
MimeMagic.by_magic(io)&.type
|
175
226
|
end
|
176
227
|
|
@@ -184,7 +235,7 @@ module MiniPaperclip
|
|
184
235
|
end
|
185
236
|
|
186
237
|
def debug(str)
|
187
|
-
MiniPaperclip.config.logger.debug(str)
|
238
|
+
MiniPaperclip.config.logger.debug("[mini_paperclip] #{str}")
|
188
239
|
end
|
189
240
|
end
|
190
241
|
end
|
@@ -8,7 +8,7 @@ module MiniPaperclip
|
|
8
8
|
instance_variable_set("@#{attachment_name}", Attachment.new(self, attachment_name, option_config))
|
9
9
|
end
|
10
10
|
define_method("#{attachment_name}=") do |file|
|
11
|
-
a =
|
11
|
+
a = public_send(attachment_name)
|
12
12
|
a.assign(file)
|
13
13
|
instance_variable_set("@#{attachment_name}", a)
|
14
14
|
end
|
@@ -19,6 +19,12 @@ module MiniPaperclip
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
|
+
before_destroy do
|
23
|
+
public_send(attachment_name).push_delete_files
|
24
|
+
end
|
25
|
+
after_commit(on: :destroy) do
|
26
|
+
public_send(attachment_name).do_delete_files
|
27
|
+
end
|
22
28
|
validates_with Validators::MediaTypeSpoofValidator, {
|
23
29
|
attributes: attachment_name,
|
24
30
|
if: -> { instance_variable_get("@#{attachment_name}")&.dirty? }
|
@@ -16,6 +16,7 @@ module MiniPaperclip
|
|
16
16
|
:s3_acl,
|
17
17
|
:s3_cache_control,
|
18
18
|
:interpolates,
|
19
|
+
:keep_old_files,
|
19
20
|
:read_timeout,
|
20
21
|
:logger,
|
21
22
|
keyword_init: true,
|
@@ -25,7 +26,7 @@ module MiniPaperclip
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def merge!(hash)
|
28
|
-
to_h.deep_merge(hash).each { |k, v| self[k] = v }
|
29
|
+
to_h.deep_merge(hash.to_h).each { |k, v| self[k] = v }
|
29
30
|
self
|
30
31
|
end
|
31
32
|
end
|
@@ -2,16 +2,17 @@
|
|
2
2
|
|
3
3
|
module MiniPaperclip
|
4
4
|
class Interpolator
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
attr_reader :attachment, :config
|
6
|
+
|
7
|
+
def initialize(attachment, config)
|
8
|
+
@attachment = attachment
|
8
9
|
@config = config
|
9
10
|
end
|
10
11
|
|
11
12
|
def interpolate(template, style)
|
12
13
|
template.dup.tap do |t|
|
13
14
|
@config.interpolates&.each do |matcher, block|
|
14
|
-
t.gsub!(matcher) { instance_exec(
|
15
|
+
t.gsub!(matcher) { instance_exec(style, &block) }
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
@@ -19,23 +20,28 @@ module MiniPaperclip
|
|
19
20
|
private
|
20
21
|
|
21
22
|
def class_result
|
22
|
-
@record.class.name.underscore.pluralize
|
23
|
+
@attachment.record.class.name.underscore.pluralize
|
23
24
|
end
|
24
25
|
|
25
26
|
def attachment_result
|
26
|
-
@attachment_name.to_s.downcase.pluralize
|
27
|
+
@attachment.attachment_name.to_s.downcase.pluralize
|
27
28
|
end
|
28
29
|
|
29
|
-
def
|
30
|
-
|
30
|
+
def extension
|
31
|
+
attachment.original_filename &&
|
32
|
+
File.extname(attachment.original_filename)[1..-1]
|
31
33
|
end
|
32
34
|
|
33
35
|
def hash_key(style)
|
34
36
|
OpenSSL::HMAC.hexdigest(
|
35
37
|
OpenSSL::Digest::SHA1.new,
|
36
38
|
@config.hash_secret,
|
37
|
-
|
39
|
+
interpolated_hash_data(style),
|
38
40
|
)
|
39
41
|
end
|
42
|
+
|
43
|
+
def interpolated_hash_data(style)
|
44
|
+
interpolate(@config.hash_data, style)
|
45
|
+
end
|
40
46
|
end
|
41
47
|
end
|
@@ -96,6 +96,7 @@ module MiniPaperclip
|
|
96
96
|
|
97
97
|
def create_dummy_image(width:, height:)
|
98
98
|
Tempfile.create(['MiniPaperclip::Shoulda::Matchers::ValidateAttachmentGeometryMatcher', ".#{@format}"]) do |f|
|
99
|
+
f.binmode
|
99
100
|
MiniMagick::Tool::Convert.new do |convert|
|
100
101
|
convert.size("#{width}x#{height}")
|
101
102
|
convert.xc("none")
|
@@ -3,13 +3,13 @@
|
|
3
3
|
module MiniPaperclip
|
4
4
|
module Storage
|
5
5
|
class Base
|
6
|
-
attr_reader :config
|
6
|
+
attr_reader :attachment, :config, :interpolator
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
@attachment_name = attachment_name
|
8
|
+
def initialize(attachment, config)
|
9
|
+
@attachment = attachment
|
11
10
|
@config = config
|
12
|
-
@interpolator = Interpolator.new(
|
11
|
+
@interpolator = Interpolator.new(attachment, config)
|
12
|
+
@deletes = []
|
13
13
|
end
|
14
14
|
|
15
15
|
def url_for_read(style)
|
@@ -17,10 +17,10 @@ module MiniPaperclip
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def path_for(style)
|
20
|
-
template = if @
|
21
|
-
@config.url_path
|
22
|
-
else
|
20
|
+
template = if @attachment.original_filename.nil?
|
23
21
|
@config.url_missing_path
|
22
|
+
else
|
23
|
+
@config.url_path
|
24
24
|
end
|
25
25
|
interpolate(template, style)
|
26
26
|
end
|
@@ -32,7 +32,7 @@ module MiniPaperclip
|
|
32
32
|
private
|
33
33
|
|
34
34
|
def debug(str)
|
35
|
-
MiniPaperclip.config.logger.debug(str)
|
35
|
+
MiniPaperclip.config.logger.debug("[mini_paperclip] #{str}")
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -5,17 +5,10 @@ module MiniPaperclip
|
|
5
5
|
class Filesystem < Base
|
6
6
|
def write(style, file)
|
7
7
|
path = file_path(style)
|
8
|
-
debug("writing by filesystem to
|
8
|
+
debug("writing by filesystem from:#{file.path} to:#{path}")
|
9
9
|
FileUtils.mkdir_p(File.dirname(path))
|
10
|
-
FileUtils.cp(file.path, path)
|
11
|
-
|
12
|
-
|
13
|
-
def copy(style, from_attachment)
|
14
|
-
raise "not supported" unless from_attachment.storage.instance_of?(Filesystem)
|
15
|
-
to_path = file_path(style)
|
16
|
-
from_path = from_attachment.storage.file_path(style)
|
17
|
-
debug("copying by filesystem from:#{from_path} to:#{to_path}")
|
18
|
-
FileUtils.cp(from_path, to_path)
|
10
|
+
FileUtils.cp(file.path, path) if file.path != path
|
11
|
+
@deletes.delete(path) # cancel deletion if overwrite
|
19
12
|
end
|
20
13
|
|
21
14
|
def file_path(style)
|
@@ -29,6 +22,20 @@ module MiniPaperclip
|
|
29
22
|
def exists?(style)
|
30
23
|
File.exists?(file_path(style))
|
31
24
|
end
|
25
|
+
|
26
|
+
def push_delete_file(style)
|
27
|
+
@deletes.push(file_path(style))
|
28
|
+
end
|
29
|
+
|
30
|
+
def do_delete_files
|
31
|
+
return if @deletes.empty?
|
32
|
+
debug("deleting by filesystem #{@deletes}")
|
33
|
+
FileUtils.rm_f(@deletes)
|
34
|
+
end
|
35
|
+
|
36
|
+
def open(style, &block)
|
37
|
+
File.open(file_path(style), 'r', &block)
|
38
|
+
end
|
32
39
|
end
|
33
40
|
end
|
34
41
|
end
|
@@ -8,24 +8,12 @@ module MiniPaperclip
|
|
8
8
|
Aws::S3::Client.new.put_object(
|
9
9
|
acl: @config.s3_acl,
|
10
10
|
cache_control: @config.s3_cache_control,
|
11
|
-
content_type: @
|
11
|
+
content_type: @attachment.content_type,
|
12
12
|
body: file.tap(&:rewind),
|
13
13
|
bucket: @config.s3_bucket_name,
|
14
14
|
key: s3_object_key(style),
|
15
15
|
)
|
16
|
-
|
17
|
-
|
18
|
-
def copy(style, from_attachment)
|
19
|
-
raise "not supported yet" unless from_attachment.storage.instance_of?(S3)
|
20
|
-
debug("copying by S3 to bucket:#{@config.s3_bucket_name},key:#{s3_object_key(style)}")
|
21
|
-
Aws::S3::Client.new.copy_object(
|
22
|
-
acl: @config.s3_acl,
|
23
|
-
cache_control: @config.s3_cache_control,
|
24
|
-
content_type: @record.read_attribute("#{@attachment_name}_content_type"),
|
25
|
-
copy_source: from_attachment.storage.object_key(style),
|
26
|
-
bucket: @config.s3_bucket_name,
|
27
|
-
key: s3_object_key(style),
|
28
|
-
)
|
16
|
+
@deletes.delete({ key: s3_object_key(style) }) # cancel deletion if overwrite
|
29
17
|
end
|
30
18
|
|
31
19
|
def s3_object_key(style)
|
@@ -38,11 +26,41 @@ module MiniPaperclip
|
|
38
26
|
end
|
39
27
|
|
40
28
|
def exists?(style)
|
41
|
-
|
29
|
+
Aws::S3::Client.new.head_object(
|
42
30
|
bucket: @config.s3_bucket_name,
|
43
31
|
key: s3_object_key(style),
|
44
32
|
)
|
45
|
-
|
33
|
+
true
|
34
|
+
rescue Aws::S3::Errors::NotFound
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
def push_delete_file(style)
|
39
|
+
@deletes.push({ key: s3_object_key(style) })
|
40
|
+
end
|
41
|
+
|
42
|
+
def do_delete_files
|
43
|
+
return if @deletes.empty?
|
44
|
+
debug("deleting by S3 to bucket:#{@config.s3_bucket_name},objects:#{@deletes}")
|
45
|
+
Aws::S3::Client.new.delete_objects(
|
46
|
+
bucket: @config.s3_bucket_name,
|
47
|
+
delete: {
|
48
|
+
objects: @deletes,
|
49
|
+
quiet: true,
|
50
|
+
}
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def open(style)
|
55
|
+
Tempfile.new(['MiniPaperclip::Storage::S3']).tap do |response_target|
|
56
|
+
response_target.binmode
|
57
|
+
Aws::S3::Client.new.get_object(
|
58
|
+
bucket: @config.s3_bucket_name,
|
59
|
+
key: s3_object_key(style),
|
60
|
+
response_target: response_target,
|
61
|
+
)
|
62
|
+
yield response_target if block_given?
|
63
|
+
end
|
46
64
|
end
|
47
65
|
end
|
48
66
|
end
|
@@ -10,8 +10,8 @@ module MiniPaperclip
|
|
10
10
|
if check_value = options[:less_than]
|
11
11
|
unless attachment_file_size < check_value
|
12
12
|
count = ActiveSupport::NumberHelper.number_to_human_size(check_value)
|
13
|
-
record.errors.add(attribute, :less_than,
|
14
|
-
record.errors.add(attachment_file_size_name, :less_than,
|
13
|
+
record.errors.add(attribute, :less_than, count: count)
|
14
|
+
record.errors.add(attachment_file_size_name, :less_than, count: count)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -16,29 +16,33 @@ module MiniPaperclip
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
19
20
|
# validate_attachment :image,
|
20
21
|
# geometry: {
|
21
22
|
# width: { less_than_or_equal_to: 3000 },
|
22
23
|
# height: { less_than_or_equal_to: 3000 } }
|
23
24
|
def validate_each(record, attribute, value)
|
24
25
|
return unless value.waiting_write_file
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
value.waiting_write_file.rewind
|
27
|
+
image_size = ImageSize.new(value.waiting_write_file)
|
28
|
+
# invalid format should not relate geometry
|
29
|
+
unless image_size.format
|
30
|
+
MiniPaperclip.config.logger.info("[mini_paperclip] cannot get image_size from #{value.waiting_write_file.inspect}")
|
31
|
+
return
|
28
32
|
end
|
29
|
-
return unless !geometry_string.empty?
|
30
|
-
width, height = geometry_string.split(',').map(&:to_i)
|
31
33
|
|
32
34
|
expected_width_less_than_or_equal_to = options.dig(:width, :less_than_or_equal_to)
|
33
35
|
expected_height_less_than_or_equal_to = options.dig(:height, :less_than_or_equal_to)
|
34
|
-
unless (!expected_width_less_than_or_equal_to || width <= expected_width_less_than_or_equal_to) &&
|
35
|
-
(!expected_height_less_than_or_equal_to || height <= expected_height_less_than_or_equal_to)
|
36
|
-
record.errors.add(
|
37
|
-
|
38
|
-
|
36
|
+
unless (!expected_width_less_than_or_equal_to || image_size.width <= expected_width_less_than_or_equal_to) &&
|
37
|
+
(!expected_height_less_than_or_equal_to || image_size.height <= expected_height_less_than_or_equal_to)
|
38
|
+
record.errors.add(
|
39
|
+
attribute,
|
40
|
+
:geometry,
|
41
|
+
actual_width: image_size.width,
|
42
|
+
actual_height: image_size.height,
|
39
43
|
expected_width_less_than_or_equal_to: expected_width_less_than_or_equal_to,
|
40
44
|
expected_height_less_than_or_equal_to: expected_height_less_than_or_equal_to,
|
41
|
-
|
45
|
+
)
|
42
46
|
end
|
43
47
|
end
|
44
48
|
end
|
data/mini_paperclip.gemspec
CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.add_runtime_dependency "activemodel"
|
18
18
|
spec.add_runtime_dependency "activesupport"
|
19
19
|
spec.add_runtime_dependency "aws-sdk-s3"
|
20
|
+
spec.add_runtime_dependency "image_size"
|
20
21
|
|
21
22
|
spec.metadata["homepage_uri"] = spec.homepage
|
22
23
|
spec.metadata["source_code_uri"] = spec.homepage
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mini_paperclip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ksss
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mini_magick
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: image_size
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
description: Subset from paperclip because paperclip has deprecated
|
84
98
|
email:
|
85
99
|
- co000ri@gmail.com
|
@@ -87,7 +101,7 @@ executables: []
|
|
87
101
|
extensions: []
|
88
102
|
extra_rdoc_files: []
|
89
103
|
files:
|
90
|
-
- ".github/workflows/
|
104
|
+
- ".github/workflows/ci.yml"
|
91
105
|
- ".gitignore"
|
92
106
|
- ".rspec"
|
93
107
|
- CODE_OF_CONDUCT.md
|
@@ -97,10 +111,10 @@ files:
|
|
97
111
|
- Rakefile
|
98
112
|
- bin/console
|
99
113
|
- bin/setup
|
114
|
+
- gemfiles/development.gemfile
|
100
115
|
- gemfiles/rails_52.gemfile
|
101
|
-
- gemfiles/rails_52.gemfile.lock
|
102
116
|
- gemfiles/rails_60.gemfile
|
103
|
-
- gemfiles/
|
117
|
+
- gemfiles/rails_head.gemfile
|
104
118
|
- lib/mini_paperclip.rb
|
105
119
|
- lib/mini_paperclip/attachment.rb
|
106
120
|
- lib/mini_paperclip/class_methods.rb
|
@@ -145,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
145
159
|
- !ruby/object:Gem::Version
|
146
160
|
version: '0'
|
147
161
|
requirements: []
|
148
|
-
rubygems_version: 3.1.
|
162
|
+
rubygems_version: 3.1.4
|
149
163
|
signing_key:
|
150
164
|
specification_version: 4
|
151
165
|
summary: MiniPaperclip is a mini paperclip
|
@@ -1,98 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: ..
|
3
|
-
specs:
|
4
|
-
mini_paperclip (0.1.0)
|
5
|
-
activemodel
|
6
|
-
activesupport
|
7
|
-
aws-sdk-s3
|
8
|
-
mimemagic
|
9
|
-
mini_magick
|
10
|
-
|
11
|
-
GEM
|
12
|
-
remote: https://rubygems.org/
|
13
|
-
specs:
|
14
|
-
activemodel (5.2.4.4)
|
15
|
-
activesupport (= 5.2.4.4)
|
16
|
-
activerecord (5.2.4.4)
|
17
|
-
activemodel (= 5.2.4.4)
|
18
|
-
activesupport (= 5.2.4.4)
|
19
|
-
arel (>= 9.0)
|
20
|
-
activesupport (5.2.4.4)
|
21
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
22
|
-
i18n (>= 0.7, < 2)
|
23
|
-
minitest (~> 5.1)
|
24
|
-
tzinfo (~> 1.1)
|
25
|
-
addressable (2.7.0)
|
26
|
-
public_suffix (>= 2.0.2, < 5.0)
|
27
|
-
arel (9.0.0)
|
28
|
-
aws-eventstream (1.1.0)
|
29
|
-
aws-partitions (1.399.0)
|
30
|
-
aws-sdk-core (3.109.3)
|
31
|
-
aws-eventstream (~> 1, >= 1.0.2)
|
32
|
-
aws-partitions (~> 1, >= 1.239.0)
|
33
|
-
aws-sigv4 (~> 1.1)
|
34
|
-
jmespath (~> 1.0)
|
35
|
-
aws-sdk-kms (1.39.0)
|
36
|
-
aws-sdk-core (~> 3, >= 3.109.0)
|
37
|
-
aws-sigv4 (~> 1.1)
|
38
|
-
aws-sdk-s3 (1.85.0)
|
39
|
-
aws-sdk-core (~> 3, >= 3.109.0)
|
40
|
-
aws-sdk-kms (~> 1)
|
41
|
-
aws-sigv4 (~> 1.1)
|
42
|
-
aws-sigv4 (1.2.2)
|
43
|
-
aws-eventstream (~> 1, >= 1.0.2)
|
44
|
-
concurrent-ruby (1.1.7)
|
45
|
-
crack (0.4.4)
|
46
|
-
diff-lcs (1.4.4)
|
47
|
-
hashdiff (1.0.1)
|
48
|
-
i18n (1.8.5)
|
49
|
-
concurrent-ruby (~> 1.0)
|
50
|
-
jmespath (1.4.0)
|
51
|
-
mimemagic (0.3.5)
|
52
|
-
mini_magick (4.11.0)
|
53
|
-
minitest (5.14.2)
|
54
|
-
public_suffix (4.0.6)
|
55
|
-
rack (2.2.3)
|
56
|
-
rack-test (1.1.0)
|
57
|
-
rack (>= 1.0, < 3)
|
58
|
-
rake (12.3.3)
|
59
|
-
rspec (3.10.0)
|
60
|
-
rspec-core (~> 3.10.0)
|
61
|
-
rspec-expectations (~> 3.10.0)
|
62
|
-
rspec-mocks (~> 3.10.0)
|
63
|
-
rspec-core (3.10.0)
|
64
|
-
rspec-support (~> 3.10.0)
|
65
|
-
rspec-expectations (3.10.0)
|
66
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
67
|
-
rspec-support (~> 3.10.0)
|
68
|
-
rspec-mocks (3.10.0)
|
69
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
70
|
-
rspec-support (~> 3.10.0)
|
71
|
-
rspec-support (3.10.0)
|
72
|
-
sqlite3 (1.4.2)
|
73
|
-
tapp (1.5.1)
|
74
|
-
thor
|
75
|
-
thor (1.0.1)
|
76
|
-
thread_safe (0.3.6)
|
77
|
-
tzinfo (1.2.8)
|
78
|
-
thread_safe (~> 0.1)
|
79
|
-
webmock (3.10.0)
|
80
|
-
addressable (>= 2.3.6)
|
81
|
-
crack (>= 0.3.2)
|
82
|
-
hashdiff (>= 0.4.0, < 2.0.0)
|
83
|
-
|
84
|
-
PLATFORMS
|
85
|
-
ruby
|
86
|
-
|
87
|
-
DEPENDENCIES
|
88
|
-
activerecord (~> 5.2.0)
|
89
|
-
mini_paperclip!
|
90
|
-
rack-test
|
91
|
-
rake (~> 12.0)
|
92
|
-
rspec (~> 3.0)
|
93
|
-
sqlite3
|
94
|
-
tapp
|
95
|
-
webmock
|
96
|
-
|
97
|
-
BUNDLED WITH
|
98
|
-
2.1.4
|
@@ -1,98 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: ..
|
3
|
-
specs:
|
4
|
-
mini_paperclip (0.1.0)
|
5
|
-
activemodel
|
6
|
-
activesupport
|
7
|
-
aws-sdk-s3
|
8
|
-
mimemagic
|
9
|
-
mini_magick
|
10
|
-
|
11
|
-
GEM
|
12
|
-
remote: https://rubygems.org/
|
13
|
-
specs:
|
14
|
-
activemodel (6.0.3.4)
|
15
|
-
activesupport (= 6.0.3.4)
|
16
|
-
activerecord (6.0.3.4)
|
17
|
-
activemodel (= 6.0.3.4)
|
18
|
-
activesupport (= 6.0.3.4)
|
19
|
-
activesupport (6.0.3.4)
|
20
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
21
|
-
i18n (>= 0.7, < 2)
|
22
|
-
minitest (~> 5.1)
|
23
|
-
tzinfo (~> 1.1)
|
24
|
-
zeitwerk (~> 2.2, >= 2.2.2)
|
25
|
-
addressable (2.7.0)
|
26
|
-
public_suffix (>= 2.0.2, < 5.0)
|
27
|
-
aws-eventstream (1.1.0)
|
28
|
-
aws-partitions (1.399.0)
|
29
|
-
aws-sdk-core (3.109.3)
|
30
|
-
aws-eventstream (~> 1, >= 1.0.2)
|
31
|
-
aws-partitions (~> 1, >= 1.239.0)
|
32
|
-
aws-sigv4 (~> 1.1)
|
33
|
-
jmespath (~> 1.0)
|
34
|
-
aws-sdk-kms (1.39.0)
|
35
|
-
aws-sdk-core (~> 3, >= 3.109.0)
|
36
|
-
aws-sigv4 (~> 1.1)
|
37
|
-
aws-sdk-s3 (1.85.0)
|
38
|
-
aws-sdk-core (~> 3, >= 3.109.0)
|
39
|
-
aws-sdk-kms (~> 1)
|
40
|
-
aws-sigv4 (~> 1.1)
|
41
|
-
aws-sigv4 (1.2.2)
|
42
|
-
aws-eventstream (~> 1, >= 1.0.2)
|
43
|
-
concurrent-ruby (1.1.7)
|
44
|
-
crack (0.4.4)
|
45
|
-
diff-lcs (1.4.4)
|
46
|
-
hashdiff (1.0.1)
|
47
|
-
i18n (1.8.5)
|
48
|
-
concurrent-ruby (~> 1.0)
|
49
|
-
jmespath (1.4.0)
|
50
|
-
mimemagic (0.3.5)
|
51
|
-
mini_magick (4.11.0)
|
52
|
-
minitest (5.14.2)
|
53
|
-
public_suffix (4.0.6)
|
54
|
-
rack (2.2.3)
|
55
|
-
rack-test (1.1.0)
|
56
|
-
rack (>= 1.0, < 3)
|
57
|
-
rake (12.3.3)
|
58
|
-
rspec (3.10.0)
|
59
|
-
rspec-core (~> 3.10.0)
|
60
|
-
rspec-expectations (~> 3.10.0)
|
61
|
-
rspec-mocks (~> 3.10.0)
|
62
|
-
rspec-core (3.10.0)
|
63
|
-
rspec-support (~> 3.10.0)
|
64
|
-
rspec-expectations (3.10.0)
|
65
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
66
|
-
rspec-support (~> 3.10.0)
|
67
|
-
rspec-mocks (3.10.0)
|
68
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
69
|
-
rspec-support (~> 3.10.0)
|
70
|
-
rspec-support (3.10.0)
|
71
|
-
sqlite3 (1.4.2)
|
72
|
-
tapp (1.5.1)
|
73
|
-
thor
|
74
|
-
thor (1.0.1)
|
75
|
-
thread_safe (0.3.6)
|
76
|
-
tzinfo (1.2.8)
|
77
|
-
thread_safe (~> 0.1)
|
78
|
-
webmock (3.10.0)
|
79
|
-
addressable (>= 2.3.6)
|
80
|
-
crack (>= 0.3.2)
|
81
|
-
hashdiff (>= 0.4.0, < 2.0.0)
|
82
|
-
zeitwerk (2.4.1)
|
83
|
-
|
84
|
-
PLATFORMS
|
85
|
-
ruby
|
86
|
-
|
87
|
-
DEPENDENCIES
|
88
|
-
activerecord (~> 6.0.0)
|
89
|
-
mini_paperclip!
|
90
|
-
rack-test
|
91
|
-
rake (~> 12.0)
|
92
|
-
rspec (~> 3.0)
|
93
|
-
sqlite3
|
94
|
-
tapp
|
95
|
-
webmock
|
96
|
-
|
97
|
-
BUNDLED WITH
|
98
|
-
2.1.4
|