bulldog 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/.gitignore +2 -0
  2. data/DESCRIPTION.txt +3 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +18 -0
  5. data/Rakefile +64 -0
  6. data/VERSION +1 -0
  7. data/bulldog.gemspec +157 -0
  8. data/lib/bulldog.rb +95 -0
  9. data/lib/bulldog/attachment.rb +49 -0
  10. data/lib/bulldog/attachment/base.rb +167 -0
  11. data/lib/bulldog/attachment/has_dimensions.rb +94 -0
  12. data/lib/bulldog/attachment/image.rb +63 -0
  13. data/lib/bulldog/attachment/maybe.rb +229 -0
  14. data/lib/bulldog/attachment/none.rb +37 -0
  15. data/lib/bulldog/attachment/pdf.rb +63 -0
  16. data/lib/bulldog/attachment/unknown.rb +11 -0
  17. data/lib/bulldog/attachment/video.rb +143 -0
  18. data/lib/bulldog/error.rb +5 -0
  19. data/lib/bulldog/has_attachment.rb +214 -0
  20. data/lib/bulldog/interpolation.rb +73 -0
  21. data/lib/bulldog/missing_file.rb +12 -0
  22. data/lib/bulldog/processor.rb +5 -0
  23. data/lib/bulldog/processor/argument_tree.rb +116 -0
  24. data/lib/bulldog/processor/base.rb +124 -0
  25. data/lib/bulldog/processor/ffmpeg.rb +172 -0
  26. data/lib/bulldog/processor/image_magick.rb +134 -0
  27. data/lib/bulldog/processor/one_shot.rb +19 -0
  28. data/lib/bulldog/reflection.rb +234 -0
  29. data/lib/bulldog/saved_file.rb +19 -0
  30. data/lib/bulldog/stream.rb +186 -0
  31. data/lib/bulldog/style.rb +38 -0
  32. data/lib/bulldog/style_set.rb +101 -0
  33. data/lib/bulldog/tempfile.rb +28 -0
  34. data/lib/bulldog/util.rb +92 -0
  35. data/lib/bulldog/validations.rb +68 -0
  36. data/lib/bulldog/vector2.rb +18 -0
  37. data/rails/init.rb +9 -0
  38. data/script/console +8 -0
  39. data/spec/data/empty.txt +0 -0
  40. data/spec/data/test.jpg +0 -0
  41. data/spec/data/test.mov +0 -0
  42. data/spec/data/test.pdf +0 -0
  43. data/spec/data/test.png +0 -0
  44. data/spec/data/test2.jpg +0 -0
  45. data/spec/helpers/image_creation.rb +8 -0
  46. data/spec/helpers/temporary_directory.rb +25 -0
  47. data/spec/helpers/temporary_models.rb +76 -0
  48. data/spec/helpers/temporary_values.rb +102 -0
  49. data/spec/helpers/test_upload_files.rb +108 -0
  50. data/spec/helpers/time_travel.rb +20 -0
  51. data/spec/integration/data/test.jpg +0 -0
  52. data/spec/integration/lifecycle_hooks_spec.rb +213 -0
  53. data/spec/integration/processing_image_attachments.rb +72 -0
  54. data/spec/integration/processing_video_attachments_spec.rb +82 -0
  55. data/spec/integration/saving_an_attachment_spec.rb +31 -0
  56. data/spec/matchers/file_operations.rb +159 -0
  57. data/spec/spec_helper.rb +76 -0
  58. data/spec/unit/attachment/base_spec.rb +311 -0
  59. data/spec/unit/attachment/image_spec.rb +128 -0
  60. data/spec/unit/attachment/maybe_spec.rb +126 -0
  61. data/spec/unit/attachment/pdf_spec.rb +137 -0
  62. data/spec/unit/attachment/video_spec.rb +176 -0
  63. data/spec/unit/attachment_spec.rb +61 -0
  64. data/spec/unit/has_attachment_spec.rb +700 -0
  65. data/spec/unit/interpolation_spec.rb +108 -0
  66. data/spec/unit/processor/argument_tree_spec.rb +159 -0
  67. data/spec/unit/processor/ffmpeg_spec.rb +467 -0
  68. data/spec/unit/processor/image_magick_spec.rb +260 -0
  69. data/spec/unit/processor/one_shot_spec.rb +70 -0
  70. data/spec/unit/reflection_spec.rb +338 -0
  71. data/spec/unit/stream_spec.rb +234 -0
  72. data/spec/unit/style_set_spec.rb +44 -0
  73. data/spec/unit/style_spec.rb +51 -0
  74. data/spec/unit/validations_spec.rb +491 -0
  75. data/spec/unit/vector2_spec.rb +27 -0
  76. data/tasks/bulldog_tasks.rake +4 -0
  77. metadata +193 -0
@@ -0,0 +1,102 @@
1
+ module TemporaryValues
2
+ def self.included(mod)
3
+ mod.extend ClassMethods
4
+ end
5
+
6
+ #
7
+ # Use +value+ for the value of the constant +mod::name+ for the
8
+ # duration of the block.
9
+ #
10
+ def with_temporary_constant_value(mod, name, value)
11
+ save_and_set_constant(mod, name, value)
12
+ yield
13
+ ensure
14
+ restore_constant(mod, name)
15
+ end
16
+
17
+ #
18
+ # Use +value+ for the value of the +name+ attribute of the given
19
+ # +object+ for the duration of the block.
20
+ #
21
+ def with_temporary_attribute_value(object, name, value)
22
+ save_and_set_attribute(object, name, value)
23
+ yield
24
+ ensure
25
+ restore_attribute(object, name)
26
+ end
27
+
28
+ module ClassMethods
29
+ #
30
+ # Use +value+ for the value of the constant +mod::name+ for the
31
+ # duration of each example in this example group.
32
+ #
33
+ # If the value should be recalculated for each example, pass a
34
+ # block instead of a value argument.
35
+ #
36
+ def use_temporary_constant_value(mod, name, value=nil, &block)
37
+ before do
38
+ value = block ? instance_eval(&block) : value
39
+ save_and_set_constant(mod, name, value)
40
+ end
41
+ after do
42
+ restore_constant(mod, name)
43
+ end
44
+ end
45
+
46
+ #
47
+ # Use +value+ for the value of the +name+ attribute of the given
48
+ # +object+ for the duration of each example in this example group.
49
+ #
50
+ # If the value should be recalculated for each example, pass a
51
+ # block instead of a value argument.
52
+ #
53
+ def use_temporary_attribute_value(object, name, value=nil, &block)
54
+ before do
55
+ value = block ? instance_eval(&block) : value
56
+ save_and_set_attribute(object, name, value)
57
+ end
58
+ after do
59
+ restore_attribute(object, name)
60
+ end
61
+ end
62
+ end
63
+
64
+ private # ---------------------------------------------------------
65
+
66
+ UNDEFINED = Object.new
67
+
68
+ def original_attribute_values
69
+ @original_attribute_values ||= Hash.new{|h,k| h[k] = Hash.new{|h2,k2| h2[k2] = {}}}
70
+ end
71
+
72
+ def save_and_set_attribute(object, name, value)
73
+ original_attribute_values[object.__id__][name] = object.send(name)
74
+ object.send("#{name}=", value)
75
+ end
76
+
77
+ def restore_attribute(object, name)
78
+ value = original_attribute_values[object.__id__].delete(name)
79
+ object.send("#{name}=", value)
80
+ end
81
+
82
+ def original_constant_values
83
+ @original_constant_values ||= Hash.new{|h,k| h[k] = Hash.new{|h2,k2| h2[k2] = {}}}
84
+ end
85
+
86
+ def save_and_set_constant(mod, name, value)
87
+ if mod.const_defined?(name)
88
+ original_constant_values[mod.name][name] = mod.send(:remove_const, name)
89
+ else
90
+ original_constant_values[mod.name][name] = UNDEFINED
91
+ end
92
+ mod.const_set(name, value)
93
+ end
94
+
95
+ def restore_constant(mod, name)
96
+ mod.send(:remove_const, name)
97
+ original_value = original_constant_values[mod.name].delete(name)
98
+ unless original_value.equal?(UNDEFINED)
99
+ mod.const_set(name, original_value)
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,108 @@
1
+ module TestUploadFiles
2
+ def self.included(mod)
3
+ mod.before{init_test_upload_files}
4
+ mod.after{close_test_upload_files}
5
+ end
6
+
7
+ def autoclose(file)
8
+ @files_to_close << file
9
+ file
10
+ end
11
+
12
+ def small_uploaded_file(path=unused_temp_path, test_file='test.jpg')
13
+ content = content_for_uploaded_file(test_file)
14
+ io = ActionController::UploadedStringIO.new(content)
15
+ io.original_path = path
16
+ io.content_type = Rack::Mime::MIME_TYPES[File.extname(path)]
17
+ io
18
+ end
19
+
20
+ def large_uploaded_file(path=unused_temp_path, test_file='test.jpg')
21
+ content = content_for_uploaded_file(test_file)
22
+ file = ActionController::UploadedTempfile.new(path)
23
+ file.write(content)
24
+ file.rewind
25
+ file.original_path = path
26
+ file.content_type = Rack::Mime::MIME_TYPES[File.extname(path)]
27
+ autoclose(file)
28
+ end
29
+
30
+ def uploaded_file_with_content(basename, content)
31
+ io = ActionController::UploadedStringIO.new(content)
32
+ io.original_path = basename
33
+ io.content_type = Rack::Mime::MIME_TYPES[File.extname(basename)]
34
+ io
35
+ end
36
+
37
+ def test_file(basename)
38
+ path = test_path(basename)
39
+ file = open(path)
40
+ autoclose(file)
41
+ end
42
+
43
+ def test_path(basename)
44
+ "#{ROOT}/spec/data/#{basename}"
45
+ end
46
+
47
+ # For when it doesn't matter if it's small or large.
48
+ alias uploaded_file small_uploaded_file
49
+
50
+ def test_image_file(basename='test.jpg')
51
+ path = test_image_path(basename)
52
+ file = open(path)
53
+ autoclose(file)
54
+ end
55
+
56
+ def test_image_path(basename='test.jpg')
57
+ test_path(basename)
58
+ end
59
+
60
+ def test_video_file(basename='test.mov')
61
+ path = test_video_path(basename)
62
+ file = open(path)
63
+ autoclose(file)
64
+ end
65
+
66
+ def test_video_path(basename='test.mov')
67
+ test_path(basename)
68
+ end
69
+
70
+ def test_pdf_file(basename='test.pdf')
71
+ path = test_pdf_path(basename)
72
+ file = open(path)
73
+ autoclose(file)
74
+ end
75
+
76
+ def test_pdf_path(basename='test.pdf')
77
+ test_path(basename)
78
+ end
79
+
80
+ def test_empty_file
81
+ file = open(test_path('empty.txt'))
82
+ autoclose(file)
83
+ end
84
+
85
+ private # ---------------------------------------------------------
86
+
87
+ def init_test_upload_files
88
+ @files_to_close = []
89
+ end
90
+
91
+ def close_test_upload_files
92
+ @files_to_close.each(&:close)
93
+ end
94
+
95
+ def content_for_uploaded_file(test_file)
96
+ path = test_path("test#{File.extname(test_file)}")
97
+ File.read(path)
98
+ end
99
+
100
+ def unused_temp_path
101
+ i = 0
102
+ path = nil
103
+ begin
104
+ path = File.join(temporary_directory, "test-#{i}.jpg")
105
+ end while File.exist?(path)
106
+ path
107
+ end
108
+ end
@@ -0,0 +1,20 @@
1
+ module TimeTravel
2
+ def self.included(mod)
3
+ mod.before{stop_time}
4
+ end
5
+
6
+ def stop_time
7
+ warp_to Time.now
8
+ end
9
+
10
+ def warp_to(time)
11
+ time = Time.parse(time) if time.is_a?(String)
12
+ Time.stubs(:now).returns(time)
13
+ end
14
+
15
+ def warp_ahead(duration)
16
+ new_now = Time.now + duration
17
+ Time.stubs(:now).returns(new_now)
18
+ new_now
19
+ end
20
+ end
@@ -0,0 +1,213 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Lifecycle hooks" do
4
+ use_model_class(:Thing,
5
+ :attachment_file_name => :string,
6
+ :value => :integer)
7
+
8
+ before do
9
+ @file = open("#{ROOT}/spec/integration/data/test.jpg")
10
+ Bulldog.default_url_template = "/attachments/:class/:id.:style.:extension"
11
+ end
12
+
13
+ after do
14
+ @file.close
15
+ end
16
+
17
+ it "should run before-validation hooks before validating the record" do
18
+ checks = []
19
+ Thing.validates_presence_of :value
20
+ Thing.has_attachment :attachment do
21
+ style :normal
22
+ process :before => :validation do
23
+ checks << record.errors.empty?
24
+ end
25
+ end
26
+ thing = Thing.new(:attachment => @file)
27
+ checks.should == []
28
+ thing.valid?.should be_false
29
+ checks.should == [true]
30
+ end
31
+
32
+ it "should run after-validation hooks after validating the record" do
33
+ checks = []
34
+ Thing.validates_presence_of :value
35
+ Thing.has_attachment :attachment do
36
+ style :normal
37
+ process :after => :validation do
38
+ checks << record.errors.empty?
39
+ end
40
+ end
41
+ thing = Thing.new(:attachment => @file)
42
+ checks.should == []
43
+ thing.valid?.should be_false
44
+ checks.should == [false]
45
+ end
46
+
47
+ it "should run before-save hooks before saving the record" do
48
+ checks = []
49
+ Thing.has_attachment :attachment do
50
+ style :normal
51
+ process :before => :save do
52
+ checks << record.new_record?
53
+ end
54
+ end
55
+ thing = Thing.new(:attachment => @file)
56
+ checks.should == []
57
+ thing.save.should be_true
58
+ checks.should == [true]
59
+ end
60
+
61
+ it "should run after-save hooks after saving the record" do
62
+ checks = []
63
+ Thing.has_attachment :attachment do
64
+ style :normal
65
+ process :after => :save do
66
+ checks << record.new_record?
67
+ end
68
+ end
69
+ thing = Thing.new(:attachment => @file)
70
+ checks.should == []
71
+ thing.save.should be_true
72
+ checks.should == [false]
73
+ end
74
+
75
+ it "should run before-create hooks before creating the record" do
76
+ checks = []
77
+ Thing.has_attachment :attachment do
78
+ style :normal
79
+ process :before => :create do
80
+ checks << record.new_record?
81
+ end
82
+ end
83
+ thing = Thing.new(:attachment => @file)
84
+ checks.should == []
85
+ thing.save.should be_true
86
+ checks.should == [true]
87
+ end
88
+
89
+ it "should run after-create hooks after creating the record" do
90
+ checks = []
91
+ Thing.has_attachment :attachment do
92
+ style :normal
93
+ process :after => :create do
94
+ checks << record.new_record?
95
+ end
96
+ end
97
+ thing = Thing.new(:attachment => @file)
98
+ checks.should == []
99
+ thing.save.should be_true
100
+ checks.should == [false]
101
+ end
102
+
103
+ it "should run before-update hooks before updating the record" do
104
+ checks = []
105
+ Thing.has_attachment :attachment do
106
+ style :normal
107
+ process :before => :update do
108
+ checks << Thing.count(:conditions => {:value => 2})
109
+ end
110
+ end
111
+ Thing.create(:attachment => @file, :value => 1)
112
+ thing = Thing.first
113
+ checks.should == []
114
+ thing.update_attributes(:value => 2).should be_true
115
+ checks.should == [0]
116
+ end
117
+
118
+ it "should run after-update hooks after updating the record" do
119
+ checks = []
120
+ Thing.has_attachment :attachment do
121
+ style :normal
122
+ process :after => :update do
123
+ checks << Thing.count(:conditions => {:value => 2})
124
+ end
125
+ end
126
+ Thing.create(:attachment => @file, :value => 1)
127
+ thing = Thing.first
128
+ checks.should == []
129
+ thing.update_attributes(:value => 2).should be_true
130
+ checks.should == [1]
131
+ end
132
+
133
+ it "should not run create hooks when updating the record" do
134
+ checks = []
135
+ Thing.has_attachment :attachment do
136
+ style :normal
137
+ process :before => :update do
138
+ checks << [:fail]
139
+ end
140
+
141
+ process :after => :update do |thing|
142
+ checks << [:fail]
143
+ end
144
+ end
145
+ Thing.create(:attachment => @file)
146
+ checks.should == []
147
+ end
148
+
149
+ it "should not run update hooks when creating the record" do
150
+ checks = []
151
+ Thing.has_attachment :attachment do
152
+ style :normal
153
+ process :before => :create do
154
+ checks << [:fail]
155
+ end
156
+
157
+ process :after => :create do
158
+ checks << [:fail]
159
+ end
160
+ end
161
+ thing = Thing.create(:attachment => @file, :value => 1)
162
+ checks = []
163
+ thing.update_attributes(:value => 2)
164
+ checks.should == []
165
+ end
166
+
167
+ it "should run callbacks for the given attachment types if given" do
168
+ runs = 0
169
+ Thing.has_attachment :attachment do
170
+ style :normal
171
+ process(:types => :image, :on => :test_event){runs += 1}
172
+ end
173
+ thing = Thing.new(:attachment => @file)
174
+ thing.process_attachment(:attachment, :test_event)
175
+ runs.should == 1
176
+ end
177
+
178
+ it "should not run callbacks if the attachment is of the wrong type" do
179
+ runs = 0
180
+ Thing.has_attachment :attachment do
181
+ style :normal
182
+ process(:types => :video, :on => :test_event){runs += 1}
183
+ end
184
+ thing = Thing.new(:attachment => @file)
185
+ thing.process_attachment(:attachment, :test_event)
186
+ runs.should == 0
187
+ end
188
+
189
+ it "should allow specifying more than one type" do
190
+ runs = 0
191
+ Thing.has_attachment :attachment do
192
+ style :normal
193
+ process(:types => [:image, :video], :on => :test_event){runs += 1}
194
+ end
195
+ thing = Thing.new(:attachment => @file)
196
+ thing.process_attachment(:attachment, :test_event)
197
+ thing.attachment = uploaded_file_with_content('test.avi', "RIFF AVI ")
198
+ thing.process_attachment(:attachment, :test_event)
199
+ runs.should == 2
200
+ end
201
+
202
+ it "should run multiple callbacks if given" do
203
+ checks = []
204
+ Thing.has_attachment :attachment do
205
+ style :normal
206
+ process(:on => :test_event){checks << 1}
207
+ process(:on => :test_event){checks << 2}
208
+ end
209
+ thing = Thing.new(:attachment => @file)
210
+ thing.process_attachment(:attachment, :test_event)
211
+ checks.should == [1, 2]
212
+ end
213
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Processing image attachments" do
4
+ use_model_class(:Thing,
5
+ :width => :integer,
6
+ :height => :integer)
7
+
8
+ def identify
9
+ Bulldog::Processor::ImageMagick.identify_command
10
+ end
11
+
12
+ before do
13
+ tmp = temporary_directory
14
+ Thing.has_attachment :photo do
15
+ path "#{tmp}/:id.:style.png"
16
+ style :small, {:size => '10x10!'}
17
+ style :large, {:size => '1000x1000!'}
18
+
19
+ process :before => :save do
20
+ width, height = dimensions
21
+ record.width = width
22
+ record.height = height
23
+ end
24
+
25
+ process :on => :resize do
26
+ resize
27
+ end
28
+ end
29
+ @thing = Thing.new
30
+ @thing.stubs(:id).returns(5)
31
+ @file = small_uploaded_file(:size => "40x30")
32
+ end
33
+
34
+ def small_uploaded_file(options={})
35
+ tmp_path = "#{temporary_directory}/small_uploaded_file.tmp.png"
36
+ create_image(tmp_path, options)
37
+ content = File.read(tmp_path)
38
+ File.unlink(tmp_path)
39
+
40
+ io = ActionController::UploadedStringIO.new(content)
41
+ io.original_path = "tmp.png"
42
+ io.content_type = 'image/png'
43
+ io
44
+ end
45
+
46
+ after do
47
+ @file.close
48
+ end
49
+
50
+ def path(style)
51
+ "#{temporary_directory}/5.#{style}.png"
52
+ end
53
+
54
+ it "should run the after_save process after saving" do
55
+ @thing.update_attributes(:photo => @file)
56
+ @thing.width.should == 40
57
+ @thing.height.should == 30
58
+ end
59
+
60
+ it "should process the attachment when the processor is run" do
61
+ File.exist?(path(:original)).should be_false
62
+ File.exist?(path(:small)).should be_false
63
+ File.exist?(path(:large)).should be_false
64
+
65
+ @thing.update_attributes(:photo => @file).should be_true
66
+ @thing.process_attachment(:photo, :resize)
67
+
68
+ `"#{identify}" -format "%w %h" "#{path(:original)}"`.chomp.should == '40 30'
69
+ `"#{identify}" -format "%w %h" "#{path(:small)}"`.chomp.should == '10 10'
70
+ `"#{identify}" -format "%w %h" "#{path(:large)}"`.chomp.should == '1000 1000'
71
+ end
72
+ end