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 +29 -3
- data/VERSION +1 -1
- data/delayed_paperclip.gemspec +2 -2
- data/lib/delayed/jobs/delayed_paperclip_job.rb +2 -1
- data/lib/delayed/jobs/resque_paperclip_job.rb +3 -2
- data/lib/delayed/paperclip.rb +56 -21
- data/test/delayed_paperclip_test.rb +47 -10
- data/test/test_helper.rb +22 -41
- metadata +4 -4
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.
|
1
|
+
0.6.0
|
data/delayed_paperclip.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{delayed_paperclip}
|
8
|
-
s.version = "0.
|
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
|
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
|
data/lib/delayed/paperclip.rb
CHANGED
@@ -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
|
-
|
12
|
+
attachment_changed?(name)
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
define_method "halt_processing_for_#{name}" do
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
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
|
37
|
-
|
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
|
-
|
42
|
-
ActiveRecord::Base.connection.change_table :dummies, &block
|
43
|
-
end
|
32
|
+
reset_class "Dummy"
|
44
33
|
|
45
|
-
|
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
|
37
|
+
def reset_class class_name
|
57
38
|
ActiveRecord::Base.send(:include, Paperclip)
|
58
|
-
Object.send(:remove_const,
|
59
|
-
Object.const_set(
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
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
|
17
|
+
date: 2010-03-02 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|