dragonfly 0.9.8 → 0.9.9

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 (58) hide show
  1. data/Gemfile +1 -1
  2. data/History.md +32 -0
  3. data/README.md +54 -32
  4. data/VERSION +1 -1
  5. data/dragonfly.gemspec +23 -21
  6. data/extra_docs/Configuration.md +0 -8
  7. data/extra_docs/DataStorage.md +13 -5
  8. data/extra_docs/ExampleUseCases.md +4 -1
  9. data/extra_docs/Heroku.md +14 -6
  10. data/extra_docs/Models.md +5 -1
  11. data/extra_docs/Rails3.md +1 -1
  12. data/extra_docs/URLs.md +8 -1
  13. data/lib/dragonfly.rb +7 -8
  14. data/lib/dragonfly/active_model_extensions/attachment.rb +37 -17
  15. data/lib/dragonfly/active_model_extensions/attachment_class_methods.rb +1 -1
  16. data/lib/dragonfly/active_model_extensions/validations.rb +53 -26
  17. data/lib/dragonfly/analyser.rb +1 -1
  18. data/lib/dragonfly/app.rb +1 -1
  19. data/lib/dragonfly/config/heroku.rb +7 -0
  20. data/lib/dragonfly/configurable.rb +19 -20
  21. data/lib/dragonfly/data_storage/couch_data_store.rb +2 -3
  22. data/lib/dragonfly/data_storage/file_data_store.rb +2 -9
  23. data/lib/dragonfly/data_storage/mongo_data_store.rb +1 -1
  24. data/lib/dragonfly/data_storage/s3data_store.rb +17 -10
  25. data/lib/dragonfly/function_manager.rb +4 -8
  26. data/lib/dragonfly/has_filename.rb +24 -0
  27. data/lib/dragonfly/image_magick/analyser.rb +1 -1
  28. data/lib/dragonfly/image_magick/generator.rb +1 -1
  29. data/lib/dragonfly/image_magick/processor.rb +5 -0
  30. data/lib/dragonfly/image_magick/utils.rb +1 -2
  31. data/lib/dragonfly/job.rb +52 -71
  32. data/lib/dragonfly/railtie.rb +1 -1
  33. data/lib/dragonfly/temp_object.rb +32 -14
  34. data/lib/dragonfly/url_attributes.rb +30 -0
  35. data/lib/dragonfly/url_mapper.rb +2 -2
  36. data/samples/DSC02119.JPG +0 -0
  37. data/samples/a.jp2 +0 -0
  38. data/samples/beach.jpg +0 -0
  39. data/spec/dragonfly/active_model_extensions/model_spec.rb +63 -40
  40. data/spec/dragonfly/active_model_extensions/spec_helper.rb +4 -0
  41. data/spec/dragonfly/app_spec.rb +11 -3
  42. data/spec/dragonfly/configurable_spec.rb +38 -20
  43. data/spec/dragonfly/data_storage/file_data_store_spec.rb +24 -36
  44. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +21 -5
  45. data/spec/dragonfly/data_storage/shared_data_store_examples.rb +2 -2
  46. data/spec/dragonfly/has_filename_spec.rb +88 -0
  47. data/spec/dragonfly/image_magick/analyser_spec.rb +10 -0
  48. data/spec/dragonfly/image_magick/processor_spec.rb +11 -0
  49. data/spec/dragonfly/job_spec.rb +173 -167
  50. data/spec/dragonfly/temp_object_spec.rb +82 -10
  51. data/spec/dragonfly/url_attributes.rb +47 -0
  52. data/spec/dragonfly/url_mapper_spec.rb +9 -1
  53. data/spec/functional/model_urls_spec.rb +36 -1
  54. metadata +179 -250
  55. data/lib/dragonfly/core_ext/string.rb +0 -9
  56. data/lib/dragonfly/core_ext/symbol.rb +0 -9
  57. data/spec/dragonfly/core_ext/string_spec.rb +0 -17
  58. data/spec/dragonfly/core_ext/symbol_spec.rb +0 -17
data/extra_docs/Rails3.md CHANGED
@@ -35,7 +35,7 @@ application.rb:
35
35
  Gemfile
36
36
  -------
37
37
 
38
- gem 'dragonfly', '~>0.9.8'
38
+ gem 'dragonfly', '~>0.9.9'
39
39
  gem 'rack-cache', :require => 'rack/cache'
40
40
 
41
41
  Capistrano
data/extra_docs/URLs.md CHANGED
@@ -87,11 +87,16 @@ Downloaded filename
87
87
  To specify the filename the browser uses for 'Save As' dialogues:
