dragonfly 0.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of dragonfly might be problematic. Click here for more details.

Files changed (75) hide show
  1. data/.gitignore +7 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +95 -0
  4. data/Rakefile +60 -0
  5. data/VERSION +1 -0
  6. data/config.rb +7 -0
  7. data/config.ru +10 -0
  8. data/dragonfly-rails.gemspec +41 -0
  9. data/dragonfly.gemspec +137 -0
  10. data/features/dragonfly.feature +38 -0
  11. data/features/steps/common_steps.rb +8 -0
  12. data/features/steps/dragonfly_steps.rb +39 -0
  13. data/features/support/env.rb +25 -0
  14. data/features/support/image_helpers.rb +9 -0
  15. data/generators/dragonfly_app/USAGE +16 -0
  16. data/generators/dragonfly_app/dragonfly_app_generator.rb +54 -0
  17. data/generators/dragonfly_app/templates/custom_processing.erb +13 -0
  18. data/generators/dragonfly_app/templates/initializer.erb +7 -0
  19. data/generators/dragonfly_app/templates/metal_file.erb +32 -0
  20. data/irbrc.rb +20 -0
  21. data/lib/dragonfly/active_record_extensions/attachment.rb +117 -0
  22. data/lib/dragonfly/active_record_extensions/class_methods.rb +41 -0
  23. data/lib/dragonfly/active_record_extensions/instance_methods.rb +28 -0
  24. data/lib/dragonfly/active_record_extensions/validations.rb +19 -0
  25. data/lib/dragonfly/active_record_extensions.rb +12 -0
  26. data/lib/dragonfly/analysis/analyser.rb +45 -0
  27. data/lib/dragonfly/analysis/base.rb +10 -0
  28. data/lib/dragonfly/analysis/r_magick_analyser.rb +40 -0
  29. data/lib/dragonfly/app.rb +85 -0
  30. data/lib/dragonfly/app_configuration.rb +9 -0
  31. data/lib/dragonfly/configurable.rb +113 -0
  32. data/lib/dragonfly/core_ext/object.rb +8 -0
  33. data/lib/dragonfly/data_storage/base.rb +19 -0
  34. data/lib/dragonfly/data_storage/file_data_store.rb +72 -0
  35. data/lib/dragonfly/data_storage.rb +9 -0
  36. data/lib/dragonfly/encoding/base.rb +13 -0
  37. data/lib/dragonfly/encoding/r_magick_encoder.rb +17 -0
  38. data/lib/dragonfly/extended_temp_object.rb +94 -0
  39. data/lib/dragonfly/middleware.rb +27 -0
  40. data/lib/dragonfly/parameters.rb +152 -0
  41. data/lib/dragonfly/processing/processor.rb +14 -0
  42. data/lib/dragonfly/processing/r_magick_processor.rb +71 -0
  43. data/lib/dragonfly/r_magick_configuration.rb +47 -0
  44. data/lib/dragonfly/rails/images.rb +20 -0
  45. data/lib/dragonfly/temp_object.rb +118 -0
  46. data/lib/dragonfly/url_handler.rb +148 -0
  47. data/lib/dragonfly.rb +33 -0
  48. data/samples/beach.png +0 -0
  49. data/samples/egg.png +0 -0
  50. data/samples/round.gif +0 -0
  51. data/samples/taj.jpg +0 -0
  52. data/spec/argument_matchers.rb +29 -0
  53. data/spec/dragonfly/active_record_extensions/attachment_spec.rb +8 -0
  54. data/spec/dragonfly/active_record_extensions/initializer.rb +1 -0
  55. data/spec/dragonfly/active_record_extensions/migration.rb +21 -0
  56. data/spec/dragonfly/active_record_extensions/model_spec.rb +400 -0
  57. data/spec/dragonfly/active_record_extensions/models.rb +2 -0
  58. data/spec/dragonfly/active_record_extensions/spec_helper.rb +23 -0
  59. data/spec/dragonfly/analysis/analyser_spec.rb +85 -0
  60. data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +35 -0
  61. data/spec/dragonfly/app_spec.rb +69 -0
  62. data/spec/dragonfly/configurable_spec.rb +193 -0
  63. data/spec/dragonfly/data_storage/data_store_spec.rb +47 -0
  64. data/spec/dragonfly/data_storage/file_data_store_spec.rb +93 -0
  65. data/spec/dragonfly/extended_temp_object_spec.rb +67 -0
  66. data/spec/dragonfly/middleware_spec.rb +44 -0
  67. data/spec/dragonfly/parameters_spec.rb +293 -0
  68. data/spec/dragonfly/processing/rmagick_processor_spec.rb +148 -0
  69. data/spec/dragonfly/temp_object_spec.rb +233 -0
  70. data/spec/dragonfly/url_handler_spec.rb +246 -0
  71. data/spec/dragonfly_spec.rb +4 -0
  72. data/spec/image_matchers.rb +31 -0
  73. data/spec/simple_matchers.rb +14 -0
  74. data/spec/spec_helper.rb +19 -0
  75. metadata +160 -0
@@ -0,0 +1,69 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'rack/mock'
3
+
4
+ describe Dragonfly::App do
5
+
6
+ def make_request(app, url)
7
+ Rack::MockRequest.new(app).get(url)
8
+ end
9
+
10
+ describe ".instance" do
11
+
12
+ it "should create a new instance if it didn't already exist" do
13
+ app = Dragonfly::App.instance(:images)
14
+ app.should be_a(Dragonfly::App)
15
+ end
16
+
17
+ it "should return an existing instance if called by name" do
18
+ app = Dragonfly::App.instance(:images)
19
+ Dragonfly::App.instance(:images).should == app
20
+ end
21
+
22
+ it "should also work using square brackets" do
23
+ Dragonfly::App[:images].should == Dragonfly::App.instance(:images)
24
+ end
25
+
26
+ end
27
+
28
+ describe ".new" do
29
+ it "should not be callable" do
30
+ lambda{
31
+ Dragonfly::App.new
32
+ }.should raise_error(NoMethodError)
33
+ end
34
+ end
35
+
36
+ describe "errors" do
37
+
38
+ before(:each) do
39
+ @app = Dragonfly::App[:images]
40
+ end
41
+
42
+ it "should return 400 if UrlHandler::IncorrectSHA is raised" do
43
+ @app.url_handler.should_receive(:url_to_parameters).and_raise(Dragonfly::UrlHandler::IncorrectSHA)
44
+ response = make_request(@app, '/some_uid.png?s=sadfas')
45
+ response.status.should == 400
46
+ end
47
+
48
+ it "should return 400 if UrlHandler::SHANotGiven is raised" do
49
+ @app.url_handler.should_receive(:url_to_parameters).and_raise(Dragonfly::UrlHandler::SHANotGiven)
50
+ response = make_request(@app, '/some_uid.png?s=asdfghsg')
51
+ response.status.should == 400
52
+ end
53
+
54
+ it "should return 404 if url handler raises an unknown url exception" do
55
+ @app.url_handler.should_receive(:url_to_parameters).and_raise(Dragonfly::UrlHandler::UnknownUrl)
56
+ response = make_request(@app, '/')
57
+ response.status.should == 404
58
+ end
59
+
60
+ it "should return 404 if the datastore raises data not found" do
61
+ @app.url_handler.protect_from_dos_attacks = false
62
+ @app.should_receive(:fetch).and_raise(Dragonfly::DataStorage::DataNotFound)
63
+ response = make_request(@app, 'hello.png')
64
+ response.status.should == 404
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,193 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Dragonfly::Configurable do
4
+
5
+ before(:each) do
6
+ class Car
7
+ include Dragonfly::Configurable
8
+ configurable_attr :colour
9
+ configurable_attr :top_speed, 216
10
+ def self.other_thing=(thing); end
11
+ end
12
+ @car = Car.new
13
+ end
14
+
15
+ describe "setup" do
16
+ it "should provide attr_readers for configurable attributes" do
17
+ @car.should respond_to(:colour)
18
+ end
19
+
20
+ it "should provide attr_writers for configurable attributes" do
21
+ @car.colour = 'verde'
22
+ @car.colour.should == 'verde'
23
+ end
24
+
25
+ it "should set default values for configurable attributes" do
26
+ @car.top_speed.should == 216
27
+ end
28
+
29
+ it "should set the default as nil if not specified" do
30
+ @car.colour.should be_nil
31
+ end
32
+
33
+ it "should allow specifying configurable attrs as strings" do
34
+ class Bike
35
+ include Dragonfly::Configurable
36
+ configurable_attr 'colour', 'rude'
37
+ end
38
+ Bike.new.colour.should == 'rude'
39
+ end
40
+ end
41
+
42
+ describe "configuring" do
43
+ it "should allow you to change values" do
44
+ @car.configure do |c|
45
+ c.colour = 'red'
46
+ end
47
+ @car.colour.should == 'red'
48
+ end
49
+
50
+ it "should not allow you to call other methods on the object via the configuration" do
51
+ lambda{
52
+ @car.configure do |c|
53
+ c.other_thing = 5
54
+ end
55
+ }.should raise_error(Dragonfly::Configurable::BadConfigAttribute)
56
+ end
57
+ end
58
+
59
+ describe "getting configuration" do
60
+ it "should return the configuration as a hash" do
61
+ @car.configuration.should == {:colour => nil, :top_speed => 216}
62
+ end
63
+ it "should not allow you to change the configuration via the hash" do
64
+ @car.configuration[:top_speed] = 555
65
+ @car.top_speed.should == 216
66
+ end
67
+ end
68
+
69
+ describe "multiple objects" do
70
+ it "should return the default configuration" do
71
+ Car.default_configuration.should == {:colour => nil, :top_speed => 216}
72
+ end
73
+ it "should allow instances to be configured differently" do
74
+ car1 = Car.new
75
+ car1.configure{|c| c.colour = 'green'}
76
+ car2 = Car.new
77
+ car2.configure{|c| c.colour = 'yellow'}
78
+ car1.configuration.should == {:colour => 'green', :top_speed => 216}
79
+ car2.configuration.should == {:colour => 'yellow', :top_speed => 216}
80
+ end
81
+ end
82
+
83
+ describe "lazy attributes" do
84
+ before(:each) do
85
+ cow = @cow = mock('cow')
86
+ class Lazy; end
87
+ Lazy.class_eval do
88
+ include Dragonfly::Configurable
89
+ configurable_attr(:sound){ cow.moo }
90
+ end
91
+ @lazy = Lazy.new
92
+ end
93
+ it "should not call the block if the configurable attribute is set to something else" do
94
+ @cow.should_not_receive(:moo)
95
+ @lazy.configure{|c| c.sound = 'baa' }
96
+ @lazy.sound.should == 'baa'
97
+ end
98
+ it "should call the block if it's not been changed, once it's accessed" do
99
+ @cow.should_receive(:moo).and_return('mooo!')
100
+ @lazy.sound.should == 'mooo!'
101
+ end
102
+ it "should not call the block when accessed again" do
103
+ @cow.should_receive(:moo).exactly(:once).and_return('mooo!')
104
+ @lazy.sound.should == 'mooo!'
105
+ @lazy.sound.should == 'mooo!'
106
+ end
107
+ it "should also call a block which has been set as part of the configuration" do
108
+ @cow.should_receive(:fart).exactly(:once).and_return('paaarrp!')
109
+ @lazy.configure{|c| c.sound = lambda{ @cow.fart }}
110
+ @lazy.sound.should == 'paaarrp!'
111
+ @lazy.sound.should == 'paaarrp!'
112
+ end
113
+ end
114
+
115
+ describe "using in the singleton class" do
116
+ it "should work" do
117
+ class OneOff
118
+ class << self
119
+ include Dragonfly::Configurable
120
+ configurable_attr :food, 'bread'
121
+ end
122
+ end
123
+ OneOff.food.should == 'bread'
124
+ end
125
+ end
126
+
127
+ describe "configuration method" do
128
+
129
+ before(:each) do
130
+ class ClassWithMethod
131
+ include Dragonfly::Configurable
132
+ def add_thing(thing)
133
+ 'poo'
134
+ end
135
+ def remove_thing(thing)
136
+ 'bum'
137
+ end
138
+ configuration_method :add_thing, :remove_thing
139
+ end
140
+ @thing = ClassWithMethod.new
141
+ end
142
+
143
+ it "should allow calling the method through 'configure'" do
144
+ @thing.configure do |c|
145
+ c.add_thing('duck')
146
+ c.remove_thing('dog')
147
+ end
148
+ end
149
+
150
+ end
151
+
152
+ describe "nested configurable objects" do
153
+
154
+ it "should allow configuring nested configurable objects" do
155
+
156
+ class NestedThing
157
+ include Dragonfly::Configurable
158
+ configurable_attr :age, 29
159
+ end
160
+
161
+ class Car
162
+ def nested_thing
163
+ @nested_thing ||= NestedThing.new
164
+ end
165
+ end
166
+
167
+ @car.configure do |c|
168
+ c.nested_thing do |nt|
169
+ nt.age = 50
170
+ end
171
+ end
172
+
173
+ @car.nested_thing.age.should == 50
174
+
175
+ end
176
+
177
+ end
178
+
179
+ describe "configuring with a configurer" do
180
+ it "should allow configuration by a configurer" do
181
+ cool_configuration = Object.new
182
+ def cool_configuration.apply_configuration(car)
183
+ car.configure do |c|
184
+ c.colour = 'vermelho'
185
+ end
186
+ end
187
+ @car.configure_with(cool_configuration)
188
+ @car.colour.should == 'vermelho'
189
+ @car.top_speed.should == 216
190
+ end
191
+ end
192
+
193
+ end
@@ -0,0 +1,47 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe "data_store", :shared => true do
4
+
5
+ # Using these shared spec requires you to set the inst var @data_store
6
+
7
+ before(:each) do
8
+ @temp_object = Dragonfly::TempObject.new('gollum')
9
+ end
10
+
11
+ describe "store" do
12
+ it "should return a unique identifier for each storage" do
13
+ @data_store.store(@temp_object).should_not == @data_store.store(@temp_object)
14
+ end
15
+ end
16
+
17
+ describe "retrieve" do
18
+ it "should retrieve the stored data" do
19
+ uid = @data_store.store(@temp_object)
20
+ Dragonfly::TempObject.new(@data_store.retrieve(uid)).data.should == @temp_object.data
21
+ end
22
+
23
+ it "should raise an exception if the data doesn't exist" do
24
+ lambda{
25
+ @data_store.retrieve('gooble/gubbub')
26
+ }.should raise_error(Dragonfly::DataStorage::DataNotFound)
27
+ end
28
+ end
29
+
30
+ describe "destroy" do
31
+
32
+ it "should destroy the stored data" do
33
+ uid = @data_store.store(@temp_object)
34
+ @data_store.destroy(uid)
35
+ lambda{
36
+ @data_store.retrieve(uid)
37
+ }.should raise_error(Dragonfly::DataStorage::DataNotFound)
38
+ end
39
+
40
+ it "should raise an error if the data doesn't exist" do
41
+ lambda{
42
+ @data_store.destroy('gooble/gubbub')
43
+ }.should raise_error(Dragonfly::DataStorage::DataNotFound)
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,93 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+ require File.dirname(__FILE__) + '/data_store_spec'
3
+
4
+ describe Dragonfly::DataStorage::FileDataStore do
5
+
6
+ before(:each) do
7
+ @data_store = Dragonfly::DataStorage::FileDataStore.new
8
+ @data_store.root_path = '/var/tmp/dragonfly_test'
9
+
10
+ # Set 'now' to a date in the past
11
+ Time.stub!(:now).and_return Time.mktime(1984,"may",4,14,28,1)
12
+ @file_pattern_prefix_without_root = '1984/05/04/142801'
13
+ @file_pattern_prefix = "#{@data_store.root_path}/#{@file_pattern_prefix_without_root}"
14
+ end
15
+
16
+ after(:each) do
17
+ # Clean up created files
18
+ FileUtils.rm_rf("#{@data_store.root_path}/1984")
19
+ end
20
+
21
+ it_should_behave_like 'data_store'
22
+
23
+ describe "store" do
24
+
25
+ before(:each) do
26
+ @temp_object = Dragonfly::TempObject.new('goobydoo')
27
+ end
28
+
29
+ def it_should_write_to_file(storage_path, temp_object)
30
+ FileUtils.should_receive(:cp).with(temp_object.path, storage_path)
31
+ end
32
+
33
+ it "should store the file in a folder based on date, with default filename" do
34
+ it_should_write_to_file("#{@file_pattern_prefix}_file", @temp_object)
35
+ @data_store.store(@temp_object)
36
+ end
37
+
38
+ it "should store the file with a numbered suffix if the filename already exists" do
39
+ FileUtils.mkdir_p(@file_pattern_prefix)
40
+ FileUtils.touch("#{@file_pattern_prefix}_file")
41
+ it_should_write_to_file("#{@file_pattern_prefix}_file_2", @temp_object)
42
+ @data_store.store(@temp_object)
43
+ end
44
+
45
+ it "should store the file with an incremented number suffix if the filename already exists" do
46
+ FileUtils.mkdir_p(@file_pattern_prefix)
47
+ FileUtils.touch("#{@file_pattern_prefix}_file")
48
+ FileUtils.touch("#{@file_pattern_prefix}_file_2")
49
+ it_should_write_to_file("#{@file_pattern_prefix}_file_3", @temp_object)
50
+ @data_store.store(@temp_object)
51
+ end
52
+
53
+ it "should use the temp_object name if it exists" do
54
+ @temp_object.name = 'hello'
55
+ it_should_write_to_file("#{@file_pattern_prefix}_hello", @temp_object)
56
+ @data_store.store(@temp_object)
57
+ end
58
+
59
+ it "should use the basename (all but part after last dot) of the temp_object name if it exists" do
60
+ @temp_object.name = 'hello.you.png'
61
+ it_should_write_to_file("#{@file_pattern_prefix}_hello.you", @temp_object)
62
+ @data_store.store(@temp_object)
63
+ end
64
+
65
+ it "should use the default file suffix if the temp_object name is blank" do
66
+ @temp_object.name = ''
67
+ it_should_write_to_file("#{@file_pattern_prefix}_file", @temp_object)
68
+ @data_store.store(@temp_object)
69
+ end
70
+
71
+ it "should return the filepath without the root of the stored file" do
72
+ @data_store.store(@temp_object).should == "#{@file_pattern_prefix_without_root}_file"
73
+ end
74
+
75
+ it "should raise an error if it can't create a directory" do
76
+ FileUtils.should_receive(:mkdir_p).and_raise(Errno::EACCES)
77
+ lambda{ @data_store.store(@temp_object) }.should raise_error(Dragonfly::DataStorage::UnableToStore)
78
+ end
79
+
80
+ it "should raise an error if it can't create a file" do
81
+ FileUtils.should_receive(:cp).and_raise(Errno::EACCES)
82
+ lambda{ @data_store.store(@temp_object) }.should raise_error(Dragonfly::DataStorage::UnableToStore)
83
+ end
84
+
85
+ it "should prune empty directories when destroying" do
86
+ uid = @data_store.store(@temp_object)
87
+ @data_store.destroy(uid)
88
+ @data_store.root_path.should be_an_empty_directory
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,67 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Dragonfly::ExtendedTempObject do
4
+
5
+ it "should raise an error if not configured with an app" do
6
+ temp_object = Dragonfly::ExtendedTempObject.new('asdf')
7
+ lambda{
8
+ temp_object.process(:dummy)
9
+ }.should raise_error(Dragonfly::ExtendedTempObject::NotConfiguredError)
10
+ end
11
+
12
+ describe "when configured correctly" do
13
+
14
+ before(:each) do
15
+ @analyser = mock('analyser', :has_analysis_method? => false)
16
+ @analyser.stub!(:has_analysis_method?).with(:width).and_return(true)
17
+ @processor = mock('processor')
18
+ @encoder = mock('encoder')
19
+ @app = mock('app', :analyser => @analyser, :processor => @processor, :encoder => @encoder)
20
+ @klass = Class.new(Dragonfly::ExtendedTempObject)
21
+ @klass.app = @app
22
+ @object = @klass.new('asdf')
23
+ end
24
+
25
+ describe "analysis" do
26
+
27
+ it "should respond to something that the analyser responds to" do
28
+ @analyser.should_receive(:has_analysis_method?).with(:some_method).and_return(true)
29
+ @object.should respond_to(:some_method)
30
+ end
31
+
32
+ it "should not respond to something that the analyser doesn't respond to" do
33
+ @analyser.should_receive(:has_analysis_method?).with(:some_method).and_return(false)
34
+ @object.should_not respond_to(:some_method)
35
+ end
36
+
37
+ it "should delegate the analysis to the analyser" do
38
+ @analyser.should_receive(:width).with(@object).and_return(4)
39
+ @object.width.should == 4
40
+ end
41
+
42
+ it "should cache the result so that it doesn't call it a second time" do
43
+ @analyser.should_receive(:width).with(@object).and_return(4)
44
+ @object.width.should == 4
45
+
46
+ @analyser.should_not_receive(:width)
47
+ @object.width.should == 4
48
+ end
49
+
50
+ it "should do the analysis again when it has been modified" do
51
+ @analyser.should_receive(:width).with(@object).and_return(4)
52
+ @object.width.should == 4
53
+
54
+ @object.modify_self!('hellothisisnew')
55
+
56
+ @analyser.should_receive(:width).with(@object).and_return(17)
57
+ @object.width.should == 17
58
+
59
+ @analyser.should_not_receive(:width)
60
+ @object.width.should == 17
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,44 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'rack'
3
+
4
+ describe Dragonfly::Middleware do
5
+
6
+ def make_request(app, url)
7
+ Rack::MockRequest.new(app).get(url)
8
+ end
9
+
10
+ before(:each) do
11
+ @stack = Rack::Builder.new do
12
+ use Dragonfly::Middleware, :images
13
+ run lambda{|env| [200, {"Content-Type" => "text/html"}, ["#{env['PATH_INFO']}, #{env['QUERY_STRING']}"]] }
14
+ end
15
+ end
16
+
17
+ it "should continue the calling chain if the app returns a 404 for that url" do
18
+ Dragonfly::App[:images].should_receive(:call).and_return(
19
+ [404, {"Content-Type" => 'text/plain'}, ['Not found']]
20
+ )
21
+ response = make_request(@stack, 'hello.png?howare=you')
22
+ response.status.should == 200
23
+ response.body.should == 'hello.png, howare=you'
24
+ end
25
+
26
+ it "should return as per the dragonfly app if the app returns a 200" do
27
+ Dragonfly::App[:images].should_receive(:call).and_return(
28
+ [200, {"Content-Type" => 'text/plain'}, ['ABCD']]
29
+ )
30
+ response = make_request(@stack, 'hello.png?howare=you')
31
+ response.status.should == 200
32
+ response.body.should == 'ABCD'
33
+ end
34
+
35
+ it "should return as per the dragonfly app if the app returns a 400" do
36
+ Dragonfly::App[:images].should_receive(:call).and_return(
37
+ [400, {"Content-Type" => 'text/plain'}, ['ABCD']]
38
+ )
39
+ response = make_request(@stack, 'hello.png?howare=you')
40
+ response.status.should == 400
41
+ response.body.should == 'ABCD'
42
+ end
43
+
44
+ end