paperclip 3.1.2 → 3.1.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of paperclip might be problematic. Click here for more details.

data/NEWS CHANGED
@@ -1,3 +1,22 @@
1
+ New in 3.1.4:
2
+
3
+ * Bug fix: Allow user to be able to set path without `:style` attribute and not raising an error.
4
+ This is a regression introduced in 3.1.3, and that feature will be postponed to another minor
5
+ release instead.
6
+ * Feature: Allow for URI Adapter as an optional paperclip io adapter.
7
+
8
+ New in 3.1.3:
9
+
10
+ * Bug fix: Copy empty attachment between instances is now working.
11
+ * Bug fix: Correctly rescue Fog error.
12
+ * Bug fix: Using default path and url options in Fog storage now work as expected.
13
+ * Bug fix: `Attachment#s3_protocol` now returns a protocol without colon suffix.
14
+ * Feature: Paperclip will now raise an error if multiple styles are defined but no `:style`
15
+ interpolation exists in `:path`.
16
+ * Feature: Add support for `#{attachment}_created_at` field
17
+ * Bug fix: Paperclip now gracefully handles msising file command.
18
+ * Bug fix: `StringIOAdapter` now accepts content type.
19
+
1
20
  New in 3.1.2:
2
21
 
3
22
  * Bug fix: #remove_attachment on 3.1.0 and 3.1.1 mistakenly trying to remove the column that has
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Paperclip
2
2
  =========
3
3
 
4
- [![Build Status](https://secure.travis-ci.org/thoughtbot/paperclip.png?branch=master)](http://travis-ci.org/thoughtbot/paperclip) [![Dependency Status](https://gemnasium.com/thoughtbot/paperclip.png?travis)](https://gemnasium.com/thoughtbot/paperclip)
4
+ [![Build Status](https://secure.travis-ci.org/thoughtbot/paperclip.png?branch=master)](http://travis-ci.org/thoughtbot/paperclip) [![Dependency Status](https://gemnasium.com/thoughtbot/paperclip.png?travis)](https://gemnasium.com/thoughtbot/paperclip) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/thoughtbot/paperclip)
5
5
 
6
6
  Paperclip is intended as an easy file attachment library for Active Record. The
7
7
  intent behind it was to keep setup as easy as possible and to treat files as
@@ -89,6 +89,7 @@ Quick Start
89
89
  In your model:
90
90
 
91
91
  class User < ActiveRecord::Base
92
+ attr_accessible :avatar
92
93
  has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
93
94
  end
94
95
 
@@ -108,7 +109,7 @@ In your migrations:
108
109
 
109
110
  In your edit and new views:
110
111
 
111
- <%= form_for :user, @user, :url => user_path, :html => { :multipart => true } do |form| %>
112
+ <%= form_for @user, :url => users_path, :html => { :multipart => true } do |form| %>
112
113
  <%= form.file_field :avatar %>
113
114
  <% end %>
114
115
 
@@ -202,7 +203,7 @@ An example Rails initializer would look something like this:
202
203
  Paperclip::Attachment.default_options[:storage] = :fog
203
204
  Paperclip::Attachment.default_options[:fog_credentials] = {:provider => "Local", :local_root => "#{Rails.root}/public"}
204
205
  Paperclip::Attachment.default_options[:fog_directory] = ""
205
- Paperclip::Attachment.default_options[:fog_host] = "http://localhost:3000"}
206
+ Paperclip::Attachment.default_options[:fog_host] = "http://localhost:3000"
206
207
  ```
207
208
 
208
209
  Migrations
@@ -274,13 +275,13 @@ gems along side with Paperclip:
274
275
 
275
276
  The files that are assigned as attachments are, by default, placed in the
276
277
  directory specified by the `:path` option to `has_attached_file`. By default, this
277
- location is `:rails_root/public/system/:attachment/:id/:style/:filename`. This
278
- location was chosen because on standard Capistrano deployments, the
278
+ location is `:rails_root/public/system/:class/:attachment/:id_partition/:style/:filename`.
279
+ This location was chosen because on standard Capistrano deployments, the
279
280
  `public/system` directory is symlinked to the app's shared directory, meaning it
280
281
  will survive between deployments. For example, using that `:path`, you may have a
281
282
  file at
282
283
 
283
- /data/myapp/releases/20081229172410/public/system/user/avatar/000/000/013/small/my_pic.png
284
+ /data/myapp/releases/20081229172410/public/system/users/avatar/000/000/013/small/my_pic.png
284
285
 
285
286
  _**NOTE**: This is a change from previous versions of Paperclip, but is overall a
286
287
  safer choice for the default file store._
@@ -447,7 +448,7 @@ allowing custom styles and processors to be applied for specific model
447
448
  instances, rather than applying defined styles and processors across all
448
449
  instances.
449
450
 
450
- Dynamic Styles:
451
+ ### Dynamic Styles:
451
452
 
452
453
  Imagine a user model that had different styles based on the role of the user.
453
454
  Perhaps some users are bosses (e.g. a User model instance responds to #boss?)
@@ -460,7 +461,7 @@ look as follows where a boss will receive a `300x300` thumbnail otherwise a
460
461
  has_attached_file :avatar, :styles => lambda { |attachment| { :thumb => (attachment.instance.boss? ? "300x300>" : "100x100>") }
461
462
  end
462
463
 
463
- Dynamic Processors:
464
+ ### Dynamic Processors:
464
465
 
465
466
  Another contrived example is a user model that is aware of which file processors
466
467
  should be applied to it (beyond the implied `thumbnail` processor invoked when
@@ -40,6 +40,7 @@ require 'paperclip/attachment'
40
40
  require 'paperclip/attachment_options'
41
41
  require 'paperclip/storage'
42
42
  require 'paperclip/callbacks'
43
+ require 'paperclip/content_type_detector'
43
44
  require 'paperclip/glue'
44
45
  require 'paperclip/errors'
45
46
  require 'paperclip/missing_attachment_styles'
@@ -102,7 +103,7 @@ module Paperclip
102
103
  # that can control permissions. You can specify the full domain and path, but usually
103
104
  # just an absolute path is sufficient. The leading slash *must* be included manually for
104
105
  # absolute paths. The default value is
105
- # "/system/:attachment/:id/:style/:filename". See
106
+ # "/system/:class/:attachment/:id_partition/:style/:filename". See
106
107
  # Paperclip::Attachment#interpolate for more information on variable interpolaton.
107
108
  # :url => "/:class/:attachment/:id/:style_:filename"
108
109
  # :url => "http://some.other.host/stuff/:class/:id_:extension"
@@ -222,3 +223,4 @@ require 'paperclip/io_adapters/stringio_adapter'
222
223
  require 'paperclip/io_adapters/nil_adapter'
223
224
  require 'paperclip/io_adapters/attachment_adapter'
224
225
  require 'paperclip/io_adapters/uploaded_file_adapter'
226
+ require 'paperclip/io_adapters/uri_adapter'
@@ -99,6 +99,7 @@ module Paperclip
99
99
  instance_write(:content_type, file.content_type.to_s.strip)
100
100
  instance_write(:file_size, file.size)
101
101
  instance_write(:fingerprint, file.fingerprint) if instance_respond_to?(:fingerprint)
102
+ instance_write(:created_at, Time.now) if has_enabled_but_unset_created_at?
102
103
  instance_write(:updated_at, Time.now)
103
104
 
104
105
  @dirty = true
@@ -254,6 +255,15 @@ module Paperclip
254
255
  instance_read(:content_type)
255
256
  end
256
257
 
258
+ # Returns the creation time of the file as originally assigned, and
259
+ # lives in the <attachment>_created_at attribute of the model.
260
+ def created_at
261
+ if able_to_store_created_at?
262
+ time = instance_read(:created_at)
263
+ time && time.to_f.to_i
264
+ end
265
+ end
266
+
257
267
  # Returns the last modified time of the file as originally assigned, and
258
268
  # lives in the <attachment>_updated_at attribute of the model.
259
269
  def updated_at
@@ -429,6 +439,7 @@ module Paperclip
429
439
  instance_write(:content_type, nil)
430
440
  instance_write(:file_size, nil)
431
441
  instance_write(:fingerprint, nil)
442
+ instance_write(:created_at, nil) if has_enabled_but_unset_created_at?
432
443
  instance_write(:updated_at, nil)
433
444
  end
434
445
 
@@ -453,5 +464,15 @@ module Paperclip
453
464
  filename
454
465
  end
455
466
  end
467
+
468
+ # Check if attachment database table has a created_at field
469
+ def able_to_store_created_at?
470
+ @instance.respond_to?("#{name}_created_at".to_sym)
471
+ end
472
+
473
+ # Check if attachment database table has a created_at field which is not yet set
474
+ def has_enabled_but_unset_created_at?
475
+ able_to_store_created_at? && !instance_read(:created_at)
476
+ end
456
477
  end
457
478
  end
@@ -0,0 +1,67 @@
1
+ module Paperclip
2
+ class ContentTypeDetector
3
+ EMPTY_TYPE = "inode/x-empty"
4
+ SENSIBLE_DEFAULT = "application/octet-stream"
5
+
6
+ def initialize(filename)
7
+ @filename = filename
8
+ end
9
+
10
+ def detect
11
+ if blank?
12
+ SENSIBLE_DEFAULT
13
+ elsif empty?
14
+ EMPTY_TYPE
15
+ elsif !match?
16
+ type_from_file_command
17
+ elsif !multiple?
18
+ possible_types.first
19
+ else
20
+ best_type_match
21
+ end.to_s
22
+ end
23
+
24
+ private
25
+
26
+ def empty?
27
+ File.exists?(@filename) && File.size(@filename) == 0
28
+ end
29
+
30
+ def blank?
31
+ @filename.nil? || @filename.empty?
32
+ end
33
+
34
+ def possible_types
35
+ @possible_types ||= MIME::Types.type_for(@filename)
36
+ end
37
+
38
+ def match?
39
+ possible_types.length > 0
40
+ end
41
+
42
+ def multiple?
43
+ possible_types.length > 1
44
+ end
45
+
46
+ def best_type_match
47
+ official_types = possible_types.reject {|type| type.content_type.match(/\/x-/) }
48
+ (official_types.first || possible_types.first).content_type
49
+ end
50
+
51
+ def type_from_file_command
52
+ type = begin
53
+ # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
54
+ Paperclip.run("file", "-b --mime :file", :file => @filename)
55
+ rescue Cocaine::CommandLineError => e
56
+ Paperclip.log("Error while determining content type: #{e}")
57
+ SENSIBLE_DEFAULT
58
+ end
59
+
60
+ if type.match(/\(.*?\)/)
61
+ type = SENSIBLE_DEFAULT
62
+ end
63
+ type.split(/[:;\s]+/)[0]
64
+ end
65
+
66
+ end
67
+ end
@@ -149,7 +149,7 @@ module Paperclip
149
149
  # scale to the requested geometry and preserve the aspect ratio
150
150
  def scale_to(new_geometry)
151
151
  scale = [new_geometry.width.to_f / self.width.to_f , new_geometry.height.to_f / self.height.to_f].min
152
- Paperclip::Geometry.new (self.width * scale).round, (self.height * scale).round
152
+ Paperclip::Geometry.new((self.width * scale).round, (self.height * scale).round)
153
153
  end
154
154
  end
155
155
  end
@@ -97,9 +97,12 @@ module Paperclip
97
97
  File.extname(attachment.original_filename).gsub(/^\.+/, "")
98
98
  end
99
99
 
100
- # Returns an extension based on the content type. e.g. "jpeg" for "image/jpeg".
100
+ # Returns an extension based on the content type. e.g. "jpeg" for
101
+ # "image/jpeg". If the style has a specified format, it will override the
102
+ # content-type detection.
103
+ #
101
104
  # Each mime type generally has multiple extensions associated with it, so
102
- # if the extension from teh original filename is one of these extensions,
105
+ # if the extension from the original filename is one of these extensions,
103
106
  # that extension is used, otherwise, the first in the list is used.
104
107
  def content_type_extension attachment, style_name
105
108
  mime_type = MIME::Types[attachment.content_type]
@@ -110,7 +113,10 @@ module Paperclip
110
113
  end
111
114
 
112
115
  original_extension = extension(attachment, style_name)
113
- if extensions_for_mime_type.include? original_extension
116
+ style = attachment.styles[style_name.to_s.to_sym]
117
+ if style && style[:format]
118
+ style[:format].to_s
119
+ elsif extensions_for_mime_type.include? original_extension
114
120
  original_extension
115
121
  elsif !extensions_for_mime_type.empty?
116
122
  extensions_for_mime_type.first
@@ -13,6 +13,10 @@ module Paperclip
13
13
  @tempfile.read(length, buffer)
14
14
  end
15
15
 
16
+ def inspect
17
+ "#{self.class}: #{self.original_filename}"
18
+ end
19
+
16
20
  private
17
21
 
18
22
  def destination
@@ -23,23 +27,5 @@ module Paperclip
23
27
  FileUtils.cp(src.path, destination.path)
24
28
  destination
25
29
  end
26
-
27
- def best_content_type_option(types)
28
- best = types.reject {|type| type.content_type.match(/\/x-/) }
29
- if best.size == 0
30
- types.first.content_type
31
- else
32
- best.first.content_type
33
- end
34
- end
35
-
36
- def type_from_file_command
37
- # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
38
- type = (File.extname(self.path.to_s)).downcase
39
- type = "octet-stream" if type.empty?
40
- mime_type = Paperclip.run("file", "-b --mime :file", :file => self.path).split(/[:;\s]+/)[0]
41
- mime_type = "application/x-#{type}" if mime_type.match(/\(.*?\)/)
42
- mime_type
43
- end
44
30
  end
45
31
  end
@@ -11,20 +11,9 @@ module Paperclip
11
11
  @original_filename = @target.original_filename if @target.respond_to?(:original_filename)
12
12
  @original_filename ||= File.basename(@target.path)
13
13
  @tempfile = copy_to_tempfile(@target)
14
- @content_type = calculate_content_type
14
+ @content_type = ContentTypeDetector.new(@target.path).detect
15
15
  @size = File.size(@target)
16
16
  end
17
-
18
- def calculate_content_type
19
- types = MIME::Types.type_for(original_filename)
20
- if types.length == 0
21
- type_from_file_command
22
- elsif types.length == 1
23
- types.first.content_type
24
- else
25
- best_content_type_option(types)
26
- end
27
- end
28
17
  end
29
18
  end
30
19
 
@@ -30,5 +30,5 @@ module Paperclip
30
30
  end
31
31
 
32
32
  Paperclip.io_adapters.register Paperclip::NilAdapter do |target|
33
- target.nil?
33
+ target.nil? || ( (Paperclip::Attachment === target) && !target.present? )
34
34
  end
@@ -6,6 +6,7 @@ module Paperclip
6
6
  @tempfile = copy_to_tempfile(@target)
7
7
  end
8
8
 
9
+ attr_writer :original_filename, :content_type
9
10
  private
10
11
 
11
12
  def cache_current_values
@@ -0,0 +1,42 @@
1
+ require 'open-uri'
2
+
3
+ module Paperclip
4
+ class UriAdapter < AbstractAdapter
5
+ def initialize(target)
6
+ @target = target
7
+ @content = download_content
8
+ cache_current_values
9
+ @tempfile = copy_to_tempfile(@content)
10
+ end
11
+
12
+ attr_writer :original_filename, :content_type
13
+ private
14
+
15
+ def download_content
16
+ open(@target)
17
+ end
18
+
19
+ def cache_current_values
20
+ @original_filename = @target.path.split("/").last
21
+ @original_filename ||= "index.html"
22
+ @original_filename = @original_filename.strip
23
+
24
+ @content_type = @content.content_type if @content.respond_to?(:content_type)
25
+ @content_type ||= "text/html"
26
+
27
+ @size = @content.size
28
+ end
29
+
30
+ def copy_to_tempfile(src)
31
+ while data = src.read(16*1024)
32
+ destination.write(data)
33
+ end
34
+ destination.rewind
35
+ destination
36
+ end
37
+ end
38
+ end
39
+
40
+ Paperclip.io_adapters.register Paperclip::UriAdapter do |target|
41
+ target.kind_of?(URI)
42
+ end
@@ -42,7 +42,7 @@ module Paperclip
42
42
 
43
43
  base.instance_eval do
44
44
  unless @options[:url].to_s.match(/^:fog.*url$/)
45
- @options[:path] = @options[:path].gsub(/:url/, @options[:url])
45
+ @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/^:rails_root\/public\/system\//, '')
46
46
  @options[:url] = ':fog_public_url'
47
47
  end
48
48
  Paperclip.interpolates(:fog_public_url) do |attachment, style|
@@ -120,7 +120,7 @@ module Paperclip
120
120
  end
121
121
  end
122
122
 
123
- def expiring_url(time = 3600, style = default_style)
123
+ def expiring_url(time = (Time.now + 3600), style = default_style)
124
124
  expiring_url = directory.files.get_http_url(path(style), time)
125
125
 
126
126
  if @options[:fog_host]
@@ -142,7 +142,7 @@ module Paperclip
142
142
  file = directory.files.get(path(style))
143
143
  local_file.write(file.body)
144
144
  local_file.close
145
- rescue Fog::Errors::Error => e
145
+ rescue ::Fog::Errors::Error => e
146
146
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
147
147
  false
148
148
  end
@@ -159,12 +159,7 @@ module Paperclip
159
159
 
160
160
  def host_name_for_directory
161
161
  if @options[:fog_directory].to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX
162
- # This:
163
- "#{@options[:fog_directory]}."
164
-
165
- # Should be modified to this:
166
- # "#{@options[:fog_directory]}.s3.amazonaws.com"
167
- # When fog with https://github.com/fog/fog/pull/857 gets released
162
+ "#{@options[:fog_directory]}.s3.amazonaws.com"
168
163
  else
169
164
  "s3.amazonaws.com/#{@options[:fog_directory]}"
170
165
  end
@@ -150,13 +150,13 @@ module Paperclip
150
150
  end
151
151
 
152
152
  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
153
- "#{attachment.s3_protocol(style)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
153
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
154
154
  end unless Paperclip::Interpolations.respond_to? :s3_alias_url
155
155
  Paperclip.interpolates(:s3_path_url) do |attachment, style|
156
- "#{attachment.s3_protocol(style)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
156
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
157
157
  end unless Paperclip::Interpolations.respond_to? :s3_path_url
158
158
  Paperclip.interpolates(:s3_domain_url) do |attachment, style|
159
- "#{attachment.s3_protocol(style)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
159
+ "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
160
160
  end unless Paperclip::Interpolations.respond_to? :s3_domain_url
161
161
  Paperclip.interpolates(:asset_host) do |attachment, style|
162
162
  "#{attachment.path(style).gsub(%r{^/}, "")}"
@@ -276,15 +276,15 @@ module Paperclip
276
276
  s3_permissions
277
277
  end
278
278
 
279
- def s3_protocol(style = default_style)
280
- protocol = if @s3_protocol.respond_to?(:call)
281
- @s3_protocol.call(style, self).to_s
279
+ def s3_protocol(style = default_style, with_colon = false)
280
+ protocol = @s3_protocol
281
+ protocol = protocol.call(style, self) if protocol.respond_to?(:call)
282
+
283
+ if with_colon && !protocol.empty?
284
+ "#{protocol}:"
282
285
  else
283
- @s3_protocol.to_s
286
+ protocol.to_s
284
287
  end
285
-
286
- protocol = protocol.split(":").first + ":" unless protocol.empty?
287
- protocol
288
288
  end
289
289
 
290
290
  def create_bucket
@@ -343,6 +343,8 @@ module Paperclip
343
343
  false
344
344
  end
345
345
 
346
+ private
347
+
346
348
  def find_credentials creds
347
349
  case creds
348
350
  when File
@@ -355,12 +357,10 @@ module Paperclip
355
357
  raise ArgumentError, "Credentials are not a path, file, proc, or hash."
356
358
  end
357
359
  end
358
- private :find_credentials
359
360
 
360
361
  def use_secure_protocol?(style_name)
361
- s3_protocol(style_name) == "https:"
362
+ s3_protocol(style_name) == "https"
362
363
  end
363
- private :use_secure_protocol?
364
364
  end
365
365
  end
366
366
  end
@@ -1,3 +1,3 @@
1
1
  module Paperclip
2
- VERSION = "3.1.2" unless defined? Paperclip::VERSION
2
+ VERSION = "3.1.4" unless defined? Paperclip::VERSION
3
3
  end
@@ -43,9 +43,11 @@ Gem::Specification.new do |s|
43
43
  s.add_development_dependency('capybara')
44
44
  s.add_development_dependency('bundler')
45
45
  s.add_development_dependency('cocaine', '~> 0.2')
46
- s.add_development_dependency('fog')
46
+ s.add_development_dependency('fog', '~> 1.4.0')
47
47
  s.add_development_dependency('pry')
48
48
  s.add_development_dependency('launchy')
49
49
  s.add_development_dependency('rake')
50
50
  s.add_development_dependency('fakeweb')
51
+ s.add_development_dependency('railties')
52
+ s.add_development_dependency('actionmailer')
51
53
  end
@@ -1101,6 +1101,36 @@ class AttachmentTest < Test::Unit::TestCase
1101
1101
  assert_equal File.size(@file), @dummy.avatar.size
1102
1102
  end
1103
1103
 
1104
+ context "and avatar_created_at column" do
1105
+ setup do
1106
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_created_at, :timestamp
1107
+ rebuild_class
1108
+ @dummy = Dummy.new
1109
+ end
1110
+
1111
+ should "not error when assigned an attachment" do
1112
+ assert_nothing_raised { @dummy.avatar = @file }
1113
+ end
1114
+
1115
+ should "return the creation time when sent #avatar_created_at" do
1116
+ now = Time.now
1117
+ Time.stubs(:now).returns(now)
1118
+ @dummy.avatar = @file
1119
+ assert_equal now.to_i, @dummy.avatar.created_at
1120
+ end
1121
+
1122
+ should "return the creation time when sent #avatar_created_at and the entry has been updated" do
1123
+ creation = 2.hours.ago
1124
+ now = Time.now
1125
+ Time.stubs(:now).returns(creation)
1126
+ @dummy.avatar = @file
1127
+ Time.stubs(:now).returns(now)
1128
+ @dummy.avatar = @file
1129
+ assert_equal creation.to_i, @dummy.avatar.created_at
1130
+ assert_not_equal now.to_i, @dummy.avatar.created_at
1131
+ end
1132
+ end
1133
+
1104
1134
  context "and avatar_updated_at column" do
1105
1135
  setup do
1106
1136
  ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp
@@ -0,0 +1,40 @@
1
+ require './test/helper'
2
+
3
+ class ContentTypeDetectorTest < Test::Unit::TestCase
4
+ context 'given a name' do
5
+ should 'return a content type based on that name' do
6
+ @filename = "/path/to/something.jpg"
7
+ assert_equal "image/jpeg", Paperclip::ContentTypeDetector.new(@filename).detect
8
+ end
9
+
10
+ should 'return a content type based on the content of the file' do
11
+ tempfile = Tempfile.new("something")
12
+ tempfile.write("This is a file.")
13
+ tempfile.rewind
14
+
15
+ assert_equal "text/plain", Paperclip::ContentTypeDetector.new(tempfile.path).detect
16
+ end
17
+
18
+ should 'return an empty content type if the file is empty' do
19
+ tempfile = Tempfile.new("something")
20
+ tempfile.rewind
21
+
22
+ assert_equal "inode/x-empty", Paperclip::ContentTypeDetector.new(tempfile.path).detect
23
+ end
24
+
25
+ should 'return a sensible default if no filename is supplied' do
26
+ assert_equal "application/octet-stream", Paperclip::ContentTypeDetector.new('').detect
27
+ end
28
+
29
+ should 'return a sensible default if something goes wrong' do
30
+ @filename = "/path/to/something"
31
+ assert_equal "application/octet-stream", Paperclip::ContentTypeDetector.new(@filename).detect
32
+ end
33
+
34
+ should 'return a sensible default when the file command is missing' do
35
+ Paperclip.stubs(:run).raises(Cocaine::CommandLineError.new)
36
+ @filename = "/path/to/something"
37
+ assert_equal "application/octet-stream", Paperclip::ContentTypeDetector.new(@filename).detect
38
+ end
39
+ end
40
+ end
@@ -630,7 +630,6 @@ class IntegrationTest < Test::Unit::TestCase
630
630
 
631
631
  context "with non-english character in the file name" do
632
632
  setup do
633
-
634
633
  @file.stubs(:original_filename).returns("クリップ.png")
635
634
  @dummy.avatar = @file
636
635
  end
@@ -641,4 +640,38 @@ class IntegrationTest < Test::Unit::TestCase
641
640
  end
642
641
  end
643
642
  end
643
+
644
+ context "Copying attachments between models" do
645
+ setup do
646
+ rebuild_model
647
+ @file = File.new(fixture_file("5k.png"), 'rb')
648
+ end
649
+
650
+ teardown { @file.close }
651
+
652
+ should "succeed when original attachment is a file" do
653
+ original = Dummy.new
654
+ original.avatar = @file
655
+ assert original.save
656
+
657
+ copy = Dummy.new
658
+ copy.avatar = original.avatar
659
+ assert copy.save
660
+
661
+ assert copy.avatar.present?
662
+ end
663
+
664
+ should "succeed when original attachment is empty" do
665
+ original = Dummy.create!
666
+
667
+ copy = Dummy.new
668
+ copy.avatar = @file
669
+ assert copy.save
670
+ assert copy.avatar.present?
671
+
672
+ copy.avatar = original.avatar
673
+ assert copy.save
674
+ assert !copy.avatar.present?
675
+ end
676
+ end
644
677
  end
@@ -56,6 +56,7 @@ class InterpolationsTest < Test::Unit::TestCase
56
56
  should "return the extension of the file based on the content type" do
57
57
  attachment = mock
58
58
  attachment.expects(:content_type).returns('image/jpeg')
59
+ attachment.expects(:styles).returns({})
59
60
  interpolations = Paperclip::Interpolations
60
61
  interpolations.expects(:extension).returns('random')
61
62
  assert_equal "jpeg", interpolations.content_type_extension(attachment, :style)
@@ -64,6 +65,7 @@ class InterpolationsTest < Test::Unit::TestCase
64
65
  should "return the original extension of the file if it matches a content type extension" do
65
66
  attachment = mock
66
67
  attachment.expects(:content_type).returns('image/jpeg')
68
+ attachment.expects(:styles).returns({})
67
69
  interpolations = Paperclip::Interpolations
68
70
  interpolations.expects(:extension).returns('jpe')
69
71
  assert_equal "jpe", interpolations.content_type_extension(attachment, :style)
@@ -72,11 +74,21 @@ class InterpolationsTest < Test::Unit::TestCase
72
74
  should "return the latter half of the content type of the extension if no match found" do
73
75
  attachment = mock
74
76
  attachment.expects(:content_type).at_least_once().returns('not/found')
77
+ attachment.expects(:styles).returns({})
75
78
  interpolations = Paperclip::Interpolations
76
79
  interpolations.expects(:extension).returns('random')
77
80
  assert_equal "found", interpolations.content_type_extension(attachment, :style)
78
81
  end
79
82
 
83
+ should "return the format if defined in the style, ignoring the content type" do
84
+ attachment = mock
85
+ attachment.expects(:content_type).returns('image/jpeg')
86
+ attachment.expects(:styles).returns({:style => {:format => "png"}})
87
+ interpolations = Paperclip::Interpolations
88
+ interpolations.expects(:extension).returns('random')
89
+ assert_equal "png", interpolations.content_type_extension(attachment, :style)
90
+ end
91
+
80
92
  should "be able to handle numeric style names" do
81
93
  attachment = mock(
82
94
  :styles => {:"4" => {:format => :expected_extension}}
@@ -5,15 +5,14 @@ class AbstractAdapterTest < Test::Unit::TestCase
5
5
  attr_accessor :original_file_name, :tempfile
6
6
 
7
7
  def content_type
8
- type_from_file_command
8
+ Paperclip::ContentTypeDetector.new(path).detect
9
9
  end
10
10
  end
11
11
 
12
12
  context "content type from file command" do
13
13
  setup do
14
14
  @adapter = TestAdapter.new
15
- @adapter.stubs(:path)
16
- Paperclip.stubs(:run).returns("image/png\n")
15
+ @adapter.stubs(:path).returns("image.png")
17
16
  end
18
17
 
19
18
  should "return the content type without newline" do
@@ -1,12 +1,12 @@
1
1
  require './test/helper'
2
2
 
3
3
  class AttachmentAdapterTest < Test::Unit::TestCase
4
-
4
+
5
5
  def setup
6
6
  rebuild_model :path => "tmp/:class/:attachment/:style/:filename", :styles => {:thumb => '50x50'}
7
7
  @attachment = Dummy.new.avatar
8
8
  end
9
-
9
+
10
10
  context "for an attachment" do
11
11
  setup do
12
12
  @file = File.new(fixture_file("5k.png"))
@@ -53,19 +53,19 @@ class AttachmentAdapterTest < Test::Unit::TestCase
53
53
  assert_equal expected.length, actual.length
54
54
  assert_equal expected, actual
55
55
  end
56
-
56
+
57
57
  end
58
-
58
+
59
59
  context "for a style" do
60
60
  setup do
61
61
  @file = File.new(fixture_file("5k.png"))
62
62
  @file.binmode
63
63
 
64
64
  @attachment.assign(@file)
65
-
65
+
66
66
  @thumb = Tempfile.new("thumbnail").tap(&:binmode)
67
67
  FileUtils.cp @attachment.queued_for_write[:thumb].path, @thumb.path
68
-
68
+
69
69
  @attachment.save
70
70
  @subject = Paperclip.io_adapters.for(@attachment.styles[:thumb])
71
71
  end
@@ -108,6 +108,6 @@ class AttachmentAdapterTest < Test::Unit::TestCase
108
108
  assert_equal expected.length, actual.length
109
109
  assert_equal expected, actual
110
110
  end
111
-
111
+
112
112
  end
113
113
  end
@@ -37,5 +37,15 @@ class StringioFileProxyTest < Test::Unit::TestCase
37
37
  assert_equal "abc123", @subject.read
38
38
  end
39
39
 
40
+ should 'accept a content_type' do
41
+ @subject.content_type = 'image/png'
42
+ assert_equal 'image/png', @subject.content_type
43
+ end
44
+
45
+ should 'accept an orgiginal_filename' do
46
+ @subject.original_filename = 'image.png'
47
+ assert_equal 'image.png', @subject.original_filename
48
+ end
49
+
40
50
  end
41
51
  end
@@ -0,0 +1,82 @@
1
+ require './test/helper'
2
+
3
+ class UriProxyTest < Test::Unit::TestCase
4
+ context "a new instance" do
5
+ setup do
6
+ @open_return = StringIO.new("xxx")
7
+ @open_return.stubs(:content_type).returns("image/png")
8
+ Paperclip::UriAdapter.any_instance.stubs(:download_content).returns(@open_return)
9
+ @uri = URI.parse("http://thoughtbot.com/images/thoughtbot-logo.png")
10
+ @subject = Paperclip.io_adapters.for(@uri)
11
+ end
12
+
13
+ should "return a file name" do
14
+ assert_equal "thoughtbot-logo.png", @subject.original_filename
15
+ end
16
+
17
+ should "return a content type" do
18
+ assert_equal "image/png", @subject.content_type
19
+ end
20
+
21
+ should "return the size of the data" do
22
+ assert_equal @open_return.size, @subject.size
23
+ end
24
+
25
+ should "generate an MD5 hash of the contents" do
26
+ assert_equal Digest::MD5.hexdigest("xxx"), @subject.fingerprint
27
+ end
28
+
29
+ should "generate correct fingerprint after read" do
30
+ fingerprint = Digest::MD5.hexdigest(@subject.read)
31
+ assert_equal fingerprint, @subject.fingerprint
32
+ end
33
+
34
+ should "generate same fingerprint" do
35
+ assert_equal @subject.fingerprint, @subject.fingerprint
36
+ end
37
+
38
+ should "return the data contained in the StringIO" do
39
+ assert_equal "xxx", @subject.read
40
+ end
41
+
42
+ should 'accept a content_type' do
43
+ @subject.content_type = 'image/png'
44
+ assert_equal 'image/png', @subject.content_type
45
+ end
46
+
47
+ should 'accept an orgiginal_filename' do
48
+ @subject.original_filename = 'image.png'
49
+ assert_equal 'image.png', @subject.original_filename
50
+ end
51
+
52
+ end
53
+
54
+ context "a directory index url" do
55
+ setup do
56
+ Paperclip::UriAdapter.any_instance.stubs(:download_content).returns(StringIO.new("xxx"))
57
+ @uri = URI.parse("http://thoughtbot.com")
58
+ @subject = Paperclip.io_adapters.for(@uri)
59
+ end
60
+
61
+ should "return a file name" do
62
+ assert_equal "index.html", @subject.original_filename
63
+ end
64
+
65
+ should "return a content type" do
66
+ assert_equal "text/html", @subject.content_type
67
+ end
68
+ end
69
+
70
+ context "a url with query params" do
71
+ setup do
72
+ Paperclip::UriAdapter.any_instance.stubs(:download_content).returns(StringIO.new("xxx"))
73
+ @uri = URI.parse("https://github.com/thoughtbot/paperclip?file=test")
74
+ @subject = Paperclip.io_adapters.for(@uri)
75
+ end
76
+
77
+ should "return a file name" do
78
+ assert_equal "paperclip", @subject.original_filename
79
+ end
80
+ end
81
+
82
+ end
@@ -66,6 +66,29 @@ class FogTest < Test::Unit::TestCase
66
66
  @dummy.avatar.path
67
67
  end
68
68
  end
69
+
70
+ context "with no path or url given and using defaults" do
71
+ setup do
72
+ rebuild_model :styles => { :medium => "300x300>", :thumb => "100x100>" },
73
+ :storage => :fog,
74
+ :fog_directory => "paperclip",
75
+ :fog_credentials => {
76
+ :provider => 'AWS',
77
+ :aws_access_key_id => 'AWS_ID',
78
+ :aws_secret_access_key => 'AWS_SECRET'
79
+ }
80
+ @file = File.new(fixture_file('5k.png'), 'rb')
81
+ @dummy = Dummy.new
82
+ @dummy.id = 1
83
+ @dummy.avatar = @file
84
+ end
85
+
86
+ teardown { @file.close }
87
+
88
+ should "have correct path and url from interpolated defaults" do
89
+ assert_equal "dummies/avatars/000/000/001/original/5k.png", @dummy.avatar.path
90
+ end
91
+ end
69
92
 
70
93
  setup do
71
94
  @fog_directory = 'papercliptests'
@@ -211,19 +234,11 @@ class FogTest < Test::Unit::TestCase
211
234
 
212
235
  context "with a valid bucket name for a subdomain" do
213
236
  should "provide an url in subdomain style" do
214
- # The following line is the correct one when this pull request in Fog is released:
215
- # https://github.com/fog/fog/pull/857
216
- # assert_match /^http:\/\/papercliptests.s3.amazonaws.com\/avatars\/5k.png\?AWSAccessKeyId=.+$/, @dummy.avatar.expiring_url
217
- # For now, use this passing one:
218
- assert_match /^https:\/\/papercliptests.\/avatars\/5k.png\?\d*$/, @dummy.avatar.url
237
+ assert_match @dummy.avatar.url, /^https:\/\/papercliptests.s3.amazonaws.com\/avatars\/5k.png/
219
238
  end
220
239
 
221
240
  should "provide an url that expires in subdomain style" do
222
- # The following line is the correct one when this pull request in Fog is released:
223
- # https://github.com/fog/fog/pull/857
224
- # assert_match /^http:\/\/papercliptests.s3.amazonaws.com\/avatars\/5k.png\?AWSAccessKeyId=.+$/, @dummy.avatar.expiring_url
225
- # For now, use this passing one:
226
- assert_match /^http:\/\/papercliptests.\/avatars\/5k.png\?AWSAccessKeyId=.+$/, @dummy.avatar.expiring_url
241
+ assert_match /^http:\/\/papercliptests.s3.amazonaws.com\/avatars\/5k.png\?AWSAccessKeyId=.+$/, @dummy.avatar.expiring_url
227
242
  end
228
243
  end
229
244
 
@@ -125,7 +125,22 @@ class S3Test < Test::Unit::TestCase
125
125
  should "use the correct key" do
126
126
  assert_equal "avatars/stringio.txt", @dummy.avatar.s3_object.key
127
127
  end
128
+ end
129
+
130
+ context "s3_protocol" do
131
+ ["http", :http, ""].each do |protocol|
132
+ context "as #{protocol.inspect}" do
133
+ setup do
134
+ rebuild_model :storage => :s3, :s3_protocol => protocol
128
135
 
136
+ @dummy = Dummy.new
137
+ end
138
+
139
+ should "return the s3_protocol in string" do
140
+ assert_equal protocol.to_s, @dummy.avatar.s3_protocol
141
+ end
142
+ end
143
+ end
129
144
  end
130
145
 
131
146
  context ":s3_protocol => 'https'" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paperclip
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 3.1.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-18 00:00:00.000000000 Z
12
+ date: 2012-07-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -288,17 +288,17 @@ dependencies:
288
288
  requirement: !ruby/object:Gem::Requirement
289
289
  none: false
290
290
  requirements:
291
- - - ! '>='
291
+ - - ~>
292
292
  - !ruby/object:Gem::Version
293
- version: '0'
293
+ version: 1.4.0
294
294
  type: :development
295
295
  prerelease: false
296
296
  version_requirements: !ruby/object:Gem::Requirement
297
297
  none: false
298
298
  requirements:
299
- - - ! '>='
299
+ - - ~>
300
300
  - !ruby/object:Gem::Version
301
- version: '0'
301
+ version: 1.4.0
302
302
  - !ruby/object:Gem::Dependency
303
303
  name: pry
304
304
  requirement: !ruby/object:Gem::Requirement
@@ -363,6 +363,38 @@ dependencies:
363
363
  - - ! '>='
364
364
  - !ruby/object:Gem::Version
365
365
  version: '0'
366
+ - !ruby/object:Gem::Dependency
367
+ name: railties
368
+ requirement: !ruby/object:Gem::Requirement
369
+ none: false
370
+ requirements:
371
+ - - ! '>='
372
+ - !ruby/object:Gem::Version
373
+ version: '0'
374
+ type: :development
375
+ prerelease: false
376
+ version_requirements: !ruby/object:Gem::Requirement
377
+ none: false
378
+ requirements:
379
+ - - ! '>='
380
+ - !ruby/object:Gem::Version
381
+ version: '0'
382
+ - !ruby/object:Gem::Dependency
383
+ name: actionmailer
384
+ requirement: !ruby/object:Gem::Requirement
385
+ none: false
386
+ requirements:
387
+ - - ! '>='
388
+ - !ruby/object:Gem::Version
389
+ version: '0'
390
+ type: :development
391
+ prerelease: false
392
+ version_requirements: !ruby/object:Gem::Requirement
393
+ none: false
394
+ requirements:
395
+ - - ! '>='
396
+ - !ruby/object:Gem::Version
397
+ version: '0'
366
398
  description: Easy upload management for ActiveRecord
367
399
  email:
368
400
  - jyurek@thoughtbot.com
@@ -410,6 +442,7 @@ files:
410
442
  - lib/paperclip/attachment.rb
411
443
  - lib/paperclip/attachment_options.rb
412
444
  - lib/paperclip/callbacks.rb
445
+ - lib/paperclip/content_type_detector.rb
413
446
  - lib/paperclip/errors.rb
414
447
  - lib/paperclip/geometry.rb
415
448
  - lib/paperclip/glue.rb
@@ -424,6 +457,7 @@ files:
424
457
  - lib/paperclip/io_adapters/registry.rb
425
458
  - lib/paperclip/io_adapters/stringio_adapter.rb
426
459
  - lib/paperclip/io_adapters/uploaded_file_adapter.rb
460
+ - lib/paperclip/io_adapters/uri_adapter.rb
427
461
  - lib/paperclip/locales/en.yml
428
462
  - lib/paperclip/logger.rb
429
463
  - lib/paperclip/matchers.rb
@@ -454,6 +488,7 @@ files:
454
488
  - shoulda_macros/paperclip.rb
455
489
  - test/attachment_options_test.rb
456
490
  - test/attachment_test.rb
491
+ - test/content_type_detector_test.rb
457
492
  - test/database.yml
458
493
  - test/fixtures/12k.png
459
494
  - test/fixtures/50x50.png
@@ -481,6 +516,7 @@ files:
481
516
  - test/io_adapters/registry_test.rb
482
517
  - test/io_adapters/stringio_adapter_test.rb
483
518
  - test/io_adapters/uploaded_file_adapter_test.rb
519
+ - test/io_adapters/uri_adapter_test.rb
484
520
  - test/matchers/have_attached_file_matcher_test.rb
485
521
  - test/matchers/validate_attachment_content_type_matcher_test.rb
486
522
  - test/matchers/validate_attachment_presence_matcher_test.rb
@@ -557,6 +593,7 @@ test_files:
557
593
  - features/support/selectors.rb
558
594
  - test/attachment_options_test.rb
559
595
  - test/attachment_test.rb
596
+ - test/content_type_detector_test.rb
560
597
  - test/database.yml
561
598
  - test/fixtures/12k.png
562
599
  - test/fixtures/50x50.png
@@ -584,6 +621,7 @@ test_files:
584
621
  - test/io_adapters/registry_test.rb
585
622
  - test/io_adapters/stringio_adapter_test.rb
586
623
  - test/io_adapters/uploaded_file_adapter_test.rb
624
+ - test/io_adapters/uri_adapter_test.rb
587
625
  - test/matchers/have_attached_file_matcher_test.rb
588
626
  - test/matchers/validate_attachment_content_type_matcher_test.rb
589
627
  - test/matchers/validate_attachment_presence_matcher_test.rb