delayed_paperclip 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -4,7 +4,7 @@ Delayed_paperclip lets you process your "Paperclip":http://github.com/thoughtbot
4
4
 
5
5
  h2. Why?
6
6
 
7
- The most common use case for Paperclip is to easily attach image files to ActiveRecord models. Most of the time these image files will have multiple styles and will need to be resized when they are created. This is usually a pretty "slow operation":http://www.jstorimer.com/ruby/2010/01/05/speep-up-your-paperclip-tests.html and should be handled in a background task.
7
+ The most common use case for Paperclip is to easily attach image files to ActiveRecord models. Most of the time these image files will have multiple styles and will need to be resized when they are created. This is usually a pretty "slow operation":http://www.jstorimer.com/ruby/2010/01/05/speep-up-your-paperclip-tests.html and should be handled in a background task.
8
8
 
9
9
  I'm sure that everyone knows this, this gem just makes it easy to do.
10
10
 
@@ -39,9 +39,9 @@ In your model:
39
39
  <pre><code>
40
40
  class User < ActiveRecord::Base
41
41
  has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
42
-
42
+
43
43
  process_in_background :avatar
44
- end
44
+ end
45
45
  </code></pre>
46
46
 
47
47
  Use your Paperclip attachment just like always in controllers and views.
@@ -56,6 +56,32 @@ h3. DJ
56
56
 
57
57
  Just make sure that you have DJ up and running.
58
58
 
59
+ h3. Displaying images during processing
60
+
61
+ In the default setup, when you upload an image for the first time and try to display it before the job has been completed, Paperclip will be none the wiser and output the url of the image which is yet to be processed, which will result in a broken image link being displayed on the page.
62
+
63
+ To have the missing image url be outputted by paperclip while the image is being processed, all you need to do is add a #{attachment_name}_processing column to the specific model you want to enable this feature for. This feature gracefully degrades and will not affect models which do not have the column added to them.
64
+
65
+ <pre><code>
66
+ class AddAvatarProessingToUser < ActiveRecord::Migration
67
+ def self.up
68
+ add_column :users, :avatar_processing, :boolean
69
+ end
70
+
71
+ def self.down
72
+ remove_column :users, :avatar_processing
73
+ end
74
+ end
75
+
76
+ @user = User.new(:avatar => File.new(...))
77
+ @user.save
78
+ @user.avatar.url #=> "/images/original/missing.png"
79
+ Delayed::Job.work_off
80
+
81
+ @user.reload
82
+ @user.avatar.url #=> "/system/images/3/original/IMG_2772.JPG?1267562148"
83
+ </code></pre>
84
+
59
85
  h2. What if I'm not using images?
60
86
 
61
87
  AFAIK this library should work no matter what kind of post-processing you are doing with Paperclip.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.2
1
+ 0.6.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{delayed_paperclip}
8
- s.version = "0.5.2"
8
+ s.version = "0.6.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jesse Storimer"]
12
- s.date = %q{2010-02-25}
12
+ s.date = %q{2010-03-02}
13
13
  s.description = %q{Process your Paperclip attachments in the background with delayed_job.}
14
14
  s.email = %q{jesse@jstorimer.com}
