jnicklas-carrierwave 0.1
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.
- data/Generators +4 -0
- data/LICENSE +20 -0
- data/README.md +211 -0
- data/Rakefile +96 -0
- data/TODO +0 -0
- data/lib/carrierwave/mount.rb +93 -0
- data/lib/carrierwave/orm/activerecord.rb +20 -0
- data/lib/carrierwave/orm/datamapper.rb +20 -0
- data/lib/carrierwave/processing/image_science.rb +70 -0
- data/lib/carrierwave/processing/rmagick.rb +161 -0
- data/lib/carrierwave/sanitized_file.rb +231 -0
- data/lib/carrierwave/storage/abstract.rb +80 -0
- data/lib/carrierwave/storage/file.rb +40 -0
- data/lib/carrierwave/storage/s3.rb +83 -0
- data/lib/carrierwave/uploader.rb +420 -0
- data/lib/carrierwave.rb +63 -0
- data/lib/generators/templates/uploader.rbt +32 -0
- data/lib/generators/uploader_generator.rb +20 -0
- data/spec/fixtures/bork.txt +1 -0
- data/spec/fixtures/test.jpeg +1 -0
- data/spec/fixtures/test.jpg +1 -0
- data/spec/mount_spec.rb +180 -0
- data/spec/orm/activerecord_spec.rb +168 -0
- data/spec/orm/datamapper_spec.rb +133 -0
- data/spec/sanitized_file_spec.rb +618 -0
- data/spec/spec_helper.rb +122 -0
- data/spec/uploader_spec.rb +709 -0
- metadata +89 -0
@@ -0,0 +1,420 @@
|
|
1
|
+
module CarrierWave
|
2
|
+
class Uploader
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
##
|
7
|
+
# Returns a list of processor callbacks which have been declared for this uploader
|
8
|
+
#
|
9
|
+
# @return [String]
|
10
|
+
#
|
11
|
+
def processors
|
12
|
+
@processors ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Adds a processor callback which applies operations as a file is uploaded.
|
17
|
+
# The argument may be the name of any method of the uploader, expressed as a symbol,
|
18
|
+
# or a list of such methods, or a hash where the key is a method and the value is
|
19
|
+
# an array of arguments to call the method with
|
20
|
+
#
|
21
|
+
# @param [*Symbol, Hash{Symbol => Array[]}] args
|
22
|
+
# @example
|
23
|
+
# class MyUploader < CarrierWave::Uploader
|
24
|
+
# process :sepiatone, :vignette
|
25
|
+
# process :scale => [200, 200]
|
26
|
+
#
|
27
|
+
# def sepiatone
|
28
|
+
# ...
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def vignette
|
32
|
+
# ...
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# def scale(height, width)
|
36
|
+
# ...
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
def process(*args)
|
41
|
+
args.each do |arg|
|
42
|
+
if arg.is_a?(Hash)
|
43
|
+
arg.each do |method, args|
|
44
|
+
processors.push([method, args])
|
45
|
+
end
|
46
|
+
else
|
47
|
+
processors.push([arg, []])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Sets the storage engine to be used when storing files with this uploader.
|
54
|
+
# Can be any class that implements a #store!(CarrierWave::SanitizedFile) and a #retrieve!
|
55
|
+
# method. See lib/carrierwave/storage/file.rb for an example. Storage engines should
|
56
|
+
# be added to CarrierWave.config[:storage_engines] so they can be referred
|
57
|
+
# to by a symbol, which should be more convenient
|
58
|
+
#
|
59
|
+
# If no argument is given, it will simply return the currently used storage engine.
|
60
|
+
#
|
61
|
+
# @param [Symbol, Class] storage The storage engine to use for this uploader
|
62
|
+
# @return [Class] the storage engine to be used with this uploader
|
63
|
+
# @example
|
64
|
+
# storage :file
|
65
|
+
# storage CarrierWave::Storage::File
|
66
|
+
# storage MyCustomStorageEngine
|
67
|
+
#
|
68
|
+
def storage(storage = nil)
|
69
|
+
if storage.is_a?(Symbol)
|
70
|
+
@storage = get_storage_by_symbol(storage)
|
71
|
+
@storage.setup!
|
72
|
+
elsif storage
|
73
|
+
@storage = storage
|
74
|
+
@storage.setup!
|
75
|
+
elsif @storage.nil?
|
76
|
+
# Get the storage from the superclass if there is one
|
77
|
+
@storage = superclass.storage rescue nil
|
78
|
+
end
|
79
|
+
if @storage.nil?
|
80
|
+
# If we were not able to find a store any other way, setup the default store
|
81
|
+
@storage ||= get_storage_by_symbol(CarrierWave.config[:storage])
|
82
|
+
@storage.setup!
|
83
|
+
end
|
84
|
+
return @storage
|
85
|
+
end
|
86
|
+
|
87
|
+
alias_method :storage=, :storage
|
88
|
+
|
89
|
+
attr_accessor :version_name
|
90
|
+
|
91
|
+
##
|
92
|
+
# Adds a new version to this uploader
|
93
|
+
#
|
94
|
+
# @param [#to_sym] name name of the version
|
95
|
+
# @param [Proc] &block a block to eval on this version of the uploader
|
96
|
+
#
|
97
|
+
def version(name, &block)
|
98
|
+
name = name.to_sym
|
99
|
+
klass = Class.new(self)
|
100
|
+
klass.version_name = name
|
101
|
+
klass.class_eval(&block) if block
|
102
|
+
versions[name] = klass
|
103
|
+
class_eval <<-RUBY
|
104
|
+
def #{name}
|
105
|
+
versions[:#{name}]
|
106
|
+
end
|
107
|
+
RUBY
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# @return [Hash{Symbol => Class}] a list of versions available for this uploader
|
112
|
+
#
|
113
|
+
def versions
|
114
|
+
@versions ||= {}
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Generates a unique cache id for use in the caching system
|
119
|
+
#
|
120
|
+
# @return [String] a cache if in the format YYYYMMDD-HHMM-PID-RND
|
121
|
+
#
|
122
|
+
def generate_cache_id
|
123
|
+
Time.now.strftime('%Y%m%d-%H%M') + '-' + Process.pid.to_s + '-' + ("%04d" % rand(9999))
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def get_storage_by_symbol(symbol)
|
129
|
+
CarrierWave.config[:storage_engines][symbol]
|
130
|
+
end
|
131
|
+
|
132
|
+
end # class << self
|
133
|
+
|
134
|
+
attr_reader :file, :model, :mounted_as
|
135
|
+
|
136
|
+
##
|
137
|
+
# If a model is given as the first parameter, it will stored in the uploader, and
|
138
|
+
# available throught +#model+. Likewise, mounted_as stores the name of the column
|
139
|
+
# where this instance of the uploader is mounted. These values can then be used inside
|
140
|
+
# your uploader.
|
141
|
+
#
|
142
|
+
# If you do not wish to mount your uploaders with the ORM extensions in -more then you
|
143
|
+
# can override this method inside your uploader.
|
144
|
+
#
|
145
|
+
# @param [Object] model Any kind of model object
|
146
|
+
# @param [Symbol] mounted_as The name of the column where this uploader is mounted
|
147
|
+
# @example
|
148
|
+
# class MyUploader < CarrierWave::Uploader
|
149
|
+
# def store_dir
|
150
|
+
# File.join('public', 'files', mounted_as, model.permalink)
|
151
|
+
# end
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
def initialize(model=nil, mounted_as=nil)
|
155
|
+
@model = model
|
156
|
+
@mounted_as = mounted_as
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# Apply all process callbacks added through CarrierWaveer.process
|
161
|
+
#
|
162
|
+
def process!
|
163
|
+
self.class.processors.each do |method, args|
|
164
|
+
self.send(method, *args)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# @return [String] the path where the file is currently located.
|
170
|
+
#
|
171
|
+
def current_path
|
172
|
+
file.path if file.respond_to?(:path)
|
173
|
+
end
|
174
|
+
|
175
|
+
##
|
176
|
+
# Returns a hash mapping the name of each version of the uploader to an instance of it
|
177
|
+
#
|
178
|
+
# @return [Hash{Symbol => CarrierWave::Uploader}] a list of uploader instances
|
179
|
+
#
|
180
|
+
def versions
|
181
|
+
return @versions if @versions
|
182
|
+
@versions = {}
|
183
|
+
self.class.versions.each do |name, klass|
|
184
|
+
@versions[name] = klass.new(model, mounted_as)
|
185
|
+
end
|
186
|
+
@versions
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# @return [String] the location where this file is accessible via a url
|
191
|
+
#
|
192
|
+
def url
|
193
|
+
if file.respond_to?(:url) and not file.url.blank?
|
194
|
+
file.url
|
195
|
+
elsif current_path
|
196
|
+
File.expand_path(current_path).gsub(File.expand_path(CarrierWave.config[:public]), '')
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
alias_method :to_s, :url
|
201
|
+
|
202
|
+
##
|
203
|
+
# Returns a string that uniquely identifies the last stored file
|
204
|
+
#
|
205
|
+
# @return [String] uniquely identifies a file
|
206
|
+
#
|
207
|
+
def identifier
|
208
|
+
file.identifier if file.respond_to?(:identifier)
|
209
|
+
end
|
210
|
+
|
211
|
+
##
|
212
|
+
# Override this in your Uploader to change the filename.
|
213
|
+
#
|
214
|
+
# Be careful using record ids as filenames. If the filename is stored in the database
|
215
|
+
# the record id will be nil when the filename is set. Don't use record ids unless you
|
216
|
+
# understand this limitation.
|
217
|
+
#
|
218
|
+
# Do not use the version_name in the filename, as it will prevent versions from being
|
219
|
+
# loaded correctly.
|
220
|
+
#
|
221
|
+
# @return [String] a filename
|
222
|
+
#
|
223
|
+
def filename
|
224
|
+
@filename
|
225
|
+
end
|
226
|
+
|
227
|
+
##
|
228
|
+
# @return [String] the name of this version of the uploader
|
229
|
+
#
|
230
|
+
def version_name
|
231
|
+
self.class.version_name
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
# @return [String] the directory relative to which we will upload
|
236
|
+
#
|
237
|
+
def root
|
238
|
+
CarrierWave.config[:root]
|
239
|
+
end
|
240
|
+
|
241
|
+
####################
|
242
|
+
## Cache
|
243
|
+
####################
|
244
|
+
|
245
|
+
##
|
246
|
+
# Override this in your Uploader to change the directory where files are cached.
|
247
|
+
#
|
248
|
+
# @return [String] a directory
|
249
|
+
#
|
250
|
+
def cache_dir
|
251
|
+
CarrierWave.config[:cache_dir]
|
252
|
+
end
|
253
|
+
|
254
|
+
##
|
255
|
+
# Returns a String which uniquely identifies the currently cached file for later retrieval
|
256
|
+
#
|
257
|
+
# @return [String] a cache name, in the format YYYYMMDD-HHMM-PID-RND/filename.txt
|
258
|
+
#
|
259
|
+
def cache_name
|
260
|
+
File.join(cache_id, [version_name, original_filename].compact.join('_')) if cache_id and original_filename
|
261
|
+
end
|
262
|
+
|
263
|
+
##
|
264
|
+
# Caches the given file unless a file has already been cached, stored or retrieved.
|
265
|
+
#
|
266
|
+
# @param [File, IOString, Tempfile] new_file any kind of file object
|
267
|
+
# @raise [CarrierWave::FormNotMultipart] if the assigned parameter is a string
|
268
|
+
#
|
269
|
+
def cache(new_file)
|
270
|
+
cache!(new_file) unless file
|
271
|
+
end
|
272
|
+
|
273
|
+
##
|
274
|
+
# Caches the given file. Calls process! to trigger any process callbacks.
|
275
|
+
#
|
276
|
+
# @param [File, IOString, Tempfile] new_file any kind of file object
|
277
|
+
# @raise [CarrierWave::FormNotMultipart] if the assigned parameter is a string
|
278
|
+
#
|
279
|
+
def cache!(new_file)
|
280
|
+
self.cache_id = CarrierWave::Uploader.generate_cache_id unless cache_id
|
281
|
+
new_file = CarrierWave::SanitizedFile.new(new_file)
|
282
|
+
raise CarrierWave::FormNotMultipart, "check that your upload form is multipart encoded" if new_file.string?
|
283
|
+
|
284
|
+
@file = new_file
|
285
|
+
|
286
|
+
@filename = new_file.filename
|
287
|
+
self.original_filename = new_file.filename
|
288
|
+
|
289
|
+
@file = @file.copy_to(cache_path)
|
290
|
+
process!
|
291
|
+
|
292
|
+
versions.each do |name, v|
|
293
|
+
v.send(:cache_id=, cache_id)
|
294
|
+
v.cache!(new_file)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
##
|
299
|
+
# Retrieves the file with the given cache_name from the cache, unless a file has
|
300
|
+
# already been cached, stored or retrieved.
|
301
|
+
#
|
302
|
+
# @param [String] cache_name uniquely identifies a cache file
|
303
|
+
#
|
304
|
+
def retrieve_from_cache(cache_name)
|
305
|
+
retrieve_from_cache!(cache_name) unless file
|
306
|
+
rescue CarrierWave::InvalidParameter
|
307
|
+
end
|
308
|
+
|
309
|
+
##
|
310
|
+
# Retrieves the file with the given cache_name from the cache.
|
311
|
+
#
|
312
|
+
# @param [String] cache_name uniquely identifies a cache file
|
313
|
+
# @raise [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
|
314
|
+
#
|
315
|
+
def retrieve_from_cache!(cache_name)
|
316
|
+
self.cache_id, self.original_filename = cache_name.split('/', 2)
|
317
|
+
@filename = original_filename
|
318
|
+
@file = CarrierWave::SanitizedFile.new(cache_path)
|
319
|
+
versions.each { |name, v| v.retrieve_from_cache!(cache_name) }
|
320
|
+
end
|
321
|
+
|
322
|
+
####################
|
323
|
+
## STORE
|
324
|
+
####################
|
325
|
+
|
326
|
+
##
|
327
|
+
# Override this in your Uploader to change the directory where the file backend stores files.
|
328
|
+
#
|
329
|
+
# Other backends may or may not use this method, depending on their specific needs.
|
330
|
+
#
|
331
|
+
# @return [String] a directory
|
332
|
+
#
|
333
|
+
def store_dir
|
334
|
+
[CarrierWave.config[:store_dir], version_name].compact.join(File::Separator)
|
335
|
+
end
|
336
|
+
|
337
|
+
##
|
338
|
+
# Stores the file by passing it to this Uploader's storage engine, unless a file has
|
339
|
+
# already been cached, stored or retrieved.
|
340
|
+
#
|
341
|
+
# If CarrierWave.config[:use_cache] is true, it will first cache the file
|
342
|
+
# and apply any process callbacks before uploading it.
|
343
|
+
#
|
344
|
+
# @param [File, IOString, Tempfile] new_file any kind of file object
|
345
|
+
#
|
346
|
+
def store(new_file)
|
347
|
+
store!(new_file) unless file
|
348
|
+
end
|
349
|
+
|
350
|
+
##
|
351
|
+
# Stores the file by passing it to this Uploader's storage engine.
|
352
|
+
#
|
353
|
+
# If new_file is omitted, a previously cached file will be stored.
|
354
|
+
#
|
355
|
+
# If CarrierWave.config[:use_cache] is true, it will first cache the file
|
356
|
+
# and apply any process callbacks before uploading it.
|
357
|
+
#
|
358
|
+
# @param [File, IOString, Tempfile] new_file any kind of file object
|
359
|
+
#
|
360
|
+
def store!(new_file=nil)
|
361
|
+
if CarrierWave.config[:use_cache]
|
362
|
+
cache!(new_file) if new_file
|
363
|
+
@file = storage.store!(self, @file)
|
364
|
+
@cache_id = nil
|
365
|
+
else
|
366
|
+
new_file = CarrierWave::SanitizedFile.new(new_file)
|
367
|
+
|
368
|
+
@filename = new_file.filename
|
369
|
+
self.original_filename = filename
|
370
|
+
|
371
|
+
@file = storage.store!(self, new_file)
|
372
|
+
end
|
373
|
+
versions.each { |name, v| v.store!(new_file) }
|
374
|
+
end
|
375
|
+
|
376
|
+
##
|
377
|
+
# Retrieves the file from the storage, unless a file has
|
378
|
+
# already been cached, stored or retrieved.
|
379
|
+
#
|
380
|
+
# @param [String] filename uniquely identifies the file to retrieve
|
381
|
+
#
|
382
|
+
def retrieve_from_store(filename)
|
383
|
+
retrieve_from_store!(filename) unless file
|
384
|
+
rescue CarrierWave::InvalidParameter
|
385
|
+
end
|
386
|
+
|
387
|
+
##
|
388
|
+
# Retrieves the file from the storage.
|
389
|
+
#
|
390
|
+
# @param [String] identifier uniquely identifies the file to retrieve
|
391
|
+
#
|
392
|
+
def retrieve_from_store!(identifier)
|
393
|
+
@file = storage.retrieve!(self, identifier)
|
394
|
+
versions.each { |name, v| v.retrieve_from_store!(identifier) }
|
395
|
+
end
|
396
|
+
|
397
|
+
private
|
398
|
+
|
399
|
+
def cache_path
|
400
|
+
File.expand_path(File.join(cache_dir, cache_name), root)
|
401
|
+
end
|
402
|
+
|
403
|
+
def storage
|
404
|
+
self.class.storage
|
405
|
+
end
|
406
|
+
|
407
|
+
attr_reader :cache_id, :original_filename
|
408
|
+
|
409
|
+
def cache_id=(cache_id)
|
410
|
+
raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /^[\d]{8}\-[\d]{4}\-[\d]+\-[\d]{4}$/
|
411
|
+
@cache_id = cache_id
|
412
|
+
end
|
413
|
+
|
414
|
+
def original_filename=(filename)
|
415
|
+
raise CarrierWave::InvalidParameter, "invalid filename" unless filename =~ /^[a-z0-9\.\-\+_]+$/i
|
416
|
+
@original_filename = filename
|
417
|
+
end
|
418
|
+
|
419
|
+
end # Uploader
|
420
|
+
end # CarrierWave
|
data/lib/carrierwave.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module CarrierWave
|
4
|
+
class << self
|
5
|
+
attr_accessor :config
|
6
|
+
end
|
7
|
+
|
8
|
+
class UploadError < StandardError; end
|
9
|
+
class NoFileError < UploadError; end
|
10
|
+
class FormNotMultipart < UploadError; end
|
11
|
+
class InvalidParameter < UploadError; end
|
12
|
+
# Should be used by methods used as process callbacks.
|
13
|
+
class ProcessingError < UploadError; end
|
14
|
+
end
|
15
|
+
|
16
|
+
dir = File.join(File.dirname(__FILE__), 'carrierwave')
|
17
|
+
|
18
|
+
require File.join(dir, 'sanitized_file')
|
19
|
+
require File.join(dir, 'uploader')
|
20
|
+
require File.join(dir, 'mount')
|
21
|
+
require File.join(dir, 'storage', 'abstract')
|
22
|
+
require File.join(dir, 'storage', 'file')
|
23
|
+
require File.join(dir, 'storage', 's3')
|
24
|
+
|
25
|
+
CarrierWave.config = {
|
26
|
+
:storage => :file,
|
27
|
+
:use_cache => true,
|
28
|
+
:storage_engines => {
|
29
|
+
:file => CarrierWave::Storage::File,
|
30
|
+
:s3 => CarrierWave::Storage::S3
|
31
|
+
},
|
32
|
+
:s3 => {
|
33
|
+
:access => :public_read
|
34
|
+
},
|
35
|
+
:store_dir => 'public/uploads',
|
36
|
+
:cache_dir => 'public/uploads/tmp'
|
37
|
+
}
|
38
|
+
|
39
|
+
if defined?(Merb)
|
40
|
+
CarrierWave.config[:root] = Merb.root
|
41
|
+
CarrierWave.config[:public] = Merb.dir_for(:public)
|
42
|
+
|
43
|
+
orm_path = File.dirname(__FILE__) / 'carrierwave' / 'orm' / Merb.orm
|
44
|
+
require orm_path if File.exist?(orm_path + '.rb')
|
45
|
+
|
46
|
+
Merb.push_path(:model, Merb.root / "app" / "uploaders")
|
47
|
+
|
48
|
+
Merb.add_generators File.dirname(__FILE__) / 'generators' / 'uploader_generator'
|
49
|
+
end
|
50
|
+
|
51
|
+
if defined?(Rails)
|
52
|
+
CarrierWave.config[:root] = Rails.root
|
53
|
+
CarrierWave.config[:public] = File.join(Rails.root, 'public')
|
54
|
+
|
55
|
+
require File.join(File.dirname(__FILE__), "carrierwave", "orm", 'activerecord')
|
56
|
+
|
57
|
+
ActiveSupport::Dependencies.load_paths << File.join(Rails.root, "app", "uploaders")
|
58
|
+
end
|
59
|
+
|
60
|
+
if defined?(Sinatra)
|
61
|
+
CarrierWave.config[:root] = Sinatra::Application.root
|
62
|
+
CarrierWave.config[:public] = Sinatra::Application.public
|
63
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class <%= class_name %>Uploader < CarrierWave::Uploader
|
2
|
+
|
3
|
+
# Include RMagick or ImageScience support
|
4
|
+
# include CarrierWave::RMagick
|
5
|
+
# include CarrierWave::ImageScience
|
6
|
+
|
7
|
+
# Choose what kind of storage to use for this uploader
|
8
|
+
storage :file
|
9
|
+
|
10
|
+
# Process files as they are uploaded.
|
11
|
+
# process :scale => [200, 300]
|
12
|
+
#
|
13
|
+
# def scale(width, height)
|
14
|
+
# # do something
|
15
|
+
# end
|
16
|
+
|
17
|
+
# Create different verions of your uploaded files
|
18
|
+
# version :thumb do
|
19
|
+
# process :scale => [50, 50]
|
20
|
+
# end
|
21
|
+
|
22
|
+
# Override the filename of the uploaded files
|
23
|
+
# def filename
|
24
|
+
# "something"
|
25
|
+
# end
|
26
|
+
|
27
|
+
# Override the directory where uploaded files will be stored
|
28
|
+
# def store_dir
|
29
|
+
# "something"
|
30
|
+
# end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Merb
|
2
|
+
module Generators
|
3
|
+
class UploaderGenerator < NamedGenerator
|
4
|
+
|
5
|
+
def self.source_root
|
6
|
+
File.join(File.dirname(__FILE__), 'templates')
|
7
|
+
end
|
8
|
+
|
9
|
+
first_argument :name, :required => true, :desc => "The name of this uploader"
|
10
|
+
|
11
|
+
template :uploader do |t|
|
12
|
+
t.source = 'uploader.rbt'
|
13
|
+
t.destination = "app/uploaders/#{file_name}_uploader.rb"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
add :uploader, UploaderGenerator
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
bork bork bork
|
@@ -0,0 +1 @@
|
|
1
|
+
this is stuff
|
@@ -0,0 +1 @@
|
|
1
|
+
this is stuff
|