88
88
 
89
89
  app.content_filename = proc{|job, request|
90
- "#{job.basename}_#{job.process_steps.first.name}.#{job.format}"
90
+ if job.process_steps.any?
91
+ "#{job.basename}_#{job.process_steps.first.name}.#{job.format}"
92
+ else
93
+ "#{job.basename}.#{job.format}"
94
+ end
91
95
  }
92
96
 
93
97
  This will for example give the following filenames for the following jobs:
94
98
 
99
+ app.fetch('some/tree.png') # -> 'tree.png'
95
100
  app.fetch('some/tree.png').process(:greyscale) # -> 'tree_greyscale.png'
96
101
  app.fetch('some/tree.png').process(:greyscale).gif # -> 'tree_greyscale.gif'
97
102
 
@@ -141,6 +146,8 @@ Rails 3 (routes.rb):
141
146
 
142
147
  In each case the url will need to be generated by the router of choice, or manually.
143
148
 
149
+ NOTE: Ruby treats curly braces slightly differently to `do`...`end` so be aware of this when using the above examples.
150
+
144
151
  Simple Endpoints
145
152
  ----------------
146
153
  {Dragonfly::Job Job} objects can also be turned straight into Rack endpoints using `to_app`, e.g. in Rails 3:
data/lib/dragonfly.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  # The convention is that dirs are modules
4
4
  # so declare them here and autoload any modules/classes inside them
5
5
  # All paths here are absolute
6
- def camelize(path)
6
+ camelize = proc do |path|
7
7
  # e.g. 'test/this_one' => Test::ThisOne
8
8
  "#{path}".
9
9
  chomp('/').
@@ -12,27 +12,26 @@ def camelize(path)
12
12
  gsub('_','').
13
13
  sub(/^(\w)/){ $1.upcase }
14
14
  end
15
- def autoload_files_in_dir(path, namespace)
15
+
16
+ autoload_files_in_dir = proc do |path, namespace|
16
17
  # Define the module
17
18
  eval("module #{namespace}; end")
18
19
  # Autoload modules/classes in that module
19
20
  Dir.glob("#{path}/*.rb").each do |file|
20
21
  file = File.expand_path(file)
21
- sub_const_name = camelize( File.basename(file, '.rb') )
22
+ sub_const_name = camelize[ File.basename(file, '.rb') ]
22
23
  eval("#{namespace}.autoload('#{sub_const_name}', '#{file}')")
23
24
  end
24
25
  # Recurse on subdirectories
25
26
  Dir.glob("#{path}/*/").each do |dir|
26
- sub_namespace = camelize( File.basename(dir) )
27
- autoload_files_in_dir(dir, "#{namespace}::#{sub_namespace}")
27
+ sub_namespace = camelize[ File.basename(dir) ]
28
+ autoload_files_in_dir[dir, "#{namespace}::#{sub_namespace}"]
28
29
  end
29
30
  end
30
31
 
31
- autoload_files_in_dir("#{File.dirname(__FILE__)}/dragonfly", 'Dragonfly')
32
+ autoload_files_in_dir["#{File.dirname(__FILE__)}/dragonfly", 'Dragonfly']
32
33
 
33
34
  require 'dragonfly/core_ext/object'
34
- require 'dragonfly/core_ext/string'
35
- require 'dragonfly/core_ext/symbol'
36
35
  require 'dragonfly/core_ext/array'
37
36
  require 'dragonfly/core_ext/hash'
38
37
 
@@ -14,15 +14,17 @@ module Dragonfly
14
14
  :data, :to_file, :file, :tempfile, :path,
15
15
  :process, :encode, :analyse,
16
16
  :meta, :meta=,
17
- :name, :basename, :ext, :size,
17
+ :name, :size,
18
18
  :url
19
19
 
20
+ include HasFilename
21
+
20
22
  alias_method :length, :size
21
23
 
22
24
  def initialize(model)
23
25
  @model = model
24
26
  self.uid = model_uid
25
- update_from_uid if uid
27
+ set_job_from_uid if uid
26
28
  @should_run_callbacks = true
27
29
  self.class.ensure_uses_cached_magic_attributes
28
30
  end
@@ -50,10 +52,11 @@ module Dragonfly
50
52
  else app.new_job(value)
51
53
  end
52
54
  set_magic_attributes
53
- update_meta
55
+ job.url_attrs = all_extra_attributes
54
56
  self.class.run_callbacks(:after_assign, model, self) if should_run_callbacks?
55
57
  retain! if should_retain?
56
58
  end
59
+ model_uid_will_change!
57
60
  value
58
61
  end
59
62
 
@@ -79,9 +82,8 @@ module Dragonfly
79
82
  end
80
83
 
81
84
  def name=(name)
82
- job.name = name
83
85
  set_magic_attribute(:name, name) if has_magic_attribute_for?(:name)
84
- name
86
+ job.name = name
85
87
  end
86
88
 
87
89
  def process!(*args)
@@ -150,7 +152,7 @@ module Dragonfly
150
152
  model.send("#{attribute}_#{key}=", value)
151
153
  end
152
154
  sync_with_model
153
- update_from_uid
155
+ set_job_from_uid
154
156
  self.retained = true
155
157
  end
156
158
  end
@@ -172,6 +174,7 @@ module Dragonfly
172
174
  end
173
175
 
174
176
  def store_job!
177
+ meta.merge!(all_extra_attributes)
175
178
  opts = self.class.evaluate_storage_opts(model, self)
176
179
  set_uid_and_model_uid job.store(opts)
177
180
  self.job = job.to_fetched_job(uid)
@@ -210,6 +213,11 @@ module Dragonfly
210
213
  model.send("#{attribute}_uid")
211
214
  end
212
215
 
216
+ def model_uid_will_change!
217
+ meth = "#{attribute}_uid_will_change!"
218
+ model.send(meth) if model.respond_to?(meth)
219
+ end
220
+
213
221
  attr_reader :model, :uid
214
222
  attr_writer :job
215
223
  attr_accessor :previous_uid
@@ -219,17 +227,6 @@ module Dragonfly
219
227
  @uid = uid
220
228
  end
221
229
 
222
- def update_meta
223
- magic_attributes.each{|property| meta[property] = model.send("#{attribute}_#{property}") }
224
- meta[:model_class] = model.class.name
225
- meta[:model_attachment] = attribute
226
- end
227
-
228
- def update_from_uid
229
- self.job = app.fetch(uid)
230
- update_meta
231
- end
232
-
233
230
  def magic_attributes
234
231
  self.class.magic_attributes
235
232
  end
@@ -254,6 +251,29 @@ module Dragonfly
254
251
  model.send("#{attribute}_#{property}")
255
252
  end
256
253
 
254
+ def magic_attributes_hash
255
+ magic_attributes.inject({}) do |attrs, property|
256
+ attrs[property] = model.send("#{attribute}_#{property}")
257
+ attrs
258
+ end
259
+ end
260
+
261
+ def extra_attributes
262
+ @extra_attributes ||= {
263
+ :model_class => model.class.name,
264
+ :model_attachment => attribute
265
+ }
266
+ end
267
+
268
+ def all_extra_attributes
269
+ magic_attributes_hash.merge(extra_attributes)
270
+ end
271
+
272
+ def set_job_from_uid
273
+ self.job = app.fetch(uid)
274
+ job.url_attrs = all_extra_attributes
275
+ end
276
+
257
277
  end
258
278
  end
259
279
  end
@@ -95,7 +95,7 @@ module Dragonfly
95
95
 
96
96
  # Magic attributes
97
97
  def allowed_magic_attributes
98
- app.analyser.analysis_method_names + [:size, :basename, :name, :ext, :meta]
98
+ app.analyser.analysis_method_names + [:size, :name]
99
99
  end
100
100
 
101
101
  def magic_attributes
@@ -2,38 +2,65 @@ module Dragonfly
2
2
  module ActiveModelExtensions
3
3
  module Validations
4
4
 
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]
15
- validates_each(*args) do |model, attr, attachment|
5
+ class PropertyValidator < ActiveModel::EachValidator
6
+
7
+ def validate_each(model, attribute, attachment)
16
8
  if attachment
17
9
  property = attachment.send(property_name)
18
- unless allowed_values.include?(property)
19
- message = opts[:message] ||
20
- "#{property_name.to_s.humanize.downcase} is incorrect. "+
21
- "It needs to be #{Validations.expected_values_string(allowed_values)}"+
22
- (property ? ", but was '#{property}'" : "")
23
- message = message.call(property, model) if message.respond_to?(:call)
24
- model.errors.add(attr, message)
25
- end
10
+ model.errors.add(attribute, message(property, model)) unless matches?(property)
26
11
  end
27
12
  end
28
-
13
+
14
+ private
15
+
16
+ def matches?(property)
17
+ if case_insensitive?
18
+ prop = property.to_s.downcase
19
+ allowed_values.any?{|v| v.to_s.downcase == prop }
20
+ else
21
+ allowed_values.include?(property)
22
+ end
23
+ end
24
+
25
+ def message(property, model)
26
+ message = options[:message] ||
27
+ "#{property_name.to_s.humanize.downcase} is incorrect. " +
28
+ "It needs to be #{expected_values_string}" +
29
+ (property ? ", but was '#{property}'" : "")
30
+ message.respond_to?(:call) ? message.call(property, model) : message
31
+ end
32
+
33
+ def check_validity!
34
+ raise ArgumentError, "you must provide either :in => [<value1>, <value2>..] or :as => <value>" unless options[:in] || options[:as]
35
+ end
36
+
37
+ def property_name
38
+ options[:property_name]
39
+ end
40
+
41
+ def case_insensitive?
42
+ options[:case_sensitive] == false
43
+ end
44
+
45
+ def allowed_values
46
+ @allowed_values ||= options[:in] || [options[:as]]
47
+ end
48
+
49
+ def expected_values_string
50
+ if allowed_values.is_a?(Range)
51
+ "between #{allowed_values.first} and #{allowed_values.last}"
52
+ else
53
+ allowed_values.length > 1 ? "one of '#{allowed_values.join('\', \'')}'" : "'#{allowed_values.first.to_s}'"
54
+ end
55
+ end
56
+
29
57
  end
30
58
 
31
- def self.expected_values_string(allowed_values)
32
- if allowed_values.is_a?(Range)
33
- "between #{allowed_values.first} and #{allowed_values.last}"
34
- else
35
- allowed_values.length > 1 ? "one of '#{allowed_values.join('\', \'')}'" : "'#{allowed_values.first.to_s}'"
36
- end
59
+ private
60
+
61
+ def validates_property(property_name, options)
62
+ raise ArgumentError, "you need to provide the attribute which has the property, using :of => <attribute_name>" unless options[:of]
63
+ validates_with PropertyValidator, options.merge(:attributes => [*options[:of]], :property_name => property_name)
37
64
  end
38
65
 
39
66
  end
@@ -21,7 +21,7 @@ module Dragonfly
21
21
 
22
22
  def analyse(temp_object, method, *args)
23
23
  if enable_cache
24
- key = [temp_object.object_id, method, *args]
24
+ key = [temp_object.unique_id, method, *args]
25
25
  cache[key] ||= call_last(method, temp_object, *args)
26
26
  else
27
27
  call_last(method, temp_object, *args)
data/lib/dragonfly/app.rb CHANGED
@@ -86,7 +86,7 @@ module Dragonfly
86
86
  end
87
87
 
88
88
  def store(object, opts={})
89
- temp_object = object.is_a?(TempObject) ? object : TempObject.new(object)
89
+ temp_object = object.is_a?(TempObject) ? object : TempObject.new(object, opts[:meta] || {})
90
90
  datastore.store(temp_object, opts)
91
91
  end
92
92
 
@@ -4,6 +4,13 @@ module Dragonfly
4
4
  module Heroku
5
5
 
6
6
  def self.apply_configuration(app, bucket_name)
7
+ app.log.warn("""HEROKU CONFIGURATION IS NOW DEPRECATED - you should configure with the S3 Datastore directly, e.g.
8
+ c.datastore = Dragonfly::DataStorage::S3DataStore.new(
9
+ :bucket_name => '#{bucket_name}',
10
+ :access_key_id => 'XXX',
11
+ :secret_access_key => 'XXX'
12
+ )
13
+ """)
7
14
  app.configure do |c|
8
15
  c.datastore = DataStorage::S3DataStore.new
9
16
  c.datastore.configure do |d|
@@ -9,13 +9,6 @@ module Dragonfly
9
9
  klass.class_eval do
10
10
  include Configurable::InstanceMethods
11
11
  extend Configurable::ClassMethods
12
-
13
- # We should use configured_class rather than self.class
14
- # because sometimes this will be the eigenclass of an object
15
- # e.g. if we configure a module, etc.
16
- define_method :configured_class do
17
- klass
18
- end
19
12
  end
20
13
  end
21
14
 
@@ -46,21 +39,17 @@ module Dragonfly
46
39
  def has_config_method?(method_name)
47
40
  config_methods.include?(method_name.to_sym)
48
41
  end
49
-
50
- def config_methods
51
- @config_methods ||= configured_class.config_methods.dup
52
- end
53
42
 
54
43
  def configuration
55
44
  @configuration ||= {}
56
45
  end
46
+
47
+ def config_methods
48
+ @config_methods ||= self.class.config_methods.dup
49
+ end
57
50
 
58
51
  def default_configuration
59
- # Merge the default configuration of all ancestor classes/modules which are configurable
60
- @default_configuration ||= [self.class, configured_class, *configured_class.ancestors].reverse.inject({}) do |default_config, klass|
61
- default_config.merge!(klass.default_configuration) if klass.respond_to? :default_configuration
62
- default_config
63
- end
52
+ @default_configuration ||= self.class.default_configuration.dup
64
53
  end
65
54
 
66
55
  def set_config_value(key, value)
@@ -106,7 +95,7 @@ module Dragonfly
106
95
  end
107
96
 
108
97
  def saved_configs
109
- configured_class.saved_configs
98
+ self.class.saved_configs
110
99
  end
111
100
 
112
101
  def saved_config_for(symbol)
@@ -123,11 +112,17 @@ module Dragonfly
123
112
  module ClassMethods
124
113
 
125
114
  def default_configuration
126
- @default_configuration ||= {}
115
+ @default_configuration ||= configurable_ancestors.reverse.inject({}) do |default_config, klass|
116
+ default_config.merge!(klass.default_configuration)
117
+ default_config
118
+ end
127
119
  end
128
120
 
129
121
  def config_methods
130
- @config_methods ||= []
122
+ @config_methods ||= configurable_ancestors.inject([]) do |conf_methods, klass|
123
+ conf_methods |= klass.config_methods
124
+ conf_methods
125
+ end
131
126
  end
132
127
 
133
128
  def nested_configurables
@@ -142,6 +137,10 @@ module Dragonfly
142
137
  @saved_configs ||= {}
143
138
  end
144
139
 
140
+ def configurable_ancestors
141
+ @configurable_ancestors ||= ancestors.select{|a| a.included_modules.include?(Configurable) } - [self]
142
+ end
143
+
145
144
  private
146
145
 
147
146
  def configurable_attr attribute, default=nil, &blk
@@ -198,7 +197,7 @@ module Dragonfly
198
197
  attr_reader :owner
199
198
 
200
199
  def nested_configurable?(method)
201
- owner.configured_class.nested_configurables.include?(method.to_sym)
200
+ owner.class.nested_configurables.include?(method.to_sym)
202
201
  end
203
202
 
204
203
  end
@@ -22,12 +22,11 @@ module Dragonfly
22
22
  end
23
23
 
24
24
  def store(temp_object, opts={})
25
- meta = opts[:meta] || {}
26
- name = meta[:name] || temp_object.original_filename || 'file'
25
+ name = temp_object.name || 'file'
27
26
  content_type = opts[:content_type] || opts[:mime_type] || 'application/octet-stream'
28
27
 
29
28
  temp_object.file do |f|
30
- doc = CouchRest::Document.new(:meta => marshal_encode(meta))
29
+ doc = CouchRest::Document.new(:meta => marshal_encode(temp_object.meta))
31
30
  response = db.save_doc(doc)
32
31
  doc.put_attachment(name, f.dup, :content_type => content_type)
33
32
  form_uid(response['id'], name)
@@ -15,11 +15,10 @@ module Dragonfly
15
15
  configurable_attr :store_meta, true
16
16
 
17
17
  def store(temp_object, opts={})
18
- meta = opts[:meta] || {}
19
18
  relative_path = if opts[:path]
20
19
  opts[:path]
21
20
  else
22
- filename = meta[:name] || temp_object.original_filename || 'file'
21
+ filename = temp_object.name || 'file'
23
22
  relative_path = relative_path_for(filename)
24
23
  end
25
24
 
@@ -28,9 +27,8 @@ module Dragonfly
28
27
  until !File.exist?(path)
29
28
  path = disambiguate(path)
30
29
  end
31
- prepare_path(path)
32
30
  temp_object.to_file(path).close
33
- store_meta_data(path, meta) if store_meta
31
+ store_meta_data(path, temp_object.meta) if store_meta
34
32
  rescue Errno::EACCES => e
35
33
  raise UnableToStore, e.message
36
34
  end
@@ -124,11 +122,6 @@ module Dragonfly
124
122
  end
125
123
  end
126
124
 
127
- def prepare_path(path)
128
- dir = File.dirname(path)
129
- FileUtils.mkdir_p(dir) unless File.exist?(dir)
130
- end
131
-
132
125
  def purge_empty_directories(path)
133
126
  containing_directory = Pathname.new(path).dirname
134
127
  containing_directory.ascend do |relative_dir|