citrusbyte-milton 0.3.0 → 0.3.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/MIT-LICENSE +20 -0
- data/README.markdown +354 -0
- data/Rakefile +9 -0
- data/init.rb +1 -0
- data/lib/milton/attachment.rb +203 -0
- data/lib/milton/core/file.rb +22 -0
- data/lib/milton/core/tempfile.rb +38 -0
- data/lib/milton/derivatives/derivative.rb +95 -0
- data/lib/milton/derivatives/thumbnail/crop_calculator.rb +64 -0
- data/lib/milton/derivatives/thumbnail/image.rb +49 -0
- data/lib/milton/derivatives/thumbnail.rb +46 -0
- data/lib/milton/storage/disk_file.rb +78 -0
- data/lib/milton/storage/s3_file.rb +89 -0
- data/lib/milton/storage/stored_file.rb +47 -0
- data/lib/milton/uploading.rb +82 -0
- data/lib/milton.rb +95 -0
- data/test/fixtures/big-milton.jpg +0 -0
- data/test/fixtures/milton.jpg +0 -0
- data/test/fixtures/mini-milton.jpg +0 -0
- data/test/fixtures/unsanitary .milton.jpg +0 -0
- data/test/milton/attachment_test.rb +329 -0
- data/test/milton/milton_test.rb +13 -0
- data/test/milton/resizing_test.rb +70 -0
- data/test/schema.rb +13 -0
- data/test/test_helper.rb +62 -0
- metadata +31 -7
@@ -0,0 +1,47 @@
|
|
1
|
+
module Milton
|
2
|
+
module Storage
|
3
|
+
class StoredFile
|
4
|
+
class << self
|
5
|
+
# Sanitizes the given filename, removes pathnames and the special chars
|
6
|
+
# needed for options seperation for derivatives
|
7
|
+
def sanitize_filename(filename, options)
|
8
|
+
File.basename(filename, File.extname(filename)).gsub(/^.*(\\|\/)/, '').
|
9
|
+
gsub(/[^\w]|#{Regexp.escape(options[:separator])}/, options[:replacement]).
|
10
|
+
strip + File.extname(filename)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(filename, id, source, options)
|
14
|
+
returning new(filename, id, options) do |file|
|
15
|
+
file.store(source)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the adapter class specified by the given type (by naming
|
20
|
+
# convention)
|
21
|
+
#
|
22
|
+
# Storage::StoredFile.adapter(:s3) => Storage::S3File
|
23
|
+
# Storage::StoredFile.adapter(:disk) => Storage::DiskFile
|
24
|
+
#
|
25
|
+
def adapter(type)
|
26
|
+
"Milton::Storage::#{type.to_s.classify}File".constantize
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :filename, :id, :options
|
31
|
+
|
32
|
+
def initialize(filename, id, options)
|
33
|
+
self.filename = filename
|
34
|
+
self.id = id
|
35
|
+
self.options = options
|
36
|
+
end
|
37
|
+
|
38
|
+
# Creates a "copy" of this StoredFile of the same type with the same id
|
39
|
+
# and options but using the given filename. Doesn't actually do any
|
40
|
+
# copying of the underlying file data, just creates a "copy" of the
|
41
|
+
# StoredFile object.
|
42
|
+
def copy(filename)
|
43
|
+
self.class.new(filename, self.id, self.options)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Milton
|
2
|
+
module Uploading
|
3
|
+
module ClassMethods
|
4
|
+
def self.extended(base)
|
5
|
+
base.setup_callbacks
|
6
|
+
end
|
7
|
+
|
8
|
+
def setup_callbacks
|
9
|
+
# Rails 2.1 fix for callbacks
|
10
|
+
define_callbacks(:before_file_saved, :after_file_saved) if defined?(::ActiveSupport::Callbacks)
|
11
|
+
after_save :save_uploaded_file
|
12
|
+
after_file_saved :create_derivatives if @after_create_callbacks.delete(:create_derivatives)
|
13
|
+
end
|
14
|
+
|
15
|
+
unless defined?(::ActiveSupport::Callbacks)
|
16
|
+
def before_file_saved(&block)
|
17
|
+
write_inheritable_array(:before_file_saved, [block])
|
18
|
+
end
|
19
|
+
|
20
|
+
def after_file_saved(&block)
|
21
|
+
write_inheritable_array(:after_file_saved, [block])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module InstanceMethods
|
27
|
+
def self.included(base)
|
28
|
+
# Rails 2.1 fix for callbacks
|
29
|
+
base.define_callbacks *[:before_file_saved, :after_file_saved] if base.respond_to?(:define_callbacks)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Set file=<uploaded file> on your model to handle an uploaded file.
|
33
|
+
def file=(file)
|
34
|
+
return nil if file.nil? || file.size == 0
|
35
|
+
@upload = Upload.new(file, self.class.milton_options)
|
36
|
+
self.filename = @upload.filename
|
37
|
+
self.size = @upload.size if respond_to?(:size=)
|
38
|
+
self.content_type = Milton::File.mime_type?(@upload) if respond_to?(:content_type=)
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def save_uploaded_file
|
44
|
+
if @upload && !@upload.stored?
|
45
|
+
callback :before_file_saved
|
46
|
+
@attached_file = @upload.store(id)
|
47
|
+
callback :after_file_saved
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Upload
|
54
|
+
attr_reader :content_type, :filename, :size, :options
|
55
|
+
|
56
|
+
def initialize(data_or_path, options)
|
57
|
+
@stored = false
|
58
|
+
@tempfile = Milton::Tempfile.create(data_or_path, options[:tempfile_path])
|
59
|
+
@content_type = data_or_path.content_type
|
60
|
+
@filename = Storage::StoredFile.sanitize_filename(data_or_path.original_filename, options) if respond_to?(:filename)
|
61
|
+
@size = File.size(self.temp_path)
|
62
|
+
@options = options
|
63
|
+
end
|
64
|
+
|
65
|
+
def stored?
|
66
|
+
@stored
|
67
|
+
end
|
68
|
+
|
69
|
+
def store(id)
|
70
|
+
return true if stored?
|
71
|
+
returning Storage::StoredFile.adapter(options[:storage]).create(filename, id, temp_path, options) do
|
72
|
+
@stored = true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def temp_path
|
79
|
+
@tempfile.respond_to?(:path) ? @tempfile.path : @tempfile.to_s
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/milton.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'milton/attachment'
|
2
|
+
require 'milton/core/tempfile'
|
3
|
+
require 'milton/core/file'
|
4
|
+
|
5
|
+
module Milton
|
6
|
+
# Raised when a file which was expected to exist appears not to exist
|
7
|
+
class MissingFileError < StandardError;end;
|
8
|
+
|
9
|
+
# Some definitions for file semantics used throughout Milton, understanding
|
10
|
+
# this will make understanding the code a bit easier and avoid ambiguity:
|
11
|
+
#
|
12
|
+
# path:
|
13
|
+
# the full path to a file or directory in the filesystem
|
14
|
+
# /var/log/apache2 or /var/log/apache2/access.log
|
15
|
+
# can also be defined as:
|
16
|
+
# path == dirname + filename
|
17
|
+
# path == dirname + basename + extension
|
18
|
+
#
|
19
|
+
# dirname:
|
20
|
+
# the directory portion of the path to a file or directory, all the chars
|
21
|
+
# up to the final /
|
22
|
+
# /var/log/apache2 => /var/log
|
23
|
+
# /var/log/apache2/ => /var/log/apache2
|
24
|
+
# /var/log/apache2/access.log => /var/log/apache2
|
25
|
+
#
|
26
|
+
# basename:
|
27
|
+
# the portion of a filename *with no extension* (ruby's "basename" may or
|
28
|
+
# may not have an extension), all the chars after the last / and before
|
29
|
+
# the last .
|
30
|
+
# /var/log/apache2 => apache2
|
31
|
+
# /var/log/apache2/ => nil
|
32
|
+
# /var/log/apache2/access.log => access
|
33
|
+
# /var/log/apache2/access.2008.log => access.2008
|
34
|
+
#
|
35
|
+
# extension:
|
36
|
+
# the extension portion of a filename w/ no preceding ., all the chars
|
37
|
+
# after the final .
|
38
|
+
# /var/log/apache2 => nil
|
39
|
+
# /var/log/apache2/ => nil
|
40
|
+
# /var/log/apache2/access.log => log
|
41
|
+
# /var/log/apache2/access.2008.log => log
|
42
|
+
#
|
43
|
+
# filename:
|
44
|
+
# the filename portion of a path w/ extension, all the chars after the
|
45
|
+
# final /
|
46
|
+
# /var/log/apache2 => apache2
|
47
|
+
# /var/log/apache2/ => nil
|
48
|
+
# /var/log/apache2/access.log => access.log
|
49
|
+
# /var/log/apache2/access.2008.log => access.2008.log
|
50
|
+
# can also be defined as:
|
51
|
+
# filename == basename + (extension ? '.' + extension : '')
|
52
|
+
#
|
53
|
+
|
54
|
+
# Gives the filename and line number of the method which called the method
|
55
|
+
# that invoked #called_by.
|
56
|
+
def called_by
|
57
|
+
caller[1].gsub(/.*\/(.*):in (.*)/, "\\1:\\2")
|
58
|
+
end
|
59
|
+
module_function :called_by
|
60
|
+
|
61
|
+
# Writes the given message to the Rails log at the info level. If given an
|
62
|
+
# invoker (just a string) it prepends the message with that. If not given
|
63
|
+
# an invoker it outputs the filename and line number which called #log.
|
64
|
+
def log(message, invoker=nil)
|
65
|
+
invoker ||= Milton.called_by
|
66
|
+
Rails.logger.info("[milton] #{invoker}: #{message}")
|
67
|
+
end
|
68
|
+
module_function :log
|
69
|
+
|
70
|
+
# Executes the given command, returning the commands output if successful
|
71
|
+
# or false if the command failed.
|
72
|
+
# Redirects stderr to log/milton.stderr.log in order to examine causes of
|
73
|
+
# failure.
|
74
|
+
def syscall(command)
|
75
|
+
log("executing #{command}", invoker = Milton.called_by)
|
76
|
+
stdout = %x{#{command} 2>>#{File.join(Rails.root, 'log', 'milton.stderr.log')}}
|
77
|
+
$?.success? ? stdout : (log("failed to execute #{command}", invoker) and return false)
|
78
|
+
end
|
79
|
+
module_function :syscall
|
80
|
+
|
81
|
+
# Wraps +require+ on the given path in a rescue which uses the given
|
82
|
+
# message for the resulting LoadError on failure instead of the default
|
83
|
+
# one to give the user a better idea of what happened (useful for
|
84
|
+
# dynamic +require+)
|
85
|
+
def try_require(path, message)
|
86
|
+
begin
|
87
|
+
require path
|
88
|
+
rescue LoadError => e
|
89
|
+
raise LoadError.new(message + " (failed to require #{path})")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
module_function :try_require
|
93
|
+
end
|
94
|
+
|
95
|
+
ActiveRecord::Base.extend Milton::Attachment
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,329 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class AttachmentTest < ActiveSupport::TestCase
|
4
|
+
context "being included into a model" do
|
5
|
+
class NotAnAttachment < ActiveRecord::Base
|
6
|
+
end
|
7
|
+
|
8
|
+
context "NotAnAttachment" do
|
9
|
+
should "not have milton_options" do
|
10
|
+
assert !NotAnAttachment.respond_to?(:milton_options)
|
11
|
+
end
|
12
|
+
|
13
|
+
should "not have attachment methods" do
|
14
|
+
assert !NotAnAttachment.respond_to?(:has_attachment_methods)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "Attachment" do
|
19
|
+
should "have milton_options on Attachment" do
|
20
|
+
assert Attachment.respond_to?(:milton_options)
|
21
|
+
end
|
22
|
+
|
23
|
+
should "have attachment methods" do
|
24
|
+
assert Attachment.respond_to?(:has_attachment_methods)
|
25
|
+
end
|
26
|
+
|
27
|
+
should "have a hash of options" do
|
28
|
+
assert Attachment.milton_options.is_a?(Hash)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "setting options" do
|
34
|
+
context "defaults" do
|
35
|
+
should "use :disk as default storage" do
|
36
|
+
assert_equal :disk, Attachment.milton_options[:storage]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "inheritence" do
|
41
|
+
class FooImage < Image
|
42
|
+
is_attachment :resizeable => { :sizes => { :foo => { :size => '10x10' } } }
|
43
|
+
end
|
44
|
+
|
45
|
+
class BarImage < FooImage # note that BarImage < FooImage < Image
|
46
|
+
is_attachment :resizeable => { :sizes => { } }
|
47
|
+
end
|
48
|
+
|
49
|
+
should "inherit settings from Image" do
|
50
|
+
assert_equal Image.milton_options[:storage_options][:root], FooImage.milton_options[:storage_options][:root]
|
51
|
+
end
|
52
|
+
|
53
|
+
should "overwrite settings from Image when redefined in FooImage" do
|
54
|
+
assert_equal({ :foo => { :size => '10x10' } }, FooImage.milton_options[:resizeable][:sizes])
|
55
|
+
end
|
56
|
+
|
57
|
+
should "overwrite settings from FooImage when redefined in BarImage" do
|
58
|
+
assert_equal({}, BarImage.milton_options[:resizeable][:sizes])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "encapsulation" do
|
63
|
+
class FooRootImage < Image
|
64
|
+
is_attachment :storage_options => { :root => '/foo' }
|
65
|
+
end
|
66
|
+
|
67
|
+
class BarRootImage < Image
|
68
|
+
is_attachment :storage_options => { :root => '/bar' }
|
69
|
+
end
|
70
|
+
|
71
|
+
should "not overwrite FooRootImage's root setting with BarRootImage's" do
|
72
|
+
assert_equal '/foo', FooRootImage.milton_options[:storage_options][:root]
|
73
|
+
end
|
74
|
+
|
75
|
+
should "not overwrite BarRootImage's root setting with FooRootImage's" do
|
76
|
+
assert_equal '/bar', BarRootImage.milton_options[:storage_options][:root]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "getting mime-type" do
|
82
|
+
setup do
|
83
|
+
@attachment = Attachment.new :file => upload('milton.jpg')
|
84
|
+
end
|
85
|
+
|
86
|
+
context "from freshly uploaded file" do
|
87
|
+
should "recognize it as an image/jpg" do
|
88
|
+
assert_equal 'image/jpg', @attachment.content_type
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "from existing file" do
|
93
|
+
setup do
|
94
|
+
@attachment.save
|
95
|
+
@attachment.reload
|
96
|
+
end
|
97
|
+
|
98
|
+
should "recognize it as an image/jpg" do
|
99
|
+
assert_equal 'image/jpg', @attachment.content_type
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "from file with no content_type set" do
|
104
|
+
setup do
|
105
|
+
@attachment.update_attribute(:content_type, nil)
|
106
|
+
@attachment.save
|
107
|
+
@attachment.reload
|
108
|
+
end
|
109
|
+
|
110
|
+
should "attempt to determine mime_type from file" do
|
111
|
+
# this is implemented w/ unix file cmd so is system dependent currently...
|
112
|
+
assert_equal 'image/jpeg', @attachment.content_type
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "creating attachment folder" do
|
118
|
+
raise "Failed to create #{File.join(output_path, 'exists')}" unless FileUtils.mkdir_p(File.join(output_path, 'exists'))
|
119
|
+
FileUtils.ln_s 'exists', File.join(output_path, 'linked')
|
120
|
+
raise "Failed to symlink #{File.join(output_path, 'linked')}" unless File.symlink?(File.join(output_path, 'linked'))
|
121
|
+
|
122
|
+
class NoRootAttachment < Attachment
|
123
|
+
is_attachment :storage_options => { :root => File.join(ActiveSupport::TestCase.output_path, 'nonexistant') }
|
124
|
+
end
|
125
|
+
|
126
|
+
class RootExistsAttachment < Attachment
|
127
|
+
is_attachment :storage_options => { :root => File.join(ActiveSupport::TestCase.output_path, 'exists') }
|
128
|
+
end
|
129
|
+
|
130
|
+
class SymlinkAttachment < Attachment
|
131
|
+
is_attachment :storage_options => { :root => File.join(ActiveSupport::TestCase.output_path, 'linked') }
|
132
|
+
end
|
133
|
+
|
134
|
+
should "create root path when root path does not exist" do
|
135
|
+
@attachment = NoRootAttachment.create :file => upload('milton.jpg')
|
136
|
+
assert File.exists?(@attachment.path)
|
137
|
+
assert File.exists?(File.join(output_path, 'nonexistant'))
|
138
|
+
assert_match /nonexistant/, @attachment.path
|
139
|
+
end
|
140
|
+
|
141
|
+
should "work when root path already exists" do
|
142
|
+
@attachment = RootExistsAttachment.create :file => upload('milton.jpg')
|
143
|
+
assert File.exists?(@attachment.path)
|
144
|
+
assert_match /exists/, @attachment.path
|
145
|
+
end
|
146
|
+
|
147
|
+
should "work when root path is a symlink" do
|
148
|
+
@attachment = SymlinkAttachment.create :file => upload('milton.jpg')
|
149
|
+
assert File.exists?(@attachment.path)
|
150
|
+
assert_match /linked/, @attachment.path
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "being destroyed" do
|
155
|
+
setup do
|
156
|
+
@attachment = Attachment.create :file => upload('milton.jpg')
|
157
|
+
end
|
158
|
+
|
159
|
+
should "delete the underlying file from the filesystem" do
|
160
|
+
@attachment.destroy
|
161
|
+
assert !File.exists?(@attachment.path)
|
162
|
+
end
|
163
|
+
|
164
|
+
# the partitioning algorithm ensures that each attachment model has its own
|
165
|
+
# folder, so we can safely delete the folder, if you write a new
|
166
|
+
# partitioner this might change!
|
167
|
+
should "delete the directory containing the file and all derivatives from the filesystem" do
|
168
|
+
@attachment.destroy
|
169
|
+
assert !File.exists?(File.dirname(@attachment.path))
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "instantiating" do
|
174
|
+
setup do
|
175
|
+
@image = Image.new :file => upload('milton.jpg')
|
176
|
+
end
|
177
|
+
|
178
|
+
should "have a file= method" do
|
179
|
+
assert @image.respond_to?(:file=)
|
180
|
+
end
|
181
|
+
|
182
|
+
should "set the filename from the uploaded file" do
|
183
|
+
assert_equal 'milton.jpg', @image.filename
|
184
|
+
end
|
185
|
+
|
186
|
+
should "strip seperator (.) from the filename and replace them with replacement (-)" do
|
187
|
+
@image.filename = 'foo.bar.baz.jpg'
|
188
|
+
assert_equal 'foo-bar-baz.jpg', @image.filename
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "path partitioning" do
|
193
|
+
setup do
|
194
|
+
@image = Image.new :file => upload('milton.jpg')
|
195
|
+
end
|
196
|
+
|
197
|
+
should "be stored in a partitioned folder based on its id" do
|
198
|
+
assert_match /^.*\/0*#{@image.id}\/#{@image.filename}$/, @image.path
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context "public path helper" do
|
203
|
+
setup do
|
204
|
+
@image = Image.new(:file => upload('milton.jpg'))
|
205
|
+
end
|
206
|
+
|
207
|
+
should "give the path from public/ on to the filename" do
|
208
|
+
flexmock(@image, :path => '/root/public/assets/1/milton.jpg')
|
209
|
+
assert_equal "/assets/1/milton.jpg", @image.public_path
|
210
|
+
end
|
211
|
+
|
212
|
+
should "give the path from foo/ on to the filename" do
|
213
|
+
flexmock(@image, :path => '/root/foo/assets/1/milton.jpg')
|
214
|
+
assert_equal "/assets/1/milton.jpg", @image.public_path({}, 'foo')
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context "handling uploads" do
|
219
|
+
context "filename column" do
|
220
|
+
should "raise an exception if there is no filename column" do
|
221
|
+
assert_raise RuntimeError do
|
222
|
+
class NotUploadable < ActiveRecord::Base # see schema.rb, there is a not_uploadables table
|
223
|
+
is_attachment
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
should "not raise an exception if the underlying table doesn't exist" do
|
229
|
+
assert_nothing_raised do
|
230
|
+
class NoTable < ActiveRecord::Base
|
231
|
+
is_attachment
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context "class extensions" do
|
238
|
+
context "class methods" do
|
239
|
+
should "add before_file_saved callback" do
|
240
|
+
assert Attachment.respond_to?(:before_file_saved)
|
241
|
+
end
|
242
|
+
|
243
|
+
should "add after_file_saved callback" do
|
244
|
+
assert Attachment.respond_to?(:after_file_saved)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
context "handling file upload" do
|
250
|
+
context "saving upload" do
|
251
|
+
setup do
|
252
|
+
@attachment = Attachment.new :file => upload('milton.jpg')
|
253
|
+
end
|
254
|
+
|
255
|
+
should "save the upload to the filesystem on save" do
|
256
|
+
@attachment.save
|
257
|
+
assert File.exists?(@attachment.path)
|
258
|
+
end
|
259
|
+
|
260
|
+
should "have the same filesize as original file when large enough not to be a StringIO" do
|
261
|
+
# FIXME: this doesn't actually upload as a StringIO, figure out how to
|
262
|
+
# force that
|
263
|
+
@attachment.save
|
264
|
+
assert_equal File.size(File.join(File.dirname(__FILE__), '..', 'fixtures', 'milton.jpg')), File.size(@attachment.path)
|
265
|
+
end
|
266
|
+
|
267
|
+
should "have the same filesize as original file when small enough to be a StringIO" do
|
268
|
+
assert_equal File.size(File.join(File.dirname(__FILE__), '..', 'fixtures', 'mini-milton.jpg')), File.size(Attachment.create(:file => upload('mini-milton.jpg')).path)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
context "stored full filename" do
|
273
|
+
setup do
|
274
|
+
@attachment = Attachment.create! :file => upload('milton.jpg')
|
275
|
+
end
|
276
|
+
|
277
|
+
should "use set root" do
|
278
|
+
assert_match /^#{@attachment.milton_options[:storage_options][:root]}.*$/, @attachment.path
|
279
|
+
end
|
280
|
+
|
281
|
+
should "use uploaded filename" do
|
282
|
+
assert_match /^.*#{@attachment.filename}$/, @attachment.path
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context "sanitizing filename" do
|
287
|
+
setup do
|
288
|
+
@attachment = Attachment.create! :file => upload('unsanitary .milton.jpg')
|
289
|
+
end
|
290
|
+
|
291
|
+
should "strip the space and . and replace them with -" do
|
292
|
+
assert_match /^.*\/unsanitary--milton.jpg$/, @attachment.path
|
293
|
+
end
|
294
|
+
|
295
|
+
should "exist with sanitized filename" do
|
296
|
+
assert File.exists?(@attachment.path)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
context "saving attachment after upload" do
|
301
|
+
setup do
|
302
|
+
@attachment = Attachment.create! :file => upload('unsanitary .milton.jpg')
|
303
|
+
end
|
304
|
+
|
305
|
+
should "save the file again" do
|
306
|
+
assert_nothing_raised do
|
307
|
+
Attachment.find(@attachment.id).save!
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
context "updating an existing attachment" do
|
315
|
+
setup do
|
316
|
+
@attachment = Attachment.create! :file => upload('milton.jpg')
|
317
|
+
@original_path = @attachment.path
|
318
|
+
@attachment.update_attributes! :file => upload('big-milton.jpg')
|
319
|
+
end
|
320
|
+
|
321
|
+
should "store the path to the updated upload" do
|
322
|
+
assert_equal 'big-milton.jpg', File.basename(@attachment.path)
|
323
|
+
end
|
324
|
+
|
325
|
+
should "save the updated upload" do
|
326
|
+
assert File.exists?(@attachment.path)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|