dragonfly 0.1.6 → 0.2.1

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 (69) hide show
  1. data/.yardopts +4 -0
  2. data/{README.markdown → README.md} +12 -24
  3. data/Rakefile +6 -6
  4. data/VERSION +1 -1
  5. data/config.rb +1 -3
  6. data/docs.watchr +1 -1
  7. data/dragonfly-rails.gemspec +2 -5
  8. data/dragonfly.gemspec +32 -12
  9. data/extra_docs/ActiveRecord.md +195 -0
  10. data/extra_docs/Analysers.md +63 -0
  11. data/extra_docs/DataStorage.md +33 -0
  12. data/extra_docs/Encoding.md +58 -0
  13. data/extra_docs/GettingStarted.md +114 -0
  14. data/extra_docs/Processing.md +58 -0
  15. data/extra_docs/Shortcuts.md +118 -0
  16. data/extra_docs/UsingWithRails.md +104 -0
  17. data/features/{dragonfly.feature → images.feature} +14 -4
  18. data/features/no_processing.feature +20 -0
  19. data/features/steps/dragonfly_steps.rb +29 -8
  20. data/features/support/env.rb +20 -8
  21. data/generators/dragonfly_app/USAGE +0 -1
  22. data/generators/dragonfly_app/dragonfly_app_generator.rb +1 -13
  23. data/generators/dragonfly_app/templates/metal_file.erb +1 -1
  24. data/lib/dragonfly/active_record_extensions.rb +1 -0
  25. data/lib/dragonfly/active_record_extensions/attachment.rb +52 -6
  26. data/lib/dragonfly/active_record_extensions/validations.rb +26 -6
  27. data/lib/dragonfly/analysis/base.rb +6 -3
  28. data/lib/dragonfly/analysis/r_magick_analyser.rb +0 -6
  29. data/lib/dragonfly/app.rb +53 -35
  30. data/lib/dragonfly/configurable.rb +1 -1
  31. data/lib/dragonfly/data_storage/file_data_store.rb +8 -8
  32. data/lib/dragonfly/delegatable.rb +14 -0
  33. data/lib/dragonfly/delegator.rb +50 -0
  34. data/lib/dragonfly/encoding/base.rb +7 -7
  35. data/lib/dragonfly/encoding/r_magick_encoder.rb +3 -0
  36. data/lib/dragonfly/encoding/transparent_encoder.rb +1 -1
  37. data/lib/dragonfly/extended_temp_object.rb +13 -7
  38. data/lib/dragonfly/parameters.rb +17 -11
  39. data/lib/dragonfly/processing/base.rb +9 -0
  40. data/lib/dragonfly/processing/r_magick_processor.rb +15 -1
  41. data/lib/dragonfly/r_magick_configuration.rb +12 -8
  42. data/lib/dragonfly/rails/images.rb +1 -1
  43. data/lib/dragonfly/temp_object.rb +14 -2
  44. data/lib/dragonfly/url_handler.rb +5 -6
  45. data/samples/sample.docx +0 -0
  46. data/spec/dragonfly/active_record_extensions/model_spec.rb +175 -84
  47. data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +0 -8
  48. data/spec/dragonfly/app_spec.rb +3 -3
  49. data/spec/dragonfly/configurable_spec.rb +1 -1
  50. data/spec/dragonfly/data_storage/file_data_store_spec.rb +55 -40
  51. data/spec/dragonfly/delegatable_spec.rb +32 -0
  52. data/spec/dragonfly/delegator_spec.rb +133 -0
  53. data/spec/dragonfly/encoding/r_magick_encoder_spec.rb +28 -0
  54. data/spec/dragonfly/extended_temp_object_spec.rb +5 -5
  55. data/spec/dragonfly/parameters_spec.rb +22 -32
  56. data/spec/dragonfly/processing/rmagick_processor_spec.rb +1 -2
  57. data/spec/dragonfly/temp_object_spec.rb +51 -0
  58. data/spec/dragonfly/url_handler_spec.rb +10 -15
  59. data/yard/handlers/configurable_attr_handler.rb +38 -0
  60. data/yard/setup.rb +9 -0
  61. data/yard/templates/default/fulldoc/html/css/common.css +27 -0
  62. data/yard/templates/default/module/html/configuration_summary.erb +31 -0
  63. data/yard/templates/default/module/setup.rb +17 -0
  64. metadata +31 -12
  65. data/features/support/image_helpers.rb +0 -9
  66. data/generators/dragonfly_app/templates/custom_processing.erb +0 -13
  67. data/lib/dragonfly/analysis/analyser.rb +0 -45
  68. data/lib/dragonfly/processing/processor.rb +0 -14
  69. data/spec/dragonfly/analysis/analyser_spec.rb +0 -85
