attacheable 1.1 → 1.2

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.
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: