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
@@ -1,10 +1,13 @@
1
1
  module Dragonfly
2
2
  module Analysis
3
3
  class Base
4
-
5
- def mime_type(temp_object)
4
+
5
+ include Delegatable
6
+
7
+ def mime_type(*args)
8
+ throw :unable_to_handle
6
9
  end
7
-
10
+
8
11
  end
9
12
  end
10
13
  end
@@ -1,5 +1,4 @@
1
1
  require 'rmagick'
2
- require 'mime/types'
3
2
 
4
3
  module Dragonfly
5
4
  module Analysis
@@ -14,11 +13,6 @@ module Dragonfly
14
13
  rmagick_image(image).rows
15
14
  end
16
15
 
17
- def mime_type(image)
18
- MIME::Types.type_for(rmagick_image(image).format).first.to_s
19
- rescue Magick::ImageMagickError
20
- end
21
-
22
16
  def depth(image)
23
17
  rmagick_image(image).depth
24
18
  end
data/lib/dragonfly/app.rb CHANGED
@@ -19,10 +19,12 @@ module Dragonfly
19
19
  # @example Example configuration options:
20
20
  #
21
21
  # Dragonfly::App[:images].configure do |c|
22
- # c.datastore = MyEC2DataStore.new # See DataStorage::Base for how to create a custom data store
23
- # c.encoder = Encoding::RMagickEncoder.new # See Encoding::Base for how to create a custom encoder
24
- # c.log = Logger.new('/tmp/my.log')
25
- # c.cache_duration = 3000 # seconds
22
+ # c.datastore = MyEC2DataStore.new # See DataStorage::Base for how to create a custom data store
23
+ # c.register_analyser(Analysis::RMagickAnalyser) # See Analysis::Base for how to create a custom analyser
24
+ # c.register_processor(Processing::RMagickProcessor) # See Processing::Base for how to create a custom analyser
25
+ # c.register_encoder(Encoding::RMagickEncoder) # See Encoding::Base for how to create a custom encoder
26
+ # c.log = Logger.new('/tmp/my.log')
27
+ # c.cache_duration = 3000 # seconds
26
28
  # end
27
29
  #
28
30
  # @example Configuration including nested items
@@ -32,12 +34,6 @@ module Dragonfly
32
34
  # c.datastore.configure do |d| # configuration depends on which data store you use
33
35
  # # ...
34
36
  # end
35
- # c.analyser.configure do |a| # see Analysis::Analyser
36
- # # ...
37
- # end
38
- # c.processor.configure do |p| # see Processing::Processor
39
- # # ...
40
- # end
41
37
  # c.parameters.configure do |p| # see Parameters (class methods)
42
38
  # # ...
43
39
  # end
@@ -70,40 +66,41 @@ module Dragonfly
70
66
  def apps
71
67
  @apps ||= {}
72
68
  end
73
-
69
+
74
70
  end
75
71
 
76
72
  def initialize
77
- @analyser = Analysis::Analyser.new
78
- @processor = Processing::Processor.new
73
+ @analysers, @processors, @encoders = Delegator.new, Delegator.new, Delegator.new
79
74
  @parameters_class = Class.new(Parameters)
80
75
  @url_handler = UrlHandler.new(@parameters_class)
81
76
  initialize_temp_object_class
82
77
  end
83
78
 
84
- # @see Analysis::Analyser
85
- attr_reader :analyser
86
- # @see Processing::Processor
87
- attr_reader :processor
88
- # @see Encoding::Base
89
- attr_reader :encoder
79
+ # @see Analysis::AnalyserList
80
+ attr_reader :analysers
81
+ # @see Processing::ProcessorList
82
+ attr_reader :processors
83
+ # @see Encoding::EncoderList
84
+ attr_reader :encoders
90
85
  # @see UrlHandler
91
86
  attr_reader :url_handler
92
87
  # @see Parameters
93
88
  attr_reader :parameters_class
89
+ # @see TempObject, and ExtendedTempObject
90
+ attr_reader :temp_object_class
94
91
 
95
92
  alias parameters parameters_class
96
93
 
97
94
  extend Forwardable
98
95
  def_delegator :url_handler, :url_for
96
+ def_delegator :datastore, :destroy
99
97
 
100
98
  include Configurable
101
99
 
102
100
  configurable_attr :datastore do DataStorage::FileDataStore.new end
103
- configurable_attr :encoder do Encoding::TransparentEncoder.new end
104
101
  configurable_attr :log do Logger.new('/var/tmp/dragonfly.log') end
105
- configurable_attr :cache_duration, 3600*24*365 # Defaults to 1 year
106
- configurable_attr :fallback_mime_type, 'application/octet-stream'
102
+ configurable_attr :cache_duration, 3600*24*365 # (1 year)
103
+ configurable_attr :fallback_mime_type, 'application/octet-stream'
107
104
 
108
105
  # The call method required by Rack to run.
109
106
  #
@@ -127,16 +124,17 @@ module Dragonfly
127
124
  "Cache-Control" => "public, max-age=#{cache_duration}"
128
125
  }, temp_object]
129
126
  rescue UrlHandler::IncorrectSHA, UrlHandler::SHANotGiven => e
127
+ warn_with_info(e.message, env)
130
128
  [400, {"Content-Type" => "text/plain"}, [e.message]]
131
129
  rescue UrlHandler::UnknownUrl, DataStorage::DataNotFound => e
132
130
  [404, {"Content-Type" => 'text/plain'}, [e.message]]
133
131
  end
134
132
 
135
- # Store an object, using the configured datastore
136
- # @param [String, File, Tempfile, TempObject] object the object holding the data
137
- # @return [String] the uid assigned to it
138
- def store(object)
139
- datastore.store(create_object(object))
133
+ # Create a temp_object from the object passed in
134
+ # @param [String, File, Tempfile, TempObject] initialization_object the object holding the data
135
+ # @return [ExtendedTempObject] a temp_object holding the data
136
+ def create_object(initialization_object)
137
+ temp_object_class.new(initialization_object)
140
138
  end
141
139
 
142
140
  # Fetch an object from the database and optionally transform
@@ -156,22 +154,42 @@ module Dragonfly
156
154
  temp_object.transform(*args)
157
155
  end
158
156
 
159
- # Create a temp_object from the object passed in
160
- # @param [String, File, Tempfile, TempObject] initialization_object the object holding the data
161
- # @return [ExtendedTempObject] a temp_object holding the data
162
- def create_object(initialization_object)
163
- temp_object_class.new(initialization_object)
157
+ def generate(*args)
158
+ create_object(processors.generate(*args))
159
+ end
160
+
161
+ # Store an object, using the configured datastore
162
+ # @param [String, File, Tempfile, TempObject] object the object holding the data
163
+ # @return [String] the uid assigned to it
164
+ def store(object)
165
+ datastore.store(create_object(object))
164
166
  end
165
167
 
168
+ def register_analyser(*args, &block)
169
+ analysers.register(*args, &block)
170
+ end
171
+ configuration_method :register_analyser
172
+
173
+ def register_processor(*args, &block)
174
+ processors.register(*args, &block)
175
+ end
176
+ configuration_method :register_processor
177
+
178
+ def register_encoder(*args, &block)
179
+ encoders.register(*args, &block)
180
+ end
181
+ configuration_method :register_encoder
182
+
166
183
  private
167
184
 
168
- # @private
169
- attr_reader :temp_object_class
170
-
171
185
  def initialize_temp_object_class
172
186
  @temp_object_class = Class.new(ExtendedTempObject)
173
187
  @temp_object_class.app = self
174
188
  end
175
189
 
190
+ def warn_with_info(message, env)
191
+ log.warn "Got error: #{message}\nPath was #{env['PATH_INFO'].inspect} and query was #{env['QUERY_STRING'].inspect}"
192
+ end
193
+
176
194
  end
177
195
  end
@@ -93,7 +93,7 @@ module Dragonfly
93
93
  if owner.has_configuration_method?(method_name)
94
94
  owner.send(method_name, *args, &block)
95
95
  elsif nested_configurable?(method_name, *args)
96
- owner.send(method_name, *args).configure(&block)
96
+ owner.send(method_name, *args)
97
97
  else
98
98
  raise BadConfigAttribute, "You tried to configure using '#{method_name.inspect}', but the valid config attributes are #{owner.configuration_methods.map{|a| %('#{a.inspect}') }.sort.join(', ')}"
99
99
  end
@@ -1,3 +1,5 @@
1
+ require 'pathname'
2
+
1
3
  module Dragonfly
2
4
  module DataStorage
3
5
 
@@ -12,7 +14,7 @@ module Dragonfly
12
14
  suffix = if temp_object.name.blank?
13
15
  'file'
14
16
  else
15
- temp_object.name
17
+ temp_object.name.sub(/\.[^.]*?$/, '')
16
18
  end
17
19
  relative_path = relative_storage_path(suffix)
18
20
 
@@ -20,20 +22,18 @@ module Dragonfly
20
22
  while File.exist?(storage_path = absolute_storage_path(relative_path))
21
23
  relative_path = increment_path(relative_path)
22
24
  end
23
- FileUtils.mkdir_p File.dirname(storage_path) unless File.exist?(storage_path)
25
+ storage_dir = File.dirname(storage_path)
26
+ FileUtils.mkdir_p(storage_dir) unless File.exist?(storage_dir)
24
27
  FileUtils.cp temp_object.path, storage_path
25
28
  rescue Errno::EACCES => e
26
29
  raise UnableToStore, e.message
27
30
  end
28
31
 
29
- relative_path.sub(/\.[^.]*?$/, '')
32
+ relative_path
30
33
  end
31
34
 
32
- def retrieve(uid)
33
- pattern = "#{absolute_storage_path(uid)}*"
34
- entries = Dir[pattern]
35
- raise DataNotFound, "Couldn't find any files matching #{pattern}" if entries.empty?
36
- File.new(entries.first)
35
+ def retrieve(relative_path)
36
+ File.new(absolute_storage_path(relative_path))
37
37
  rescue Errno::ENOENT => e
38
38
  raise DataNotFound, e.message
39
39
  end
@@ -0,0 +1,14 @@
1
+ module Dragonfly
2
+ module Delegatable
3
+
4
+ def delegatable_methods
5
+ if @delegatable_methods
6
+ @delegatable_methods
7
+ else
8
+ ancestors = self.class.ancestors
9
+ @delegatable_methods = ancestors[0...ancestors.index(Delegatable)].map{|i| i.instance_methods(false) }.flatten
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,50 @@
1
+ module Dragonfly
2
+ class Delegator
3
+
4
+ # This gets raised if no delegated objects are able to handle
5
+ # the method call, even though they respond to that method.
6
+ class UnableToHandle < StandardError; end
7
+
8
+ def register(klass, *args, &block)
9
+ object = klass.new(*args)
10
+ object.configure(&block) if block
11
+ registered_objects << object
12
+ end
13
+
14
+ def unregister(klass)
15
+ registered_objects.delete_if{|obj| obj.is_a?(klass) }
16
+ end
17
+
18
+ def unregister_all
19
+ self.registered_objects = []
20
+ end
21
+
22
+ def registered_objects
23
+ @registered_objects ||= []
24
+ end
25
+
26
+ def callable_methods
27
+ registered_objects.map{|a| a.delegatable_methods }.flatten.uniq
28
+ end
29
+
30
+ def has_callable_method?(method)
31
+ callable_methods.include?(method.to_s)
32
+ end
33
+
34
+ private
35
+
36
+ attr_writer :registered_objects
37
+
38
+ def method_missing(meth, *args)
39
+ registered_objects.reverse.each do |object|
40
+ catch :unable_to_handle do
41
+ return object.send(meth, *args) if object.respond_to?(meth)
42
+ end
43
+ end
44
+ raise UnableToHandle, "None of the registered objects for #{self} were able to deal with the method call " +
45
+ "#{meth}(#{args.map{|a| a.inspect[0..100]}.join(',')}), even though the method is implemented" if self.has_callable_method?(meth)
46
+ super
47
+ end
48
+
49
+ end
50
+ end
@@ -1,13 +1,13 @@
1
1
  module Dragonfly
2
- module Encoding
3
-
2
+ module Encoding
4
3
  class Base
5
-
6
- def encode(temp_object, format, options={})
7
- raise NotImplementedError
4
+
5
+ include Delegatable
6
+
7
+ def encode(*args)
8
+ throw :unable_to_handle
8
9
  end
9
-
10
+
10
11
  end
11
-
12
12
  end
13
13
  end
@@ -5,7 +5,10 @@ module Dragonfly
5
5
 
6
6
  class RMagickEncoder < Base
7
7
 
8
+ SUPPORTED_FORMATS = Magick.formats.select{|k,v| v =~ /.*rw./ }.map{|f| f.first.downcase }
9
+
8
10
  def encode(image, format, encoding={})
11
+ throw :unable_to_handle unless SUPPORTED_FORMATS.include?(format.to_s)
9
12
  encoded_image = Magick::Image.from_blob(image.data).first
10
13
  encoded_image.format = format.to_s
11
14
  encoded_image.to_blob
@@ -5,7 +5,7 @@ module Dragonfly
5
5
 
6
6
  # Does nothing
7
7
  def encode(temp_object, format, encoding={})
8
- temp_object.file
8
+ temp_object
9
9
  end
10
10
 
11
11
  end
@@ -35,25 +35,31 @@ module Dragonfly
35
35
  self
36
36
  end
37
37
 
38
+ # As we create customly configured subclasses of this class, the console
39
+ # gives a confusing output when inspecting, so modify here
40
+ def inspect
41
+ super.sub('Class:','Custom Dragonfly::ExtendedTempObject:')
42
+ end
43
+
38
44
  # Modify methods, public_methods and respond_to?, because method_missing
39
45
  # allows methods from the analyser
40
46
 
41
47
  def methods(*args)
42
- (super + analyser.analysis_methods).uniq
48
+ (super + analyser.callable_methods).uniq
43
49
  end
44
50
 
45
51
  def public_methods(*args)
46
- (super + analyser.analysis_methods).uniq
52
+ (super + analyser.callable_methods).uniq
47
53
  end
48
54
 
49
55
  def respond_to?(method)
50
- super || analyser.has_analysis_method?(method)
56
+ super || analyser.has_callable_method?(method)
51
57
  end
52
58
 
53
59
  private
54
60
 
55
61
  def method_missing(method, *args, &block)
56
- if analyser.has_analysis_method?(method)
62
+ if analyser.has_callable_method?(method)
57
63
  # Define the method so we don't use method_missing next time
58
64
  instance_var = "@#{method}"
59
65
  self.class.class_eval do
@@ -75,15 +81,15 @@ module Dragonfly
75
81
  end
76
82
 
77
83
  def analyser
78
- app.analyser
84
+ app.analysers
79
85
  end
80
86
 
81
87
  def processor
82
- app.processor
88
+ app.processors
83
89
  end
84
90
 
85
91
  def encoder
86
- app.encoder
92
+ app.encoders
87
93
  end
88
94
 
89
95
  def parameters_class
@@ -17,6 +17,15 @@ module Dragonfly
17
17
  configurable_attr :default_format
18
18
  configurable_attr :default_encoding, {}
19
19
 
20
+ def new_with_defaults(attributes={})
21
+ new({
22
+ :processing_method => default_processing_method,
23
+ :processing_options => default_processing_options,
24
+ :format => default_format,
25
+ :encoding => default_encoding
26
+ }.merge(attributes))
27
+ end
28
+
20
29
  def add_shortcut(*args, &block)
21
30
  if block
22
31
  block_shortcuts_of_length(args.length).unshift([args, block])
@@ -39,7 +48,7 @@ module Dragonfly
39
48
  configuration_method :hash_from_shortcut
40
49
 
41
50
  def from_shortcut(*args)
42
- new(hash_from_shortcut(*args))
51
+ new_with_defaults(hash_from_shortcut(*args))
43
52
  end
44
53
 
45
54
  def from_args(*args)
@@ -96,17 +105,18 @@ module Dragonfly
96
105
 
97
106
  # Instance methods
98
107
 
99
- attr_accessor :uid, :processing_method, :processing_options, :format, :encoding
100
-
101
108
  def initialize(attributes={})
102
109
  attributes = attributes.dup
103
- %w(processing_method processing_options format encoding).each do |attribute|
104
- instance_variable_set "@#{attribute}", (attributes.delete(attribute.to_sym) || self.class.send("default_#{attribute}"))
105
- end
106
- @uid = attributes.delete(:uid)
110
+ self.uid = attributes.delete(:uid)
111
+ self.processing_method = attributes.delete(:processing_method)
112
+ self.processing_options = attributes.delete(:processing_options) || {}
113
+ self.format = attributes.delete(:format)
114
+ self.encoding = attributes.delete(:encoding) || {}
107
115
  raise ArgumentError, "Parameters doesn't recognise the following parameters: #{attributes.keys.join(', ')}" if attributes.any?
108
116
  end
109
117
 
118
+ attr_accessor :uid, :processing_method, :processing_options, :format, :encoding
119
+
110
120
  def [](attribute)
111
121
  send(attribute)
112
122
  end
@@ -137,10 +147,6 @@ module Dragonfly
137
147
  }
138
148
  end
139
149
 
140
- def validate!
141
- raise InvalidParameters, "Parameters requires that at least the uid and the format are set" if uid.nil? || format.nil?
142
- end
143
-
144
150
  private
145
151
 
146
152
  def to_sorted_array