paperclip-youtube 2.3.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/LICENSE +26 -0
  2. data/README.md +91 -0
  3. data/Rakefile +80 -0
  4. data/generators/paperclip/USAGE +5 -0
  5. data/generators/paperclip/paperclip_generator.rb +27 -0
  6. data/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  7. data/init.rb +1 -0
  8. data/lib/generators/paperclip/USAGE +8 -0
  9. data/lib/generators/paperclip/paperclip_generator.rb +31 -0
  10. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  11. data/lib/paperclip.rb +378 -0
  12. data/lib/paperclip/attachment.rb +376 -0
  13. data/lib/paperclip/callback_compatability.rb +61 -0
  14. data/lib/paperclip/command_line.rb +86 -0
  15. data/lib/paperclip/geometry.rb +115 -0
  16. data/lib/paperclip/interpolations.rb +130 -0
  17. data/lib/paperclip/iostream.rb +45 -0
  18. data/lib/paperclip/matchers.rb +33 -0
  19. data/lib/paperclip/matchers/have_attached_file_matcher.rb +57 -0
  20. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +75 -0
  21. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +54 -0
  22. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +95 -0
  23. data/lib/paperclip/processor.rb +58 -0
  24. data/lib/paperclip/railtie.rb +24 -0
  25. data/lib/paperclip/storage.rb +3 -0
  26. data/lib/paperclip/storage/filesystem.rb +73 -0
  27. data/lib/paperclip/storage/s3.rb +192 -0
  28. data/lib/paperclip/storage/youtube.rb +331 -0
  29. data/lib/paperclip/style.rb +90 -0
  30. data/lib/paperclip/thumbnail.rb +79 -0
  31. data/lib/paperclip/upfile.rb +55 -0
  32. data/lib/paperclip/version.rb +3 -0
  33. data/lib/tasks/paperclip.rake +72 -0
  34. data/rails/init.rb +2 -0
  35. data/shoulda_macros/paperclip.rb +118 -0
  36. data/test/attachment_test.rb +921 -0
  37. data/test/command_line_test.rb +138 -0
  38. data/test/database.yml +4 -0
  39. data/test/fixtures/12k.png +0 -0
  40. data/test/fixtures/50x50.png +0 -0
  41. data/test/fixtures/5k.png +0 -0
  42. data/test/fixtures/bad.png +1 -0
  43. data/test/fixtures/s3.yml +8 -0
  44. data/test/fixtures/text.txt +0 -0
  45. data/test/fixtures/twopage.pdf +0 -0
  46. data/test/fixtures/uppercase.PNG +0 -0
  47. data/test/geometry_test.rb +177 -0
  48. data/test/helper.rb +146 -0
  49. data/test/integration_test.rb +570 -0
  50. data/test/interpolations_test.rb +143 -0
  51. data/test/iostream_test.rb +71 -0
  52. data/test/matchers/have_attached_file_matcher_test.rb +24 -0
  53. data/test/matchers/validate_attachment_content_type_matcher_test.rb +47 -0
  54. data/test/matchers/validate_attachment_presence_matcher_test.rb +26 -0
  55. data/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
  56. data/test/paperclip_test.rb +301 -0
  57. data/test/processor_test.rb +10 -0
  58. data/test/storage_test.rb +386 -0
  59. data/test/style_test.rb +141 -0
  60. data/test/thumbnail_test.rb +227 -0
  61. data/test/upfile_test.rb +36 -0
  62. metadata +195 -0
@@ -0,0 +1,55 @@
1
+ module Paperclip
2
+ # The Upfile module is a convenience module for adding uploaded-file-type methods
3
+ # to the +File+ class. Useful for testing.
4
+ # user.avatar = File.new("test/test_avatar.jpg")
5
+ module Upfile
6
+
7
+ # Infer the MIME-type of the file from the extension.
8
+ def content_type
9
+ type = (self.path.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
10
+ case type
11
+ when %r"jp(e|g|eg)" then "image/jpeg"
12
+ when %r"tiff?" then "image/tiff"
13
+ when %r"png", "gif", "bmp" then "image/#{type}"
14
+ when "txt" then "text/plain"
15
+ when %r"html?" then "text/html"
16
+ when "js" then "application/js"
17
+ when "csv", "xml", "css" then "text/#{type}"
18
+ else
19
+ # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
20
+ content_type = (Paperclip.run("file", "-b --mime-type :file", :file => self.path).split(':').last.strip rescue "application/x-#{type}")
21
+ content_type = "application/x-#{type}" if content_type.match(/\(.*?\)/)
22
+ content_type
23
+ end
24
+ end
25
+
26
+ # Returns the file's normal name.
27
+ def original_filename
28
+ File.basename(self.path)
29
+ end
30
+
31
+ # Returns the size of the file.
32
+ def size
33
+ File.size(self)
34
+ end
35
+ end
36
+ end
37
+
38
+ if defined? StringIO
39
+ class StringIO
40
+ attr_accessor :original_filename, :content_type, :fingerprint
41
+ def original_filename
42
+ @original_filename ||= "stringio.txt"
43
+ end
44
+ def content_type
45
+ @content_type ||= "text/plain"
46
+ end
47
+ def fingerprint
48
+ @fingerprint ||= Digest::MD5.hexdigest(self.string)
49
+ end
50
+ end
51
+ end
52
+
53
+ class File #:nodoc:
54
+ include Paperclip::Upfile
55
+ end
@@ -0,0 +1,3 @@
1
+ module Paperclip
2
+ VERSION = "2.3.8.1" unless defined? Paperclip::VERSION
3
+ end
@@ -0,0 +1,72 @@
1
+ def obtain_class
2
+ class_name = ENV['CLASS'] || ENV['class']
3
+ raise "Must specify CLASS" unless class_name
4
+ class_name
5
+ end
6
+
7
+ def obtain_attachments(klass)
8
+ klass = Object.const_get(klass.to_s)
9
+ name = ENV['ATTACHMENT'] || ENV['attachment']
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)
12
+ [ name ]
13
+ else
14
+ klass.attachment_definitions.keys
15
+ end
16
+ end
17
+
18
+ namespace :paperclip do
19
+ desc "Refreshes both metadata and thumbnails."
20
+ task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"]
21
+
22
+ namespace :refresh do
23
+ desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT)."
24
+ task :thumbnails => :environment do
25
+ errors = []
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
33
+ end
34
+ errors.each{|e| puts "#{e.first}: #{e.last.full_messages.inspect}" }
35
+ end
36
+
37
+ desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)."
38
+ task :metadata => :environment do
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
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ desc "Cleans out invalid attachments. Useful after you've added new validations."
57
+ task :clean => :environment do
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
69
+ end
70
+ end
71
+ end
72
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'paperclip/railtie'
2
+ Paperclip::Railtie.insert
@@ -0,0 +1,118 @@
1
+ require 'paperclip/matchers'
2
+
3
+ module Paperclip
4
+ # =Paperclip Shoulda Macros
5
+ #
6
+ # These macros are intended for use with shoulda, and will be included into
7
+ # your tests automatically. All of the macros use the standard shoulda
8
+ # assumption that the name of the test is based on the name of the model
9
+ # you're testing (that is, UserTest is the test for the User model), and
10
+ # will load that class for testing purposes.
11
+ module Shoulda
12
+ include Matchers
13
+ # This will test whether you have defined your attachment correctly by
14
+ # checking for all the required fields exist after the definition of the
15
+ # attachment.
16
+ def should_have_attached_file name
17
+ klass = self.name.gsub(/Test$/, '').constantize
18
+ matcher = have_attached_file name
19
+ should matcher.description do
20
+ assert_accepts(matcher, klass)
21
+ end
22
+ end
23
+
24
+ # Tests for validations on the presence of the attachment.
25
+ def should_validate_attachment_presence name
26
+ klass = self.name.gsub(/Test$/, '').constantize
27
+ matcher = validate_attachment_presence name
28
+ should matcher.description do
29
+ assert_accepts(matcher, klass)
30
+ end
31
+ end
32
+
33
+ # Tests that you have content_type validations specified. There are two
34
+ # options, :valid and :invalid. Both accept an array of strings. The
35
+ # strings should be a list of content types which will pass and fail
36
+ # validation, respectively.
37
+ def should_validate_attachment_content_type name, options = {}
38
+ klass = self.name.gsub(/Test$/, '').constantize
39
+ valid = [options[:valid]].flatten
40
+ invalid = [options[:invalid]].flatten
41
+ matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid)
42
+ should matcher.description do
43
+ assert_accepts(matcher, klass)
44
+ end
45
+ end
46
+
47
+ # Tests to ensure that you have file size validations turned on. You
48
+ # can pass the same options to this that you can to
49
+ # validate_attachment_file_size - :less_than, :greater_than, and :in.
50
+ # :less_than checks that a file is less than a certain size, :greater_than
51
+ # checks that a file is more than a certain size, and :in takes a Range or
52
+ # Array which specifies the lower and upper limits of the file size.
53
+ def should_validate_attachment_size name, options = {}
54
+ klass = self.name.gsub(/Test$/, '').constantize
55
+ min = options[:greater_than] || (options[:in] && options[:in].first) || 0
56
+ max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
57
+ range = (min..max)
58
+ matcher = validate_attachment_size(name).in(range)
59
+ should matcher.description do
60
+ assert_accepts(matcher, klass)
61
+ end
62
+ end
63
+
64
+ # Stubs the HTTP PUT for an attachment using S3 storage.
65
+ #
66
+ # @example
67
+ # stub_paperclip_s3('user', 'avatar', 'png')
68
+ def stub_paperclip_s3(model, attachment, extension)
69
+ definition = model.gsub(" ", "_").classify.constantize.
70
+ attachment_definitions[attachment.to_sym]
71
+
72
+ path = "http://s3.amazonaws.com/:id/#{definition[:path]}"
73
+ path.gsub!(/:([^\/\.]+)/) do |match|
74
+ "([^\/\.]+)"
75
+ end
76
+
77
+ begin
78
+ FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
79
+ rescue NameError
80
+ raise NameError, "the stub_paperclip_s3 shoulda macro requires the fakeweb gem."
81
+ end
82
+ end
83
+
84
+ # Stub S3 and return a file for attachment. Best with Factory Girl.
85
+ # Uses a strict directory convention:
86
+ #
87
+ # features/support/paperclip
88
+ #
89
+ # This method is used by the Paperclip-provided Cucumber step:
90
+ #
91
+ # When I attach a "demo_tape" "mp3" file to a "band" on S3
92
+ #
93
+ # @example
94
+ # Factory.define :band_with_demo_tape, :parent => :band do |band|
95
+ # band.demo_tape { band.paperclip_fixture("band", "demo_tape", "png") }
96
+ # end
97
+ def paperclip_fixture(model, attachment, extension)
98
+ stub_paperclip_s3(model, attachment, extension)
99
+ base_path = File.join(File.dirname(__FILE__), "..", "..",
100
+ "features", "support", "paperclip")
101
+ File.new(File.join(base_path, model, "#{attachment}.#{extension}"))
102
+ end
103
+ end
104
+ end
105
+
106
+ if defined?(ActionController::Integration::Session)
107
+ class ActionController::Integration::Session #:nodoc:
108
+ include Paperclip::Shoulda
109
+ end
110
+ end
111
+
112
+ class Factory
113
+ include Paperclip::Shoulda #:nodoc:
114
+ end
115
+
116
+ class Test::Unit::TestCase #:nodoc:
117
+ extend Paperclip::Shoulda
118
+ end
@@ -0,0 +1,921 @@
1
+ # encoding: utf-8
2
+ require './test/helper'
3
+
4
+ class Dummy
5
+ # This is a dummy class
6
+ end
7
+
8
+ class AttachmentTest < Test::Unit::TestCase
9
+ should "return the path based on the url by default" do
10
+ @attachment = attachment :url => "/:class/:id/:basename"
11
+ @model = @attachment.instance
12
+ @model.id = 1234
13
+ @model.avatar_file_name = "fake.jpg"
14
+ assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path
15
+ end
16
+
17
+ context "Attachment default_options" do
18
+ setup do
19
+ rebuild_model
20
+ @old_default_options = Paperclip::Attachment.default_options.dup
21
+ @new_default_options = @old_default_options.merge({
22
+ :path => "argle/bargle",
23
+ :url => "fooferon",
24
+ :default_url => "not here.png"
25
+ })
26
+ end
27
+
28
+ teardown do
29
+ Paperclip::Attachment.default_options.merge! @old_default_options
30
+ end
31
+
32
+ should "be overrideable" do
33
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
34
+ @new_default_options.keys.each do |key|
35
+ assert_equal @new_default_options[key],
36
+ Paperclip::Attachment.default_options[key]
37
+ end
38
+ end
39
+
40
+ context "without an Attachment" do
41
+ setup do
42
+ @dummy = Dummy.new
43
+ end
44
+
45
+ should "return false when asked exists?" do
46
+ assert !@dummy.avatar.exists?
47
+ end
48
+ end
49
+
50
+ context "on an Attachment" do
51
+ setup do
52
+ @dummy = Dummy.new
53
+ @attachment = @dummy.avatar
54
+ end
55
+
56
+ Paperclip::Attachment.default_options.keys.each do |key|
57
+ should "be the default_options for #{key}" do
58
+ assert_equal @old_default_options[key],
59
+ @attachment.instance_variable_get("@#{key}"),
60
+ key
61
+ end
62
+ end
63
+
64
+ context "when redefined" do
65
+ setup do
66
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
67
+ @dummy = Dummy.new
68
+ @attachment = @dummy.avatar
69
+ end
70
+
71
+ Paperclip::Attachment.default_options.keys.each do |key|
72
+ should "be the new default_options for #{key}" do
73
+ assert_equal @new_default_options[key],
74
+ @attachment.instance_variable_get("@#{key}"),
75
+ key
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ context "An attachment with similarly named interpolations" do
83
+ setup do
84
+ rebuild_model :path => ":id.omg/:id-bbq/:idwhat/:id_partition.wtf"
85
+ @dummy = Dummy.new
86
+ @dummy.stubs(:id).returns(1024)
87
+ @file = File.new(File.join(File.dirname(__FILE__),
88
+ "fixtures",
89
+ "5k.png"), 'rb')
90
+ @dummy.avatar = @file
91
+ end
92
+
93
+ teardown { @file.close }
94
+
95
+ should "make sure that they are interpolated correctly" do
96
+ assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path
97
+ end
98
+ end
99
+
100
+ context "An attachment with :timestamp interpolations" do
101
+ setup do
102
+ @file = StringIO.new("...")
103
+ @zone = 'UTC'
104
+ Time.stubs(:zone).returns(@zone)
105
+ @zone_default = 'Eastern Time (US & Canada)'
106
+ Time.stubs(:zone_default).returns(@zone_default)
107
+ end
108
+
109
+ context "using default time zone" do
110
+ setup do
111
+ rebuild_model :path => ":timestamp", :use_default_time_zone => true
112
+ @dummy = Dummy.new
113
+ @dummy.avatar = @file
114
+ end
115
+
116
+ should "return a time in the default zone" do
117
+ assert_equal @dummy.avatar_updated_at.in_time_zone(@zone_default).to_s, @dummy.avatar.path
118
+ end
119
+ end
120
+
121
+ context "using per-thread time zone" do
122
+ setup do
123
+ rebuild_model :path => ":timestamp", :use_default_time_zone => false
124
+ @dummy = Dummy.new
125
+ @dummy.avatar = @file
126
+ end
127
+
128
+ should "return a time in the per-thread zone" do
129
+ assert_equal @dummy.avatar_updated_at.in_time_zone(@zone).to_s, @dummy.avatar.path
130
+ end
131
+ end
132
+ end
133
+
134
+ context "An attachment with :hash interpolations" do
135
+ setup do
136
+ @file = StringIO.new("...")
137
+ end
138
+
139
+ should "raise if no secret is provided" do
140
+ @attachment = attachment :path => ":hash"
141
+ @attachment.assign @file
142
+
143
+ assert_raise ArgumentError do
144
+ @attachment.path
145
+ end
146
+ end
147
+
148
+ context "when secret is set" do
149
+ setup do
150
+ @attachment = attachment :path => ":hash", :hash_secret => "w00t"
151
+ @attachment.stubs(:instance_read).with(:updated_at).returns(Time.at(1234567890))
152
+ @attachment.stubs(:instance_read).with(:file_name).returns("bla.txt")
153
+ @attachment.instance.id = 1234
154
+ @attachment.assign @file
155
+ end
156
+
157
+ should "interpolate the hash data" do
158
+ @attachment.expects(:interpolate).with(@attachment.options[:hash_data],anything).returns("interpolated_stuff")
159
+ @attachment.hash
160
+ end
161
+
162
+ should "result in the correct interpolation" do
163
+ assert_equal "fake_models/avatars/1234/original/1234567890", @attachment.send(:interpolate,@attachment.options[:hash_data])
164
+ end
165
+
166
+ should "result in a correct hash" do
167
+ assert_equal "d22b617d1bf10016aa7d046d16427ae203f39fce", @attachment.path
168
+ end
169
+
170
+ should "generate a hash digest with the correct style" do
171
+ OpenSSL::HMAC.expects(:hexdigest).with(anything, anything, "fake_models/avatars/1234/medium/1234567890")
172
+ @attachment.path("medium")
173
+ end
174
+ end
175
+ end
176
+
177
+ context "An attachment with a :rails_env interpolation" do
178
+ setup do
179
+ @rails_env = "blah"
180
+ @id = 1024
181
+ rebuild_model :path => ":rails_env/:id.png"
182
+ @dummy = Dummy.new
183
+ @dummy.stubs(:id).returns(@id)
184
+ @file = StringIO.new(".")
185
+ @dummy.avatar = @file
186
+ Rails.stubs(:env).returns(@rails_env)
187
+ end
188
+
189
+ should "return the proper path" do
190
+ assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path
191
+ end
192
+ end
193
+
194
+ context "An attachment with a default style and an extension interpolation" do
195
+ setup do
196
+ @attachment = attachment :path => ":basename.:extension",
197
+ :styles => { :default => ["100x100", :png] },
198
+ :default_style => :default
199
+ @file = StringIO.new("...")
200
+ @file.stubs(:original_filename).returns("file.jpg")
201
+ end
202
+ should "return the right extension for the path" do
203
+ @attachment.assign(@file)
204
+ assert_equal "file.png", @attachment.path
205
+ end
206
+ end
207
+
208
+ context "An attachment with :convert_options" do
209
+ setup do
210
+ rebuild_model :styles => {
211
+ :thumb => "100x100",
212
+ :large => "400x400"
213
+ },
214
+ :convert_options => {
215
+ :all => "-do_stuff",
216
+ :thumb => "-thumbnailize"
217
+ }
218
+ @dummy = Dummy.new
219
+ @dummy.avatar
220
+ end
221
+
222
+ should "report the correct options when sent #extra_options_for(:thumb)" do
223
+ assert_equal "-thumbnailize -do_stuff", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
224
+ end
225
+
226
+ should "report the correct options when sent #extra_options_for(:large)" do
227
+ assert_equal "-do_stuff", @dummy.avatar.send(:extra_options_for, :large)
228
+ end
229
+ end
230
+
231
+ context "An attachment with :convert_options that is a proc" do
232
+ setup do
233
+ rebuild_model :styles => {
234
+ :thumb => "100x100",
235
+ :large => "400x400"
236
+ },
237
+ :convert_options => {
238
+ :all => lambda{|i| i.all },
239
+ :thumb => lambda{|i| i.thumb }
240
+ }
241
+ Dummy.class_eval do
242
+ def all; "-all"; end
243
+ def thumb; "-thumb"; end
244
+ end
245
+ @dummy = Dummy.new
246
+ @dummy.avatar
247
+ end
248
+
249
+ should "report the correct options when sent #extra_options_for(:thumb)" do
250
+ assert_equal "-thumb -all", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
251
+ end
252
+
253
+ should "report the correct options when sent #extra_options_for(:large)" do
254
+ assert_equal "-all", @dummy.avatar.send(:extra_options_for, :large)
255
+ end
256
+ end
257
+
258
+ context "An attachment with :path that is a proc" do
259
+ setup do
260
+ rebuild_model :path => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
261
+
262
+ @file = File.new(File.join(File.dirname(__FILE__),
263
+ "fixtures",
264
+ "5k.png"), 'rb')
265
+ @dummyA = Dummy.new(:other => 'a')
266
+ @dummyA.avatar = @file
267
+ @dummyB = Dummy.new(:other => 'b')
268
+ @dummyB.avatar = @file
269
+ end
270
+
271
+ teardown { @file.close }
272
+
273
+ should "return correct path" do
274
+ assert_equal "path/a.png", @dummyA.avatar.path
275
+ assert_equal "path/b.png", @dummyB.avatar.path
276
+ end
277
+ end
278
+
279
+ context "An attachment with :styles that is a proc" do
280
+ setup do
281
+ rebuild_model :styles => lambda{ |attachment| {:thumb => "50x50#", :large => "400x400"} }
282
+
283
+ @attachment = Dummy.new.avatar
284
+ end
285
+
286
+ should "have the correct geometry" do
287
+ assert_equal "50x50#", @attachment.styles[:thumb][:geometry]
288
+ end
289
+ end
290
+
291
+ context "An attachment with :url that is a proc" do
292
+ setup do
293
+ rebuild_model :url => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
294
+
295
+ @file = File.new(File.join(File.dirname(__FILE__),
296
+ "fixtures",
297
+ "5k.png"), 'rb')
298
+ @dummyA = Dummy.new(:other => 'a')
299
+ @dummyA.avatar = @file
300
+ @dummyB = Dummy.new(:other => 'b')
301
+ @dummyB.avatar = @file
302
+ end
303
+
304
+ teardown { @file.close }
305
+
306
+ should "return correct url" do
307
+ assert_equal "path/a.png", @dummyA.avatar.url(:original, false)
308
+ assert_equal "path/b.png", @dummyB.avatar.url(:original, false)
309
+ end
310
+ end
311
+
312
+ geometry_specs = [
313
+ [ lambda{|z| "50x50#" }, :png ],
314
+ lambda{|z| "50x50#" },
315
+ { :geometry => lambda{|z| "50x50#" } }
316
+ ]
317
+ geometry_specs.each do |geometry_spec|
318
+ context "An attachment geometry like #{geometry_spec}" do
319
+ setup do
320
+ rebuild_model :styles => { :normal => geometry_spec }
321
+ @attachment = Dummy.new.avatar
322
+ end
323
+
324
+ context "when assigned" do
325
+ setup do
326
+ @file = StringIO.new(".")
327
+ @attachment.assign(@file)
328
+ end
329
+
330
+ should "have the correct geometry" do
331
+ assert_equal "50x50#", @attachment.styles[:normal][:geometry]
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ context "An attachment with both 'normal' and hash-style styles" do
338
+ setup do
339
+ rebuild_model :styles => {
340
+ :normal => ["50x50#", :png],
341
+ :hash => { :geometry => "50x50#", :format => :png }
342
+ }
343
+ @dummy = Dummy.new
344
+ @attachment = @dummy.avatar
345
+ end
346
+
347
+ [:processors, :whiny, :convert_options, :geometry, :format].each do |field|
348
+ should "have the same #{field} field" do
349
+ assert_equal @attachment.styles[:normal][field], @attachment.styles[:hash][field]
350
+ end
351
+ end
352
+ end
353
+
354
+ context "An attachment with :processors that is a proc" do
355
+ setup do
356
+ rebuild_model :styles => { :normal => '' }, :processors => lambda { |a| [ :test ] }
357
+ @attachment = Dummy.new.avatar
358
+ end
359
+
360
+ context "when assigned" do
361
+ setup do
362
+ @attachment.assign(StringIO.new("."))
363
+ end
364
+
365
+ should "have the correct processors" do
366
+ assert_equal [ :test ], @attachment.styles[:normal][:processors]
367
+ end
368
+ end
369
+ end
370
+
371
+ context "An attachment with erroring processor" do
372
+ setup do
373
+ rebuild_model :processor => [:thumbnail], :styles => { :small => '' }, :whiny_thumbnails => true
374
+ @dummy = Dummy.new
375
+ Paperclip::Thumbnail.expects(:make).raises(Paperclip::PaperclipError, "cannot be processed.")
376
+ @file = StringIO.new("...")
377
+ @file.stubs(:to_tempfile).returns(@file)
378
+ @dummy.avatar = @file
379
+ end
380
+
381
+ should "correctly forward processing error message to the instance" do
382
+ @dummy.valid?
383
+ assert_contains @dummy.errors.full_messages, "Avatar cannot be processed."
384
+ end
385
+ end
386
+
387
+ context "An attachment with multiple processors" do
388
+ setup do
389
+ class Paperclip::Test < Paperclip::Processor; end
390
+ @style_params = { :once => {:one => 1, :two => 2} }
391
+ rebuild_model :processors => [:thumbnail, :test], :styles => @style_params
392
+ @dummy = Dummy.new
393
+ @file = StringIO.new("...")
394
+ @file.stubs(:to_tempfile).returns(@file)
395
+ Paperclip::Test.stubs(:make).returns(@file)
396
+ Paperclip::Thumbnail.stubs(:make).returns(@file)
397
+ end
398
+
399
+ context "when assigned" do
400
+ setup { @dummy.avatar = @file }
401
+
402
+ before_should "call #make on all specified processors" do
403
+ Paperclip::Thumbnail.expects(:make).with(any_parameters).returns(@file)
404
+ Paperclip::Test.expects(:make).with(any_parameters).returns(@file)
405
+ end
406
+
407
+ before_should "call #make with the right parameters passed as second argument" do
408
+ expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => true, :convert_options => ""})
409
+ Paperclip::Thumbnail.expects(:make).with(anything, expected_params, anything).returns(@file)
410
+ end
411
+
412
+ before_should "call #make with attachment passed as third argument" do
413
+ Paperclip::Test.expects(:make).with(anything, anything, @dummy.avatar).returns(@file)
414
+ end
415
+ end
416
+ end
417
+
418
+ should "include the filesystem module when loading the filesystem storage" do
419
+ rebuild_model :storage => :filesystem
420
+ @dummy = Dummy.new
421
+ assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
422
+ end
423
+
424
+ should "include the filesystem module even if capitalization is wrong" do
425
+ rebuild_model :storage => :FileSystem
426
+ @dummy = Dummy.new
427
+ assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem)
428
+ end
429
+
430
+ should "raise an error if you try to include a storage module that doesn't exist" do
431
+ rebuild_model :storage => :not_here
432
+ @dummy = Dummy.new
433
+ assert_raises(Paperclip::StorageMethodNotFound) do
434
+ @dummy.avatar
435
+ end
436
+ end
437
+
438
+ context "An attachment with styles but no processors defined" do
439
+ setup do
440
+ rebuild_model :processors => [], :styles => {:something => '1'}
441
+ @dummy = Dummy.new
442
+ @file = StringIO.new("...")
443
+ end
444
+ should "raise when assigned to" do
445
+ assert_raises(RuntimeError){ @dummy.avatar = @file }
446
+ end
447
+ end
448
+
449
+ context "An attachment without styles and with no processors defined" do
450
+ setup do
451
+ rebuild_model :processors => [], :styles => {}
452
+ @dummy = Dummy.new
453
+ @file = StringIO.new("...")
454
+ end
455
+ should "not raise when assigned to" do
456
+ @dummy.avatar = @file
457
+ end
458
+ end
459
+
460
+ context "Assigning an attachment with post_process hooks" do
461
+ setup do
462
+ rebuild_class :styles => { :something => "100x100#" }
463
+ Dummy.class_eval do
464
+ before_avatar_post_process :do_before_avatar
465
+ after_avatar_post_process :do_after_avatar
466
+ before_post_process :do_before_all
467
+ after_post_process :do_after_all
468
+ def do_before_avatar; end
469
+ def do_after_avatar; end
470
+ def do_before_all; end
471
+ def do_after_all; end
472
+ end
473
+ @file = StringIO.new(".")
474
+ @file.stubs(:to_tempfile).returns(@file)
475
+ @dummy = Dummy.new
476
+ Paperclip::Thumbnail.stubs(:make).returns(@file)
477
+ @attachment = @dummy.avatar
478
+ end
479
+
480
+ should "call the defined callbacks when assigned" do
481
+ @dummy.expects(:do_before_avatar).with()
482
+ @dummy.expects(:do_after_avatar).with()
483
+ @dummy.expects(:do_before_all).with()
484
+ @dummy.expects(:do_after_all).with()
485
+ Paperclip::Thumbnail.expects(:make).returns(@file)
486
+ @dummy.avatar = @file
487
+ end
488
+
489
+ should "not cancel the processing if a before_post_process returns nil" do
490
+ @dummy.expects(:do_before_avatar).with().returns(nil)
491
+ @dummy.expects(:do_after_avatar).with()
492
+ @dummy.expects(:do_before_all).with().returns(nil)
493
+ @dummy.expects(:do_after_all).with()
494
+ Paperclip::Thumbnail.expects(:make).returns(@file)
495
+ @dummy.avatar = @file
496
+ end
497
+
498
+ should "cancel the processing if a before_post_process returns false" do
499
+ @dummy.expects(:do_before_avatar).never
500
+ @dummy.expects(:do_after_avatar).never
501
+ @dummy.expects(:do_before_all).with().returns(false)
502
+ @dummy.expects(:do_after_all)
503
+ Paperclip::Thumbnail.expects(:make).never
504
+ @dummy.avatar = @file
505
+ end
506
+
507
+ should "cancel the processing if a before_avatar_post_process returns false" do
508
+ @dummy.expects(:do_before_avatar).with().returns(false)
509
+ @dummy.expects(:do_after_avatar)
510
+ @dummy.expects(:do_before_all).with().returns(true)
511
+ @dummy.expects(:do_after_all)
512
+ Paperclip::Thumbnail.expects(:make).never
513
+ @dummy.avatar = @file
514
+ end
515
+ end
516
+
517
+ context "Assigning an attachment" do
518
+ setup do
519
+ rebuild_model :styles => { :something => "100x100#" }
520
+ @file = StringIO.new(".")
521
+ @file.stubs(:original_filename).returns("5k.png\n\n")
522
+ @file.stubs(:content_type).returns("image/png\n\n")
523
+ @file.stubs(:to_tempfile).returns(@file)
524
+ @dummy = Dummy.new
525
+ Paperclip::Thumbnail.expects(:make).returns(@file)
526
+ @attachment = @dummy.avatar
527
+ @dummy.avatar = @file
528
+ end
529
+
530
+ should "strip whitespace from original_filename field" do
531
+ assert_equal "5k.png", @dummy.avatar.original_filename
532
+ end
533
+
534
+ should "strip whitespace from content_type field" do
535
+ assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
536
+ end
537
+ end
538
+
539
+ context "Attachment with strange letters" do
540
+ setup do
541
+ rebuild_model
542
+
543
+ @not_file = mock("not_file")
544
+ @tempfile = mock("tempfile")
545
+ @not_file.stubs(:nil?).returns(false)
546
+ @not_file.expects(:size).returns(10)
547
+ @tempfile.expects(:size).returns(10)
548
+ @not_file.expects(:original_filename).returns("sheep_say_bæ.png\r\n")
549
+ @not_file.expects(:content_type).returns("image/png\r\n")
550
+
551
+ @dummy = Dummy.new
552
+ @attachment = @dummy.avatar
553
+ @attachment.expects(:valid_assignment?).with(@not_file).returns(true)
554
+ @attachment.expects(:queue_existing_for_delete)
555
+ @attachment.expects(:post_process)
556
+ @attachment.expects(:to_tempfile).returns(@tempfile)
557
+ @attachment.expects(:generate_fingerprint).with(@tempfile).returns("12345")
558
+ @attachment.expects(:generate_fingerprint).with(@not_file).returns("12345")
559
+ @dummy.avatar = @not_file
560
+ end
561
+
562
+ should "not remove strange letters" do
563
+ assert_equal "sheep_say_bæ.png", @dummy.avatar.original_filename
564
+ end
565
+ end
566
+
567
+ context "Attachment with uppercase extension and a default style" do
568
+ setup do
569
+ @old_defaults = Paperclip::Attachment.default_options.dup
570
+ Paperclip::Attachment.default_options.merge!({
571
+ :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
572
+ })
573
+ FileUtils.rm_rf("tmp")
574
+ rebuild_model
575
+ @instance = Dummy.new
576
+ @instance.stubs(:id).returns 123
577
+
578
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "uppercase.PNG"), 'rb')
579
+
580
+ styles = {:styles => { :large => ["400x400", :jpg],
581
+ :medium => ["100x100", :jpg],
582
+ :small => ["32x32#", :jpg]},
583
+ :default_style => :small}
584
+ @attachment = Paperclip::Attachment.new(:avatar,
585
+ @instance,
586
+ styles)
587
+ now = Time.now
588
+ Time.stubs(:now).returns(now)
589
+ @attachment.assign(@file)
590
+ @attachment.save
591
+ end
592
+
593
+ teardown do
594
+ @file.close
595
+ Paperclip::Attachment.default_options.merge!(@old_defaults)
596
+ end
597
+
598
+ should "should have matching to_s and url methods" do
599
+ file = @attachment.to_file
600
+ assert file
601
+ assert_match @attachment.to_s, @attachment.url
602
+ assert_match @attachment.to_s(:small), @attachment.url(:small)
603
+ file.close
604
+ end
605
+ end
606
+
607
+ context "An attachment" do
608
+ setup do
609
+ @old_defaults = Paperclip::Attachment.default_options.dup
610
+ Paperclip::Attachment.default_options.merge!({
611
+ :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
612
+ })
613
+ FileUtils.rm_rf("tmp")
614
+ rebuild_model
615
+ @instance = Dummy.new
616
+ @instance.stubs(:id).returns 123
617
+ @attachment = Paperclip::Attachment.new(:avatar, @instance)
618
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
619
+ end
620
+
621
+ teardown do
622
+ @file.close
623
+ Paperclip::Attachment.default_options.merge!(@old_defaults)
624
+ end
625
+
626
+ should "raise if there are not the correct columns when you try to assign" do
627
+ @other_attachment = Paperclip::Attachment.new(:not_here, @instance)
628
+ assert_raises(Paperclip::PaperclipError) do
629
+ @other_attachment.assign(@file)
630
+ end
631
+ end
632
+
633
+ should "return its default_url when no file assigned" do
634
+ assert @attachment.to_file.nil?
635
+ assert_equal "/avatars/original/missing.png", @attachment.url
636
+ assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
637
+ end
638
+
639
+ should "return nil as path when no file assigned" do
640
+ assert @attachment.to_file.nil?
641
+ assert_equal nil, @attachment.path
642
+ assert_equal nil, @attachment.path(:blah)
643
+ end
644
+
645
+ context "with a file assigned in the database" do
646
+ setup do
647
+ @attachment.stubs(:instance_read).with(:file_name).returns("5k.png")
648
+ @attachment.stubs(:instance_read).with(:content_type).returns("image/png")
649
+ @attachment.stubs(:instance_read).with(:file_size).returns(12345)
650
+ dtnow = DateTime.now
651
+ @now = Time.now
652
+ Time.stubs(:now).returns(@now)
653
+ @attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)
654
+ end
655
+
656
+ should "return a correct url even if the file does not exist" do
657
+ assert_nil @attachment.to_file
658
+ assert_match %r{^/system/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
659
+ end
660
+
661
+ should "make sure the updated_at mtime is in the url if it is defined" do
662
+ assert_match %r{#{@now.to_i}$}, @attachment.url(:blah)
663
+ end
664
+
665
+ should "make sure the updated_at mtime is NOT in the url if false is passed to the url method" do
666
+ assert_no_match %r{#{@now.to_i}$}, @attachment.url(:blah, false)
667
+ end
668
+
669
+ context "with the updated_at field removed" do
670
+ setup do
671
+ @attachment.stubs(:instance_read).with(:updated_at).returns(nil)
672
+ end
673
+
674
+ should "only return the url without the updated_at when sent #url" do
675
+ assert_match "/avatars/#{@instance.id}/blah/5k.png", @attachment.url(:blah)
676
+ end
677
+ end
678
+
679
+ should "return the proper path when filename has a single .'s" do
680
+ assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.png"), File.expand_path(@attachment.path)
681
+ end
682
+
683
+ should "return the proper path when filename has multiple .'s" do
684
+ @attachment.stubs(:instance_read).with(:file_name).returns("5k.old.png")
685
+ assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.old.png"), File.expand_path(@attachment.path)
686
+ end
687
+
688
+ context "when expecting three styles" do
689
+ setup do
690
+ styles = {:styles => { :large => ["400x400", :png],
691
+ :medium => ["100x100", :gif],
692
+ :small => ["32x32#", :jpg]}}
693
+ @attachment = Paperclip::Attachment.new(:avatar,
694
+ @instance,
695
+ styles)
696
+ end
697
+
698
+ context "and assigned a file" do
699
+ setup do
700
+ now = Time.now
701
+ Time.stubs(:now).returns(now)
702
+ @attachment.assign(@file)
703
+ end
704
+
705
+ should "be dirty" do
706
+ assert @attachment.dirty?
707
+ end
708
+
709
+ context "and saved" do
710
+ setup do
711
+ @attachment.save
712
+ end
713
+
714
+ should "return the real url" do
715
+ file = @attachment.to_file
716
+ assert file
717
+ assert_match %r{^/system/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
718
+ assert_match %r{^/system/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
719
+ file.close
720
+ end
721
+
722
+ should "commit the files to disk" do
723
+ [:large, :medium, :small].each do |style|
724
+ io = @attachment.to_file(style)
725
+ # p "in commit to disk test, io is #{io.inspect} and @instance.id is #{@instance.id}"
726
+ assert File.exists?(io.path)
727
+ assert ! io.is_a?(::Tempfile)
728
+ io.close
729
+ end
730
+ end
731
+
732
+ should "save the files as the right formats and sizes" do
733
+ [[:large, 400, 61, "PNG"],
734
+ [:medium, 100, 15, "GIF"],
735
+ [:small, 32, 32, "JPEG"]].each do |style|
736
+ cmd = %Q[identify -format "%w %h %b %m" "#{@attachment.path(style.first)}"]
737
+ out = `#{cmd}`
738
+ width, height, size, format = out.split(" ")
739
+ assert_equal style[1].to_s, width.to_s
740
+ assert_equal style[2].to_s, height.to_s
741
+ assert_equal style[3].to_s, format.to_s
742
+ end
743
+ end
744
+
745
+ should "still have its #file attribute not be nil" do
746
+ assert ! (file = @attachment.to_file).nil?
747
+ file.close
748
+ end
749
+
750
+ context "and trying to delete" do
751
+ setup do
752
+ @existing_names = @attachment.styles.keys.collect do |style|
753
+ @attachment.path(style)
754
+ end
755
+ end
756
+
757
+ should "delete the files after assigning nil" do
758
+ @attachment.expects(:instance_write).with(:file_name, nil)
759
+ @attachment.expects(:instance_write).with(:content_type, nil)
760
+ @attachment.expects(:instance_write).with(:file_size, nil)
761
+ @attachment.expects(:instance_write).with(:updated_at, nil)
762
+ @attachment.assign nil
763
+ @attachment.save
764
+ @existing_names.each{|f| assert ! File.exists?(f) }
765
+ end
766
+
767
+ should "delete the files when you call #clear and #save" do
768
+ @attachment.expects(:instance_write).with(:file_name, nil)
769
+ @attachment.expects(:instance_write).with(:content_type, nil)
770
+ @attachment.expects(:instance_write).with(:file_size, nil)
771
+ @attachment.expects(:instance_write).with(:updated_at, nil)
772
+ @attachment.clear
773
+ @attachment.save
774
+ @existing_names.each{|f| assert ! File.exists?(f) }
775
+ end
776
+
777
+ should "delete the files when you call #delete" do
778
+ @attachment.expects(:instance_write).with(:file_name, nil)
779
+ @attachment.expects(:instance_write).with(:content_type, nil)
780
+ @attachment.expects(:instance_write).with(:file_size, nil)
781
+ @attachment.expects(:instance_write).with(:updated_at, nil)
782
+ @attachment.destroy
783
+ @existing_names.each{|f| assert ! File.exists?(f) }
784
+ end
785
+ end
786
+ end
787
+ end
788
+ end
789
+
790
+ end
791
+
792
+ context "when trying a nonexistant storage type" do
793
+ setup do
794
+ rebuild_model :storage => :not_here
795
+ end
796
+
797
+ should "not be able to find the module" do
798
+ assert_raise(Paperclip::StorageMethodNotFound){ Dummy.new.avatar }
799
+ end
800
+ end
801
+ end
802
+
803
+ context "An attachment with only a avatar_file_name column" do
804
+ setup do
805
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
806
+ table.column :avatar_file_name, :string
807
+ end
808
+ rebuild_class
809
+ @dummy = Dummy.new
810
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
811
+ end
812
+
813
+ teardown { @file.close }
814
+
815
+ should "not error when assigned an attachment" do
816
+ assert_nothing_raised { @dummy.avatar = @file }
817
+ end
818
+
819
+ should "return the time when sent #avatar_updated_at" do
820
+ now = Time.now
821
+ Time.stubs(:now).returns(now)
822
+ @dummy.avatar = @file
823
+ assert_equal now.to_i, @dummy.avatar.updated_at.to_i
824
+ end
825
+
826
+ should "return nil when reloaded and sent #avatar_updated_at" do
827
+ @dummy.save
828
+ @dummy.reload
829
+ assert_nil @dummy.avatar.updated_at
830
+ end
831
+
832
+ should "return the right value when sent #avatar_file_size" do
833
+ @dummy.avatar = @file
834
+ assert_equal @file.size, @dummy.avatar.size
835
+ end
836
+
837
+ context "and avatar_updated_at column" do
838
+ setup do
839
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp
840
+ rebuild_class
841
+ @dummy = Dummy.new
842
+ end
843
+
844
+ should "not error when assigned an attachment" do
845
+ assert_nothing_raised { @dummy.avatar = @file }
846
+ end
847
+
848
+ should "return the right value when sent #avatar_updated_at" do
849
+ now = Time.now
850
+ Time.stubs(:now).returns(now)
851
+ @dummy.avatar = @file
852
+ assert_equal now.to_i, @dummy.avatar.updated_at
853
+ end
854
+ end
855
+
856
+ context "and avatar_content_type column" do
857
+ setup do
858
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_content_type, :string
859
+ rebuild_class
860
+ @dummy = Dummy.new
861
+ end
862
+
863
+ should "not error when assigned an attachment" do
864
+ assert_nothing_raised { @dummy.avatar = @file }
865
+ end
866
+
867
+ should "return the right value when sent #avatar_content_type" do
868
+ @dummy.avatar = @file
869
+ assert_equal "image/png", @dummy.avatar.content_type
870
+ end
871
+ end
872
+
873
+ context "and avatar_file_size column" do
874
+ setup do
875
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_file_size, :integer
876
+ rebuild_class
877
+ @dummy = Dummy.new
878
+ end
879
+
880
+ should "not error when assigned an attachment" do
881
+ assert_nothing_raised { @dummy.avatar = @file }
882
+ end
883
+
884
+ should "return the right value when sent #avatar_file_size" do
885
+ @dummy.avatar = @file
886
+ assert_equal @file.size, @dummy.avatar.size
887
+ end
888
+
889
+ should "return the right value when saved, reloaded, and sent #avatar_file_size" do
890
+ @dummy.avatar = @file
891
+ @dummy.save
892
+ @dummy = Dummy.find(@dummy.id)
893
+ assert_equal @file.size, @dummy.avatar.size
894
+ end
895
+ end
896
+
897
+ context "and avatar_fingerprint column" do
898
+ setup do
899
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_fingerprint, :string
900
+ rebuild_class
901
+ @dummy = Dummy.new
902
+ end
903
+
904
+ should "not error when assigned an attachment" do
905
+ assert_nothing_raised { @dummy.avatar = @file }
906
+ end
907
+
908
+ should "return the right value when sent #avatar_fingerprint" do
909
+ @dummy.avatar = @file
910
+ assert_equal 'aec488126c3b33c08a10c3fa303acf27', @dummy.avatar_fingerprint
911
+ end
912
+
913
+ should "return the right value when saved, reloaded, and sent #avatar_fingerprint" do
914
+ @dummy.avatar = @file
915
+ @dummy.save
916
+ @dummy = Dummy.find(@dummy.id)
917
+ assert_equal 'aec488126c3b33c08a10c3fa303acf27', @dummy.avatar_fingerprint
918
+ end
919
+ end
920
+ end
921
+ end