@@ -0,0 +1,104 @@
1
+ Using With Rails
2
+ ================
3
+
4
+ There are two main ways to use Dragonfly with Rails - as a {Dragonfly::Middleware middleware},
5
+ and as a Rails Metal.
6
+
7
+ Using as Middleware
8
+ -------------------
9
+ In environment.rb:
10
+
11
+ config.gem 'dragonfly-rails', :lib => 'dragonfly/rails/images'
12
+ config.middleware.use 'Dragonfly::MiddlewareWithCache', :images
13
+
14
+ The gem dragonfly-rails does nothing more than tie together the gem dependencies dragonfly,
15
+ rack-cache and rmagick.
16
+
17
+ The required file 'dragonfly/rails/images.rb' initializes a dragonfly app, configures it to use rmagick processing, encoding, etc.,
18
+ and registers the app so that you can use ActiveRecord accessors.
19
+
20
+ For reference, the contents are as follows:
21
+
22
+ require 'dragonfly'
23
+
24
+ ### The dragonfly app ###
25
+
26
+ app = Dragonfly::App[:images]
27
+ app.configure_with(Dragonfly::RMagickConfiguration)
28
+ app.configure do |c|
29
+ c.log = RAILS_DEFAULT_LOGGER
30
+ c.datastore.configure do |d|
31
+ d.root_path = "#{Rails.root}/public/system/dragonfly/#{Rails.env}"
32
+ end
33
+ c.url_handler.configure do |u|
34
+ u.protect_from_dos_attacks = false
35
+ u.path_prefix = '/media'
36
+ end
37
+ end
38
+
39
+ ### Extend active record ###
40
+ ActiveRecord::Base.extend Dragonfly::ActiveRecordExtensions
41
+ ActiveRecord::Base.register_dragonfly_app(:image, app)
42
+
43
+ The second line configures rails to use a {Dragonfly::MiddlewareWithCache middleware} which uses the named app (named `:images`), and puts
44
+ {http://tomayko.com/src/rack-cache/ Rack::Cache} in front of it for performance.
45
+ You can pass extra arguments to this line which will go directly to configuring Rack::Cache (see its docs for how to configure it).
46
+ The default configuration for Rack::Cache is
47
+
48
+ {
49
+ :verbose => true,
50
+ :metastore => 'file:/var/cache/rack/meta',
51
+ :entitystore => 'file:/var/cache/rack/body'
52
+ }
53
+
54
+ To see what you can do with the active record accessors, see {file:ActiveRecord}.
55
+
56
+ Using as a Rails Metal
57
+ ----------------------
58
+ The easiest way of setting up as a rails metal is using the supplied generator.
59
+ (NB I've had a couple of problems with the generator with plural/singular names with early versions of metal in Rails 2.3 -
60
+ this should be resolvable by making sure the metal name matches its filename).
61
+
62
+ ./script/generate dragonfly_app images
63
+
64
+ The argument 'images' could be anything - it is an arbitrary app name.
65
+
66
+ This does two things:
67
+
68
+ 1. Creates and configures an app as a rails metal
69
+ 2. Registers the app for use with ActiveRecord - see {file:ActiveRecord}
70
+
71
+ For reference, the contents of the metal file is given below. You could do something similar yourself without the generator.
72
+
73
+ # Allow the metal piece to run in isolation
74
+ require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)
75
+ require 'dragonfly'
76
+
77
+ # Configuration of the Dragonfly App
78
+ Dragonfly::App[:images].configure_with(Dragonfly::RMagickConfiguration)
79
+ Dragonfly::App[:images].configure do |c|
80
+ c.log = RAILS_DEFAULT_LOGGER
81
+ c.datastore.configure do |d|
82
+ d.root_path = "#{Rails.root}/public/system/dragonfly/#{Rails.env}"
83
+ end
84
+ c.url_handler.configure do |u|
85
+ u.secret = '29205f3e01648d0966bd5b119cd53347390a9ba9'
86
+ u.path_prefix = '/images'
87
+ end
88
+ end
89
+
90
+ # The metal, for running the app
91
+ app = Dragonfly::App[:images]
92
+ Images = Rack::Builder.new do
93
+
94
+ # UNCOMMENT ME!!!
95
+ # ... if you want to use super-dooper middleware 'rack-cache'
96
+ # require 'rack/cache'
97
+ # use Rack::Cache,
98
+ # :verbose => true,
99
+ # :metastore => 'file:/var/cache/rack/meta',
100
+ # :entitystore => 'file:/var/cache/rack/body'
101
+
102
+ run app
103
+
104
+ end
@@ -3,10 +3,11 @@ Feature: champion uses dragonfly to process images
3
3
  A user uses dragonfly
4
4
 
5
5
  Background:
6
+ Given we are using the app for images
6
7
  Given a stored image "beach.png" with dimensions 200x100
7
8
 
8
9
  Scenario: Go to url for original
9
- When I go to the url for image "beach.png", with format 'png'
10
+ When I go to the url for "beach.png", with format 'png'
10
11
  Then the response should be OK
11
12
  And the response should have mime-type 'image/png'
12
13
  And the image should have width '200'
@@ -14,7 +15,7 @@ Feature: champion uses dragonfly to process images
14
15
  And the image should have format 'png'
15
16
 
16
17
  Scenario: Go to url for changed format version
17
- When I go to the url for image "beach.png", with format 'gif'
18
+ When I go to the url for "beach.png", with format 'gif'
18
19
  Then the response should be OK
19
20
  And the response should have mime-type 'image/gif'
20
21
  And the image should have width '200'
@@ -22,7 +23,7 @@ Feature: champion uses dragonfly to process images
22
23
  And the image should have format 'gif'
23
24
 
24
25
  Scenario: Go to url for soft resized version
25
- When I go to the url for image "beach.png", with format 'png' and resize geometry '100x150'
26
+ When I go to the url for "beach.png", with format 'png' and resize geometry '100x150'
26
27
  Then the response should be OK
27
28
  And the response should have mime-type 'image/png'
28
29
  And the image should have width '100'
@@ -30,9 +31,18 @@ Feature: champion uses dragonfly to process images
30
31
  And the image should have format 'png'
31
32
 
32
33
  Scenario: Go to url for hard resized version
33
- When I go to the url for image "beach.png", with format 'png' and resize geometry '100x150!'
34
+ When I go to the url for "beach.png", with format 'png' and resize geometry '100x150!'
34
35
  Then the response should be OK
35
36
  And the response should have mime-type 'image/png'
36
37
  And the image should have width '100'
37
38
  And the image should have height '150'
38
39
  And the image should have format 'png'
40
+
41
+ Scenario: use a parameters shortcut
42
+ # Note that this scenario makes use of the configured default format
43
+ When I go to the url for "beach.png", with shortcut '100x150!'
44
+ Then the response should be OK
45
+ And the response should have mime-type 'image/jpeg'
46
+ And the image should have width '100'
47
+ And the image should have height '150'
48
+ And the image should have format 'jpeg'
@@ -0,0 +1,20 @@
1
+ Feature: winner uses dragonfly to serve different kinds of files
2
+ In order to be a winner
3
+ As a potential loser
4
+ I want to be a winner
5
+
6
+ Background:
7
+ Given we are using the app for files
8
+
9
+ Scenario: Go to url for original, without extension
10
+ Given a stored file "sample.docx"
11
+ When I go to the url for "sample.docx"
12
+ Then the response should be OK
13
+ And the response should have mime-type 'application/zip'
14
+ And the response should have the same content as the file "sample.docx"
15
+
16
+ Scenario: Go to url for original, with extension
17
+ Given a stored file "sample.docx"
18
+ When I go to the url for "sample.docx", with format 'docx'
19
+ Then the response should be OK
20
+ And the response should have mime-type 'application/zip'
@@ -1,28 +1,45 @@
1
+ Given /^we are using the app for (\w+)$/ do |app_name|
2
+ $app = Dragonfly::App[app_name.to_sym]
3
+ end
4
+
5
+ Given /^a stored file "(.+?)"$/ do |name|
6
+ file = File.new(File.dirname(__FILE__) + "/../../samples/#{name}")
7
+ uid = $app.store(file)
8
+ TEMP_FILES[name] = uid
9
+ end
10
+
1
11
  Given /^a stored image "(.+?)" with dimensions (\d+)x(\d+)$/ do |name, width, height|
2
12
  tempfile = Tempfile.new(name)
3
13
  `convert -resize #{width}x#{height}! #{SAMPLE_IMAGE_PATH} #{tempfile.path}`
4
- temp_object = APP.create_object(tempfile)
5
- uid = APP.datastore.store(temp_object)
6
- TEMP_IMAGES[name] = uid
14
+ uid = $app.store(tempfile)
15
+ TEMP_FILES[name] = uid
16
+ end
17
+
18
+ When /^I go to the url for "(.+?)"$/ do |name|
19
+ make_request name
7
20
  end
8
21
 
9
- When /^I go to the url for image "(.+?)", with format '([^']+?)'$/ do |name, ext|
10
- make_image_request name, :format => ext
22
+ When /^I go to the url for "(.+?)", with format '([^']+?)'$/ do |name, ext|
23
+ make_request name, :format => ext
11
24
  end
12
25
 
13
- When /^I go to the url for image "(.+?)", with format '(.+?)' and resize geometry '(.+?)'$/ do |name, ext, geometry|
14
- make_image_request(name,
26
+ When /^I go to the url for "(.+?)", with format '(.+?)' and resize geometry '(.+?)'$/ do |name, ext, geometry|
27
+ make_request(name,
15
28
  :format => ext,
16
29
  :processing_method => :resize,
17
30
  :processing_options => {:geometry => geometry}
18
31
  )
19
32
  end
20
33
 
34
+ When /^I go to the url for "(.+?)", with shortcut '([^']+?)'$/ do |name, arg1|
35
+ make_request name, arg1
36
+ end
37
+
21
38
  Then "the response should be OK" do
22
39
  @response.status.should == 200
23
40
  end
24
41
 
25
- Then "the response should have mime-type '(.+?)'" do |mime_type|
42
+ Then /the response should have mime-type '(.+?)'/ do |mime_type|
26
43
  @response.headers['Content-Type'].should == mime_type
27
44
  end
28
45
 
@@ -37,3 +54,7 @@ end
37
54
  Then "the image should have format '(.+?)'" do |format|
38
55
  @response.body.should have_format(format)
39
56
  end
57
+
58
+ Then /^the response should have the same content as the file "([^\"]*)"$/ do |name|
59
+ @response.body.should == $app.fetch(TEMP_FILES[name]).data
60
+ end
@@ -3,23 +3,35 @@ require 'dragonfly'
3
3
  require 'spec/expectations'
4
4
  require 'test/unit/assertions'
5
5
  require 'ruby-debug'
6
- require File.dirname(__FILE__) + '/image_helpers.rb'
7
6
  require File.dirname(__FILE__) + '/../../spec/image_matchers.rb'
8
7
 
9
8
  # A hash of <name for reference> => <dragonfly uid> pairs
10
- TEMP_IMAGES = {}
9
+ TEMP_FILES = {}
11
10
 
12
- APP = Dragonfly::App[:images]
13
- APP.configure_with(Dragonfly::RMagickConfiguration)
11
+ Dragonfly::App[:images].configure_with(Dragonfly::RMagickConfiguration)
12
+ Dragonfly::App[:files].configure do |c|
13
+ c.register_analyser(Dragonfly::Analysis::FileCommandAnalyser)
14
+ c.register_encoder(Dragonfly::Encoding::TransparentEncoder)
15
+ end
14
16
 
15
17
  SAMPLE_IMAGE_PATH = File.dirname(__FILE__)+'/../../samples/beach.png'
16
18
 
17
19
  Before do
18
20
  # Remove temporary images
