public
Description: Treat an ActiveRecord model as a file attachment, storing its patch, size, content type, etc.
Homepage: http://weblog.techno-weenie.net
Clone URL: git://github.com/technoweenie/attachment_fu.git
Click here to lend your support to: attachment_fu and make a donation at www.pledgie.com !
fix up thumbnail support
rick (author)
Wed Apr 23 15:20:04 -0700 2008
commit  3792dbd2105580d8536214ca92f7ebcf74f15062
tree    c094b4dabadf99e1f60074391bda48abd37df3f9
parent  dadcae59a75ec98fbb91bd4d166501ca52bdf5d7
...
38
39
40
41
 
42
43
44
...
81
82
83
84
 
 
 
85
86
87
...
134
135
136
137
 
138
139
140
141
142
143
 
144
145
146
 
147
148
149
...
189
190
191
192
193
 
 
194
195
196
...
271
272
273
274
275
 
 
 
276
277
278
...
38
39
40
 
41
42
43
44
...
81
82
83
 
84
85
86
87
88
89
...
136
137
138
 
139
140
141
142
143
144
 
145
146
147
148
149
150
151
152
...
192
193
194
 
 
195
196
197
198
199
...
274
275
276
 
 
277
278
279
280
281
282
0
@@ -38,7 +38,7 @@ module AttachmentFu
0
     #
0
     # See individual tasks for what options they take. See AttachmentFu::Tasks to see how tasks are processed.
0
     #
0
- # The \table schema should look like this:
0
+ # The table schema should look like this:
0
     #
0
     # create_table :foo do |t|
0
     # t.string :filename
0
@@ -81,7 +81,9 @@ module AttachmentFu
0
         attr_writer :attachment_tasks
0
       
0
         def attachment_tasks(&block)
0
- @attachment_tasks ||= superclass.respond_to?(:attachment_tasks) ? superclass.attachment_tasks.copy(&block) : AttachmentFu::Tasks.new(self, &block)
0
+ @attachment_tasks ||= superclass.respond_to?(:attachment_tasks) ? superclass.attachment_tasks.copy : AttachmentFu::Tasks.new(self)
0
+ @attachment_tasks.instance_eval(&block) if block
0
+ @attachment_tasks
0
         end
0
       end
0
       base.send :attr_reader, :temp_path
0
@@ -134,16 +136,17 @@ module AttachmentFu
0
     # overrwrite this to do your own app-specific partitioning.
0
     # you can thank Jamis Buck for this: http://www.37signals.com/svn/archives2/id_partitioning.php
0
     def partitioned_path(*args)
0
- return nil if new_record?
0
+ return nil if attachment_path_id.nil?
0
       ("%08d" % attachment_path_id).scan(/..../) + args
0
     end
0
 
0
     # Returns the full path for an attachment
0
     def full_filename
0
- new_record? ? nil : full_path(filename)
0
+ full_path(filename)
0
     end
0
 
0
     def full_path(*args)
0
+ return nil if attachment_path_id.nil?
0
       File.expand_path(File.join(AttachmentFu.root_path, attachment_path, partitioned_path(*args)))
0
     end
0
 
0
@@ -189,8 +192,8 @@ module AttachmentFu
0
           process_single_task(task, options, false)
0
         else
0
           process_all_tasks(has_progress)
0
- end
0
- save unless task_key == false
0
+ end
0
+ save unless options[:skip_save]
0
     end
0
   
0
     # Returns true/false if an attachment has been processed.
0
@@ -271,8 +274,9 @@ module AttachmentFu
0
         FileUtils.mv(old_path, full_filename)
0
       end
0
       File.chmod(0644, full_filename)
0
- queued_attachment ? queue_processing : process(false)
0
- @temp_path = @new_attachment = nil
0
+ @temp_path = nil # if a task tries to re-save, we don't want to re-store the attachment
0
+ queued_attachment ? queue_processing : process
0
+ @new_attachment = nil
0
     end
0
   
0
     # Could be a string, Pathname, Tempfile, who knows?
...
26
27
28
29
30
 
 
 
 
 
 
 
 
 
 
 
 
 
31
32
33
34
35
36
37
 
38
39
40
...
26
27
28
 
 
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 
48
49
50
51
0
@@ -26,15 +26,26 @@ module AttachmentFu
0
         options[:with] ||= :mojo_magick
0
         new options[:with], attachment.full_filename do
0
           data = with_image { |img| resize_image img, :size => options[:to], :to => options[:destination] }
0
- attachment.width = data.width if attachment.respond_to?(:width)
0
- attachment.height = data.height if attachment.respond_to?(:height)
0
+ unless options[:skip_size]
0
+ attachment.width = data.width if attachment.respond_to?(:width)
0
+ attachment.height = data.height if attachment.respond_to?(:height)
0
+ end
0
+ end
0
+ end
0
+ end
0
+
0
+ def self.image_size_task
0
+ lambda do |attachment, options|
0
+ options[:with] ||= :mojo_magick
0
+ new options[:with], attachment.full_filename do
0
+ attachment.width, attachment.height = with_image { |img| get_image_size(img) }
0
         end
0
       end
0
     end
0
 
0
     class Image
0
       attr_accessor :filename, :width, :height, :size
0
-
0
+
0
       def initialize(filename = nil)
0
         @filename = filename
0
         yield self if block_given?
...
8
9
10
 
 
 
 
11
12
13
...
26
27
28
29
 
30
31
32
33
34
35
36
37
 
38
39
40
...
8
9
10
11
12
13
14
15
16
17
...
30
31
32
 
33
34
35
36
37
38
39
 
 
40
41
42
43
0
@@ -8,6 +8,10 @@ module AttachmentFu # :nodoc:
0
         block.call OSX::CIImage.from(@file)
0
       end
0
 
0
+ def get_image_size(image)
0
+ [image.extent.size.width, image.extent.size.height]
0
+ end
0
+
0
       # Performs the actual resizing operation for a thumbnail.
0
       # Returns a AttachmentFu::Pixels::Image object.
0
       #
0
@@ -26,15 +30,14 @@ module AttachmentFu # :nodoc:
0
             processor.resize(size[0], size[1])
0
           end
0
         else
0
- new_size = [image.extent.size.width, image.extent.size.height] / size.to_s
0
+ new_size = get_image_size(image) / size.to_s
0
           processor.resize(new_size[0], new_size[1])
0
         end
0
         
0
         destination = options[:to] || @file
0
         AttachmentFu::Pixels::Image.new destination do |img|
0
           processor.render do |result|
0
- img.width = result.extent.size.width
0
- img.height = result.extent.size.height
0
+ img.width, img.height = get_image_size(result)
0
             result.save destination, OSX::NSJPEGFileType
0
           end
0
         end
...
9
10
11
 
 
 
 
 
12
13
14
...
17
18
19
20
21
22
23
24
 
25
26
27
28
29
30
31
 
32
33
34
...
9
10
11
12
13
14
15
16
17
18
19
...
22
23
24
 
25
26
27
 
28
29
30
31
32
 
 
 
33
34
35
36
0
@@ -9,6 +9,11 @@ module AttachmentFu # :nodoc:
0
         block.call @file
0
       end
0
 
0
+ def get_image_size(image)
0
+ size = ::MojoMagick.get_image_size(image)
0
+ [size[:width], size[:height]]
0
+ end
0
+
0
       # Performs the actual resizing operation for a thumbnail.
0
       # Returns a AttachmentFu::Pixels::Image object.
0
       #
0
@@ -17,18 +22,15 @@ module AttachmentFu # :nodoc:
0
       # - :to - Final location of the saved image. Defaults to the pixel instance's location.
0
       #
0
       def resize_image(image, options = {})
0
- dimensions = ::MojoMagick.get_image_size(image)
0
         size = options[:size]
0
         case size
0
           when Fixnum then size = [size, size]
0
- when String then size = [dimensions[:width], dimensions[:height]] / size
0
+ when String then size = get_image_size(image) / size
0
         end
0
         destination = options[:to] || image
0
         AttachmentFu::Pixels::Image.new destination do |img|
0
           ::MojoMagick.resize(image, destination, :width => size[0], :height => size[1])
0
- new_dimensions = ::MojoMagick.get_image_size(destination)
0
- img.width = new_dimensions[:width]
0
- img.height = new_dimensions[:height]
0
+ img.width, img.height = get_image_size(destination)
0
         end
