adrift 0.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/.gitignore +6 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +10 -0
- data/Rakefile +22 -0
- data/adrift.gemspec +38 -0
- data/autotest/discover.rb +1 -0
- data/features/active_record_integration.feature +42 -0
- data/features/data_mapper_integration.feature +42 -0
- data/features/step_definitions/model_steps.rb +54 -0
- data/features/support/env.rb +55 -0
- data/features/support/world.rb +58 -0
- data/lib/adrift.rb +10 -0
- data/lib/adrift/attachment.rb +246 -0
- data/lib/adrift/file_to_attach.rb +121 -0
- data/lib/adrift/integration.rb +39 -0
- data/lib/adrift/integration/active_record.rb +29 -0
- data/lib/adrift/integration/base.rb +87 -0
- data/lib/adrift/integration/data_mapper.rb +29 -0
- data/lib/adrift/pattern.rb +219 -0
- data/lib/adrift/processor.rb +100 -0
- data/lib/adrift/railtie.rb +12 -0
- data/lib/adrift/storage.rb +82 -0
- data/lib/adrift/version.rb +3 -0
- data/spec/adrift/attachment_spec.rb +488 -0
- data/spec/adrift/file_to_attach_spec.rb +78 -0
- data/spec/adrift/integration/active_record_spec.rb +21 -0
- data/spec/adrift/integration/base_spec.rb +7 -0
- data/spec/adrift/integration/data_mapper_spec.rb +21 -0
- data/spec/adrift/pattern_spec.rb +98 -0
- data/spec/adrift/processor_spec.rb +61 -0
- data/spec/adrift/storage_spec.rb +181 -0
- data/spec/fixtures/me.png +0 -0
- data/spec/fixtures/me_no_colors.png +0 -0
- data/spec/shared_examples/integration/base.rb +128 -0
- data/spec/spec_helper.rb +47 -0
- metadata +277 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
module Adrift
|
2
|
+
# Namespace containing the procesor objects used by Attachment.
|
3
|
+
#
|
4
|
+
# They are used to do whatever it's needed with the attached file,
|
5
|
+
# and need to satisfy the following interface:
|
6
|
+
#
|
7
|
+
# [<tt>#process(attached_file_path, styles)</tt>]
|
8
|
+
# Do whatever it needs to do. Generally this means creating new
|
9
|
+
# files from the attached one, but it can also mean transforming
|
10
|
+
# the attached file.
|
11
|
+
#
|
12
|
+
# [<tt>#processed_files</tt>]
|
13
|
+
# Hash with the style names as keys and the paths of the processed
|
14
|
+
# files as values.
|
15
|
+
module Processor
|
16
|
+
# Creates a set of thumbnails of an image. To be fair, it just
|
17
|
+
# tells ImageMagick to do it.
|
18
|
+
class Thumbnail
|
19
|
+
# A wrapper around ImageMagick's convert command line tool.
|
20
|
+
class Cli
|
21
|
+
# Runs *convert* with the given +input+ and +options+, which
|
22
|
+
# are expressed in a Hash. The resulting image is stored in
|
23
|
+
# +output+.
|
24
|
+
def run(input, output, options={})
|
25
|
+
options = options.map { |name, value| %(-#{name} "#{value}") }
|
26
|
+
`convert #{input} #{options.join(' ')} #{output}`
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Hash with the style names as keys and the paths as values of
|
31
|
+
# the files generated in the last #process.
|
32
|
+
attr_reader :processed_files
|
33
|
+
|
34
|
+
# Creates a new Thumbnail object. +cli+ is a wrapper around
|
35
|
+
# convert (see Cli).
|
36
|
+
def initialize(cli=Cli.new)
|
37
|
+
@processed_files = {}
|
38
|
+
@cli = cli
|
39
|
+
end
|
40
|
+
|
41
|
+
# Creates a set of thumbnails for +image_path+ with the
|
42
|
+
# dimensions specified in +styles+, which has the following
|
43
|
+
# general form:
|
44
|
+
#
|
45
|
+
# { style_name: 'definition', ... }
|
46
|
+
#
|
47
|
+
# where 'definition' is an
|
48
|
+
# {ImageMagick's image geometry}[http://www.imagemagick.org/script/command-line-processing.php#geometry]
|
49
|
+
# or has the form 'widthxheight#'. For instance:
|
50
|
+
#
|
51
|
+
# {
|
52
|
+
# fixed_width: '100',
|
53
|
+
# fixed_height: 'x100',
|
54
|
+
# max: '100x100',
|
55
|
+
# fixed: '100x100#'
|
56
|
+
# }
|
57
|
+
#
|
58
|
+
# will create, respectively, a thumbnail with a 100px width and
|
59
|
+
# the corresponding height to preserve the ratio, a thumbnail
|
60
|
+
# with a 100px height and the corresponding width to preserve
|
61
|
+
# the ratio, a thumbnail with at most 100px width and at most
|
62
|
+
# 100px height preserving the ratio, and a thumbnail with 100px
|
63
|
+
# width and 100px height preserving the ratio (to do that, it
|
64
|
+
# will resize the image trying to make it fit the specified
|
65
|
+
# dimensions and then will crop its center).
|
66
|
+
#
|
67
|
+
# The thumbnail files are named after +image_path+ prefixed with
|
68
|
+
# the style name and a hypen for every style. The last created
|
69
|
+
# thumbnails are accesible through #processed_files.
|
70
|
+
def process(image_path, styles={})
|
71
|
+
@processed_files.clear
|
72
|
+
styles.each do |name, definition|
|
73
|
+
thumbnail_path = File.join(
|
74
|
+
File.dirname(image_path),
|
75
|
+
"#{name}-#{File.basename(image_path)}"
|
76
|
+
)
|
77
|
+
@cli.run(image_path, thumbnail_path, options_for(definition))
|
78
|
+
@processed_files[name] = thumbnail_path
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# Returns a Hash with the options needed by convert to build a
|
85
|
+
# thumbnail vgiven its +definition+.
|
86
|
+
def options_for(definition)
|
87
|
+
if definition.end_with?('#')
|
88
|
+
{
|
89
|
+
:resize => definition.tr('#', '^'),
|
90
|
+
:gravity => 'center',
|
91
|
+
:background => 'None',
|
92
|
+
:extent => definition.tr('#', '')
|
93
|
+
}
|
94
|
+
else
|
95
|
+
{ :resize => definition }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Adrift
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer "adrift.setup" do
|
4
|
+
Pattern::Tags::Root.path = Rails.root
|
5
|
+
ActiveSupport.on_load :active_record do
|
6
|
+
require 'adrift/integration/active_record'
|
7
|
+
end
|
8
|
+
# TODO find out a better way to go (?)
|
9
|
+
require 'adrift/integration/data_mapper'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Adrift
|
2
|
+
# Namespace containing the storage objects used by Attachment.
|
3
|
+
#
|
4
|
+
# They are used to save and remove files, and need to satisfy the
|
5
|
+
# following interface:
|
6
|
+
#
|
7
|
+
# [<tt>#store(source_path, destination_path)</tt>]
|
8
|
+
# Adds a file to be stored.
|
9
|
+
#
|
10
|
+
# [<tt>#remove(path)</tt>]
|
11
|
+
# Indicates that a file will be removed.
|
12
|
+
#
|
13
|
+
# [<tt>#flush</tt>]
|
14
|
+
# Store and remove the previously specified files.
|
15
|
+
#
|
16
|
+
# [<tt>#stored</tt>]
|
17
|
+
# Array of stored files in the last flush.
|
18
|
+
#
|
19
|
+
# [<tt>#removed</tt>]
|
20
|
+
# Array of removed files in the last flush.
|
21
|
+
module Storage
|
22
|
+
# Stores and removes files to and from the filesystem using
|
23
|
+
# queues.
|
24
|
+
class Filesystem
|
25
|
+
attr_reader :stored, :removed
|
26
|
+
|
27
|
+
# Creates a new Filesystem object.
|
28
|
+
def initialize
|
29
|
+
@queue_for_storage = []
|
30
|
+
@queue_for_removal = []
|
31
|
+
@stored = []
|
32
|
+
@removed = []
|
33
|
+
end
|
34
|
+
|
35
|
+
# Indicates whether or not there are files that need to be
|
36
|
+
# stored or removed.
|
37
|
+
def dirty?
|
38
|
+
@queue_for_storage.any? || @queue_for_removal.any?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Adds the file +source_path+ to the storage queue, that will be
|
42
|
+
# saved in +destination_path+. Note that in order to actually
|
43
|
+
# store the file you need to call #flush.
|
44
|
+
def store(source_path, destination_path)
|
45
|
+
@queue_for_storage << [source_path, destination_path]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Stores the files placed in the storage queue.
|
49
|
+
def store!
|
50
|
+
@queue_for_storage.each do |source_path, destination_path|
|
51
|
+
FileUtils.mkdir_p(File.dirname(destination_path))
|
52
|
+
FileUtils.cp(source_path, destination_path)
|
53
|
+
FileUtils.chmod(0644, destination_path)
|
54
|
+
end
|
55
|
+
@stored = @queue_for_storage.dup
|
56
|
+
@queue_for_storage.clear
|
57
|
+
end
|
58
|
+
|
59
|
+
# Adds the file +path+ to the removal queue. Note that in order
|
60
|
+
# to actually remove the file you need to call #flush.
|
61
|
+
def remove(path)
|
62
|
+
@queue_for_removal << path
|
63
|
+
end
|
64
|
+
|
65
|
+
# Removes the files placed in the removal queue.
|
66
|
+
def remove!
|
67
|
+
@queue_for_removal.each do |path|
|
68
|
+
FileUtils.rm(path) if File.exist?(path)
|
69
|
+
end
|
70
|
+
@removed = @queue_for_removal.dup
|
71
|
+
@queue_for_removal.clear
|
72
|
+
end
|
73
|
+
|
74
|
+
# Removes and then stores the files placed in the removal and
|
75
|
+
# storage queues, repectively.
|
76
|
+
def flush
|
77
|
+
remove!
|
78
|
+
store!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,488 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Adrift
|
4
|
+
shared_examples_for "any attachment" do
|
5
|
+
describe "#dirty?" do
|
6
|
+
context "for a newly instantiated attachment" do
|
7
|
+
it "returns false" do
|
8
|
+
attachment.should_not be_dirty
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "when a file has been assigned" do
|
13
|
+
before { attachment.assign(up_file_double) }
|
14
|
+
|
15
|
+
it "returns true" do
|
16
|
+
attachment.should be_dirty
|
17
|
+
end
|
18
|
+
|
19
|
+
context "and the attachment is saved" do
|
20
|
+
it "returns false" do
|
21
|
+
attachment.save
|
22
|
+
attachment.should_not be_dirty
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "and the attachment is cleared" do
|
27
|
+
it "returns true" do
|
28
|
+
attachment.should be_dirty
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#empty?" do
|
35
|
+
context "when a file has been assigned" do
|
36
|
+
it "returns true" do
|
37
|
+
attachment.assign(up_file_double)
|
38
|
+
attachment.should_not be_empty
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#assign" do
|
44
|
+
let(:up_file) { up_file_double }
|
45
|
+
|
46
|
+
it "updates the attachment's filename in the model" do
|
47
|
+
up_file.stub(:original_filename => 'new_me.png')
|
48
|
+
attachment.assign(up_file)
|
49
|
+
user.avatar_filename.should == 'new_me.png'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "replaces the filename's non alphanumeric characters with '_' (except '.')" do
|
53
|
+
up_file.stub(:original_filename => 'my awesome-avatar!.png')
|
54
|
+
attachment.assign(up_file)
|
55
|
+
attachment.filename.should == 'my_awesome_avatar_.png'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#save" do
|
60
|
+
context "when a file hasn't been assigned" do
|
61
|
+
it "doesn't store anything" do
|
62
|
+
attachment.save
|
63
|
+
attachment.storage.stored.should be_empty
|
64
|
+
end
|
65
|
+
|
66
|
+
it "doesn't remove anything" do
|
67
|
+
attachment.save
|
68
|
+
attachment.storage.removed.should be_empty
|
69
|
+
end
|
70
|
+
|
71
|
+
it "doesn't proccess anything" do
|
72
|
+
attachment.processor.should_not_receive(:process)
|
73
|
+
attachment.save
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "when a file has been assigned" do
|
78
|
+
before do
|
79
|
+
attachment.styles = { :normal => '100x100', :small => '50x50' }
|
80
|
+
attachment.path = '/:class_name/:id/:style/:filename'
|
81
|
+
attachment.assign up_file_double(:original_filename => 'new_me.png', :path => '/tmp/123')
|
82
|
+
end
|
83
|
+
|
84
|
+
it "process the assigned file" do
|
85
|
+
attachment.processor.should_receive(:process).with('/tmp/123', attachment.styles)
|
86
|
+
attachment.save
|
87
|
+
end
|
88
|
+
|
89
|
+
it "stores the assigned file and the processed ones" do
|
90
|
+
attachment.save
|
91
|
+
attachment.storage.stored.should include(['/tmp/123', '/users/1/original/new_me.png'])
|
92
|
+
attachment.storage.stored.should include(['/tmp/normal-123', '/users/1/normal/new_me.png'])
|
93
|
+
attachment.storage.stored.should include(['/tmp/small-123', '/users/1/small/new_me.png'])
|
94
|
+
attachment.storage.stored.size.should == 3
|
95
|
+
end
|
96
|
+
|
97
|
+
context "when an ':original' style has been set" do
|
98
|
+
before do
|
99
|
+
attachment.styles[:original] = '500x500'
|
100
|
+
attachment.save
|
101
|
+
end
|
102
|
+
|
103
|
+
it "doesn't store the uploaded file" do
|
104
|
+
attachment.storage.stored.should_not include(['/tmp/123', '/users/1/original/new_me.png'])
|
105
|
+
end
|
106
|
+
|
107
|
+
it "stores the processed one" do
|
108
|
+
attachment.storage.stored.should include(['/tmp/original-123', '/users/1/original/new_me.png'])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when two files has been asigned without saving" do
|
114
|
+
before do
|
115
|
+
attachment.styles = { :normal => '100x100', :small => '50x50' }
|
116
|
+
attachment.path = '/:class_name/:id/:style/:filename'
|
117
|
+
attachment.assign up_file_double(:original_filename => 'first_me.png', :path => '/tmp/123')
|
118
|
+
attachment.assign up_file_double(:original_filename => 'second_me.png', :path => '/tmp/456')
|
119
|
+
attachment.save
|
120
|
+
end
|
121
|
+
|
122
|
+
it "stores and process only the second assigned file" do
|
123
|
+
attachment.storage.stored.should include(['/tmp/456', '/users/1/original/second_me.png'])
|
124
|
+
attachment.storage.stored.should include(['/tmp/normal-456', '/users/1/normal/second_me.png'])
|
125
|
+
attachment.storage.stored.should include(['/tmp/small-456', '/users/1/small/second_me.png'])
|
126
|
+
attachment.storage.stored.size.should == 3
|
127
|
+
end
|
128
|
+
|
129
|
+
it "doesn't try to remove the first assigned file" do
|
130
|
+
attachment.storage.removed.should_not include('/users/1/original/first_me.png')
|
131
|
+
attachment.storage.removed.should_not include('/users/1/normal/first_me.png')
|
132
|
+
attachment.storage.removed.should_not include('/users/1/small/first_me.png')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "#destroy" do
|
137
|
+
context "when a file hasn't been assigned" do
|
138
|
+
before { attachment.destroy }
|
139
|
+
|
140
|
+
it "doesn't store anything" do
|
141
|
+
attachment.storage.stored.should be_empty
|
142
|
+
end
|
143
|
+
|
144
|
+
it "sets to nil the attachment filename in the model" do
|
145
|
+
user.avatar_filename.should be_nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "when a file has been assigned" do
|
150
|
+
before do
|
151
|
+
attachment.styles = { :normal => '100x100', :small => '50x50' }
|
152
|
+
attachment.path = '/:class_name/:id/:style/:filename'
|
153
|
+
attachment.assign up_file_double(:original_filename => 'new_me.png', :path => '/tmp/123')
|
154
|
+
attachment.destroy
|
155
|
+
end
|
156
|
+
|
157
|
+
it "doesn't remove the assigned file nor its processed files" do
|
158
|
+
attachment.storage.removed.should_not include('/users/1/original/new_me.png')
|
159
|
+
attachment.storage.removed.should_not include('/users/1/normal/new_me.png')
|
160
|
+
attachment.storage.removed.should_not include('/users/1/small/new_me.png')
|
161
|
+
end
|
162
|
+
|
163
|
+
it "doesn't store anything" do
|
164
|
+
attachment.storage.stored.should be_empty
|
165
|
+
end
|
166
|
+
|
167
|
+
it "sets to nil the attachment filename in the model" do
|
168
|
+
user.avatar_filename.should be_nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe Attachment do
|
176
|
+
describe ".reset_default_options" do
|
177
|
+
it "revert changes made to the default options" do
|
178
|
+
original_value = Attachment.default_options[:url]
|
179
|
+
Attachment.default_options[:url] = '/:filename'
|
180
|
+
Attachment.default_options[:url].should_not == original_value
|
181
|
+
Attachment.reset_default_options
|
182
|
+
Attachment.default_options[:url].should == original_value
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe ".new" do
|
187
|
+
let(:user) { user_double }
|
188
|
+
let(:attachment) { Attachment.new(:avatar, user) }
|
189
|
+
|
190
|
+
it "sets the attachment's name" do
|
191
|
+
attachment.name.should == :avatar
|
192
|
+
end
|
193
|
+
|
194
|
+
it "sets the model to which the attachment belongs" do
|
195
|
+
attachment.model.should == user
|
196
|
+
end
|
197
|
+
|
198
|
+
context "when not providing custom options" do
|
199
|
+
it "uses the default options" do
|
200
|
+
default_style_option = Attachment.default_options[:default_style]
|
201
|
+
default_style_option.should_not be_nil
|
202
|
+
attachment.default_style.should == default_style_option
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context "when providing custom options" do
|
207
|
+
let(:styles) { { :small => '50x50', :normal => '100x100' } }
|
208
|
+
let(:attachment) { Attachment.new(:avatar, user, :styles => styles) }
|
209
|
+
|
210
|
+
it "prefers the custom over the default options" do
|
211
|
+
Attachment.default_options[:styles].should_not == styles
|
212
|
+
attachment.styles.should == styles
|
213
|
+
end
|
214
|
+
|
215
|
+
it "uses the default options when there's not a custom one" do
|
216
|
+
Attachment.default_options[:default_style].should_not be_nil
|
217
|
+
attachment.default_style.should == Attachment.default_options[:default_style]
|
218
|
+
end
|
219
|
+
|
220
|
+
it "doesn't complain if it doesn't know the option" do
|
221
|
+
expect {
|
222
|
+
Attachment.new(:avatar, user, :imaginary => 'option')
|
223
|
+
}.to_not raise_error
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe ".config" do
|
229
|
+
after { Attachment.reset_default_options }
|
230
|
+
|
231
|
+
it "changes the default attachment options" do
|
232
|
+
default_url = '/missing.png'
|
233
|
+
Attachment.default_options[:default_url].should_not == default_url
|
234
|
+
Attachment.config { default_url default_url }
|
235
|
+
Attachment.default_options[:default_url].should == default_url
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe Attachment, "when is empty" do
|
241
|
+
let(:user) { user_double(:id => 1, :avatar_filename => nil) }
|
242
|
+
let(:attachment) { Attachment.new(:avatar, user) }
|
243
|
+
|
244
|
+
it_behaves_like "any attachment"
|
245
|
+
|
246
|
+
describe "#empty?" do
|
247
|
+
it "returns true" do
|
248
|
+
attachment.should be_empty
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
describe "#url" do
|
253
|
+
it "returns a default url" do
|
254
|
+
attachment.url.should == '/avatars/original/missing.png'
|
255
|
+
end
|
256
|
+
|
257
|
+
it "builds the default url from a pattern if there's one" do
|
258
|
+
attachment.default_url = '/images/:class_name/missing.png'
|
259
|
+
attachment.url.should == '/images/users/missing.png'
|
260
|
+
end
|
261
|
+
|
262
|
+
it "accepts a style" do
|
263
|
+
attachment.default_url = '/images/:class_name/missing_:style.png'
|
264
|
+
attachment.url(:small).should == '/images/users/missing_small.png'
|
265
|
+
end
|
266
|
+
|
267
|
+
it "uses a default style if there isn't one" do
|
268
|
+
attachment.default_style = :normal
|
269
|
+
attachment.default_url = '/images/:class_name/missing_:style.png'
|
270
|
+
attachment.url.should == '/images/users/missing_normal.png'
|
271
|
+
end
|
272
|
+
|
273
|
+
it "assumes an ':original' default style" do
|
274
|
+
attachment.default_url = '/images/:class_name/missing_:style.png'
|
275
|
+
attachment.url.should == '/images/users/missing_original.png'
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
describe "#path" do
|
280
|
+
it "returns nil" do
|
281
|
+
attachment.path.should be_nil
|
282
|
+
end
|
283
|
+
|
284
|
+
it "accepts a style" do
|
285
|
+
attachment.path(:small).should be_nil
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
describe "#save" do
|
290
|
+
context "when a file has been assigned" do
|
291
|
+
before do
|
292
|
+
attachment.assign(up_file_double)
|
293
|
+
attachment.save
|
294
|
+
end
|
295
|
+
|
296
|
+
it "doesn't remove anything" do
|
297
|
+
attachment.storage.removed.should be_empty
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
context "when a file has been assigned and then cleared" do
|
302
|
+
before do
|
303
|
+
attachment.assign(up_file_double)
|
304
|
+
attachment.clear
|
305
|
+
attachment.save
|
306
|
+
end
|
307
|
+
|
308
|
+
it "doesn't store anything" do
|
309
|
+
attachment.storage.stored.should be_empty
|
310
|
+
end
|
311
|
+
|
312
|
+
it "doesn't remove anything" do
|
313
|
+
attachment.storage.removed.should be_empty
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
describe "#destroy" do
|
319
|
+
it "doesn't remove anything" do
|
320
|
+
attachment.destroy
|
321
|
+
attachment.storage.removed.should be_empty
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
describe Attachment, "when isn't empty" do
|
327
|
+
let(:user) { user_double(:id => 1, :avatar_filename => 'me.png') }
|
328
|
+
let(:attachment) { Attachment.new(:avatar, user) }
|
329
|
+
|
330
|
+
it_behaves_like "any attachment"
|
331
|
+
|
332
|
+
describe "#dirty?" do
|
333
|
+
context "when the attachment is cleared" do
|
334
|
+
before { attachment.clear }
|
335
|
+
|
336
|
+
it "returns true" do
|
337
|
+
attachment.should be_dirty
|
338
|
+
end
|
339
|
+
|
340
|
+
context "and the attachment is saved" do
|
341
|
+
it "returns false" do
|
342
|
+
attachment.save
|
343
|
+
attachment.should_not be_dirty
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe "#empty?" do
|
350
|
+
it "returns false" do
|
351
|
+
attachment.should_not be_empty
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
describe "#url" do
|
356
|
+
it "builds its url from a default pattern" do
|
357
|
+
attachment.url.should == '/system/avatars/1/original/me.png'
|
358
|
+
end
|
359
|
+
|
360
|
+
it "builds its url from a pattern if there's one" do
|
361
|
+
attachment.url = '/:class_name/:id/:attachment/:filename'
|
362
|
+
attachment.url.should == '/users/1/avatars/me.png'
|
363
|
+
end
|
364
|
+
|
365
|
+
it "accepts a style" do
|
366
|
+
attachment.url = '/:class_name/:id/:attachment/:style/:filename'
|
367
|
+
attachment.url(:small).should == '/users/1/avatars/small/me.png'
|
368
|
+
end
|
369
|
+
|
370
|
+
it "uses a default style if there isn't one" do
|
371
|
+
attachment.default_style = :normal
|
372
|
+
attachment.url = '/:class_name/:id/:attachment/:style/:filename'
|
373
|
+
attachment.url.should == '/users/1/avatars/normal/me.png'
|
374
|
+
end
|
375
|
+
|
376
|
+
it "assumes an ':original' default style" do
|
377
|
+
attachment.url = '/:class_name/:id/:attachment/:style/:filename'
|
378
|
+
attachment.url.should == '/users/1/avatars/original/me.png'
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
describe "#path" do
|
383
|
+
it "builds its path from the url by default" do
|
384
|
+
attachment.stub(:url => '/users/1/avatars/me.png')
|
385
|
+
attachment.path.should == './public/users/1/avatars/me.png'
|
386
|
+
end
|
387
|
+
|
388
|
+
it "builds its path from a pattern if there's one" do
|
389
|
+
attachment.path = './:class_name/:id/:attachment/:filename'
|
390
|
+
attachment.path.should == './users/1/avatars/me.png'
|
391
|
+
end
|
392
|
+
|
393
|
+
it "accepts a style" do
|
394
|
+
attachment.path = './:class_name/:id/:attachment/:style/:filename'
|
395
|
+
attachment.path(:small).should == './users/1/avatars/small/me.png'
|
396
|
+
end
|
397
|
+
|
398
|
+
it "uses a default style if there isn't one" do
|
399
|
+
attachment.default_style = :normal
|
400
|
+
attachment.path = './:class_name/:id/:attachment/:style/:filename'
|
401
|
+
attachment.path.should == './users/1/avatars/normal/me.png'
|
402
|
+
end
|
403
|
+
|
404
|
+
it "assumes an ':original' default style" do
|
405
|
+
attachment.path = './:class_name/:id/:attachment/:style/:filename'
|
406
|
+
attachment.path.should == './users/1/avatars/original/me.png'
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
describe "#clear" do
|
411
|
+
it "sets to nil the attachment filename in the model" do
|
412
|
+
attachment.clear
|
413
|
+
attachment.filename.should be_nil
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
describe "#save" do
|
418
|
+
context "when a file has been assigned" do
|
419
|
+
before do
|
420
|
+
attachment.styles = { :normal => '100x100', :small => '50x50' }
|
421
|
+
attachment.path = '/:class_name/:id/:style/:filename'
|
422
|
+
attachment.assign up_file_double(:original_filename => 'new_me.png', :path => '/tmp/123')
|
423
|
+
attachment.save
|
424
|
+
end
|
425
|
+
|
426
|
+
it "removes the previous files for each style" do
|
427
|
+
attachment.storage.removed.should include('/users/1/original/me.png')
|
428
|
+
attachment.storage.removed.should include('/users/1/normal/me.png')
|
429
|
+
attachment.storage.removed.should include('/users/1/small/me.png')
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
context "when the attachment has been cleared" do
|
434
|
+
let(:attachment) { Attachment.new(:avatar, user) }
|
435
|
+
before do
|
436
|
+
attachment.styles = { :normal => '100x100', :small => '50x50' }
|
437
|
+
attachment.path = '/:class_name/:id/:style/:filename'
|
438
|
+
attachment.clear
|
439
|
+
attachment.save
|
440
|
+
end
|
441
|
+
|
442
|
+
it "doesn't store anything" do
|
443
|
+
attachment.storage.stored.should be_empty
|
444
|
+
end
|
445
|
+
|
446
|
+
it "removes the files for each style" do
|
447
|
+
attachment.storage.removed.should include('/users/1/original/me.png')
|
448
|
+
attachment.storage.removed.should include('/users/1/normal/me.png')
|
449
|
+
attachment.storage.removed.should include('/users/1/small/me.png')
|
450
|
+
attachment.storage.removed.size.should == 3
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
describe "#destroy" do
|
456
|
+
context "when a file hasn't been assigned" do
|
457
|
+
before do
|
458
|
+
attachment.styles = { :normal => '100x100', :small => '50x50' }
|
459
|
+
attachment.path = '/:class_name/:id/:style/:filename'
|
460
|
+
attachment.destroy
|
461
|
+
end
|
462
|
+
|
463
|
+
it "removes the files for each style" do
|
464
|
+
attachment.storage.removed.should include('/users/1/original/me.png')
|
465
|
+
attachment.storage.removed.should include('/users/1/normal/me.png')
|
466
|
+
attachment.storage.removed.should include('/users/1/small/me.png')
|
467
|
+
attachment.storage.removed.size.should == 3
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
context "when a file has been assigned" do
|
472
|
+
before do
|
473
|
+
attachment.styles = { :normal => '100x100', :small => '50x50' }
|
474
|
+
attachment.path = '/:class_name/:id/:style/:filename'
|
475
|
+
attachment.assign up_file_double(:original_filename => 'new_me.png', :path => '/tmp/123')
|
476
|
+
attachment.destroy
|
477
|
+
end
|
478
|
+
|
479
|
+
it "removes the files for every style" do
|
480
|
+
attachment.storage.removed.should include('/users/1/original/me.png')
|
481
|
+
attachment.storage.removed.should include('/users/1/normal/me.png')
|
482
|
+
attachment.storage.removed.should include('/users/1/small/me.png')
|
483
|
+
attachment.storage.removed.size.should == 3
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|