19
- TEMP_IMAGES.each do |name, uid|
20
- APP.datastore.destroy(uid)
21
- TEMP_IMAGES.delete(name)
21
+ TEMP_FILES.each do |name, uid|
22
+ $app.datastore.destroy(uid)
23
+ TEMP_FILES.delete(name)
24
+ end
25
+ end
26
+
27
+ module MyHelpers
28
+
29
+ def make_request(name, *args)
30
+ request = Rack::MockRequest.new($app)
31
+ url = $app.url_for(TEMP_FILES[name], *args)
32
+ @response = request.get(url)
22
33
  end
34
+
23
35
  end
24
36
 
25
- World(ImageHelpers)
37
+ World(MyHelpers)
@@ -11,6 +11,5 @@ This will:
11
11
  image_accessor :preview_image
12
12
 
13
13
  in your models.
14
- - create an empty placeholder for putting your own custom processing methods
15
14
 
16
15
  ------------------------------------------------------------------------
@@ -7,7 +7,6 @@ class DragonflyAppGenerator < Rails::Generator::NamedBase
7
7
  metal_name = plural_name.camelize
8
8
  path_prefix = plural_name
9
9
  single_name = singular_name.singularize
10
- custom_processor_name = "Custom#{single_name.camelize}Processing"
11
10
 
12
11
  record do |m|
13
12
  # The initializer
@@ -33,18 +32,7 @@ class DragonflyAppGenerator < Rails::Generator::NamedBase
33
32
  :app_name => app_name,
34
33
  :metal_name => metal_name,
35
34
  :path_prefix => path_prefix,
36
- :random_secret => Digest::SHA1.hexdigest(Time.now.to_s),
37
- :custom_processor_name => custom_processor_name
38
- }
39
- )
40
-
41
- # The custom processor
42
- m.template(
43
- 'custom_processing.erb',
44
- "lib/#{custom_processor_name.underscore}.rb",
45
- :assigns => {
46
- :module_name => custom_processor_name,
47
- :temp_object_name => single_name
35
+ :random_secret => Digest::SHA1.hexdigest(Time.now.to_s)
48
36
  }
49
37
  )
50
38
 
@@ -9,7 +9,7 @@ Dragonfly::App[:<%= app_name %>].configure do |c|
9
9
  c.datastore.configure do |d|
10
10
  d.root_path = "#{Rails.root}/public/system/dragonfly/#{Rails.env}"
11
11
  end
12
- c.url_handler do |u|
12
+ c.url_handler.configure do |u|
13
13
  u.secret = '<%= random_secret %>'
14
14
  u.path_prefix = '/<%= path_prefix %>'
15
15
  end
@@ -1,4 +1,5 @@
1
1
  module Dragonfly
2
+
2
3
  module ActiveRecordExtensions
3
4
 
4
5
  def self.extended(klass)
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  module Dragonfly
2
4
  module ActiveRecordExtensions
3
5
 
@@ -5,6 +7,10 @@ module Dragonfly
5
7
 
6
8
  class Attachment
7
9
 
10
+ extend Forwardable
11
+ def_delegators :temp_object,
12
+ :data, :to_file, :file, :tempfile, :path, :process, :process!, :encode, :encode!, :transform, :transform!
13
+
8
14
  def initialize(app, parent_model, attribute_name)
9
15
  @app, @parent_model, @attribute_name = app, parent_model, attribute_name
10
16
  end
@@ -38,10 +44,6 @@ module Dragonfly
38
44
  end
39
45
  end
40
46
 
41
- def size
42
- temp_object.size
43
- end
44
-
45
47
  def temp_object
46
48
  if @temp_object
47
49
  @temp_object
@@ -60,6 +62,30 @@ module Dragonfly
60
62
  end
61
63
  end
62
64
 
65
+ def methods(*args)
66
+ (super + methods_to_delegate_to_temp_object).uniq
67
+ end
68
+
69
+ def public_methods(*args)
70
+ (super + methods_to_delegate_to_temp_object).uniq
71
+ end
72
+
73
+ def respond_to?(method)
74
+ super || methods_to_delegate_to_temp_object.include?(method.to_s)
75
+ end
76
+
77
+ def ext
78
+ has_magic_attribute_for?(:ext) ? magic_attribute_for(:ext) : temp_object.ext
79
+ end
80
+
81
+ def name
82
+ has_magic_attribute_for?(:name) ? magic_attribute_for(:name) : temp_object.name
83
+ end
84
+
85
+ def size
86
+ has_magic_attribute_for?(:size) ? magic_attribute_for(:size) : temp_object.size
87
+ end
88
+
63
89
  private
64
90
 
65
91
  def been_assigned?
@@ -91,13 +117,17 @@ module Dragonfly
91
117
  attr_writer :temp_object
92
118
 
93
119
  def analyser
94
- app.analyser
120
+ app.analysers
121
+ end
122
+
123
+ def methods_to_delegate_to_temp_object
124
+ analyser.callable_methods
95
125
  end
96
126
 
97
127
  def magic_attributes
98
128
  parent_model.class.column_names.select { |name|
99
129
  name =~ /^#{attribute_name}_(.+)$/ &&
100
- (analyser.has_analysis_method?($1) || %w(size name ext).include?($1))
130
+ (methods_to_delegate_to_temp_object.include?($1) || %w(size ext name).include?($1))
101
131
  }
102
132
  end
103
133
 
@@ -112,6 +142,22 @@ module Dragonfly
112
142
  magic_attributes.each{|attribute| parent_model.send("#{attribute}=", nil) }
113
143
  end
114
144
 
145
+ def has_magic_attribute_for?(property)
146
+ magic_attributes.include?("#{attribute_name}_#{property}")
147
+ end
148
+
149
+ def magic_attribute_for(property)
150
+ parent_model.send("#{attribute_name}_#{property}")
151
+ end
152
+
153
+ def method_missing(meth, *args, &block)
154
+ if methods_to_delegate_to_temp_object.include?(meth.to_s)
155
+ has_magic_attribute_for?(meth) ? magic_attribute_for(meth) : temp_object.send(meth, *args, &block)
156
+ else
157
+ super
158
+ end
159
+ end
160
+
115
161
  end
116
162
  end
117
163
  end
@@ -2,16 +2,36 @@ module Dragonfly
2
2
  module ActiveRecordExtensions
3
3
  module Validations
4
4
 
5
- def validates_mime_type_of(*args)
6
- opts = args.last
7
- raise ArgumentError, "you must provide either :in => [(mime-types)] or :as => '(mime-type)' to validates_mime_type_of" unless opts.is_a?(::Hash) &&
8
- (allowed_mime_types = opts[:in] || [opts[:as]])
5
+ private
6
+
7
+ def validates_property(property_name, opts)
8
+ attrs = opts[:of] or raise ArgumentError, "you need to provide the attribute which has the property, using :of => <attribute_name>"
9
+ attrs = [attrs].flatten #(make sure it's an array)
10
+
11
+ raise ArgumentError, "you must provide either :in => [<value1>, <value2>..] or :as => <value>" unless opts[:in] || opts[:as]
12
+ allowed_values = opts[:in] || [opts[:as]]
13
+
14
+ args = attrs + [opts]
9
15
  validates_each(*args) do |record, attr, attachment|
10
16
  if attachment
11
- mime_type = attachment.temp_object.mime_type
12
- record.errors.add attr, "doesn't have the correct MIME-type. It needs to be #{'one of ' if allowed_mime_types.length > 1}'#{allowed_mime_types.join('\', \'')}', but was '#{mime_type || 'unknown'}'" unless allowed_mime_types.include?(mime_type)
17
+ property = attachment.send(property_name)
18
+ record.errors.add attr, "#{property_name.to_s.humanize.downcase} is incorrect. It needs to be #{expected_values_string(allowed_values)}," +
19
+ " but was '#{property}'" unless allowed_values.include?(property)
13
20
  end
14
21
  end
22
+
23
+ end
24
+
25
+ def validates_mime_type_of(attribute, opts)
26
+ validates_property :mime_type, opts.merge(:of => attribute)
27
+ end
28
+
29
+ def expected_values_string(allowed_values)
30
+ if allowed_values.is_a?(Range)
31
+ "between #{allowed_values.first} and #{allowed_values.last}"
32
+ else
33
+ allowed_values.length > 1 ? "one of '#{allowed_values.join('\', \'')}'" : "'#{allowed_values.first.to_s}'"
34
+ end
15
35
  end
16
36
 
17
37
  end