0
       end
0
     end
...
11
12
13
14
 
15
16
17
...
121
122
123
124
 
125
126
127
128
129
 
130
131
 
132
133
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
136
137
...
149
150
151
152
153
 
 
154
...
11
12
13
 
14
15
16
17
...
121
122
123
 
124
125
126
127
128
 
129
130
 
131
132
133
 
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
...
163
164
165
 
166
167
168
169
0
@@ -11,7 +11,7 @@ module AttachmentFu
0
   # an instance of the class is created. The #call method is then used to process.
0
   #
0
   # class SampleObjectTask
0
- # def initialize(klass)
0
+ # def initialize(klass, options)
0
   # end
0
   #
0
   # def call(attachment, options)
0
@@ -121,17 +121,31 @@ module AttachmentFu
0
     # retrieved task from the global Tasks.all collection is a class
0
     # it is instantiated.
0
     def task(key, options = {})
0
- @stack << [load(key), options]
0
+ @stack << [load(key, options), options]
0
     end
0
     
0
     # Loads a new task to this Tasks instance, but does not put it
0
     # in the stack to be called during processing.
0
- def load(key)
0
+ def load(key, options = {})
0
       t = @all[key] || self.class[key]
0
- if t.is_a?(Class) then t = t.new(@klass) end
0
+ if t.is_a?(Class) then t = t.new(@klass, options) end
0
       @all[key] = t
0
     end
0
-
0
+
0
+ def key?(key_or_index)
0
+ case key_or_index
0
+ when Symbol then @all.key?(key_or_index)
0
+ when Fixnum then @stack.key?(key_or_index)
0
+ end
0
+ end
0
+
0
+ def queued?(key_or_index)
0
+ case key_or_index
0
+ when Symbol then false
0
+ when Fixnum then @stack.key?(key_or_index)
0
+ end
0
+ end
0
+
0
     # Gets either a task instance by key, or a stack item by index.
0
     #
0
     # @tasks[:foo] # => <#FooTaskInstance>
0
@@ -149,4 +163,5 @@ end
0
 # default tasks
0
 [:resize, :thumbnails].each do |task|
0
   AttachmentFu.create_task task, "attachment_fu/tasks/#{task}"
0
-end
0
\ No newline at end of file
0
+end
0
+AttachmentFu.create_task :get_image_size, "attachment_fu/tasks/resize"
0
\ No newline at end of file
...
2
3
4
5
6
 
 
7
...
2
3
4
 
5
6
7
8
0
@@ -2,4 +2,5 @@ require 'attachment_fu/pixels'
0
 
0
 # task :resize, :with => :mojo_magic, :to => '50x50'
0
 #
0
-AttachmentFu.create_task :resize, AttachmentFu::Pixels.resize_task
0
\ No newline at end of file
0
+AttachmentFu.create_task :resize, AttachmentFu::Pixels.resize_task
0
+AttachmentFu.create_task :get_image_size, AttachmentFu::Pixels.image_size_task
0
\ No newline at end of file
...
1
2
3
4
5
6
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
9
10
11
12
13
14
15
16
17
18
19
20
 
21
 
22
23
24
25
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
28
 
29
30
31
...
1
2
3
 
 
 
 
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 
 
 
 
 
37
38
 
39
40
41
42
43
44
45
 
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
0
@@ -1,31 +1,68 @@
0
 module AttachmentFu
0
   class Pixels
0
     class Thumbnails
0
- def initialize(klass)
0
- klass.attachment_tasks.load :resize
0
- puts "#{klass.name} #{klass.object_id} #{klass.attachment_tasks.all.keys}"
0
- @thumbnail_class = nil
0
+ # Some valid options:
0
+ #
0
+ # :parent_association => :parent
0
+ # :parent_foreign_key => parent_association.to_s.foreign_key
0
+ # :thumbnails_association => :thumbnails
0
+ #
0
+ def initialize(klass, options)
0
+ options[:parent_association] ||= :parent
0
+ options[:parent_foreign_key] ||= options[:parent_association].to_s.foreign_key
0
+ options[:thumbnails_association] ||= :thumbnails
0
+
0
+ @thumbnail_class = options[:thumbnail_class] || thumbnail_class_for(klass, options)
0
+
0
+ @thumbnail_class.attachment_tasks do
0
+ task :get_image_size, :with => options[:with] unless queued?(:get_image_size)
0
+ end
0
+ klass.attachment_tasks do
0
+ load :resize
0
+ task :get_image_size, :with => options[:with] unless queued?(:get_image_size)
0
+ end
0
+
0
+ unless klass.reflect_on_association(:parent)
0
+ klass.belongs_to options[:parent_association], :class_name => "::#{@thumbnail_class.name}", :foreign_key => options[:parent_foreign_key]
0
+ end
0
+
0
+ unless klass.reflect_on_association(:thumbnails)
0
+ klass.has_many options[:thumbnails_association], :class_name => "::#{@thumbnail_class.name}", :foreign_key => options[:parent_foreign_key]
0
+ end
0
       end
0
 
0
       # task :thumbnails, :sizes => {:thumb => '50x50', :tiny => [10, 10]}
0
       #
0
       def call(attachment, options)
0
- if @thumbnail_class.nil?
0
- @thumbnail_class = options[:thumbnail_class] || Class.new(attachment.class)
0
- @thumbnail_class.attachment_tasks.clear
0
- end
0
- puts "#{attachment.class.name} #{attachment.class.object_id} #{attachment.class.attachment_tasks.all.keys}"
0
         options[:sizes].each do |name, size|
0
           thumb_name = thumbnail_name_for(attachment, name)
0
- attachment.process :resize, :with => options[:with], :to => size, :destination => attachment.full_path(thumb_name)
0
+ attachment.process :resize, :with => options[:with], :to => size, :destination => attachment.full_path(thumb_name), :skip_save => true, :skip_size => true
0
           thumb = @thumbnail_class.new do |thumb|
0
+ thumb.send("#{options[:parent_foreign_key]}=", attachment.id)
0
             thumb.thumbnail = name.to_s
0
             thumb.filename = thumb_name
0
             thumb.content_type = attachment.content_type
0
             thumb.temp_path = attachment.full_path(thumb_name)
0
- puts thumb.inspect
0
+ end
0
+ thumb.save!
0
+ end
0
+ end
0
+
0
+ # Creates a default thumbnail class, which is just a subclass
0
+ # of the attachment with no tasks, and a modified #attachment_path_id
0
+ # to use #parent_id instead of #id
0
+ def thumbnail_class_for(klass, options)
0
+ thumb_class = Class.new klass do
0
+ attachment_tasks.clear
0
+
0
+ validates_presence_of options[:parent_foreign_key]
0
+
0
+ # The attachment ID used in the full path of a file
0
+ def attachment_path_id
0
+ parent_id
0
           end
0
         end
0
+ klass.const_set(:Thumbnail, thumb_class)
0
       end
0
 
0
       def thumbnail_name_for(attachment, thumbnail = nil)
...
4
5
6
7
 
8
9
10
...
18
19
20
21
 
22
23
24
25
26
 
27
28
29
30
31
32
 
33
34
 
35
36
37
...
56
57
58
59
 
60
61
62
...
4
5
6
 
7
8
9
10
...
18
19
20
 
21
22
23
24
25
 
26
27
28
29
30
31
 
32
33
 
34
35
36
37
...
56
57
58
 
59
60
61
62
0
@@ -4,7 +4,7 @@ module AttachmentFu
0
   class BasicAsset < ActiveRecord::Base
0
     is_faux_attachment
0
   end
0
-
0
+
0
   class QueuedAsset < ActiveRecord::Base
0
     is_faux_attachment :queued => true
0
   end
0
@@ -18,20 +18,20 @@ module AttachmentFu
0
       it "has nil #full_filename" do
0
         @asset.full_filename.should be_nil
0
       end
0
-
0
+
0
       it "has nil #partitioned_path" do
0
         @asset.partitioned_path.should == nil
0
       end
0
     end
0
-
0
+
0
     describe "being processed" do
0
       before do
0
         @file = File.join(File.dirname(__FILE__), 'guinea_pig.rb')
0
         FileUtils.cp __FILE__, @file
0
       end
0
-
0
+
0
       after { @asset.destroy }
0
-
0
+
0
       it "attempts to process the attachment" do
0
         @asset = BasicAsset.create!(:content_type => 'application/x-ruby', :temp_path => @file)
