attacheable 1.1 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
5
5
 
6
6
  spec = Gem::Specification.new do |s|
7
7
  s.name = 'attacheable'
8
- s.version = '1.1'
8
+ s.version = '1.2'
9
9
  s.summary = 'Library to handle image uploads'
10
10
  s.autorequire = 'attacheable'
11
11
  s.author = "Max Lapshin"
@@ -25,7 +25,7 @@ spec = Gem::Specification.new do |s|
25
25
  5. create only one row in table for one image. No separate rows for each thumbnail."
26
26
  s.rubyforge_project = "attacheable"
27
27
  s.has_rdoc = false
28
- s.files = FileList["**"].exclude(".git").to_a
28
+ s.files = FileList["**/**"].exclude(".git").to_a
29
29
 
30
30
  end
31
31
 
@@ -0,0 +1,239 @@
1
+ require File.dirname(__FILE__)+"/attacheable/file_naming"
2
+ require File.dirname(__FILE__)+"/attacheable/uploading"
3
+ class ActiveRecord::Base
4
+ #
5
+ # In model write has_attachment (conflicts with acts_as_attachment) with options:
6
+ #
7
+ # :thumbnails => list of thumbnails, i.e. {:medium => "120x", :large => "800x600"}
8
+ # :croppable_thumbnails => list of thumbnails, which must be cropped to center, i.e.: [:large, :preview]
9
+ # :path_prefix => path, where to store photos, i.e.: "public/system/photos"
10
+ # :replicas => [{:user => "user1", :host => "host1"}], list of hosts, where to clone all loaded originals
11
+ # :autocreate => true/false, whether to autocreate thumbnails on requesting thumbnail
12
+ #
13
+ # After this, add to routes:
14
+ # map.assets 'system/photos/*path_info', :controller => "photos", :action => "show"
15
+ # and add to PhotosController:
16
+ # def show
17
+ # photo, data = Photo.data_by_path_info(params[:path_info])
18
+ # render :text => data, :content_type => photo && photo.content_type
19
+ # end
20
+ # This will enable creation on demand
21
+ #
22
+ # You can also add
23
+ # uri "/system/photos/", :handler => Attacheable::PhotoHandler.new("/system/photos"), :in_front => true
24
+ # to any mongrel scripts for nonblocking image creation
25
+ #
26
+ # Table for this plugin should have fields:
27
+ # filename : string
28
+ # content_type : string (optional)
29
+ # width : integer (optional)
30
+ # heigth : integer (optional)
31
+ #
32
+ #
33
+ def self.has_attachment(options = {})
34
+ class_inheritable_accessor :attachment_options
35
+ self.attachment_options = options
36
+
37
+ options.with_indifferent_access
38
+ options[:autocreate] ||= false
39
+ options[:thumbnails] ||= {}
40
+ options[:thumbnails].symbolize_keys!.with_indifferent_access
41
+ options[:croppable_thumbnails] ||= []
42
+ options[:croppable_thumbnails] = options[:croppable_thumbnails].map(&:to_sym)
43
+ options[:path_prefix] ||= "public/system/#{table_name}"
44
+ options[:valid_filetypes] ||= %w(jpeg gif png psd)
45
+ include(Attacheable)
46
+ end
47
+
48
+ #
49
+ # Currently it will check valid filetype (unless option valid_filetypes set to :all)
50
+ #
51
+ def self.validates_as_attachment(options = {})
52
+ options[:message] ||= "Incorrect file type. Valid file types include: #{attachment_options[:valid_filetypes].to_sentence}"
53
+ self.attachment_options[:validation_message] = options[:message]
54
+
55
+ validate :valid_filetype?
56
+ end
57
+ end
58
+
59
+
60
+ module Attacheable
61
+ include Attacheable::Uploading
62
+ include Attacheable::FileNaming
63
+
64
+ def self.included(base) #:nodoc:
65
+ base.before_update :rename_file
66
+ base.after_save :save_to_storage
67
+ base.after_destroy :remove_files
68
+ base.extend(ClassMethods)
69
+ end
70
+
71
+
72
+ def self.root
73
+ return RAILS_ROOT if defined?(RAILS_ROOT)
74
+ return Merb.root if defined?(Merb)
75
+ return File.dirname(__FILE__)+"/../.."
76
+ end
77
+
78
+ module ClassMethods
79
+
80
+ #
81
+ # You can delete all thumbnails or with selected type
82
+ #
83
+ def regenerate_thumbnails!(thumbnail = nil)
84
+ connection.select_values("select id from #{table_name}").each do |object_id|
85
+ object = find_by_id(object_id)
86
+ if object && object.filename
87
+ if thumbnail
88
+ FileUtils.rm_f(object.full_filename_without_creation(thumbnail))
89
+ else
90
+ to_remove = Dir["#{File.dirname(object.full_filename_without_creation)}/*"] - [object.full_filename_without_creation]
91
+ FileUtils.rm_f(to_remove)
92
+ end
93
+ #object.full_filename_with_creation(thumbnail)
94
+ end
95
+ end
96
+ end
97
+
98
+ #
99
+ # It is designed to read params[:path_info], or splitted PATH_INFO in mongrel handler
100
+ # It assumes, that path_info is of the following format ["0000", "0001", "file_medium.jpg"]
101
+ def data_by_path_info(path_info)
102
+ id1, id2, path = path_info
103
+ return [nil, nil] unless id1 && id2 && path
104
+ object = find(id1.to_i*1000 + id2.to_i)
105
+ if path = object.full_filename_by_path(path)
106
+ return [object, File.read(path)] if File.exists?(path)
107
+ end
108
+ [object, nil]
109
+ end
110
+ end
111
+
112
+ def attachment_options #:nodoc:
113
+ self.class.attachment_options
114
+ end
115
+
116
+ #
117
+ # Returns real path to original file if thumbnail is nil or path with thumbnail part inserted
118
+ # If options[:autocreate] is set to true, this method will autogenerate thumbnail
119
+ #
120
+ def full_filename(thumbnail = nil)
121
+ return "" if filename.blank?
122
+ attachment_options[:autocreate] ? full_filename_with_creation(thumbnail) : full_filename_without_creation(thumbnail)
123
+ end
124
+
125
+ def full_filename_by_path(path) #:nodoc:
126
+ return if filename.blank?
127
+ thumbnail = path.gsub(%r((^#{Regexp.escape(attachment_basename)}_)(\w+)(#{Regexp.escape(attachment_extname)})$), '\2')
128
+ return unless thumbnail
129
+ return unless attachment_options[:thumbnails][thumbnail.to_sym]
130
+ full_filename_with_creation(thumbnail.to_sym)
131
+ end
132
+
133
+ # Gets the public path to the file, visible to browser
134
+ # The optional thumbnail argument will output the thumbnail's filename.
135
+ # If options[:autocreate] is set to true, this method will autogenerate thumbnail
136
+ def public_filename(thumbnail = nil)
137
+ return "" if filename.blank?
138
+ full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
139
+ end
140
+
141
+ protected
142
+
143
+ # overrwrite this to do your own app-specific partitioning.
144
+ # you can thank Jamis Buck for this: http://www.37signals.com/svn/archives2/id_partitioning.php
145
+ def partitioned_path(*args)
146
+ ("%08d" % id).scan(/..../) + args
147
+ end
148
+
149
+ def create_thumbnail_if_required(thumbnail)
150
+ thumbnail_path = full_filename_without_creation(thumbnail)
151
+ return thumbnail_path unless thumbnail
152
+ return thumbnail_path if File.exists?(thumbnail_path)
153
+ return unless /image\//.match(content_type)
154
+ if attachment_options[:croppable_thumbnails].include?(thumbnail.to_sym)
155
+ crop_and_thumbnail(thumbnail, thumbnail_path)
156
+ else
157
+ create_thumbnail(thumbnail, thumbnail_path)
158
+ end
159
+ thumbnail_path
160
+ end
161
+
162
+ def create_thumbnail(thumbnail, thumbnail_path)
163
+ return nil unless File.exists?(full_filename)
164
+ return nil unless attachment_options[:thumbnails][thumbnail.to_sym]
165
+ `convert -thumbnail #{attachment_options[:thumbnails][thumbnail.to_sym]} "#{full_filename}" "#{thumbnail_path}"`
166
+ thumbnail_path
167
+ end
168
+
169
+
170
+ public
171
+
172
+ def valid_filetype? #:nodoc:
173
+ errors.add("uploaded_data", attachment_options[:validation_message]) if @save_new_attachment && !@valid_filetype
174
+ end
175
+
176
+ # Main method, that accepts uploaded data
177
+ #
178
+ def uploaded_data=(file_data)
179
+ prepare_uploaded_file(file_data)
180
+ file_type = identify_uploaded_file_type
181
+ if accepts_file_type_for_upload?(file_type)
182
+ handle_uploaded_file
183
+ end
184
+ end
185
+
186
+ def image_size
187
+ [width.to_s, height.to_s] * 'x'
188
+ end
189
+
190
+ def filename=(value)
191
+ @old_filename = full_filename unless filename.nil? || @old_filename
192
+ write_attribute :filename, sanitize_filename(value)
193
+ end
194
+
195
+ protected
196
+
197
+
198
+ def crop_and_thumbnail(thumbnail, thumbnail_path)
199
+ if !respond_to?(:width) || !respond_to?(:height)
200
+ file_type, width, height = identify_image_properties(full_filename)
201
+ end
202
+ album_x, album_y = attachment_options[:thumbnails][thumbnail.to_sym].split("x").map &:to_i
203
+ scale_x = width.to_f / album_x
204
+ scale_y = height.to_f / album_y
205
+ if scale_x > scale_y
206
+ x, y = (album_x*scale_y).floor, height
207
+ shift_x, shift_y = (width.to_i - x)/2, 0
208
+ else
209
+ x, y = width, (album_y*scale_x).floor
210
+ shift_x, shift_y = 0, (height.to_i - y)/2
211
+ end
212
+ # FileUtils.cp(full_filename_without_creation, thumbnail_path)
213
+ `convert -crop #{x}x#{y}+#{shift_x}+#{shift_y} "#{full_filename}" "#{thumbnail_path}"`
214
+ `mogrify -geometry #{album_x}x#{album_y} "#{thumbnail_path}"`
215
+ thumbnail_path
216
+ end
217
+
218
+ # Destroys the file. Called in the after_destroy callback
219
+ def remove_files
220
+ return unless filename
221
+ FileUtils.rm_rf(File.dirname(full_filename_without_creation))
222
+ rescue
223
+ logger.info "Exception destroying #{full_filename.inspect}: [#{$!.class.name}] #{$1.to_s}"
224
+ logger.warn $!.backtrace.collect { |b| " > #{b}" }.join("\n")
225
+ end
226
+
227
+ # Renames the given file before saving
228
+ def rename_file
229
+ return unless @old_filename && @old_filename != full_filename
230
+ if @save_new_attachment && File.exists?(@old_filename)
231
+ FileUtils.rm @old_filename
232
+ elsif File.exists?(@old_filename)
233
+ FileUtils.mv @old_filename, full_filename
234
+ end
235
+ @old_filename = nil
236
+ true
237
+ end
238
+
239
+ end
@@ -0,0 +1,48 @@
1
+ module Attacheable
2
+ module FileNaming
3
+ def full_filename_with_creation(thumbnail = nil) #:nodoc:
4
+ create_thumbnail_if_required(thumbnail)
5
+ end
6
+
7
+ def full_filename_without_creation(thumbnail = nil) #:nodoc:
8
+ file_system_path = attachment_options[:path_prefix]
9
+ File.join(Attacheable.root, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
10
+ end
11
+
12
+ def thumbnail_name_for(thumbnail = nil) #:nodoc:
13
+ return filename if thumbnail.blank?
14
+ ext = nil
15
+ basename = filename.gsub /\.\w+$/ do |s|
16
+ ext = s; ''
17
+ end
18
+ "#{basename}_#{thumbnail}#{ext}"
19
+ end
20
+
21
+ def public_filename_without_creation(thumbnail = nil)
22
+ full_filename_without_creation(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
23
+ end
24
+
25
+ def base_path #:nodoc:
26
+ @base_path ||= File.join(Attacheable.root, 'public')
27
+ end
28
+
29
+ def attachment_basename
30
+ filename && filename.gsub(/\.[^\.]+$/, '')
31
+ end
32
+
33
+ def attachment_extname
34
+ filename && filename.gsub(/^(.*)(\.[^\.]+)$/, '\2')
35
+ end
36
+
37
+ def sanitize_filename(filename)
38
+ returning filename.strip do |name|
39
+ # NOTE: File.basename doesn't work right with Windows paths on Unix
40
+ # get only the filename, not the whole path
41
+ name.gsub! /^.*(\\|\/)/, ''
42
+
43
+ # Finally, replace all non alphanumeric, underscore or periods with underscore
44
+ name.gsub! /[^\w\.\-]/, '_'
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,48 @@
1
+ module Attacheable
2
+ class PhotoHandler < Mongrel::HttpHandler
3
+ def initialize(prefix, options)
4
+ @prefix = prefix
5
+ @class_name = options[:class_name]
6
+ end
7
+
8
+ def logger
9
+ ActiveRecord::Base.logger
10
+ end
11
+
12
+ def klass
13
+ @class_name.constantize
14
+ end
15
+
16
+ def process(request, response)
17
+ if File.exists?(Attacheable.root+"/public#{@prefix}/#{request.params["PATH_INFO"]}")
18
+ response.start(200) do |headers, out|
19
+ headers["Content-Type"] = "image/jpeg"
20
+ out.write(File.read(Attacheable.root+"/public#{@prefix}/#{request.params["PATH_INFO"]}"))
21
+ end
22
+ return
23
+ end
24
+
25
+ start_time = Time.now
26
+ photo, data = klass.data_by_path_info(request.params["PATH_INFO"].split("/"))
27
+ if photo
28
+ response.start(200) do |headers, out|
29
+ headers["Content-Type"] = photo.content_type
30
+ out.write(data)
31
+ end
32
+ else
33
+ response.start(404) do |headers, out|
34
+ headers["Content-Type"] = "text/plain"
35
+ out.write("No such image\n")
36
+ end
37
+ end
38
+ logger.info "Processed request in #{Time.now - start_time} seconds\n URI: #{request.params["REQUEST_URI"]}\n\n"
39
+ rescue Exception => e
40
+ logger.info "!! Internal server error\nURI: #{request.params["REQUEST_URI"]}\n#{e}\n#{e.backtrace.join("\n")}"
41
+ logger.flush
42
+ response.start(200) do |headers, out|
43
+ headers["Content-Type"] = "text/plain"
44
+ out.write("Some error on server\n")
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,93 @@
1
+ module Attacheable
2
+ module Uploading
3
+ def prepare_uploaded_file(file_data)
4
+ return prepare_merb_uploaded_file(file_data) if file_data.is_a?(Hash) && file_data["content_type"] && file_data["tempfile"]
5
+ return nil if file_data.nil? || !file_data.respond_to?(:original_filename) || !respond_to?(:filename=)
6
+
7
+ self.filename = file_data.original_filename
8
+ if file_data.is_a?(StringIO)
9
+ file_data.rewind
10
+ @tempfile = Tempfile.new(filename)
11
+ @tempfile.write(file_data.read)
12
+ @tempfile.close
13
+ else
14
+ @tempfile = file_data
15
+ end
16
+ if respond_to?(:size=)
17
+ self.size = file_data.respond_to?(:size) ? file_data.size : @tempfile.respond_to?(:size) ? @tempfile.size : File.size(@tempfile.path)
18
+ end
19
+ @save_new_attachment = true
20
+ @valid_filetype = false
21
+ end
22
+
23
+ def prepare_merb_uploaded_file(file_data)
24
+ return nil if file_data["tempfile"].blank? || file_data["filename"].blank? || file_data["content_type"].blank?
25
+ self.filename = file_data["filename"]
26
+ self.size = file_data["size"] if respond_to?(:size=)
27
+ @tempfile = file_data["tempfile"]
28
+ @save_new_attachment = true
29
+ @valid_filetype = false
30
+ end
31
+
32
+ def identify_uploaded_file_type
33
+ return unless @tempfile
34
+ self.content_type = @tempfile.content_type if @tempfile.respond_to?(:content_type)
35
+
36
+ if content_type.blank? || content_type =~ /image\//
37
+ file_type, width, height = identify_image_properties(@tempfile.path)
38
+ if file_type
39
+ self.width = width if(respond_to?(:width=))
40
+ self.height = height if(respond_to?(:height=))
41
+ self.content_type = "image/#{file_type}"
42
+ file_type
43
+ end
44
+ end
45
+ end
46
+
47
+ def identify_image_properties(path)
48
+ return [nil,nil,nil] if path.blank?
49
+
50
+ output = nil
51
+ silence_stderr do
52
+ output = `identify "#{path}"`
53
+ end
54
+ if output && match_data = / (\w+) (\d+)x(\d+) /.match(output)
55
+ file_type = match_data[1].to_s.downcase
56
+ width = match_data[2]
57
+ height = match_data[3]
58
+ return [file_type, width, height]
59
+ end
60
+ end
61
+
62
+ def accepts_file_type_for_upload?(file_type)
63
+ return false unless @tempfile
64
+ return true if attachment_options[:valid_filetypes] == :all
65
+ return true if attachment_options[:valid_filetypes].include?(file_type)
66
+ end
67
+
68
+ def handle_uploaded_file
69
+ @save_new_attachment = true
70
+ @valid_filetype = true
71
+ end
72
+
73
+ # Saves the file to the file system
74
+ def save_to_storage
75
+ if @save_new_attachment
76
+ FileUtils.mkdir_p(File.dirname(full_filename))
77
+ FileUtils.cp(@tempfile.path, full_filename)
78
+ File.chmod(0644, full_filename)
79
+ save_to_replicas
80
+ end
81
+ @save_new_attachment = false
82
+ @tempfile = nil
83
+ true
84
+ end
85
+
86
+ def save_to_replicas
87
+ attachment_options[:replicas].each do |replica|
88
+ system("ssh #{replica[:user]}@#{replica[:host]} mkdir -p \"#{File.dirname(full_filename)}\"")
89
+ system("scp \"#{full_filename}\" \"#{replica[:user]}@#{replica[:host]}:#{full_filename}\"")
90
+ end if attachment_options[:replicas]
91
+ end
92
+ end
93
+ end
Binary file
@@ -0,0 +1,194 @@
1
+ require File.dirname(__FILE__)+'/test_helper'
2
+
3
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
4
+
5
+ def setup_db
6
+ ActiveRecord::Migration.verbose = false
7
+ ActiveRecord::Schema.define(:version => 1) do
8
+ create_table :images do |t|
9
+ t.string :something
10
+ t.string :filename
11
+ t.string :content_type
12
+ #t.integer :width
13
+ #t.integer :height
14
+ t.string :type
15
+ end
16
+ end
17
+ end
18
+
19
+ def teardown_db
20
+ ActiveRecord::Base.connection.tables.each do |table|
21
+ ActiveRecord::Base.connection.drop_table(table)
22
+ end
23
+ end
24
+
25
+ class Image < ActiveRecord::Base
26
+ has_attachment :thumbnails => {:medium => "120x", :large => "800x600", :preview => "100x100"},
27
+ :croppable_thumbnails => %w(preview)
28
+ validates_as_attachment :message => "Please upload an image file."
29
+ end
30
+
31
+ class Photo < Image
32
+ end
33
+
34
+ module TestUploadExtension
35
+ attr_accessor :content_type
36
+ def original_filename
37
+ File.basename(path)
38
+ end
39
+
40
+ def size
41
+ File.stat(path).size
42
+ end
43
+ end
44
+
45
+ class AttacheableTest < Test::Unit::TestCase
46
+ def setup
47
+ setup_db
48
+ end
49
+
50
+ def teardown
51
+ teardown_db
52
+ FileUtils.rm_rf(File.dirname(__FILE__)+"/public")
53
+ Image.attachment_options[:autocreate] = false
54
+ Image.attachment_options[:valid_filetypes] = %w(jpeg gif png psd)
55
+ end
56
+
57
+ def test_image_autoconf
58
+ assert_equal "public/system/images", Image.attachment_options[:path_prefix], "Should guess path prefix right"
59
+ end
60
+
61
+ def test_image_creation
62
+ input = File.open(File.dirname(__FILE__)+"/fixtures/life.jpg")
63
+ input.extend(TestUploadExtension)
64
+ assert_equal "life.jpg", input.original_filename, "should look like uploaded file"
65
+ image = Image.new(:uploaded_data => input)
66
+ assert_equal "life_medium.jpg", image.send(:thumbnail_name_for, :medium), "should generate right thumbnail filename"
67
+ assert image.save, "Image should be saved"
68
+ assert_equal "life", image.attachment_basename
69
+ assert_equal ".jpg", image.attachment_extname
70
+ assert File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life.jpg"), "File should be saved"
71
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life_medium.jpg"), "Thumbnails should not be generated"
72
+ image.destroy
73
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001"), "Directory should be cleaned"
74
+ end
75
+
76
+ def test_merb_image_creation
77
+ path = File.dirname(__FILE__)+"/fixtures/life.jpg"
78
+ input = {"content_type"=>"image/jpeg", "size"=>File.size(path), "tempfile"=>File.open(path), "filename"=>"life.jpg"}
79
+ image = Image.new(:uploaded_data => input)
80
+ assert image.save, "Image should be saved"
81
+ assert_equal "life", image.attachment_basename
82
+ assert_equal ".jpg", image.attachment_extname
83
+ assert File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life.jpg"), "File should be saved"
84
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life_medium.jpg"), "Thumbnails should not be generated"
85
+ image.destroy
86
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001"), "Directory should be cleaned"
87
+ end
88
+
89
+ def test_image_with_autocreation
90
+ Image.attachment_options[:autocreate] = true
91
+ input = File.open(File.dirname(__FILE__)+"/fixtures/life.jpg")
92
+ input.extend(TestUploadExtension)
93
+ image = Image.new(:uploaded_data => input)
94
+ assert image.save, "Image should be saved"
95
+ assert File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life.jpg"), "File should be saved"
96
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life_medium.jpg"), "Thumbnails should not be generated"
97
+ assert_equal "life_medium.jpg", image.send(:thumbnail_name_for, :medium), "should generate right thumbnail filename"
98
+ image.public_filename(:medium)
99
+ assert File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life_medium.jpg"), "Thumbnails should be generated on demand"
100
+ image.destroy
101
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001"), "Directory should be cleaned"
102
+ end
103
+
104
+ def test_autocrop_image
105
+ Image.attachment_options[:autocreate] = true
106
+ input = File.open(File.dirname(__FILE__)+"/fixtures/life.jpg")
107
+ input.extend(TestUploadExtension)
108
+ image = Image.new(:uploaded_data => input)
109
+ assert image.save, "Image should be saved"
110
+ image.public_filename(:preview)
111
+ assert File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life_preview.jpg"), "Thumbnails should be generated on demand"
112
+ identify = `identify "#{File.dirname(__FILE__)+"/public/system/images/0000/0001/life_preview.jpg"}"`
113
+ assert Regexp.new(" JPEG 100x100 ").match(identify), "Image should be cropped to format"
114
+ image.destroy
115
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001"), "Directory should be cleaned"
116
+ end
117
+
118
+ def test_image_with_wrong_type
119
+ input = File.open(File.dirname(__FILE__)+"/fixtures/wrong_type")
120
+ input.extend(TestUploadExtension)
121
+ image = Image.new(:uploaded_data => input)
122
+ assert !image.save, "Image should not be saved"
123
+ assert image.errors.on(:uploaded_data).size > 0, "Uploaded data is of wrong type"
124
+ assert_equal "Please upload an image file.", image.errors.on(:uploaded_data), "Validation message is the one used with :message"
125
+ end
126
+
127
+ def test_with_sti
128
+ input = File.open(File.dirname(__FILE__)+"/fixtures/life.jpg")
129
+ input.extend(TestUploadExtension)
130
+ image = Photo.new(:uploaded_data => input)
131
+ assert image.save, "Image should be saved"
132
+ assert File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life.jpg"), "File should be uploaded as for Image"
133
+ image.destroy
134
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001"), "Directory should be cleaned"
135
+ end
136
+
137
+ def test_save_raw_binary
138
+ Image.attachment_options[:valid_filetypes] = :all
139
+ input = File.open(File.dirname(__FILE__)+"/fixtures/wrong_type")
140
+ input.extend(TestUploadExtension)
141
+ image = Image.new(:uploaded_data => input)
142
+ assert image.save, "Image should be saved"
143
+ assert File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/wrong_type"), "File should be uploaded"
144
+ assert !image.send(:full_filename_with_creation, :preview)
145
+ image.destroy
146
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001"), "Directory should be cleaned"
147
+ end
148
+
149
+ def test_save_msword
150
+ Image.attachment_options[:valid_filetypes] = :all
151
+ input = File.open(File.dirname(__FILE__)+"/fixtures/test.doc")
152
+ input.extend(TestUploadExtension)
153
+ input.content_type = "application/ms-word"
154
+ image = Image.new(:uploaded_data => input)
155
+ assert image.save, "Image should be saved"
156
+ assert File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/test.doc"), "File should be uploaded"
157
+ assert !image.send(:full_filename_with_creation, :preview)
158
+ assert_equal "/system/images/0000/0001/test.doc", image.public_filename
159
+ image.destroy
160
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001"), "Directory should be cleaned"
161
+ end
162
+
163
+ def test_create_thumbnail
164
+ input = File.open(File.dirname(__FILE__)+"/fixtures/life.jpg")
165
+ input.extend(TestUploadExtension)
166
+ assert_equal "life.jpg", input.original_filename, "should look like uploaded file"
167
+ image = Image.new(:uploaded_data => input)
168
+ assert_equal "life_medium.jpg", image.send(:thumbnail_name_for, :medium), "should generate right thumbnail filename"
169
+ assert image.save, "Image should be saved"
170
+ assert File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life.jpg"), "File should be saved"
171
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life_medium.jpg"), "Thumbnails should not be autogenerated"
172
+ photo, data = Image.data_by_path_info(%w(0000 0001 life_medium.jpg))
173
+ assert photo, "Image should be guessed"
174
+ assert File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001/life_medium.jpg"), "Thumbnails should be generated on demand"
175
+ assert data, "New file should be generated"
176
+ image.destroy
177
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000/0001"), "Directory should be cleaned"
178
+ end
179
+
180
+ def test_nil_upload
181
+ image = Image.new(:uploaded_data => nil)
182
+ assert image.save, "Image should be saved with empty file"
183
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000"), "nothing should be created"
184
+ assert image.update_attributes(:something => "empty"), "should be updateable"
185
+ assert image.destroy, "should be destroyable"
186
+ end
187
+
188
+ def test_string_upload
189
+ image = Image.new(:uploaded_data => "life.jpg")
190
+ assert image.save, "Image should be saved with empty file"
191
+ assert !File.exists?(File.dirname(__FILE__)+"/public/system/images/0000"), "nothing should be created"
192
+ assert image.destroy, "should be destroyable"
193
+ end
194
+ end
Binary file
Binary file
Binary file
@@ -0,0 +1,16 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'active_support'
4
+ require 'active_record'
5
+ require 'action_pack'
6
+ $KCODE = 'u'
7
+
8
+ $:.unshift File.join(File.dirname(__FILE__), '../lib')
9
+ if defined?(RAILS_ROOT)
10
+ RAILS_ROOT.replace(File.dirname(__FILE__))
11
+ else
12
+ RAILS_ROOT = File.dirname(__FILE__)
13
+ end
14
+ require 'attacheable'
15
+
16
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attacheable
3
3
  version: !ruby/object:Gem::Version
4
- version: "1.1"
4
+ version: "1.2"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Lapshin
@@ -24,12 +24,24 @@ extra_rdoc_files: []
24
24
  files:
25
25
  - init.rb
26
26
  - lib
27
+ - lib/attacheable
28
+ - lib/attacheable/file_naming.rb
29
+ - lib/attacheable/photo_handler.rb
30
+ - lib/attacheable/uploading.rb
31
+ - lib/attacheable.rb
27
32
  - MIT-LICENSE
28
33
  - pkg
34
+ - pkg/attacheable-1.1.gem
29
35
  - Rakefile
30
36
  - README
31
37
  - README.ru
32
38
  - test
39
+ - test/attacheable_test.rb
40
+ - test/fixtures
41
+ - test/fixtures/life.jpg
42
+ - test/fixtures/test.doc
43
+ - test/fixtures/wrong_type
44
+ - test/test_helper.rb
33
45
  has_rdoc: false
34
46
  homepage:
35
47
  post_install_message: