paperclip 3.3.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (41) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +2 -0
  3. data/NEWS +1 -0
  4. data/README.md +1 -1
  5. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +1 -1
  6. data/lib/paperclip.rb +4 -1
  7. data/lib/paperclip/attachment.rb +27 -6
  8. data/lib/paperclip/callbacks.rb +2 -2
  9. data/lib/paperclip/content_type_detector.rb +1 -12
  10. data/lib/paperclip/file_command_content_type_detector.rb +32 -0
  11. data/lib/paperclip/geometry.rb +33 -30
  12. data/lib/paperclip/geometry_detector_factory.rb +41 -0
  13. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  14. data/lib/paperclip/helpers.rb +7 -7
  15. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +17 -1
  16. data/lib/paperclip/io_adapters/uri_adapter.rb +1 -0
  17. data/lib/paperclip/storage/filesystem.rb +11 -2
  18. data/lib/paperclip/storage/fog.rb +28 -13
  19. data/lib/paperclip/storage/s3.rb +39 -18
  20. data/lib/paperclip/thumbnail.rb +3 -0
  21. data/lib/paperclip/validators/attachment_content_type_validator.rb +29 -8
  22. data/lib/paperclip/version.rb +1 -1
  23. data/paperclip.gemspec +1 -1
  24. data/test/attachment_processing_test.rb +29 -0
  25. data/test/attachment_test.rb +97 -11
  26. data/test/file_command_content_type_detector_test.rb +25 -0
  27. data/test/generator_test.rb +3 -3
  28. data/test/geometry_detector_test.rb +24 -0
  29. data/test/geometry_parser_test.rb +73 -0
  30. data/test/geometry_test.rb +39 -7
  31. data/test/helper.rb +5 -1
  32. data/test/integration_test.rb +46 -1
  33. data/test/io_adapters/uploaded_file_adapter_test.rb +28 -4
  34. data/test/io_adapters/uri_adapter_test.rb +4 -0
  35. data/test/paperclip_test.rb +17 -7
  36. data/test/storage/fog_test.rb +66 -7
  37. data/test/storage/s3_test.rb +1 -1
  38. data/test/style_test.rb +18 -14
  39. data/test/thumbnail_test.rb +10 -56
  40. data/test/validators/attachment_content_type_validator_test.rb +155 -55
  41. metadata +137 -127
@@ -3,7 +3,7 @@ module Paperclip
3
3
  # The default place to store attachments is in the filesystem. Files on the local
4
4
  # filesystem can be very easily served by Apache without requiring a hit to your app.
5
5
  # They also can be processed more easily after they've been saved, as they're just
6
- # normal files. There is one Filesystem-specific option for has_attached_file.
6
+ # normal files. There are two Filesystem-specific options for has_attached_file:
7
7
  # * +path+: The location of the repository of attachments on disk. This can (and, in
8
8
  # almost all cases, should) be coordinated with the value of the +url+ option to
9
9
  # allow files to be saved into a place where Apache can serve them without
@@ -15,6 +15,12 @@ module Paperclip
15
15
  # public directory.
16
16
  # See Paperclip::Attachment#interpolate for more information on variable interpolaton.
17
17
  # :path => "/var/app/attachments/:class/:id/:style/:basename.:extension"
18
+ # * +override_file_permissions+: This allows you to override the file permissions for files
19
+ # saved by paperclip. If you set this to an explicit octal value (0755, 0644, etc) then
20
+ # that value will be used to set the permissions for an uploaded file. The default is 0666.
21
+ # If you set :override_file_permissions to false, the chmod will be skipped. This allows
22
+ # you to use paperclip on filesystems that don't understand unix file permissions, and has the
23
+ # added benefit of using the storage directories default umask on those that do.
18
24
  module Filesystem
19
25
  def self.extended base
20
26
  end
@@ -35,7 +41,10 @@ module Paperclip
35
41
  new_file.write(chunk)
36
42
  end
37
43
  end
38
- FileUtils.chmod(0666&~File.umask, path(style_name))
44
+ unless @options[:override_file_permissions] == false
45
+ resolved_chmod = (@options[:override_file_permissions] &~ 0111) || (0666 &~ File.umask)
46
+ FileUtils.chmod( resolved_chmod, path(style_name) )
47
+ end
39
48
  file.rewind
40
49
  end
41
50
 
@@ -18,6 +18,8 @@ module Paperclip
18
18
  # store your files. Remember that the bucket must be unique across
19
19
  # all of Amazon S3. If the bucket does not exist, Paperclip will
20
20
  # attempt to create it.
21
+ # * +fog_file*: This can be hash or lambda returning hash. The
22
+ # value is used as base properties for new uploaded file.
21
23
  # * +path+: This is the key under the bucket in which the file will
22
24
  # be stored. The URL will be constructed from the bucket and the
23
25
  # path. This is what you will want to interpolate. Keys should be
@@ -66,18 +68,27 @@ module Paperclip
66
68
  end
67
69
 
68
70
  def fog_file
69
- @fog_file ||= @options[:fog_file] || {}
71
+ @fog_file ||= begin
72
+ value = @options[:fog_file]
73
+ if !value
74
+ {}
75
+ elsif value.respond_to?(:call)
76
+ value.call(self)
77
+ else
78
+ value
79
+ end
80
+ end
70
81
  end
71
82
 
72
83
  def fog_public(style = default_style)
73
- if defined?(@options[:fog_public])
74
- if defined?(@options[:fog_public][style])
75
- return @options[:fog_public][style]
84
+ if @options.has_key?(:fog_public)
85
+ if @options[:fog_public].respond_to?(:has_key?) && @options[:fog_public].has_key?(style)
86
+ @options[:fog_public][style]
76
87
  else
77
- return @options[:fog_public]
88
+ @options[:fog_public]
78
89
  end
79
90
  else
80
- return true
91
+ true
81
92
  end
82
93
  end
83
94
 
@@ -128,10 +139,14 @@ module Paperclip
128
139
  end
129
140
 
130
141
  def expiring_url(time = (Time.now + 3600), style = default_style)
131
- expiring_url = directory.files.get_http_url(path(style), time)
142
+ if directory.files.respond_to?(:get_http_url)
143
+ expiring_url = directory.files.get_http_url(path(style), time)
132
144
 
133
- if @options[:fog_host]
134
- expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style))
145
+ if @options[:fog_host]
146
+ expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style))
147
+ end
148
+ else
149
+ expiring_url = public_url
135
150
  end
136
151
 
137
152
  return expiring_url
@@ -145,10 +160,10 @@ module Paperclip
145
160
 
146
161
  def copy_to_local_file(style, local_dest_path)
147
162
  log("copying #{path(style)} to local file #{local_dest_path}")
148
- local_file = ::File.open(local_dest_path, 'wb')
149
- file = directory.files.get(path(style))
150
- local_file.write(file.body)
151
- local_file.close
163
+ ::File.open(local_dest_path, 'wb') do |local_file|
164
+ file = directory.files.get(path(style))
165
+ local_file.write(file.body)
166
+ end
152
167
  rescue ::Fog::Errors::Error => e
153
168
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
154
169
  false
@@ -35,7 +35,7 @@ module Paperclip
35
35
  # :s3_permissions => {
36
36
  # :original => :private
37
37
  # }
38
- # Or globaly:
38
+ # Or globally:
39
39
  # :s3_permissions => :private
40
40
  #
41
41
  # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
@@ -45,6 +45,7 @@ module Paperclip
45
45
  # * +s3_headers+: A hash of headers or a Proc. You may specify a hash such as
46
46
  # {'Expires' => 1.year.from_now.httpdate}. If you use a Proc, headers are determined at
47
47
  # runtime. Paperclip will call that Proc with attachment as the only argument.
48
+ # Can be defined both globally and within a style-specific hash.
48
49
  # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
49
50
  # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
50
51
  # Paperclip will attempt to create it. The bucket name will not be interpolated.
@@ -83,7 +84,7 @@ module Paperclip
83
84
  # * +s3_metadata+: These key/value pairs will be stored with the
84
85
  # object. This option works by prefixing each key with
85
86
  # "x-amz-meta-" before sending it as a header on the object
86
- # upload request.
87
+ # upload request. Can be defined both globally and within a style-specific hash.
87
88
  # * +s3_storage_class+: If this option is set to
88
89
  # <tt>:reduced_redundancy</tt>, the object will be stored using Reduced
89
90
  # Redundancy Storage. RRS enables customers to reduce their
@@ -123,26 +124,17 @@ module Paperclip
123
124
  (permission == :public_read) ? 'http' : 'https'
124
125
  end
125
126
  @s3_metadata = @options[:s3_metadata] || {}
126
- @s3_headers = @options[:s3_headers] || {}
127
- @s3_headers = @s3_headers.call(instance) if @s3_headers.respond_to?(:call)
128
- @s3_headers = (@s3_headers).inject({}) do |headers,(name,value)|
129
- case name.to_s
130
- when /^x-amz-meta-(.*)/i
131
- @s3_metadata[$1.downcase] = value
132
- else
133
- name = name.to_s.downcase.sub(/^x-amz-/,'').tr("-","_").to_sym
134
- headers[name] = value
135
- end
136
- headers
137
- end
127
+ @s3_headers = {}
128
+ merge_s3_headers(@options[:s3_headers], @s3_headers, @s3_metadata)
138
129
 
139
130
  @s3_headers[:storage_class] = @options[:s3_storage_class] if @options[:s3_storage_class]
140
131
 
132
+ @s3_server_side_encryption = :aes256
141
133
  if @options[:s3_server_side_encryption].blank?
142
- @options[:s3_server_side_encryption] = false
134
+ @s3_server_side_encryption = false
143
135
  end
144
- if @options[:s3_server_side_encryption]
145
- @s3_headers['x-amz-server-side-encryption'] = @options[:s3_server_side_encryption].to_s.upcase
136
+ if @s3_server_side_encryption
137
+ @s3_server_side_encryption = @options[:s3_server_side_encryption].to_s.upcase
146
138
  end
147
139
 
148
140
  unless @options[:url].to_s.match(/^:s3.*url$/) || @options[:url] == ":asset_host"
@@ -221,10 +213,15 @@ module Paperclip
221
213
  config[opt] = s3_credentials[opt] if s3_credentials[opt]
222
214
  end
223
215
 
224
- AWS::S3.new(config.merge(@s3_options))
216
+ obtain_s3_instance_for(config.merge(@s3_options))
225
217
  end
226
218
  end
227
219
 
220
+ def obtain_s3_instance_for(options)
221
+ instances = (Thread.current[:paperclip_s3_instances] ||= {})
222
+ instances[options] ||= AWS::S3.new(options)
223
+ end
224
+
228
225
  def s3_bucket
229
226
  @s3_bucket ||= s3_interface.buckets[bucket_name]
230
227
  end
@@ -306,8 +303,19 @@ module Paperclip
306
303
  :content_type => file.content_type,
307
304
  :acl => acl
308
305
  }
306
+ if @s3_server_side_encryption
307
+ write_options[:server_side_encryption] = @s3_server_side_encryption
308
+ end
309
+
310
+ style_specific_options = @options[:styles][style]
311
+ if style_specific_options.is_a?(Hash)
312
+ merge_s3_headers( style_specific_options[:s3_headers], @s3_headers, @s3_metadata) if style_specific_options.has_key?(:s3_headers)
313
+ @s3_metadata.merge!(style_specific_options[:s3_metadata]) if style_specific_options.has_key?(:s3_metadata)
314
+ end
315
+
309
316
  write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
310
317
  write_options.merge!(@s3_headers)
318
+
311
319
  s3_object(style).write(file, write_options)
312
320
  rescue AWS::S3::Errors::NoSuchBucket => e
313
321
  create_bucket
@@ -363,6 +371,19 @@ module Paperclip
363
371
  def use_secure_protocol?(style_name)
364
372
  s3_protocol(style_name) == "https"
365
373
  end
374
+
375
+ def merge_s3_headers(http_headers, s3_headers, s3_metadata)
376
+ return if http_headers.nil?
377
+ http_headers = http_headers.call(instance) if http_headers.respond_to?(:call)
378
+ http_headers.inject({}) do |headers,(name,value)|
379
+ case name.to_s
380
+ when /^x-amz-meta-(.*)/i
381
+ s3_metadata[$1.downcase] = value
382
+ else
383
+ s3_headers[name.to_s.downcase.sub(/^x-amz-/,'').tr("-","_").to_sym] = value
384
+ end
385
+ end
386
+ end
366
387
  end
367
388
  end
368
389
  end
@@ -39,6 +39,9 @@ module Paperclip
39
39
  @format = options[:format]
40
40
  @animated = options[:animated].nil? ? true : options[:animated]
41
41
  @auto_orient = options[:auto_orient].nil? ? true : options[:auto_orient]
42
+ if @auto_orient && @current_geometry.respond_to?(:auto_orient)
43
+ @current_geometry.auto_orient
44
+ end
42
45
 
43
46
  @source_file_options = @source_file_options.split(/\s+/) if @source_file_options.respond_to?(:split)
44
47
  @convert_options = @convert_options.split(/\s+/) if @convert_options.respond_to?(:split)
@@ -8,21 +8,41 @@ module Paperclip
8
8
 
9
9
  def validate_each(record, attribute, value)
10
10
  attribute = "#{attribute}_content_type".to_sym
11
- value = record.send(:read_attribute_for_validation, attribute)
12
- allowed_types = [options[:content_type]].flatten
11
+ value = record.send :read_attribute_for_validation, attribute
13
12
 
14
13
  return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
15
14
 
16
- if allowed_types.none? { |type| type === value }
17
- record.errors.add(attribute, :invalid, options.merge(
18
- :types => allowed_types.join(', ')
19
- ))
15
+ validate_whitelist(record, attribute, value)
16
+ validate_blacklist(record, attribute, value)
17
+ end
18
+
19
+ def validate_whitelist(record, attribute, value)
20
+ if allowed_types.present? && allowed_types.none? { |type| type === value }
21
+ mark_invalid record, attribute, allowed_types
22
+ end
23
+ end
24
+
25
+ def validate_blacklist(record, attribute, value)
26
+ if forbidden_types.present? && forbidden_types.any? { |type| type === value }
27
+ mark_invalid record, attribute, forbidden_types
20
28
  end
21
29
  end
22
30
 
31
+ def mark_invalid(record, attribute, types)
32
+ record.errors.add attribute, :invalid, options.merge(:types => types.join(', '))
33
+ end
34
+
35
+ def allowed_types
36
+ [options[:content_type]].flatten.compact
37
+ end
38
+
39
+ def forbidden_types
40
+ [options[:not]].flatten.compact
41
+ end
42
+
23
43
  def check_validity!
24
- unless options.has_key?(:content_type)
25
- raise ArgumentError, "You must pass in :content_type to the validator"
44
+ unless options.has_key?(:content_type) || options.has_key?(:not)
45
+ raise ArgumentError, "You must pass in either :content_type or :not to the validator"
26
46
  end
27
47
  end
28
48
  end
@@ -36,6 +56,7 @@ module Paperclip
36
56
  # may not expect. For example, JPEG images are given image/pjpeg and
37
57
  # PNGs are image/x-png, so keep that in mind when determining how you
38
58
  # match. Allows all by default.
59
+ # * +not+: Forbidden content types.
39
60
  # * +message+: The message to display when the uploaded file has an invalid
40
61
  # content type.
41
62
  # * +if+: A lambda or name of an instance method. Validation will only
@@ -1,3 +1,3 @@
1
1
  module Paperclip
2
- VERSION = "3.3.1" unless defined? Paperclip::VERSION
2
+ VERSION = "3.4.0" unless defined? Paperclip::VERSION
3
3
  end
@@ -34,7 +34,7 @@ Gem::Specification.new do |s|
34
34
  s.add_development_dependency('shoulda')
35
35
  s.add_development_dependency('appraisal')
36
36
  s.add_development_dependency('mocha')
37
- s.add_development_dependency('aws-sdk')
37
+ s.add_development_dependency('aws-sdk', '>= 1.2.0')
38
38
  s.add_development_dependency('bourne')
39
39
  s.add_development_dependency('sqlite3', '~> 1.3.4')
40
40
  s.add_development_dependency('cucumber', '~> 1.2.1')
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+ require './test/helper'
3
+ require 'paperclip/attachment'
4
+
5
+ class AttachmentProcessingTest < Test::Unit::TestCase
6
+ def setup
7
+ rebuild_model
8
+ end
9
+
10
+ should 'process attachments given a valid assignment' do
11
+ file = File.new(fixture_file("5k.png"))
12
+ Dummy.validates_attachment_content_type :avatar, :content_type => "image/png"
13
+ instance = Dummy.new
14
+ attachment = instance.avatar
15
+ attachment.expects(:post_process)
16
+
17
+ attachment.assign(file)
18
+ end
19
+
20
+ should 'not process attachments if the assignment does not pass validation' do
21
+ file = File.new(fixture_file("5k.png"))
22
+ Dummy.validates_attachment_content_type :avatar, :content_type => "image/tiff"
23
+ instance = Dummy.new
24
+ attachment = instance.avatar
25
+ attachment.expects(:post_process).never
26
+
27
+ attachment.assign(file)
28
+ end
29
+ end
@@ -6,6 +6,29 @@ class Dummy; end
6
6
 
7
7
  class AttachmentTest < Test::Unit::TestCase
8
8
 
9
+ context "presence" do
10
+ setup do
11
+ rebuild_class
12
+ @dummy = Dummy.new
13
+ end
14
+
15
+ context "when file not set" do
16
+ should "not be present" do
17
+ assert @dummy.avatar.blank?
18
+ refute @dummy.avatar.present?
19
+ end
20
+ end
21
+
22
+ context "when file set" do
23
+ setup { @dummy.avatar = File.new(fixture_file("50x50.png"), "rb") }
24
+
25
+ should "be present" do
26
+ refute @dummy.avatar.blank?
27
+ assert @dummy.avatar.present?
28
+ end
29
+ end
30
+ end
31
+
9
32
  should "process :original style first" do
10
33
  file = File.new(fixture_file("50x50.png"), 'rb')
11
34
  rebuild_class :styles => { :small => '100x>', :original => '42x42#' }
@@ -115,6 +138,39 @@ class AttachmentTest < Test::Unit::TestCase
115
138
  assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true)
116
139
  end
117
140
 
141
+ should "render JSON as default style" do
142
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
143
+ attachment = Paperclip::Attachment.new(:name,
144
+ :instance,
145
+ :default_style => 'default style',
146
+ :url_generator => mock_url_generator_builder)
147
+
148
+ attachment_json = attachment.as_json
149
+ assert mock_url_generator_builder.has_generated_url_with_style_name?('default style')
150
+ end
151
+
152
+ should "pass the option :escape => true if :escape_url is true and :escape is not passed" do
153
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
154
+ attachment = Paperclip::Attachment.new(:name,
155
+ :instance,
156
+ :url_generator => mock_url_generator_builder,
157
+ :escape_url => true)
158
+
159
+ attachment.url(:style_name)
160
+ assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true)
161
+ end
162
+
163
+ should "pass the option :escape => false if :escape_url is false and :escape is not passed" do
164
+ mock_url_generator_builder = MockUrlGeneratorBuilder.new
165
+ attachment = Paperclip::Attachment.new(:name,
166
+ :instance,
167
+ :url_generator => mock_url_generator_builder,
168
+ :escape_url => false)
169
+
170
+ attachment.url(:style_name)
171
+ assert mock_url_generator_builder.has_generated_url_with_options?(:escape => false)
172
+ end
173
+
118
174
  should "return the path based on the url by default" do
119
175
  @attachment = attachment :url => "/:class/:id/:basename"
120
176
  @model = @attachment.instance
@@ -132,6 +188,24 @@ class AttachmentTest < Test::Unit::TestCase
132
188
  assert_equal expected_path, avatar_attachment.path
133
189
  end
134
190
 
191
+ should "render JSON as the URL to the attachment" do
192
+ avatar_attachment = attachment
193
+ model = avatar_attachment.instance
194
+ model.id = 1234
195
+ model.avatar_file_name = "fake.jpg"
196
+ assert_equal attachment.url, attachment.as_json
197
+ end
198
+
199
+ should "render JSON from the model when requested by :methods" do
200
+ rebuild_model
201
+ dummy = Dummy.new
202
+ dummy.id = 1234
203
+ dummy.avatar_file_name = "fake.jpg"
204
+ expected_string = '{"dummy":{"avatar":"/system/dummies/avatars/000/001/234/original/fake.jpg"}}'
205
+ # active_model pre-3.2 checks only by calling any? on it, thus it doesn't work if it is empty
206
+ assert_equal expected_string, dummy.to_json(:only => [:dummy_key_for_old_active_model], :methods => [:avatar])
207
+ end
208
+
135
209
  context "Attachment default_options" do
136
210
  setup do
137
211
  rebuild_model
@@ -385,6 +459,25 @@ class AttachmentTest < Test::Unit::TestCase
385
459
  end
386
460
  end
387
461
 
462
+ context "An attachment with :only_process that is a proc" do
463
+ setup do
464
+ rebuild_model :styles => {
465
+ :thumb => "100x100",
466
+ :large => "400x400"
467
+ },
468
+ :only_process => lambda { |attachment| [:thumb] }
469
+
470
+ @file = StringIO.new("...")
471
+ @attachment = Dummy.new.avatar
472
+ end
473
+
474
+ should "only process the provided style" do
475
+ @attachment.expects(:post_process).with(:thumb)
476
+ @attachment.expects(:post_process).with(:large).never
477
+ @attachment.assign(@file)
478
+ end
479
+ end
480
+
388
481
  context "An attachment with :convert_options that is a proc" do
389
482
  setup do
390
483
  rebuild_model :styles => {
@@ -691,9 +784,7 @@ class AttachmentTest < Test::Unit::TestCase
691
784
  context "Assigning an attachment" do
692
785
  setup do
693
786
  rebuild_model :styles => { :something => "100x100#" }
694
- @file = StringIO.new(".")
695
- @file.stubs(:original_filename).returns("5k.png\n\n")
696
- @file.stubs(:content_type).returns("image/png\n\n")
787
+ @file = File.new(fixture_file("5k.png"), "rb")
697
788
  @dummy = Dummy.new
698
789
  @dummy.avatar = @file
699
790
  end
@@ -710,9 +801,7 @@ class AttachmentTest < Test::Unit::TestCase
710
801
  context "Assigning an attachment" do
711
802
  setup do
712
803
  rebuild_model :styles => { :something => "100x100#" }
713
- @file = StringIO.new(".")
714
- @file.stubs(:original_filename).returns("5k.png\n\n")
715
- @file.stubs(:content_type).returns(MIME::Type.new("image/png"))
804
+ @file = File.new(fixture_file("5k.png"), "rb")
716
805
  @dummy = Dummy.new
717
806
  @dummy.avatar = @file
718
807
  end
@@ -725,11 +814,8 @@ class AttachmentTest < Test::Unit::TestCase
725
814
  context "Attachment with strange letters" do
726
815
  setup do
727
816
  rebuild_model
728
-
729
- @file = StringIO.new(".")
730
- @file.stubs(:original_filename).returns("sheep_say_bæ.png\r\n")
731
- @file.stubs(:content_type).returns("image/png\r\n")
732
-
817
+ @file = File.new(fixture_file("5k.png"), "rb")
818
+ @file.stubs(:original_filename).returns("sheep_say_bæ.png")
733
819
  @dummy = Dummy.new
734
820
  @dummy.avatar = @file
735
821
  end