15
15
  s.extra_rdoc_files = [
@@ -2,6 +2,7 @@ class DelayedPaperclipJob < Struct.new(:instance_klass, :instance_id, :attachmen
2
2
  def perform
3
3
  instance = instance_klass.constantize.find(instance_id)
4
4
 
5
- instance.send(attachment_name).reprocess!
5
+ instance.send(attachment_name).reprocess!
6
+ instance.send("#{attachment_name}_processed!")
6
7
  end
7
8
  end
@@ -1,9 +1,10 @@
1
1
  class ResquePaperclipJob
2
2
  @queue = :paperclip
3
-
3
+
4
4
  def self.perform(instance_klass, instance_id, attachment_name)
5
5
  instance = instance_klass.constantize.find(instance_id)
6
6
 
7
- instance.send(attachment_name).reprocess!
7
+ instance.send(attachment_name).reprocess!
8
+ instance.send("#{attachment_name}_processed!")
8
9
  end
9
10
  end
@@ -3,57 +3,92 @@ module Delayed
3
3
  def self.included(base) #:nodoc:
4
4
  base.extend(ClassMethods)
5
5
  end
6
-
6
+
7
7
  module ClassMethods
8
8
  def process_in_background(name)
9
9
  include InstanceMethods
10
10
 
11
11
  define_method "#{name}_changed?" do
12
- attachment_has_changed?(name)
12
+ attachment_changed?(name)
13
13
  end
14
-
14
+
15
15
  define_method "halt_processing_for_#{name}" do
16
- if self.send("#{name}_changed?")
17
- false # halts processing
18
- end
16
+ return unless self.send("#{name}_changed?")
17
+
18
+ false # halts processing
19
19
  end
20
-
20
+
21
21
  define_method "enqueue_job_for_#{name}" do
22
- if self.send("#{name}_changed?")
23
- if delayed_job?
24
- Delayed::Job.enqueue DelayedPaperclipJob.new(self.class.name, read_attribute(:id), name.to_sym)
25
- elsif resque?
26
- Resque.enqueue(ResquePaperclipJob, self.class.name, read_attribute(:id), name.to_sym)
27
- end
22
+ return unless self.send("#{name}_changed?")
23
+
24
+ if delayed_job?
25
+ Delayed::Job.enqueue DelayedPaperclipJob.new(self.class.name, read_attribute(:id), name.to_sym)
26
+ elsif resque?
27
+ Resque.enqueue(ResquePaperclipJob, self.class.name, read_attribute(:id), name.to_sym)
28
28
  end
29
29
  end
30
-
30
+
31
+ define_method "#{name}_processed!" do
32
+ return unless column_exists?(:"#{name}_processing")
33
+ return unless self.send(:"#{name}_processing?")
34
+
35
+ self.send("#{name}_processing=", false)
36
+ self.save(false)
37
+ end
38
+
39
+ define_method "#{name}_processing!" do
40
+ return unless column_exists?(:"#{name}_processing")
41
+ return if self.send(:"#{name}_processing?")
42
+ return unless self.send(:"#{name}_changed?")
43
+
44
+ self.send("#{name}_processing=", true)
45
+ end
46
+
31
47
  self.send("before_#{name}_post_process", :"halt_processing_for_#{name}")
32
- after_save :"enqueue_job_for_#{name}"
48
+
49
+ before_save :"#{name}_processing!"
50
+ after_save :"enqueue_job_for_#{name}"
33
51
  end
34
52
  end
35
-
53
+
36
54
  module InstanceMethods
37
55
  PAPERCLIP_ATTRIBUTES = ['_file_size', '_file_name', '_content_type', '_updated_at']
38
-
39
- def attachment_has_changed?(name)
56
+
57
+ def attachment_changed?(name)
40
58
  PAPERCLIP_ATTRIBUTES.each do |attribute|
41
59
  full_attribute = "#{name}#{attribute}_changed?".to_sym
42
60
 
43
61
  next unless self.respond_to?(full_attribute)
44
62
  return true if self.send("#{name}#{attribute}_changed?")
45
63
  end
46
-
64
+
47
65
  false
48
66
  end
49
-
67
+
50
68
  def delayed_job?
51
69
  defined? Delayed::Job
52
70
  end
53
-
71
+
54
72
  def resque?
55
73
  defined? Resque
56
74
  end
75
+
76
+ def column_exists?(column)
77
+ self.class.columns_hash.has_key?(column.to_s)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ module Paperclip
84
+ class Attachment
85
+ def url_with_processed style = default_style, include_updated_timestamp = true
86
+ if @instance.column_exists?(:"#{@name}_processing") && !@instance.send(:"#{@name}_processing?")
87
+ url_without_processed style, include_updated_timestamp
88
+ else
89
+ interpolate(@default_url, style)
90
+ end
57
91
  end
92
+ alias_method_chain :url, :processed
58
93
  end
59
94
  end
@@ -7,40 +7,42 @@ class DelayedPaperclipTest < Test::Unit::TestCase
7
7
  build_delayed_jobs
8
8
  reset_dummy
9
9
  end
10
-
10
+
11
11
  def test_attachment_changed
12
12
  @dummy.stubs(:image_file_size_changed?).returns(false)
13
13
  @dummy.stubs(:image_file_name_changed?).returns(false)
14
-
14
+ @dummy.stubs(:image_content_type_changed?).returns(false)
15
+ @dummy.stubs(:image_updated_at_changed?).returns(false)
16
+
15
17
  assert !@dummy.image_changed?
16
18
  end
17
-
19
+
18
20
  def test_attachment_changed_when_image_changes
19
21
  @dummy.stubs(:image_file_size_changed?).returns(true)
20
22
 
21
23
  assert @dummy.image_changed?
22
24
  end
23
-
25
+
24
26
  def test_before_post_process
25
27
  Dummy.expects(:before_image_post_process)
26
28
  @dummy_class.process_in_background :image
27
29
  end
28
-
30
+
29
31
  def test_halt_processing_if_source_changed
30
32
  @dummy.stubs(:image_changed?).returns(true)
31
33
  assert !@dummy.halt_processing_for_image
32
34
  end
33
-
35
+
34
36
  def test_halt_processing_if_source_has_not_changed
35
37
  @dummy.stubs(:image_changed?).returns(false)
36
38
  assert_not_equal false, @dummy.halt_processing_for_image
37
39
  end
38
-
40
+
39
41
  def test_after_save
40
42
  Dummy.expects(:after_save)
41
43
  @dummy_class.process_in_background :image
42
44
  end
43
-
45
+
44
46
  def test_enqueue_job_if_source_changed
45
47
  @dummy.stubs(:image_changed?).returns(true)
46
48
 
@@ -57,13 +59,48 @@ class DelayedPaperclipTest < Test::Unit::TestCase
57
59
  @dummy.save!
58
60
  Delayed::Job.last.payload_object.perform
59
61
  end
60
-
62
+
61
63
  def test_after_callback_is_functional
62
64
  @dummy_class.send(:define_method, :done_processing) { puts 'done' }
63
- @dummy_class.after_image_post_process :done_processing
65
+ @dummy_class.after_image_post_process :done_processing
64
66
  Dummy.any_instance.expects(:done_processing)
65
67
 
66
68
  @dummy.save!
67
69
  DelayedPaperclipJob.new(@dummy.class.name, @dummy.id, :image).perform
68
70
  end
71
+
72
+ def test_processed_method_returns_nil_if_column_does_not_exist
73
+ assert_equal nil, @dummy.image_processed!
74
+ end
75
+
76
+ def test_processing_true_when_new_image_added
77
+ @dummy = reset_dummy(true)
78
+
79
+ assert !@dummy.image_processing?
80
+ assert @dummy.new_record?
81
+ @dummy.save!
82
+ assert @dummy.reload.image_processing?
83
+ end
84
+
85
+ def test_processed_true_when_delayed_jobs_completed
86
+ @dummy = reset_dummy(true)
87
+ @dummy.save!
88
+
89
+ Delayed::Job.work_off
90
+
91
+ @dummy.reload
92
+ assert !@dummy.image_processing?
93
+ end
94
+
95
+ def test_unprocessed_image_returns_missing_url
96
+ @dummy = reset_dummy(true)
97
+ @dummy.save!
98
+
99
+ assert_equal "/images/original/missing.png", @dummy.image.url
100
+
101
+ Delayed::Job.first.invoke_job
102
+
103
+ @dummy.reload
104
+ assert_match(/\/system\/images\/1\/original\/12k.png/, @dummy.image.url)
105
+ end
69
106
  end
data/test/test_helper.rb CHANGED
@@ -10,7 +10,7 @@ gem 'sqlite3-ruby'
10
10
  gem 'paperclip'
11
11
  require 'paperclip'
12
12
 
13
- FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
13
+ FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
14
14
  config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
15
15
  ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
16
16
  ActiveRecord::Base.establish_connection(config['test'])
@@ -20,59 +20,40 @@ RAILS_ROOT = ROOT
20
20
  RAILS_ENV = "test"
21
21
 
22
22
  $LOAD_PATH << File.join(ROOT, 'lib')
23
- $LOAD_PATH << File.join(ROOT, 'lib', 'delayed', 'paperclip')
24
- $LOAD_PATH << File.join(ROOT, 'test')
23
+ $LOAD_PATH << File.join(ROOT, 'lib', 'delayed', 'paperclip')
24
+ $LOAD_PATH << File.join(ROOT, 'test')
25
25
 
26
26
  require File.join(ROOT, 'lib', 'delayed_paperclip.rb')
27
27
 
28
- def reset_class class_name
29
- ActiveRecord::Base.send(:include, Paperclip)
30
- Object.send(:remove_const, class_name) rescue nil
31
- klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
32
- klass.class_eval{ include Paperclip }
33
- klass
34
- end
35
28
 
36
- def reset_table table_name, &block
37
- block ||= lambda { |table| true }
38
- ActiveRecord::Base.connection.create_table :dummies, {:force => true}, &block
39
- end
29
+ def reset_dummy(with_processed = false)
30
+ build_dummy_table(with_processed)
40
31
 
41
- def modify_table table_name, &block
42
- ActiveRecord::Base.connection.change_table :dummies, &block
43
- end
32
+ reset_class "Dummy"
44
33
 
45
- def rebuild_model options = {}
46
- ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
47
- table.column :other, :string
48
- table.column :avatar_file_name, :string
49
- table.column :avatar_content_type, :string
50
- table.column :avatar_file_size, :integer
51
- table.column :avatar_updated_at, :datetime
52
- end
53
- rebuild_class options
34
+ @dummy = Dummy.new(:image => File.open("#{RAILS_ROOT}/test/fixtures/12k.png"))
54
35
  end
55
36
 
56
- def rebuild_class options = {}
37
+ def reset_class class_name
57
38
  ActiveRecord::Base.send(:include, Paperclip)
58
- Object.send(:remove_const, "Dummy") rescue nil
59
- Object.const_set("Dummy", Class.new(ActiveRecord::Base))
60
- Dummy.class_eval do
61
- include Paperclip
62
- has_attached_file :avatar, options
39
+ Object.send(:remove_const, class_name) rescue nil
40
+ klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
41
+ klass.class_eval do
42
+ has_attached_file :image
43
+ process_in_background :image
63
44
  end
45
+ @dummy_class = klass
64
46
  end
65
47
 
66
- def reset_dummy
67
- reset_table("dummies") do |d|
68
- d.string :image_file_name
69
- d.integer :image_file_size
48
+ def build_dummy_table(with_processed)
49
+ ActiveRecord::Base.connection.create_table :dummies, { :force => true } do |t|
50
+ t.string :image_file_name
51
+ t.string :image_content_type
52
+ t.integer :image_file_size
53
+ t.datetime :image_updated_at
54
+
55
+ t.boolean(:image_processing, :default => false) if with_processed
70
56
  end
71
- @dummy_class = reset_class "Dummy"
72
- @dummy_class.has_attached_file :image
73
- @dummy_class.process_in_background :image
74
-
75
- @dummy = Dummy.new(:image => File.open("#{RAILS_ROOT}/test/fixtures/12k.png"))
76
57
  end
77
58
 
78
59
  def build_delayed_jobs
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 5
8
- - 2
9
- version: 0.5.2
7
+ - 6
8
+ - 0
9
+ version: 0.6.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Jesse Storimer
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-02-25 00:00:00 -05:00
17
+ date: 2010-03-02 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency