attachs 0.4.5 → 4.0.0.0
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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +83 -218
- data/Rakefile +1 -14
- data/lib/attachs.rb +98 -31
- data/lib/attachs/attachment.rb +301 -72
- data/lib/attachs/builder.rb +78 -0
- data/lib/attachs/collection.rb +84 -0
- data/lib/attachs/concern.rb +47 -0
- data/lib/attachs/configuration.rb +11 -0
- data/lib/attachs/console.rb +40 -0
- data/lib/attachs/extensions/active_record/base.rb +24 -0
- data/lib/attachs/extensions/active_record/validations/attachment_content_type_validator.rb +37 -0
- data/lib/attachs/extensions/active_record/validations/attachment_presence_validator.rb +27 -0
- data/lib/attachs/extensions/active_record/validations/attachment_size_validator.rb +55 -0
- data/lib/attachs/extensions/active_record/validations/attachment_validator.rb +24 -0
- data/lib/attachs/interpolations.rb +27 -0
- data/lib/attachs/jobs/base.rb +14 -0
- data/lib/attachs/jobs/delete_job.rb +15 -0
- data/lib/attachs/jobs/update_job.rb +11 -0
- data/lib/attachs/locales/en.yml +2 -6
- data/lib/attachs/locales/es.yml +2 -6
- data/lib/attachs/processors/base.rb +4 -14
- data/lib/attachs/processors/image.rb +45 -0
- data/lib/attachs/railtie.rb +7 -27
- data/lib/attachs/storages/base.rb +10 -59
- data/lib/attachs/storages/s3.rb +68 -63
- data/lib/attachs/version.rb +1 -1
- data/lib/generators/attachs/install/install_generator.rb +15 -0
- data/lib/generators/attachs/install/templates/initializer.rb +7 -0
- data/lib/generators/attachs/upload/templates/migration.rb +11 -0
- data/lib/generators/attachs/upload/templates/model.rb +19 -0
- data/lib/generators/attachs/upload/upload_generator.rb +24 -0
- data/lib/tasks/attachs.rake +12 -26
- data/test/attachment_test.rb +175 -12
- data/test/collection_test.rb +52 -0
- data/test/concern_test.rb +10 -0
- data/test/dummy/Rakefile +1 -2
- data/test/dummy/app/assets/javascripts/application.js +2 -2
- data/test/dummy/app/assets/stylesheets/application.css +6 -4
- data/test/dummy/app/models/product.rb +16 -0
- data/test/dummy/app/models/shop.rb +14 -0
- data/test/dummy/app/models/upload.rb +19 -0
- data/test/dummy/app/views/layouts/application.html.erb +9 -11
- data/test/dummy/bin/bundle +1 -0
- data/test/dummy/bin/rails +2 -1
- data/test/dummy/bin/rake +1 -0
- data/test/dummy/bin/setup +30 -0
- data/test/dummy/config.ru +1 -1
- data/test/dummy/config/application.rb +3 -2
- data/test/dummy/config/boot.rb +1 -1
- data/test/dummy/config/database.yml +4 -22
- data/test/dummy/config/database.yml.travis +3 -0
- data/test/dummy/config/environment.rb +1 -1
- data/test/dummy/config/environments/development.rb +15 -3
- data/test/dummy/config/environments/production.rb +21 -26
- data/test/dummy/config/environments/test.rb +10 -12
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/attachs.rb +7 -21
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/mime_types.rb +1 -2
- data/test/dummy/config/initializers/session_store.rb +1 -1
- data/test/dummy/config/routes.rb +54 -0
- data/test/dummy/config/secrets.yml +24 -0
- data/test/dummy/db/migrate/20161024234003_create_shops.rb +11 -0
- data/test/dummy/db/migrate/20161024234029_create_products.rb +12 -0
- data/test/dummy/db/migrate/20161031135453_create_uploads.rb +11 -0
- data/test/dummy/db/schema.rb +27 -16
- data/test/dummy/log/development.log +61 -0
- data/test/dummy/log/test.log +5873 -0
- data/test/dummy/public/404.html +58 -55
- data/test/dummy/public/422.html +58 -55
- data/test/dummy/public/500.html +57 -54
- data/test/fixtures/big_file.txt +50 -0
- data/test/fixtures/big_image.jpg +0 -0
- data/test/fixtures/file.txt +1 -0
- data/test/fixtures/image.jpg +0 -0
- data/test/{dummy/test/fixtures/file.txt → fixtures/small_file.txt} +0 -0
- data/test/fixtures/small_image.jpg +0 -0
- data/test/generator_test.rb +14 -6
- data/test/helper_test.rb +28 -0
- data/test/query_test.rb +26 -0
- data/test/support/storage_helper.rb +65 -0
- data/test/task_test.rb +69 -0
- data/test/test_helper.rb +6 -45
- data/test/upload_test.rb +27 -0
- data/test/validator_test.rb +160 -0
- metadata +114 -68
- data/lib/attachs/active_record/base.rb +0 -103
- data/lib/attachs/active_record/connection_adapters.rb +0 -40
- data/lib/attachs/active_record/migration.rb +0 -17
- data/lib/attachs/active_record/validators.rb +0 -7
- data/lib/attachs/active_record/validators/attachment_content_type_validator.rb +0 -30
- data/lib/attachs/active_record/validators/attachment_presence_validator.rb +0 -22
- data/lib/attachs/active_record/validators/attachment_size_validator.rb +0 -47
- data/lib/attachs/processors/thumbnail.rb +0 -69
- data/lib/attachs/storages/local.rb +0 -95
- data/lib/attachs/types/base.rb +0 -22
- data/lib/attachs/types/default.rb +0 -29
- data/lib/attachs/types/regular.rb +0 -21
- data/lib/generators/attachs/install_generator.rb +0 -13
- data/lib/generators/attachs/templates/attachs.rb +0 -2
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/app/models/medium.rb +0 -5
- data/test/dummy/app/models/picture.rb +0 -2
- data/test/dummy/config/initializers/secret_token.rb +0 -13
- data/test/dummy/config/s3.yml +0 -15
- data/test/dummy/db/migrate/20140808012639_create_media.rb +0 -11
- data/test/dummy/test/fixtures/image.gif +0 -0
- data/test/local_storage_test.rb +0 -72
- data/test/migration_test.rb +0 -65
- data/test/processor_test.rb +0 -49
- data/test/s3_storage_tes.rb +0 -66
- data/test/tasks_test.rb +0 -57
- data/test/validators_test.rb +0 -100
data/lib/attachs/attachment.rb
CHANGED
@@ -1,118 +1,347 @@
|
|
1
1
|
module Attachs
|
2
2
|
class Attachment
|
3
|
+
include ActiveModel::Validations
|
3
4
|
|
4
|
-
attr_reader :record, :
|
5
|
+
attr_reader :record, :record_attribute, :options, :attributes, :value
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
def initialize(record, attribute, options, source=false)
|
7
|
+
def initialize(record, record_attribute, options={}, attributes={})
|
9
8
|
@record = record
|
10
|
-
@
|
9
|
+
@record_attribute = record_attribute
|
11
10
|
@options = options
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
@original_attributes = @attributes = normalize_attributes(attributes)
|
12
|
+
end
|
13
|
+
|
14
|
+
%i(id size filename content_type uploaded_at).each do |name|
|
15
|
+
define_method name do
|
16
|
+
attributes[name]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def basename
|
21
|
+
if filename
|
22
|
+
File.basename filename, extension
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def extension
|
27
|
+
if filename
|
28
|
+
File.extname filename
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def present?
|
33
|
+
filename.present? && content_type.present? && size.present?
|
34
|
+
end
|
35
|
+
|
36
|
+
def blank?
|
37
|
+
!present?
|
38
|
+
end
|
39
|
+
|
40
|
+
def persisted?
|
41
|
+
record.persisted? && paths.any? && uploaded_at
|
42
|
+
end
|
43
|
+
|
44
|
+
def changed?
|
45
|
+
@original_attributes != @attributes
|
46
|
+
end
|
47
|
+
|
48
|
+
def type
|
49
|
+
if content_type
|
50
|
+
if content_type.starts_with?('image/')
|
51
|
+
'image'
|
52
|
+
else
|
53
|
+
'file'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def url(style=:original)
|
59
|
+
paths = attributes[:paths]
|
60
|
+
if paths.has_key?(style)
|
61
|
+
storage.url paths[style]
|
62
|
+
elsif options.has_key?(:default_path)
|
63
|
+
template = options[:default_path]
|
64
|
+
path = generate_path(template, style)
|
65
|
+
storage.url path
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def position
|
70
|
+
if options[:multiple]
|
71
|
+
attributes[:position]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def position=(value)
|
76
|
+
if options[:multiple]
|
77
|
+
attributes[:position] = value
|
78
|
+
write_record
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def assign(value)
|
83
|
+
source, new_attributes = process_value(value)
|
84
|
+
if new_attributes
|
85
|
+
unless changed?
|
86
|
+
@original_attributes = attributes
|
16
87
|
end
|
17
|
-
|
18
|
-
|
19
|
-
|
88
|
+
@attributes = new_attributes
|
89
|
+
write_record
|
90
|
+
@source = source
|
91
|
+
@value = value
|
92
|
+
end
|
93
|
+
end
|
94
|
+
alias_method :value=, :assign
|
95
|
+
|
96
|
+
def _destroy=(value)
|
97
|
+
if ActiveRecord::Type::Boolean.new.type_cast_from_user(value)
|
98
|
+
assign nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def save
|
103
|
+
if changed?
|
104
|
+
if original_attributes[:paths].any? || original_attributes[:old_paths].any?
|
105
|
+
Jobs::DeleteJob.perform_later(
|
106
|
+
original_attributes[:paths].values +
|
107
|
+
original_attributes[:old_paths]
|
108
|
+
)
|
109
|
+
end
|
110
|
+
case source.class.name
|
111
|
+
when 'Attachs::Attachment'
|
112
|
+
file = storage.get(source.paths[:original])
|
113
|
+
storage.process file, paths, styles
|
114
|
+
when 'Upload'
|
115
|
+
source.file.paths.each do |style, path|
|
116
|
+
storage.copy path, paths[style]
|
117
|
+
end
|
118
|
+
when 'ActionDispatch::Http::UploadedFile'
|
119
|
+
storage.process source, paths, styles
|
20
120
|
end
|
121
|
+
@source = @value = nil
|
122
|
+
@original_attributes = @attributes
|
123
|
+
elsif present?
|
124
|
+
if paths != generate_paths
|
125
|
+
Jobs::UpdateJob.perform_later record, record_attribute.to_s
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def update_paths
|
131
|
+
if changed?
|
132
|
+
raise 'Save attachment before update paths'
|
21
133
|
else
|
22
|
-
|
23
|
-
|
24
|
-
|
134
|
+
new_paths = generate_paths
|
135
|
+
if paths != new_paths
|
136
|
+
new_paths.each do |style, new_path|
|
137
|
+
original_path = paths[style]
|
138
|
+
if original_path != new_path
|
139
|
+
unless storage.exist?(new_path)
|
140
|
+
storage.copy original_path, new_path
|
141
|
+
end
|
142
|
+
attributes[:paths][style] = new_path
|
143
|
+
attributes[:old_paths] |= [original_path]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
update_record
|
25
147
|
end
|
26
148
|
end
|
27
149
|
end
|
28
150
|
|
29
|
-
def
|
30
|
-
|
151
|
+
def reprocess
|
152
|
+
if changed?
|
153
|
+
raise 'Save attachment before reprocess'
|
154
|
+
elsif present? && styles
|
155
|
+
file = storage.get(paths[:original])
|
156
|
+
paths.each do |style, path|
|
157
|
+
if storage.exist?(path)
|
158
|
+
storage.delete path
|
159
|
+
end
|
160
|
+
Rails.logger.info "Regenerating: #{style} => #{path}"
|
161
|
+
end
|
162
|
+
new_paths = generate_paths
|
163
|
+
storage.process file, new_paths, styles
|
164
|
+
attributes[:paths] = new_paths
|
165
|
+
update_record
|
166
|
+
end
|
31
167
|
end
|
32
168
|
|
33
|
-
def
|
34
|
-
|
169
|
+
def fix_missings
|
170
|
+
if changed?
|
171
|
+
raise 'Save attachment before fix missings'
|
172
|
+
elsif present? && styles
|
173
|
+
missings = paths.slice(:original)
|
174
|
+
paths.except(:original).each do |style, path|
|
175
|
+
unless storage.exist?(path)
|
176
|
+
missings[style] = path
|
177
|
+
Rails.logger.info "Generating: #{style} => #{path}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
if missings.size > 1
|
181
|
+
file = storage.get(paths[:original])
|
182
|
+
storage.process file, missings, styles
|
183
|
+
end
|
184
|
+
end
|
35
185
|
end
|
36
186
|
|
37
|
-
def
|
38
|
-
|
187
|
+
def destroy
|
188
|
+
assign nil
|
189
|
+
save
|
39
190
|
end
|
40
191
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
192
|
+
def method_missing(name, *args, &block)
|
193
|
+
if attributes.has_key?(name)
|
194
|
+
attributes[name]
|
195
|
+
else
|
196
|
+
super
|
44
197
|
end
|
45
198
|
end
|
46
199
|
|
47
|
-
def
|
48
|
-
|
200
|
+
def respond_to_missing?(name, include_private=false)
|
201
|
+
attributes.has_key?(name) || super
|
49
202
|
end
|
50
203
|
|
51
|
-
def
|
52
|
-
|
204
|
+
def persist
|
205
|
+
if changed? && present?
|
206
|
+
new_paths = generate_paths
|
207
|
+
if paths != new_paths
|
208
|
+
attributes[:paths] = new_paths
|
209
|
+
attributes[:uploaded_at] = Time.now
|
210
|
+
write_record
|
211
|
+
end
|
212
|
+
end
|
53
213
|
end
|
54
214
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
)
|
215
|
+
def unpersist
|
216
|
+
if changed? && present?
|
217
|
+
attributes[:paths] = original_attributes[:paths]
|
218
|
+
attributes[:uploaded_at] = original_attributes[:paths]
|
219
|
+
write_record
|
220
|
+
end
|
62
221
|
end
|
63
222
|
|
64
|
-
def
|
65
|
-
|
223
|
+
def styles
|
224
|
+
case value = options[:styles]
|
225
|
+
when Proc
|
226
|
+
value.call(record) || {}
|
227
|
+
when Hash
|
228
|
+
value
|
229
|
+
else
|
230
|
+
{}
|
231
|
+
end
|
66
232
|
end
|
67
233
|
|
68
|
-
|
69
|
-
|
234
|
+
private
|
235
|
+
|
236
|
+
attr_reader :original_attributes, :source
|
237
|
+
|
238
|
+
def storage
|
239
|
+
Attachs.storage
|
70
240
|
end
|
71
241
|
|
72
|
-
def
|
73
|
-
|
242
|
+
def normalize_attributes(attributes)
|
243
|
+
attributes.reverse_merge(paths: {}, old_paths: []).deep_symbolize_keys
|
74
244
|
end
|
75
245
|
|
76
|
-
def
|
77
|
-
|
246
|
+
def write_record(value=nil)
|
247
|
+
unless record.destroyed?
|
248
|
+
record.send "#{record_attribute}_will_change!"
|
249
|
+
record.send :write_attribute, record_attribute, (value || raw_attributes)
|
250
|
+
end
|
78
251
|
end
|
79
252
|
|
80
|
-
|
253
|
+
def raw_attributes
|
254
|
+
if options[:multiple]
|
255
|
+
record.send(:read_attribute, record_attribute).reject{ |h| h['id'] == id }.append attributes
|
256
|
+
else
|
257
|
+
attributes
|
258
|
+
end
|
259
|
+
end
|
81
260
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
261
|
+
def update_record(value=nil)
|
262
|
+
unless record.destroyed?
|
263
|
+
record.update_column record_attribute, (value || raw_attributes)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def generate_path(template, style)
|
268
|
+
if path = template.try(:dup)
|
269
|
+
path.gsub! ':style', style.to_s.gsub('_', '-')
|
270
|
+
path.scan(/:[a-z_]+/).each do |token|
|
271
|
+
name = token.from(1).to_sym
|
272
|
+
path.gsub! token, interpolate(name).to_s.parameterize
|
89
273
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
274
|
+
path.squeeze! '-'
|
275
|
+
path.squeeze! '/'
|
276
|
+
path.gsub! '-.', '.'
|
277
|
+
path.gsub! '/-', '/'
|
278
|
+
path.gsub! '-/', '/'
|
279
|
+
path.sub! /^\//, ''
|
280
|
+
path
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def generate_paths
|
285
|
+
template = options[:path]
|
286
|
+
paths = { original: generate_path(template, :original) }
|
287
|
+
styles.each do |style, geometry|
|
288
|
+
paths[style] = generate_path(template, style)
|
289
|
+
end
|
290
|
+
paths
|
291
|
+
end
|
292
|
+
|
293
|
+
def interpolate(name)
|
294
|
+
if %i(basename extension).include?(name)
|
295
|
+
send name
|
296
|
+
elsif name == :attribute
|
297
|
+
record_attribute
|
298
|
+
elsif attributes.except(:upload_at, :paths, :old_paths).has_key?(name)
|
299
|
+
attributes[name]
|
100
300
|
else
|
101
|
-
|
102
|
-
@filename = source.original_filename.downcase
|
103
|
-
@updated_at = Time.zone.now
|
301
|
+
Attachs.interpolations.find(name).call record
|
104
302
|
end
|
105
|
-
@content_type = source.content_type
|
106
|
-
@size = source.size
|
107
303
|
end
|
108
304
|
|
109
|
-
def
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
305
|
+
def generate_id
|
306
|
+
SecureRandom.uuid.gsub '-', ''
|
307
|
+
end
|
308
|
+
|
309
|
+
def process_value(value)
|
310
|
+
case value.class.name
|
311
|
+
when 'NilClass'
|
312
|
+
[nil, {}]
|
313
|
+
when 'Attachs::Attachment'
|
314
|
+
attributes = value.attributes.merge(id: generate_id)
|
315
|
+
[value, attributes]
|
316
|
+
when 'Upload'
|
317
|
+
attributes = value.file.attributes.merge(id: generate_id)
|
318
|
+
[value.file, attributes]
|
319
|
+
when 'String','Fixnum','Bignum'
|
320
|
+
if Rails.configuration.cache_classes == false
|
321
|
+
Rails.application.eager_load!
|
322
|
+
end
|
323
|
+
if defined?(Upload)
|
324
|
+
upload = Upload.find(value)
|
325
|
+
attributes = upload.file.attributes.merge(id: generate_id)
|
326
|
+
[upload, attributes]
|
327
|
+
end
|
328
|
+
when 'ActionDispatch::Http::UploadedFile'
|
329
|
+
attributes = {
|
330
|
+
id: generate_id,
|
331
|
+
filename: value.original_filename,
|
332
|
+
content_type: value.content_type,
|
333
|
+
size: value.size.to_i,
|
334
|
+
uploaded_at: Time.now,
|
335
|
+
paths: {},
|
336
|
+
old_paths: []
|
337
|
+
}
|
338
|
+
if value.content_type.starts_with?('image/')
|
339
|
+
width, height = Console.find_dimensions(value.path)
|
340
|
+
attributes[:width] = width
|
341
|
+
attributes[:height] = height
|
342
|
+
attributes[:ratio] = (height.to_d / width.to_d).to_f
|
115
343
|
end
|
344
|
+
[value, attributes]
|
116
345
|
end
|
117
346
|
end
|
118
347
|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Attachs
|
2
|
+
class Builder
|
3
|
+
|
4
|
+
attr_reader :model, :concern
|
5
|
+
|
6
|
+
def initialize(model)
|
7
|
+
@model = model
|
8
|
+
@concern = Module.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def define(attribute, options={})
|
12
|
+
unless options.has_key?(:path)
|
13
|
+
raise 'Path required'
|
14
|
+
end
|
15
|
+
model.include Attachs::Concern
|
16
|
+
model.attachments[attribute] = options
|
17
|
+
define_writer attribute
|
18
|
+
define_reader attribute, options
|
19
|
+
define_attributes_writer attribute, options
|
20
|
+
model.include concern
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def define_writer(attribute)
|
26
|
+
concern.class_eval do
|
27
|
+
define_method "#{attribute}=" do |value|
|
28
|
+
send(attribute).assign value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def define_reader(attribute, options)
|
34
|
+
concern.class_eval do
|
35
|
+
define_method attribute do
|
36
|
+
variable = "@#{attribute}"
|
37
|
+
if instance = instance_variable_get(variable)
|
38
|
+
instance
|
39
|
+
else
|
40
|
+
if options[:multiple] == true
|
41
|
+
klass = Attachs::Collection
|
42
|
+
else
|
43
|
+
klass = Attachs::Attachment
|
44
|
+
end
|
45
|
+
instance_variable_set(
|
46
|
+
variable,
|
47
|
+
klass.new(self, attribute, options, super())
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_attributes_writer(attribute, options)
|
55
|
+
concern.class_eval do
|
56
|
+
define_method "#{attribute}_attributes=" do |collection_or_attributes|
|
57
|
+
if options[:multiple] == true
|
58
|
+
collection_or_attributes.each do |attributes|
|
59
|
+
if id = attributes.delete(:id)
|
60
|
+
attachment = send(attribute).find(id)
|
61
|
+
else
|
62
|
+
attachment = send(attribute).new
|
63
|
+
end
|
64
|
+
attributes.each do |name, value|
|
65
|
+
attachment.send "#{name}=", value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
else
|
69
|
+
collection_or_attributes.each do |name, value|
|
70
|
+
send(attribute).send "#{name}=", value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|