0
         @asset.should_not be_queued
0
@@ -56,7 +56,7 @@ module AttachmentFu
0
       end
0
       
0
       it "stores asset in AttachmentFu root_path" do
0
- @asset.full_filename.should == File.join(AttachmentFu.root_path, "public/afu_spec_assets/#{@asset.partitioned_path * '/'}/guinea_pig.rb")
0
+ @asset.full_filename.should == File.expand_path(File.join(AttachmentFu.root_path, "public/afu_spec_assets/#{@asset.partitioned_path * '/'}/guinea_pig.rb"))
0
       end
0
       
0
       it "creates partitioned path from the record id" do
...
1
2
 
3
4
5
...
1
 
2
3
4
5
0
@@ -1,5 +1,5 @@
0
 class SampleObjectTask
0
- def initialize(klass)
0
+ def initialize(klass, options)
0
   end
0
   
0
   def call(attachment, options)
...
65
66
67
68
 
69
70
71
...
65
66
67
 
68
69
70
71
0
@@ -65,6 +65,6 @@ module AttachmentFu
0
   end
0
 end
0
 
0
-AttachmentFu.root_path = File.join(File.dirname(__FILE__), 'assets')
0
+AttachmentFu.root_path = File.expand_path(File.join(File.dirname(__FILE__), 'assets'))
0
 
0
 Debugger.start
0
\ No newline at end of file
...
5
6
7
8
9
10
11
12
...
15
16
17
18
 
19
20
21
22
23
 
24
25
26
27
28
29
30
31
32
33
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
36
37
...
5
6
7
 
 
8
9
10
...
13
14
15
 
16
17
18
19
20
 
21
22
23
24
 
 
 
 
 
 
 
 
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
0
@@ -5,8 +5,6 @@ module AttachmentFu
0
     is_faux_attachment do
0
       task :thumbnails, :sizes => {:small => '40x40', :tiny => '10x10'}
0
     end
0
- belongs_to :parent, :class_name => "AttachmentFu::ThumbnailsTaskAsset", :foreign_key => 'parent_id'
0
- has_many :thumbnails, :class_name => "AttachmentFu::ThumbnailsTaskAsset", :foreign_key => 'parent_id'
0
   end
0
   
0
   describe "Thumbnails Task" do
0
@@ -15,23 +13,30 @@ module AttachmentFu
0
       @original = File.join(@samples, 'casshern.jpg')
0
       @sample = File.join(@samples, 'sample.jpg')
0
     end
0
-
0
+
0
     before do
0
       FileUtils.cp @original, @sample
0
       @asset = ThumbnailsTaskAsset.create! :content_type => 'image/jpg', :temp_path => @sample
0
     end
0
-
0
+
0
     it "saves attachment" do
0
       File.exist? @asset.full_filename
0
     end
0
-
0
- #it "resizes image" do
0
- # @pixels = AttachmentFu::Pixels.new :core_image, @asset.full_filename
0
- # @pixels.with_image do |image|
0
- # image.extent.size.width.should == 40
0
- # image.extent.size.height.should == 38
0
- # end
0
- #end
0
+
0
+ it "creates correct number of thumbnails with matching thumbnail keys" do
0
+ @asset.should have(2).thumbnails
0
+ @asset.thumbnails.sort_by { |t| t.thumbnail }.map(&:thumbnail).should == %w(small tiny)
0
+ end
0
+
0
+ it "keeps the attachment's original width" do
0
+ @asset.width.should == 80
0
+ end
0
+
0
+ it "resizes thumbnails to the resized width" do
0
+ @asset.thumbnails.each do |thumb|
0
+ thumb.width.should == (thumb.thumbnail == 'small' ? 40 : 10)
0
+ end
0
+ end
0
 
0
     before :all do
0
       ThumbnailsTaskAsset.setup_spec_env
...
329
330
331
332
 
333
334
335
...
329
330
331
 
332
333
334
335
0
@@ -329,7 +329,7 @@ module AttachmentFu
0
   
0
   # simulates task that just might raise an error
0
   class FlakyTask
0
- def initialize(whatever)
0
+ def initialize(whatever, options)
0
     end
0
     
0
     def call(attachment, options)

Comments

    No one has commented yet.