bulldog 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 +2 -0
- data/DESCRIPTION.txt +3 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/bulldog.gemspec +157 -0
- data/lib/bulldog.rb +95 -0
- data/lib/bulldog/attachment.rb +49 -0
- data/lib/bulldog/attachment/base.rb +167 -0
- data/lib/bulldog/attachment/has_dimensions.rb +94 -0
- data/lib/bulldog/attachment/image.rb +63 -0
- data/lib/bulldog/attachment/maybe.rb +229 -0
- data/lib/bulldog/attachment/none.rb +37 -0
- data/lib/bulldog/attachment/pdf.rb +63 -0
- data/lib/bulldog/attachment/unknown.rb +11 -0
- data/lib/bulldog/attachment/video.rb +143 -0
- data/lib/bulldog/error.rb +5 -0
- data/lib/bulldog/has_attachment.rb +214 -0
- data/lib/bulldog/interpolation.rb +73 -0
- data/lib/bulldog/missing_file.rb +12 -0
- data/lib/bulldog/processor.rb +5 -0
- data/lib/bulldog/processor/argument_tree.rb +116 -0
- data/lib/bulldog/processor/base.rb +124 -0
- data/lib/bulldog/processor/ffmpeg.rb +172 -0
- data/lib/bulldog/processor/image_magick.rb +134 -0
- data/lib/bulldog/processor/one_shot.rb +19 -0
- data/lib/bulldog/reflection.rb +234 -0
- data/lib/bulldog/saved_file.rb +19 -0
- data/lib/bulldog/stream.rb +186 -0
- data/lib/bulldog/style.rb +38 -0
- data/lib/bulldog/style_set.rb +101 -0
- data/lib/bulldog/tempfile.rb +28 -0
- data/lib/bulldog/util.rb +92 -0
- data/lib/bulldog/validations.rb +68 -0
- data/lib/bulldog/vector2.rb +18 -0
- data/rails/init.rb +9 -0
- data/script/console +8 -0
- data/spec/data/empty.txt +0 -0
- data/spec/data/test.jpg +0 -0
- data/spec/data/test.mov +0 -0
- data/spec/data/test.pdf +0 -0
- data/spec/data/test.png +0 -0
- data/spec/data/test2.jpg +0 -0
- data/spec/helpers/image_creation.rb +8 -0
- data/spec/helpers/temporary_directory.rb +25 -0
- data/spec/helpers/temporary_models.rb +76 -0
- data/spec/helpers/temporary_values.rb +102 -0
- data/spec/helpers/test_upload_files.rb +108 -0
- data/spec/helpers/time_travel.rb +20 -0
- data/spec/integration/data/test.jpg +0 -0
- data/spec/integration/lifecycle_hooks_spec.rb +213 -0
- data/spec/integration/processing_image_attachments.rb +72 -0
- data/spec/integration/processing_video_attachments_spec.rb +82 -0
- data/spec/integration/saving_an_attachment_spec.rb +31 -0
- data/spec/matchers/file_operations.rb +159 -0
- data/spec/spec_helper.rb +76 -0
- data/spec/unit/attachment/base_spec.rb +311 -0
- data/spec/unit/attachment/image_spec.rb +128 -0
- data/spec/unit/attachment/maybe_spec.rb +126 -0
- data/spec/unit/attachment/pdf_spec.rb +137 -0
- data/spec/unit/attachment/video_spec.rb +176 -0
- data/spec/unit/attachment_spec.rb +61 -0
- data/spec/unit/has_attachment_spec.rb +700 -0
- data/spec/unit/interpolation_spec.rb +108 -0
- data/spec/unit/processor/argument_tree_spec.rb +159 -0
- data/spec/unit/processor/ffmpeg_spec.rb +467 -0
- data/spec/unit/processor/image_magick_spec.rb +260 -0
- data/spec/unit/processor/one_shot_spec.rb +70 -0
- data/spec/unit/reflection_spec.rb +338 -0
- data/spec/unit/stream_spec.rb +234 -0
- data/spec/unit/style_set_spec.rb +44 -0
- data/spec/unit/style_spec.rb +51 -0
- data/spec/unit/validations_spec.rb +491 -0
- data/spec/unit/vector2_spec.rb +27 -0
- data/tasks/bulldog_tasks.rake +4 -0
- metadata +193 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Processing video attachments" do
|
4
|
+
use_model_class(:Thing,
|
5
|
+
:video_file_name => :string,
|
6
|
+
:still_frame_file_name => :string)
|
7
|
+
|
8
|
+
before do
|
9
|
+
spec = self
|
10
|
+
Thing.class_eval do
|
11
|
+
has_attachment :video do
|
12
|
+
style :encoded, :format => 'ogg', :size => '640x360',
|
13
|
+
:video => 'libtheora 24fps', :audio => 'libvorbis 44100Hz 128kbps',
|
14
|
+
:pixel_format => 'yuv420p'
|
15
|
+
style :frame, :format => 'jpg'
|
16
|
+
path "#{spec.temporary_directory}/:id.:style.:extension"
|
17
|
+
|
18
|
+
# TODO: fix problem with :after => save running when the
|
19
|
+
# attachment wasn't changed, and process on after save.
|
20
|
+
process :on => :process, :styles => [:encoded]
|
21
|
+
process :on => :process, :styles => [:frame] do
|
22
|
+
record_frame(:assign_to => :still_frame)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
has_attachment :still_frame do
|
27
|
+
style :thumbnail, :format => 'png'
|
28
|
+
path "#{spec.temporary_directory}/:id-still_frame.:style.:extension"
|
29
|
+
process :on => :process
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@file = open("#{ROOT}/spec/data/test.mov")
|
34
|
+
@thing = Thing.new(:video => @file)
|
35
|
+
end
|
36
|
+
|
37
|
+
after do
|
38
|
+
@file.close
|
39
|
+
end
|
40
|
+
|
41
|
+
def original_video_path
|
42
|
+
"#{temporary_directory}/#{@thing.id}.original.mov"
|
43
|
+
end
|
44
|
+
|
45
|
+
def encoded_video_path
|
46
|
+
"#{temporary_directory}/#{@thing.id}.encoded.ogg"
|
47
|
+
end
|
48
|
+
|
49
|
+
def original_frame_path
|
50
|
+
"#{temporary_directory}/#{@thing.id}-still_frame.original.jpg"
|
51
|
+
end
|
52
|
+
|
53
|
+
def frame_thumbnail_path
|
54
|
+
"#{temporary_directory}/#{@thing.id}-still_frame.thumbnail.png"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should not yet have a still frame assigned" do
|
58
|
+
@thing.still_frame.should be_blank
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "when the record is saved" do
|
62
|
+
before do
|
63
|
+
@thing.save
|
64
|
+
@thing.process_attachment(:video, :process)
|
65
|
+
@thing.process_attachment(:still_frame, :process)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should encode the video" do
|
69
|
+
File.should exist(original_video_path)
|
70
|
+
File.should exist(encoded_video_path)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should assign to the still frame" do
|
74
|
+
@thing.still_frame.should_not be_blank
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should create the thumbnail" do
|
78
|
+
File.should exist(original_frame_path)
|
79
|
+
File.should exist(frame_thumbnail_path)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Saving an attachment" do
|
4
|
+
use_model_class(:Thing, :value => :integer)
|
5
|
+
|
6
|
+
#
|
7
|
+
# The list of files this process has open.
|
8
|
+
#
|
9
|
+
def open_files
|
10
|
+
`lsof -p #{Process.pid} -F n`.split(/\n/).sort
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not leave any file handles left open" do
|
14
|
+
tmp = temporary_directory
|
15
|
+
Thing.has_attachment :photo do
|
16
|
+
path "#{tmp}/:style.png"
|
17
|
+
style :small, :size => '10x10'
|
18
|
+
process :after => :save do
|
19
|
+
resize
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
path = create_image("#{temporary_directory}/tmp.jpg")
|
24
|
+
open(path) do |file|
|
25
|
+
@initial_open_files = open_files
|
26
|
+
thing = Thing.new(:photo => file)
|
27
|
+
thing.save.should be_true
|
28
|
+
open_files.should == @initial_open_files
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Matchers
|
2
|
+
#
|
3
|
+
# Matches if the Proc object creates the file at the given path.
|
4
|
+
# The file also must not exist to begin with.
|
5
|
+
#
|
6
|
+
def create_file(path)
|
7
|
+
CreateFile.new(path)
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# Matches if the Proc deletes the file at the given path. The file
|
12
|
+
# also must exist at the start.
|
13
|
+
#
|
14
|
+
def delete_file(path)
|
15
|
+
DeleteFile.new(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Matches if the Proc modifies the file at the given path. Checks
|
20
|
+
# by examining the mtime of the file.
|
21
|
+
#
|
22
|
+
# In order to avoid false-positives, the mtime of the file is set to
|
23
|
+
# a temporary value during the block.
|
24
|
+
#
|
25
|
+
def modify_file(path)
|
26
|
+
ModifyFile.new(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
class FileOperation
|
30
|
+
def initialize(path)
|
31
|
+
@path = path
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :path
|
35
|
+
end
|
36
|
+
|
37
|
+
class CreateFile < FileOperation
|
38
|
+
def matches?(proc)
|
39
|
+
@file_already_exists = File.exist?(path) and
|
40
|
+
return false
|
41
|
+
proc.call
|
42
|
+
File.exist?(path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def does_not_match?(proc)
|
46
|
+
@file_already_exists = File.exist?(path) and
|
47
|
+
return false
|
48
|
+
proc.call
|
49
|
+
!File.exist?(path)
|
50
|
+
end
|
51
|
+
|
52
|
+
def failure_message_for_should
|
53
|
+
if @file_already_exists
|
54
|
+
"`#{path}' already exists"
|
55
|
+
else
|
56
|
+
"expected block to create `#{path}'"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def failure_message_for_should_not
|
61
|
+
if @file_already_exists
|
62
|
+
"`#{path}' already exists"
|
63
|
+
else
|
64
|
+
"expected block to not create `#{path}'"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class DeleteFile < FileOperation
|
70
|
+
def matches?(proc)
|
71
|
+
@file_did_not_exist = !File.exist?(path) and
|
72
|
+
return false
|
73
|
+
proc.call
|
74
|
+
!File.exist?(path)
|
75
|
+
end
|
76
|
+
|
77
|
+
def does_not_match?(proc)
|
78
|
+
@file_did_not_exist = !File.exist?(path) and
|
79
|
+
return false
|
80
|
+
proc.call
|
81
|
+
File.exist?(path)
|
82
|
+
end
|
83
|
+
|
84
|
+
def failure_message_for_should
|
85
|
+
if @file_did_not_exist
|
86
|
+
"`#{path}' does not exist"
|
87
|
+
else
|
88
|
+
"expected block to delete `#{path}'"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def failure_message_for_should_not
|
93
|
+
if @file_did_not_exist
|
94
|
+
"`#{path}' does not exist"
|
95
|
+
else
|
96
|
+
"expected block to not delete `#{path}'"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class ModifyFile < FileOperation
|
102
|
+
def matches?(proc)
|
103
|
+
@file_did_not_exist = !File.exist?(path) and
|
104
|
+
return false
|
105
|
+
modified?(proc)
|
106
|
+
end
|
107
|
+
|
108
|
+
def does_not_match?(proc)
|
109
|
+
@file_did_not_exist = !File.exist?(path) and
|
110
|
+
return false
|
111
|
+
!modified?(proc)
|
112
|
+
end
|
113
|
+
|
114
|
+
def failure_message_for_should
|
115
|
+
if @file_did_not_exist
|
116
|
+
"`#{path}' does not exist"
|
117
|
+
else
|
118
|
+
"expected block to modify `#{path}'"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def failure_message_for_should_not
|
123
|
+
if @file_did_not_exist
|
124
|
+
"`#{path}' does not exist"
|
125
|
+
else
|
126
|
+
"expected block to not modify `#{path}'"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private # -------------------------------------------------------
|
131
|
+
|
132
|
+
def modified?(proc)
|
133
|
+
temporarily_setting_mtime_to(1.minute.ago) do
|
134
|
+
start_mtime = mtime
|
135
|
+
proc.call
|
136
|
+
end_mtime = mtime
|
137
|
+
start_mtime.to_i != end_mtime.to_i
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def temporarily_setting_mtime_to(time)
|
142
|
+
original_time = mtime
|
143
|
+
set_mtime_to(time)
|
144
|
+
yield
|
145
|
+
ensure
|
146
|
+
set_mtime_to(original_time)
|
147
|
+
end
|
148
|
+
|
149
|
+
def mtime
|
150
|
+
File.mtime(path)
|
151
|
+
end
|
152
|
+
|
153
|
+
def set_mtime_to(time)
|
154
|
+
atime = File.atime(path)
|
155
|
+
File.utime(atime, time, path)
|
156
|
+
time
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
require 'spec'
|
5
|
+
require 'rspec_outlines'
|
6
|
+
require 'mocha'
|
7
|
+
require 'tempfile'
|
8
|
+
require 'active_record'
|
9
|
+
require 'action_controller'
|
10
|
+
|
11
|
+
require 'bulldog'
|
12
|
+
include Bulldog
|
13
|
+
|
14
|
+
ROOT = File.dirname( File.dirname(__FILE__) )
|
15
|
+
|
16
|
+
require 'helpers/time_travel'
|
17
|
+
require 'helpers/temporary_models'
|
18
|
+
require 'helpers/temporary_values'
|
19
|
+
require 'helpers/temporary_directory'
|
20
|
+
require 'helpers/test_upload_files'
|
21
|
+
require 'helpers/image_creation'
|
22
|
+
require 'matchers/file_operations'
|
23
|
+
|
24
|
+
class Time
|
25
|
+
#
|
26
|
+
# Return a new Time object with subsecond components dropped.
|
27
|
+
#
|
28
|
+
# This is useful for testing Time values that have been roundtripped
|
29
|
+
# through the database, as not all databases store subsecond
|
30
|
+
# precision.
|
31
|
+
#
|
32
|
+
def drop_subseconds
|
33
|
+
self.class.mktime(year, month, day, hour, min, sec)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module SpecHelper
|
38
|
+
def self.included(mod)
|
39
|
+
mod.use_temporary_attribute_value Bulldog, :default_url_template do
|
40
|
+
":class/:id.:style"
|
41
|
+
end
|
42
|
+
mod.use_temporary_attribute_value Bulldog, :default_path_template do
|
43
|
+
"#{temporary_directory}/attachments/:class/:id.:style"
|
44
|
+
end
|
45
|
+
|
46
|
+
mod.use_temporary_attribute_value Bulldog, :logger do
|
47
|
+
buffer = StringIO.new
|
48
|
+
logger = Logger.new(buffer)
|
49
|
+
(class << logger; self; end).send(:define_method, :content) do
|
50
|
+
buffer.string
|
51
|
+
end
|
52
|
+
logger
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Stub out all system calls. Pretend they were successful.
|
58
|
+
#
|
59
|
+
def stub_system_calls
|
60
|
+
Kernel.stubs(:system).returns(true)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Spec::Runner.configure do |config|
|
65
|
+
config.mock_with :mocha
|
66
|
+
config.include TimeTravel
|
67
|
+
config.include TemporaryModels
|
68
|
+
config.include TemporaryValues
|
69
|
+
config.include TemporaryDirectory
|
70
|
+
config.include TestUploadFiles
|
71
|
+
config.include ImageCreation
|
72
|
+
config.include Matchers
|
73
|
+
config.include SpecHelper
|
74
|
+
end
|
75
|
+
|
76
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
@@ -0,0 +1,311 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Attachment::Base do
|
4
|
+
def self.configure_attachment(&block)
|
5
|
+
before do
|
6
|
+
spec = self
|
7
|
+
Thing.has_attachment :photo do
|
8
|
+
instance_exec(spec, &block)
|
9
|
+
end
|
10
|
+
@thing = Thing.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#path" do
|
15
|
+
use_model_class(:Thing, :photo_file_name => :string)
|
16
|
+
|
17
|
+
configure_attachment do |spec|
|
18
|
+
path "#{spec.temporary_directory}/:style.jpg"
|
19
|
+
style :small, {}
|
20
|
+
style :png, :format => :png
|
21
|
+
end
|
22
|
+
|
23
|
+
def original_path
|
24
|
+
"#{temporary_directory}/original.jpg"
|
25
|
+
end
|
26
|
+
|
27
|
+
def small_path
|
28
|
+
"#{temporary_directory}/small.jpg"
|
29
|
+
end
|
30
|
+
|
31
|
+
def png_path
|
32
|
+
"#{temporary_directory}/png.png"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return the path of the given style, interpolated from the path template" do
|
36
|
+
@thing.photo = test_image_file
|
37
|
+
@thing.stubs(:id).returns(5)
|
38
|
+
@thing.photo.path(:original).should == original_path
|
39
|
+
@thing.photo.path(:small).should == small_path
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "when the :extension interpolation key is used" do
|
43
|
+
before do
|
44
|
+
spec = self
|
45
|
+
Thing.attachment_reflections[:photo].configure do
|
46
|
+
path "#{spec.temporary_directory}/:style.:extension"
|
47
|
+
end
|
48
|
+
@thing.photo = test_image_file
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should use the extension of the original file for the original style" do
|
52
|
+
@thing.photo.path(:original).should == "#{temporary_directory}/original.jpg"
|
53
|
+
end
|
54
|
+
it "should use the format of the output file for other styles" do
|
55
|
+
@thing.photo.path(:png).should == "#{temporary_directory}/png.png"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "when the :extension interpolation key is not used" do
|
60
|
+
before do
|
61
|
+
spec = self
|
62
|
+
Thing.attachment_reflections[:photo].configure do
|
63
|
+
path "#{spec.temporary_directory}/:style.xyz"
|
64
|
+
end
|
65
|
+
@thing.photo = test_image_file
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should use the extension of the path template for the original style" do
|
69
|
+
@thing.photo.path(:original).should == "#{temporary_directory}/original.xyz"
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should use the extension of the path template for other styles" do
|
73
|
+
@thing.photo.path(:png).should == "#{temporary_directory}/png.xyz"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "when no style is given" do
|
78
|
+
configure_attachment do
|
79
|
+
path "/tmp/:style.jpg"
|
80
|
+
style :small, {}
|
81
|
+
default_style :small
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should default to the attachment's default style" do
|
85
|
+
@thing.photo = test_image_file
|
86
|
+
@thing.photo.path.should == "/tmp/small.jpg"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#url" do
|
92
|
+
use_model_class(:Thing, :photo_file_name => :string)
|
93
|
+
|
94
|
+
configure_attachment do
|
95
|
+
path "/tmp/:style.jpg"
|
96
|
+
url "/assets/:style.jpg"
|
97
|
+
style :small
|
98
|
+
style :png, :format => :png
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should return the url of the given style, interpolated from the url template" do
|
102
|
+
@thing.photo = test_image_file
|
103
|
+
@thing.photo.url(:original).should == "/assets/original.jpg"
|
104
|
+
@thing.photo.url(:small).should == "/assets/small.jpg"
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "when the :extension interpolation key is used" do
|
108
|
+
before do
|
109
|
+
spec = self
|
110
|
+
Thing.attachment_reflections[:photo].configure do
|
111
|
+
path "/tmp/:style.:extension"
|
112
|
+
url "/assets/:style.:extension"
|
113
|
+
end
|
114
|
+
@thing.photo = test_image_file
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should use the extension of the original file for the original style" do
|
118
|
+
@thing.photo.url(:original).should == "/assets/original.jpg"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should use the format of the output file for the other styles" do
|
122
|
+
@thing.photo.url(:png).should == "/assets/png.png"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "when the :extension interpolation key is not used" do
|
127
|
+
before do
|
128
|
+
spec = self
|
129
|
+
Thing.attachment_reflections[:photo].configure do
|
130
|
+
path "/tmp/:style.xyz"
|
131
|
+
url "/assets/:style.xyz"
|
132
|
+
end
|
133
|
+
@thing.photo = test_image_file
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should use the extension of the url template for the original style" do
|
137
|
+
@thing.photo.url(:original).should == "/assets/original.xyz"
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should use the extension of the url template for the other styles" do
|
141
|
+
@thing.photo.url(:png).should == "/assets/png.xyz"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "when no style is given" do
|
146
|
+
configure_attachment do
|
147
|
+
url "/assets/:style.jpg"
|
148
|
+
style :small, {}
|
149
|
+
default_style :small
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should default to the attachment's default style" do
|
153
|
+
@thing.photo = test_image_file
|
154
|
+
@thing.photo.url.should == "/assets/small.jpg"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "#file_size" do
|
160
|
+
use_model_class(:Thing)
|
161
|
+
|
162
|
+
configure_attachment do |spec|
|
163
|
+
path "#{spec.temporary_directory}/:id.:style.jpg"
|
164
|
+
style :small, {}
|
165
|
+
end
|
166
|
+
|
167
|
+
def original_path
|
168
|
+
"#{temporary_directory}/#{@thing.id}.original.jpg"
|
169
|
+
end
|
170
|
+
|
171
|
+
def with_temporary_file(path, content)
|
172
|
+
FileUtils.mkdir_p File.dirname(path)
|
173
|
+
open(path, 'w'){|f| f.print '...'}
|
174
|
+
begin
|
175
|
+
yield path
|
176
|
+
ensure
|
177
|
+
File.delete(path)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
before do
|
182
|
+
@thing = Thing.new(:photo => test_image_file)
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should return the size of the file" do
|
186
|
+
@thing.photo.file_size.should == File.size(test_image_path)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "#file_name" do
|
191
|
+
use_model_class(:Thing, :photo_file_name => :string)
|
192
|
+
|
193
|
+
configure_attachment do |spec|
|
194
|
+
path "#{spec.temporary_directory}/:id.:style.jpg"
|
195
|
+
style :small, {}
|
196
|
+
store_attributes :file_name
|
197
|
+
end
|
198
|
+
|
199
|
+
def original_path
|
200
|
+
"#{temporary_directory}/#{@thing.id}.original.jpg"
|
201
|
+
end
|
202
|
+
|
203
|
+
def with_temporary_file(path, content)
|
204
|
+
FileUtils.mkdir_p File.dirname(path)
|
205
|
+
open(path, 'w'){|f| f.print '...'}
|
206
|
+
begin
|
207
|
+
yield path
|
208
|
+
ensure
|
209
|
+
File.delete(path)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
before do
|
214
|
+
@thing = Thing.new(:photo => test_image_file)
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should return the original base name of the file" do
|
218
|
+
@thing.photo.file_name.should == File.basename(test_image_path)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe "#process" do
|
223
|
+
use_model_class(:Thing)
|
224
|
+
|
225
|
+
use_temporary_constant_value Processor, :Test do
|
226
|
+
Class.new(Processor::Base)
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should use the default processor if no processor was specified" do
|
230
|
+
context = nil
|
231
|
+
Thing.has_attachment :photo do
|
232
|
+
style :normal
|
233
|
+
process :on => :test_event do
|
234
|
+
context = self
|
235
|
+
end
|
236
|
+
end
|
237
|
+
thing = Thing.new(:photo => test_image_file)
|
238
|
+
thing.photo.stubs(:default_processor_type).returns(:test)
|
239
|
+
thing.photo.process(:test_event)
|
240
|
+
context.should be_a(Processor::Test)
|
241
|
+
end
|
242
|
+
|
243
|
+
it "should use the configured processor if one was specified" do
|
244
|
+
context = nil
|
245
|
+
Thing.has_attachment :photo do
|
246
|
+
style :normal
|
247
|
+
process :on => :test_event, :with => :test do
|
248
|
+
context = self
|
249
|
+
end
|
250
|
+
end
|
251
|
+
thing = Thing.new(:photo => test_image_file)
|
252
|
+
thing.photo.process(:test_event)
|
253
|
+
context.should be_a(Processor::Test)
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should not run any processors if no attachment is set" do
|
257
|
+
run = false
|
258
|
+
Thing.has_attachment :photo do
|
259
|
+
style :normal
|
260
|
+
process :on => :test_event, :with => :test do
|
261
|
+
run = true
|
262
|
+
end
|
263
|
+
end
|
264
|
+
thing = Thing.new(:photo => nil)
|
265
|
+
thing.photo.process(:test_event)
|
266
|
+
run.should be_false
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should run the processors only for the specified styles" do
|
270
|
+
styles = nil
|
271
|
+
Thing.has_attachment :photo do
|
272
|
+
style :small, :size => '10x10'
|
273
|
+
style :large, :size => '1000x1000'
|
274
|
+
process :on => :test_event, :styles => [:small], :with => :test do
|
275
|
+
styles = self.styles
|
276
|
+
end
|
277
|
+
end
|
278
|
+
thing = Thing.new(:photo => test_image_file)
|
279
|
+
thing.photo.process(:test_event)
|
280
|
+
styles.should be_a(StyleSet)
|
281
|
+
styles.map(&:name).should == [:small]
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
describe "storable attributes" do
|
286
|
+
use_model_class(:Thing,
|
287
|
+
:photo_file_name => :string,
|
288
|
+
:photo_file_size => :integer,
|
289
|
+
:photo_content_type => :string)
|
290
|
+
|
291
|
+
before do
|
292
|
+
Thing.has_attachment :photo
|
293
|
+
@thing = Thing.new(:photo => uploaded_file_with_content('test.jpg', "\xff\xd8"))
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should set the stored attributes on assignment" do
|
297
|
+
@thing.photo_file_name.should == 'test.jpg'
|
298
|
+
@thing.photo_file_size.should == 2
|
299
|
+
@thing.photo_content_type.should =~ /image\/jpeg/
|
300
|
+
end
|
301
|
+
|
302
|
+
it "should successfully roundtrip the stored attributes" do
|
303
|
+
warp_ahead 1.minute
|
304
|
+
@thing.save
|
305
|
+
@thing = Thing.find(@thing.id)
|
306
|
+
@thing.photo_file_name.should == 'test.jpg'
|
307
|
+
@thing.photo_file_size.should == 2
|
308
|
+
@thing.photo_content_type.should =~ /image\/jpeg/
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|