twm_paperclip 2.3.6 → 2.3.8t

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,24 @@
1
- =Paperclip
1
+ =TWM Paperclip
2
+
3
+ The TWM Paperclip branch is intended to make life using the S3 storage engine
4
+ easier.
5
+
6
+ In attempting to use it on one of my personal projects, I found two showstopper
7
+ issues that I had to get around:
8
+
9
+ 1. When storing JPG thumbnails of PDF documents, the JPGs were uploaded to
10
+ S3 with the mime_type "application/pdf", which breaks behaviour in many
11
+ browsers and is semantically incorrect.
12
+
13
+ 2. When these PDFs were protected documents (stored with S3 under a secure
14
+ URL), I needed the thumbnails to be uploaded to S3 as publicly accessible
15
+
16
+ This patch is not elegant, and should not be contributed as it is to Paperclip
17
+ core. However it does illustrate some key use-cases that should be considered
18
+ for core Paperclip.
19
+
20
+ Paperclip
21
+ =========
2
22
 
3
23
  Paperclip is intended as an easy file attachment library for ActiveRecord. The
4
24
  intent behind it was to keep setup as easy as possible and to treat files as
@@ -12,64 +32,77 @@ packages). Attached files are saved to the filesystem and referenced in the
12
32
  browser by an easily understandable specification, which has sensible and
13
33
  useful defaults.
14
34
 
15
- See the documentation for +has_attached_file+ in Paperclip::ClassMethods for
35
+ See the documentation for `has_attached_file` in Paperclip::ClassMethods for
16
36
  more detailed options.
17
37
 
18
- The complete RDoc[http://rdoc.info/projects/thoughtbot/paperclip] is online.
38
+ The complete [RDoc](http://rdoc.info/gems/paperclip) is online.
19
39
 
20
- ==Quick Start
40
+ Installation
41
+ ------------
21
42
 
22
- In your model:
43
+ Include the gem in your Gemfile:
23
44
 
24
- class User < ActiveRecord::Base
25
- has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
26
- end
45
+ gem "paperclip", "~> 2.3"
27
46
 
28
- In your migrations:
47
+ Or as a plugin:
48
+
49
+ ruby script/plugin install git://github.com/thoughtbot/paperclip.git
50
+
51
+ Quick Start
52
+ -----------
53
+
54
+ In your model:
29
55
 
30
- class AddAvatarColumnsToUser < ActiveRecord::Migration
31
- def self.up
32
- add_column :users, :avatar_file_name, :string
33
- add_column :users, :avatar_content_type, :string
34
- add_column :users, :avatar_file_size, :integer
35
- add_column :users, :avatar_updated_at, :datetime
56
+ class User < ActiveRecord::Base
57
+ has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
36
58
  end
37
59
 
38
- def self.down
39
- remove_column :users, :avatar_file_name
40
- remove_column :users, :avatar_content_type
41
- remove_column :users, :avatar_file_size
42
- remove_column :users, :avatar_updated_at
60
+ In your migrations:
61
+
62
+ class AddAvatarColumnsToUser < ActiveRecord::Migration
63
+ def self.up
64
+ add_column :users, :avatar_file_name, :string
65
+ add_column :users, :avatar_content_type, :string
66
+ add_column :users, :avatar_file_size, :integer
67
+ add_column :users, :avatar_updated_at, :datetime
68
+ end
69
+
70
+ def self.down
71
+ remove_column :users, :avatar_file_name
72
+ remove_column :users, :avatar_content_type
73
+ remove_column :users, :avatar_file_size
74
+ remove_column :users, :avatar_updated_at
75
+ end
43
76
  end
44
- end
45
77
 
46
78
  In your edit and new views:
47
79
 
48
- <% form_for :user, @user, :url => user_path, :html => { :multipart => true } do |form| %>
49
- <%= form.file_field :avatar %>
50
- <% end %>
80
+ <% form_for :user, @user, :url => user_path, :html => { :multipart => true } do |form| %>
81
+ <%= form.file_field :avatar %>
82
+ <% end %>
51
83
 
52
84
  In your controller:
53
85
 
54
- def create
55
- @user = User.create( params[:user] )
56
- end
86
+ def create
87
+ @user = User.create( params[:user] )
88
+ end
57
89
 
58
90
  In your show view:
59
91
 
60
- <%= image_tag @user.avatar.url %>
61
- <%= image_tag @user.avatar.url(:medium) %>
62
- <%= image_tag @user.avatar.url(:thumb) %>
92
+ <%= image_tag @user.avatar.url %>
93
+ <%= image_tag @user.avatar.url(:medium) %>
94
+ <%= image_tag @user.avatar.url(:thumb) %>
63
95
 
64
- ==Usage
96
+ Usage
97
+ -----
65
98
 
66
99
  The basics of paperclip are quite simple: Declare that your model has an
67
100
  attachment with the has_attached_file method, and give it a name. Paperclip
68
101
  will wrap up up to four attributes (all prefixed with that attachment's name,
69
102
  so you can have multiple attachments per model if you wish) and give the a
70
- friendly front end. The attributes are <attachment>_file_name,
71
- <attachment>_file_size, <attachment>_content_type, and <attachment>_updated_at.
72
- Only <attachment>_file_name is required for paperclip to operate. More
103
+ friendly front end. The attributes are `<attachment>_file_name`,
104
+ `<attachment>_file_size`, `<attachment>_content_type`, and `<attachment>_updated_at`.
105
+ Only `<attachment>_file_name` is required for paperclip to operate. More
73
106
  information about the options to has_attached_file is available in the
74
107
  documentation of Paperclip::ClassMethods.
75
108
 
@@ -77,7 +110,8 @@ Attachments can be validated with Paperclip's validation methods,
77
110
  validates_attachment_presence, validates_attachment_content_type, and
78
111
  validates_attachment_size.
79
112
 
80
- ==Storage
113
+ Storage
114
+ -------
81
115
 
82
116
  The files that are assigned as attachments are, by default, placed in the
83
117
  directory specified by the :path option to has_attached_file. By default, this
@@ -87,10 +121,10 @@ public/system directory is symlinked to the app's shared directory, meaning it
87
121
  will survive between deployments. For example, using that :path, you may have a
88
122
  file at
89
123
 
90
- /data/myapp/releases/20081229172410/public/system/avatars/13/small/my_pic.png
124
+ /data/myapp/releases/20081229172410/public/system/avatars/13/small/my_pic.png
91
125
 
92
- NOTE: This is a change from previous versions of Paperclip, but is overall a
93
- safer choice for the default file store.
126
+ _NOTE: This is a change from previous versions of Paperclip, but is overall a
127
+ safer choice for the default file store._
94
128
 
95
129
  You may also choose to store your files using Amazon's S3 service. You can find
96
130
  more information about S3 storage at the description for
@@ -103,7 +137,8 @@ both the :path and :url options in order to make sure the files are unavailable
103
137
  to the public. Both :path and :url allow the same set of interpolated
104
138
  variables.
105
139
 
106
- ==Post Processing
140
+ Post Processing
141
+ ---------------
107
142
 
108
143
  Paperclip supports an extensible selection of post-processors. When you define
109
144
  a set of styles for an attachment, by default it is expected that those
@@ -114,8 +149,8 @@ your Rails app's lib/paperclip_processors directory is automatically loaded by
114
149
  paperclip, allowing you to easily define custom processors. You can specify a
115
150
  processor with the :processors option to has_attached_file:
116
151
 
117
- has_attached_file :scan, :styles => { :text => { :quality => :better } },
118
- :processors => [:ocr]
152
+ has_attached_file :scan, :styles => { :text => { :quality => :better } },
153
+ :processors => [:ocr]
119
154
 
120
155
  This would load the hypothetical class Paperclip::Ocr, which would have the
121
156
  hash "{ :quality => :better }" passed to it along with the uploaded file. For
@@ -125,7 +160,7 @@ The default processor is Paperclip::Thumbnail. For backwards compatability
125
160
  reasons, you can pass a single geometry string or an array containing a
126
161
  geometry and a format, which the file will be converted to, like so:
127
162
 
128
- has_attached_file :avatar, :styles => { :thumb => ["32x32#", :png] }
163
+ has_attached_file :avatar, :styles => { :thumb => ["32x32#", :png] }
129
164
 
130
165
  This will convert the "thumb" style to a 32x32 square in png format, regardless
131
166
  of what was uploaded. If the format is not specified, it is kept the same (i.e.
@@ -137,39 +172,42 @@ be given the result of the previous processor's execution. All processors will
137
172
  receive the same parameters, which are what you define in the :styles hash.
138
173
  For example, assuming we had this definition:
139
174
 
140
- has_attached_file :scan, :styles => { :text => { :quality => :better } },
141
- :processors => [:rotator, :ocr]
175
+ has_attached_file :scan, :styles => { :text => { :quality => :better } },
176
+ :processors => [:rotator, :ocr]
142
177
 
143
178
  then both the :rotator processor and the :ocr processor would receive the
144
179
  options "{ :quality => :better }". This parameter may not mean anything to one
145
180
  or more or the processors, and they are expected to ignore it.
146
181
 
147
- NOTE: Because processors operate by turning the original attachment into the
148
- styles, no processors will be run if there are no styles defined.
182
+ _NOTE: Because processors operate by turning the original attachment into the
183
+ styles, no processors will be run if there are no styles defined._
149
184
 
150
- ==Events
185
+ Events
186
+ ------
151
187
 
152
188
  Before and after the Post Processing step, Paperclip calls back to the model
153
189
  with a few callbacks, allowing the model to change or cancel the processing
154
- step. The callbacks are "before_post_process" and "after_post_process" (which
190
+ step. The callbacks are `before_post_process` and `after_post_process` (which
155
191
  are called before and after the processing of each attachment), and the
156
- attachment-specific "before_<attachment>_post_process" and
157
- "after_<attachment>_post_process". The callbacks are intended to be as close to
192
+ attachment-specific `before_<attachment>_post_process` and
193
+ `after_<attachment>_post_process`. The callbacks are intended to be as close to
158
194
  normal ActiveRecord callbacks as possible, so if you return false (specifically
159
195
  - returning nil is not the same) in a before_ filter, the post processing step
160
196
  will halt. Returning false in an after_ filter will not halt anything, but you
161
197
  can access the model and the attachment if necessary.
162
198
 
163
- NOTE: Post processing will not even *start* if the attachment is not valid
199
+ _NOTE: Post processing will not even *start* if the attachment is not valid
164
200
  according to the validations. Your callbacks and processors will *only* be
165
- called with valid attachments.
201
+ called with valid attachments._
166
202
 
167
- ==Testing
203
+ Testing
204
+ -------
168
205
 
169
206
  Paperclip provides rspec-compatible matchers for testing attachments. See the
170
207
  documentation on Paperclip::Shoulda::Matchers for more information.
171
208
 
172
- ==Contributing
209
+ Contributing
210
+ ------------
173
211
 
174
212
  If you'd like to contribute a feature or bugfix: Thanks! To make sure your
175
213
  fix/feature has a high chance of being included, please read the following
@@ -180,3 +218,19 @@ guidelines:
180
218
  2. Make sure there are tests! We will not accept any patch that is not tested.
181
219
  It's a rare time when explicit tests aren't needed. If you have questions
182
220
  about writing tests for paperclip, please ask the mailing list.
221
+
222
+ Credits
223
+ -------
224
+
225
+ ![thoughtbot](http://thoughtbot.com/images/tm/logo.png)
226
+
227
+ Paperclip is maintained and funded by [thoughtbot, inc](http://thoughtbot.com/community)
228
+
229
+ Thank you to all [the contributors](https://github.com/thoughtbot/paperclip/contributors)!
230
+
231
+ The names and logos for thoughtbot are trademarks of thoughtbot, inc.
232
+
233
+ License
234
+ -------
235
+
236
+ Paperclip is Copyright © 2008-2011 thoughtbot. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
@@ -235,6 +235,9 @@ module Paperclip
235
235
  else
236
236
  true
237
237
  end
238
+ rescue Errno::EACCES => e
239
+ warn "#{e} - skipping file"
240
+ false
238
241
  end
239
242
 
240
243
  # Returns true if a file has been assigned.
@@ -94,8 +94,8 @@ module Paperclip
94
94
  end
95
95
  end
96
96
 
97
- def expiring_url(time = 3600)
98
- AWS::S3::S3Object.url_for(path, bucket_name, :expires_in => time )
97
+ def expiring_url(time = 3600, style = default_style)
98
+ AWS::S3::S3Object.url_for(path(style), bucket_name, :expires_in => time )
99
99
  end
100
100
 
101
101
  def bucket_name
@@ -147,21 +147,28 @@ module Paperclip
147
147
  log("saving #{path(style)}")
148
148
  # Nasty hack, thumbnails for PDFs generated in jpg/png/gif are NOT the content_type of the original file!
149
149
  # Override the content_type based on these extensions.
150
- content_type = case(file.path)
151
- when /.jpg$/
152
- "image/jpeg"
153
- when /.png$/
154
- "image/png"
155
- when /.gif$/
156
- "image/gif"
157
- else
158
- instance_read(:content_type)
150
+ access = @s3_permissions
151
+ unless style == :default_style
152
+ content_type = case(file.path)
153
+ when /.jpg$/
154
+ "image/jpeg"
155
+ access = :public_read
156
+ when /.png$/
157
+ "image/png"
158
+ access = :public_read
159
+ when /.gif$/
160
+ "image/gif"
161
+ access = :public_read
162
+ else
163
+ instance_read(:content_type)
164
+ end
159
165
  end
160
166
  AWS::S3::S3Object.store(path(style),
161
167
  file,
162
168
  bucket_name,
163
- {:content_type => content_type,#instance_read(:content_type),
164
- :access => @s3_permissions,
169
+ {
170
+ :content_type => content_type,#instance_read(:content_type),
171
+ :access => access,
165
172
  }.merge(@s3_headers))
166
173
  rescue AWS::S3::NoSuchBucket => e
167
174
  create_bucket
@@ -1,3 +1,3 @@
1
1
  module Paperclip
2
- VERSION = "2.3.6" unless defined? Paperclip::VERSION
2
+ VERSION = "2.3.8t" unless defined? Paperclip::VERSION
3
3
  end
data/lib/paperclip.rb CHANGED
@@ -113,6 +113,12 @@ module Paperclip
113
113
  processor
114
114
  end
115
115
 
116
+ def each_instance_with_attachment(klass, name)
117
+ Object.const_get(klass).all.each do |instance|
118
+ yield(instance) if instance.send(:"#{name}?")
119
+ end
120
+ end
121
+
116
122
  # Log a paperclip-specific line. Uses ActiveRecord::Base.logger
117
123
  # by default. Set Paperclip.options[:log] to false to turn off.
118
124
  def log message
@@ -1,38 +1,20 @@
1
1
  def obtain_class
2
2
  class_name = ENV['CLASS'] || ENV['class']
3
3
  raise "Must specify CLASS" unless class_name
4
- @klass = Object.const_get(class_name)
4
+ class_name
5
5
  end
6
6
 
7
- def obtain_attachments
7
+ def obtain_attachments(klass)
8
+ klass = Object.const_get(klass.to_s)
8
9
  name = ENV['ATTACHMENT'] || ENV['attachment']
9
- raise "Class #{@klass.name} has no attachments specified" unless @klass.respond_to?(:attachment_definitions)
10
- if !name.blank? && @klass.attachment_definitions.keys.include?(name)
10
+ raise "Class #{klass.name} has no attachments specified" unless klass.respond_to?(:attachment_definitions)
11
+ if !name.blank? && klass.attachment_definitions.keys.include?(name)
11
12
  [ name ]
12
13
  else
13
- @klass.attachment_definitions.keys
14
+ klass.attachment_definitions.keys
14
15
  end
15
16
  end
16
17
 
17
- def for_all_attachments
18
- klass = obtain_class
19
- names = obtain_attachments
20
- ids = klass.connection.select_values(klass.send(:construct_finder_sql, :select => 'id'))
21
-
22
- ids.each do |id|
23
- instance = klass.find(id)
24
- names.each do |name|
25
- result = if instance.send("#{ name }?")
26
- yield(instance, name)
27
- else
28
- true
29
- end
30
- print result ? "." : "x"; $stdout.flush
31
- end
32
- end
33
- puts " Done."
34
- end
35
-
36
18
  namespace :paperclip do
37
19
  desc "Refreshes both metadata and thumbnails."
38
20
  task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"]
@@ -41,24 +23,31 @@ namespace :paperclip do
41
23
  desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT)."
42
24
  task :thumbnails => :environment do
43
25
  errors = []
44
- for_all_attachments do |instance, name|
45
- result = instance.send(name).reprocess!
46
- errors << [instance.id, instance.errors] unless instance.errors.blank?
47
- result
26
+ klass = obtain_class
27
+ names = obtain_attachments(klass)
28
+ names.each do |name|
29
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
30
+ result = instance.send(name).reprocess!
31
+ errors << [instance.id, instance.errors] unless instance.errors.blank?
32
+ end
48
33
  end
49
34
  errors.each{|e| puts "#{e.first}: #{e.last.full_messages.inspect}" }
50
35
  end
51
36
 
52
37
  desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)."
53
38
  task :metadata => :environment do
54
- for_all_attachments do |instance, name|
55
- if file = instance.send(name).to_file
56
- instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip)
57
- instance.send("#{name}_content_type=", file.content_type.strip)
58
- instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size")
59
- instance.save(false)
60
- else
61
- true
39
+ klass = obtain_class
40
+ names = obtain_attachments(klass)
41
+ names.each do |name|
42
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
43
+ if file = instance.send(name).to_file
44
+ instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip)
45
+ instance.send("#{name}_content_type=", file.content_type.strip)
46
+ instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size")
47
+ instance.save(false)
48
+ else
49
+ true
50
+ end
62
51
  end
63
52
  end
64
53
  end
@@ -66,13 +55,17 @@ namespace :paperclip do
66
55
 
67
56
  desc "Cleans out invalid attachments. Useful after you've added new validations."
68
57
  task :clean => :environment do
69
- for_all_attachments do |instance, name|
70
- instance.send(name).send(:validate)
71
- if instance.send(name).valid?
72
- true
73
- else
74
- instance.send("#{name}=", nil)
75
- instance.save
58
+ klass = obtain_class
59
+ names = obtain_attachments(klass)
60
+ names.each do |name|
61
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
62
+ instance.send(name).send(:validate)
63
+ if instance.send(name).valid?
64
+ true
65
+ else
66
+ instance.send("#{name}=", nil)
67
+ instance.save
68
+ end
76
69
  end
77
70
  end
78
71
  end
@@ -34,6 +34,22 @@ class IntegrationTest < Test::Unit::TestCase
34
34
  should "create its thumbnails properly" do
35
35
  assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:thumb)}"`
36
36
  end
37
+
38
+ context 'reprocessing with unreadable original' do
39
+ setup { File.chmod(0000, @dummy.avatar.path) }
40
+
41
+ should "not raise an error" do
42
+ assert_nothing_raised do
43
+ @dummy.avatar.reprocess!
44
+ end
45
+ end
46
+
47
+ should "return false" do
48
+ assert ! @dummy.avatar.reprocess!
49
+ end
50
+
51
+ teardown { File.chmod(0644, @dummy.avatar.path) }
52
+ end
37
53
 
38
54
  context "redefining its attachment styles" do
39
55
  setup do
@@ -43,6 +43,23 @@ class PaperclipTest < Test::Unit::TestCase
43
43
  end
44
44
  end
45
45
 
46
+ context "Paperclip.each_instance_with_attachment" do
47
+ setup do
48
+ @file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
49
+ d1 = Dummy.create(:avatar => @file)
50
+ d2 = Dummy.create
51
+ d3 = Dummy.create(:avatar => @file)
52
+ @expected = [d1, d3]
53
+ end
54
+ should "yield every instance of a model that has an attachment" do
55
+ actual = []
56
+ Paperclip.each_instance_with_attachment("Dummy", "avatar") do |instance|
57
+ actual << instance
58
+ end
59
+ assert_same_elements @expected, actual
60
+ end
61
+ end
62
+
46
63
  should "raise when sent #processor and the name of a class that exists but isn't a subclass of Processor" do
47
64
  assert_raises(Paperclip::PaperclipError){ Paperclip.processor(:attachment) }
48
65
  end
data/test/storage_test.rb CHANGED
@@ -7,6 +7,29 @@ class StorageTest < Test::Unit::TestCase
7
7
  Object.const_set(:Rails, stub('Rails', :env => env))
8
8
  end
9
9
  end
10
+
11
+ context "filesystem" do
12
+ setup do
13
+ rebuild_model :styles => { :thumbnail => "25x25#" }
14
+ @dummy = Dummy.create!
15
+
16
+ @dummy.avatar = File.open(File.join(File.dirname(__FILE__), "fixtures", "5k.png"))
17
+ end
18
+
19
+ should "allow file assignment" do
20
+ assert @dummy.save
21
+ end
22
+
23
+ should "store the original" do
24
+ @dummy.save
25
+ assert File.exists?(@dummy.avatar.path)
26
+ end
27
+
28
+ should "store the thumbnail" do
29
+ @dummy.save
30
+ assert File.exists?(@dummy.avatar.path(:thumbnail))
31
+ end
32
+ end
10
33
 
11
34
  context "Parsing S3 credentials" do
12
35
  setup do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twm_paperclip
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
5
- prerelease: false
4
+ hash: 3788463
5
+ prerelease: true
6
6
  segments:
7
7
  - 2
8
8
  - 3
9
- - 6
10
- version: 2.3.6
9
+ - 8t
10
+ version: 2.3.8t
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jon Yurek
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-28 00:00:00 -05:00
18
+ date: 2011-02-02 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -123,9 +123,9 @@ executables: []
123
123
  extensions: []
124
124
 
125
125
  extra_rdoc_files:
126
- - README.rdoc
126
+ - README.md
127
127
  files:
128
- - README.rdoc
128
+ - README.md
129
129
  - LICENSE
130
130
  - Rakefile
131
131
  - init.rb
@@ -206,12 +206,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
206
206
  required_rubygems_version: !ruby/object:Gem::Requirement
207
207
  none: false
208
208
  requirements:
209
- - - ">="
209
+ - - ">"
210
210
  - !ruby/object:Gem::Version
211
- hash: 3
211
+ hash: 25
212
212
  segments:
213
- - 0
214
- version: "0"
213
+ - 1
214
+ - 3
215
+ - 1
216
+ version: 1.3.1
215
217
  requirements:
216
218
  - ImageMagick
217
219
  rubyforge_project: paperclip