has_image 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/FAQ +25 -0
- data/MIT-LICENSE +20 -0
- data/README +139 -0
- data/Rakefile +47 -0
- data/init.rb +1 -0
- data/lib/has_image/processor.rb +76 -0
- data/lib/has_image/storage.rb +167 -0
- data/lib/has_image/view_helpers.rb +39 -0
- data/lib/has_image.rb +242 -0
- data/test/processor_test.rb +49 -0
- data/test/storage_test.rb +98 -0
- data/test_rails/database.yml +3 -0
- data/test_rails/fixtures/bad_image.jpg +0 -0
- data/test_rails/fixtures/image.jpg +0 -0
- data/test_rails/fixtures/image.png +0 -0
- data/test_rails/pic.rb +3 -0
- data/test_rails/pic_test.rb +79 -0
- data/test_rails/schema.rb +9 -0
- data/test_rails/test_helper.rb +54 -0
- metadata +76 -0
data/CHANGELOG
ADDED
data/FAQ
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
= Frequently Asked Questions
|
2
|
+
|
3
|
+
HasImage is too new to have many FAQ items yet. {Ask
|
4
|
+
me}[mailto:norman@randomba.org] and they will be included; this is a work in
|
5
|
+
progress.
|
6
|
+
|
7
|
+
= How do I validate the mime type of my uploaded images?
|
8
|
+
|
9
|
+
You don't. Rather than examine the mime type, HasImage runs the "identify"
|
10
|
+
command on the file to determine if it is processable by ImageMagick, and if
|
11
|
+
it is, converts it to the format you specify, which defaults to JPEG.
|
12
|
+
|
13
|
+
This is better than checking for mime types, because your users may upload
|
14
|
+
exotic image types that you didn't even realize would work, such as Truevision
|
15
|
+
Targa images, or Seattle Film Works files.
|
16
|
+
|
17
|
+
If you wish to give users a list of file types they can upload, a good start
|
18
|
+
would be jpeg, png, bmp, and maybe gif and ttf if your installation of
|
19
|
+
ImageMagick understands them. You can find out what image types your
|
20
|
+
ImageMagick understands by running:
|
21
|
+
|
22
|
+
identify -list format
|
23
|
+
|
24
|
+
Ideally, if your users just upload files that "look like" images on their
|
25
|
+
computers, it HasImage should "just work."
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
= HasImage[http://github.com/norman/has_image] -- Image attachment gem/plugin for Ruby on Rails
|
2
|
+
|
3
|
+
HasImage[http://github.com/norman/has_image] was created as a smaller,
|
4
|
+
simpler, lighter alternative to
|
5
|
+
attachment_fu[http://github.com/technoweenie/attachment_fu] for applications
|
6
|
+
that need to handle uploaded images.
|
7
|
+
|
8
|
+
It creates only one database record per image, requires only one column in
|
9
|
+
your model, and creates great-looking fixed-dimension thumbnails by using
|
10
|
+
{ImageMagick's}[http://www.imagemagick.org/]
|
11
|
+
resize[http://www.imagemagick.org/script/command-line-options.php#resize],
|
12
|
+
crop[http://www.imagemagick.org/script/command-line-options.php#crop] and
|
13
|
+
gravity[http://www.imagemagick.org/script/command-line-options.php#gravity]
|
14
|
+
functions.
|
15
|
+
|
16
|
+
Some typical use cases are: websites that want to create photo galleries with
|
17
|
+
fixed-dimension thumbnails, or that want to store user profile pictures
|
18
|
+
without creating a separate model for the images.
|
19
|
+
|
20
|
+
It supports only filesystem storage, and uses only MiniMagick[http://github.com/probablycorey/mini_magick] to process
|
21
|
+
images. However, the codebase is very small, simple, readable, and hackable.
|
22
|
+
So it should be easy to modify or enhance its functionality with different
|
23
|
+
storage or processor options.
|
24
|
+
|
25
|
+
== Another image attachment library? Why?
|
26
|
+
|
27
|
+
<em>The three chief virtues of a programmer are: Laziness, Impatience and Hubris.</em> - {Larry Wall}[http://en.wikipedia.org/wiki/Larry_Wall]
|
28
|
+
|
29
|
+
Attachment_fu is too large and general for some of the places I want to use
|
30
|
+
images. I sometimes found myself writing more code to hack attachment_fu than
|
31
|
+
it took to create this gem. In fact, most of the code here has been plucked
|
32
|
+
from my various projects that use attachment_fu.
|
33
|
+
|
34
|
+
The other image attachment libraries I found fell short of my needs for
|
35
|
+
various other reasons, so I decided to roll my own.
|
36
|
+
|
37
|
+
== Examples
|
38
|
+
|
39
|
+
Point-and-drool use case. It's probably not what you want, but it may be
|
40
|
+
useful for bootstrapping.
|
41
|
+
|
42
|
+
class Member < ActiveRecord::Base
|
43
|
+
has_image
|
44
|
+
end
|
45
|
+
|
46
|
+
Single image, no thumbnails, with some size limits:
|
47
|
+
|
48
|
+
class Picture < ActiveRecord::Base
|
49
|
+
has_image :resize_to => "200x200",
|
50
|
+
:max_size => 3.megabytes,
|
51
|
+
:min_size => 4.kilobytes
|
52
|
+
end
|
53
|
+
|
54
|
+
Image with some thumbnails:
|
55
|
+
|
56
|
+
class Photo < ActiveRecord::Base
|
57
|
+
has_image :resize_to => "640x480",
|
58
|
+
:thumbnails => {
|
59
|
+
:square => "200x200",
|
60
|
+
:medium => "320x240"
|
61
|
+
},
|
62
|
+
:max_size => 3.megabytes,
|
63
|
+
:min_size => 4.kilobytes
|
64
|
+
end
|
65
|
+
|
66
|
+
It also provides a view helper to make displaying the images extremely simple:
|
67
|
+
|
68
|
+
<%= image_tag_for(@photo, :thumb => :square) %>
|
69
|
+
|
70
|
+
== Getting it
|
71
|
+
|
72
|
+
Has image can be installed as a gem, or as a Rails plugin. Gem installation
|
73
|
+
is easiest, and recommended:
|
74
|
+
|
75
|
+
gem install norman-has_image --source http://gems.github.com
|
76
|
+
|
77
|
+
and add
|
78
|
+
|
79
|
+
require 'has_image'
|
80
|
+
|
81
|
+
to your environment.rb file.
|
82
|
+
|
83
|
+
Alternatively, you can install it as a Rails plugin:
|
84
|
+
|
85
|
+
./script plugin install git://github.com/norman/has_image.git
|
86
|
+
|
87
|
+
Rails versions before 2.1 do not support plugin installation using Git, so if
|
88
|
+
you're on 2.0 (or earlier), then please install the gem rather than the
|
89
|
+
plugin.
|
90
|
+
|
91
|
+
Then, make sure the model has a column named "has_image_file."
|
92
|
+
|
93
|
+
{Git repository}[http://github.com/norman/has_image]:
|
94
|
+
|
95
|
+
git://github.com/norman/has_image.git
|
96
|
+
|
97
|
+
|
98
|
+
== Hacking it
|
99
|
+
|
100
|
+
Don't like the way it makes images? Want to pipe the images through some
|
101
|
+
{crazy fast seam carving library written in
|
102
|
+
OCaml}[http://eigenclass.org/hiki/seam-carving-in-ocaml], or watermark them
|
103
|
+
with your corporate logo? Happiness is just a monkey-patch[http://en.wikipedia.org/wiki/Monkey_patch] away:
|
104
|
+
|
105
|
+
module HasImage
|
106
|
+
class Processor
|
107
|
+
def resize_image(size)
|
108
|
+
# your new-and-improved thumbnailer code goes here.
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
HasImage[http://github.com/norman/has_image] follows a philosophy of "{skinny
|
114
|
+
model, fat plugin}[http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model]."
|
115
|
+
This means that it tries to pollute your ActiveRecord model with as little
|
116
|
+
functionality as possible, so that in a sense, the model is acts like a
|
117
|
+
"controller" and the plugin like a "model" as regards the image handling
|
118
|
+
functionality. This makes it easier to test, hack, and reuse, because the
|
119
|
+
storage and processing functionality is largely independent of your model, and
|
120
|
+
of Rails.
|
121
|
+
|
122
|
+
My goal for HasImage[http://github.com/norman/has_image] is to keep it very
|
123
|
+
small. If you need <strong>a lot</strong> of functionality that's not here, instead of patching
|
124
|
+
this code, you will likely be better off using
|
125
|
+
attachment_fu[http://github.com/technoweenie/attachment_fu], which is much
|
126
|
+
more powerful, but also more complex.
|
127
|
+
|
128
|
+
== Bugs
|
129
|
+
|
130
|
+
Please report them on Lighthouse[http://randomba.lighthouseapp.com/projects/14674-has_image].
|
131
|
+
|
132
|
+
At the time of writing (July 2008),
|
133
|
+
HasImage[http://github.com/norman/has_image] is in its infancy. Your patches,
|
134
|
+
bug reports and withering criticism are more than welcome.
|
135
|
+
|
136
|
+
|
137
|
+
|
138
|
+
Copyright (c) 2008 {Norman Clarke}[mailto:norman@randomba.org], released under
|
139
|
+
the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the non-Rails part of has_image.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Test the Rails part of has_image.'
|
17
|
+
Rake::TestTask.new(:test_rails) do |t|
|
18
|
+
t.libs << 'lib'
|
19
|
+
t.libs << 'test_rails'
|
20
|
+
t.pattern = 'test_rails/**/*_test.rb'
|
21
|
+
t.verbose = true
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Run rcov"
|
25
|
+
task :rcov do
|
26
|
+
rm_f "coverage"
|
27
|
+
rm_f "coverage.data"
|
28
|
+
if PLATFORM =~ /darwin/
|
29
|
+
exclude = '--exclude "gems"'
|
30
|
+
else
|
31
|
+
exclude = '--exclude "rubygems"'
|
32
|
+
end
|
33
|
+
rcov = "rcov --rails -Ilib:test --sort coverage --text-report #{exclude} --no-validator-links"
|
34
|
+
cmd = "#{rcov} #{Dir["test/**/*.rb"].join(" ")}"
|
35
|
+
sh cmd
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'Generate documentation for has_image.'
|
39
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
40
|
+
rdoc.rdoc_dir = 'rdoc'
|
41
|
+
rdoc.title = 'HasImage'
|
42
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '-c UTF-8'
|
43
|
+
rdoc.rdoc_files.include('README')
|
44
|
+
rdoc.rdoc_files.include('FAQ')
|
45
|
+
rdoc.rdoc_files.include('CHANGELOG')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'has_image'
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'mini_magick'
|
2
|
+
|
3
|
+
module HasImage
|
4
|
+
|
5
|
+
# Image processing functionality for the HasImage gem.
|
6
|
+
class Processor
|
7
|
+
|
8
|
+
attr_accessor :options
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# Arg should be either a file, or a path. This runs ImageMagick's
|
12
|
+
# "identify" command and looks for an exit status indicating an error. If
|
13
|
+
# there is no error, then ImageMagick has identified the file as something
|
14
|
+
# it can work with and it will be converted to the desired output format.
|
15
|
+
def valid?(arg)
|
16
|
+
arg.close if arg.respond_to?(:close) && !arg.closed?
|
17
|
+
silence_stderr do
|
18
|
+
`identify #{arg.respond_to?(:path) ? arg.path : arg.to_s}`
|
19
|
+
$? == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# The constuctor should be invoked with the options set by has_image.
|
25
|
+
def initialize(options) # :nodoc:
|
26
|
+
@options = options
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create the resized image, and transforms it to the desired output
|
30
|
+
# format if necessary. The size should be a valid ImageMagick {geometry
|
31
|
+
# string}[http://www.imagemagick.org/script/command-line-options.php#resize].
|
32
|
+
def resize(file, size)
|
33
|
+
silence_stderr do
|
34
|
+
path = file.respond_to?(:path) ? file.path : file
|
35
|
+
file.close if file.respond_to?(:close) && !file.closed?
|
36
|
+
@image = MiniMagick::Image.from_file(path)
|
37
|
+
convert_image
|
38
|
+
resize_image(size)
|
39
|
+
return @image
|
40
|
+
end
|
41
|
+
rescue MiniMagick::MiniMagickError
|
42
|
+
raise ProcessorError.new("That doesn't look like an image file.")
|
43
|
+
end
|
44
|
+
|
45
|
+
# Image resizing is placed in a separate method for easy monkey-patching.
|
46
|
+
# This is intended to be invoked from resize, rather than directly.
|
47
|
+
# By default, the following ImageMagick functionality is invoked:
|
48
|
+
# * auto-orient[http://www.imagemagick.org/script/command-line-options.php#auto-orient]
|
49
|
+
# * strip[http://www.imagemagick.org/script/command-line-options.php#strip]
|
50
|
+
# * resize[http://www.imagemagick.org/script/command-line-options.php#resize]
|
51
|
+
# * gravity[http://www.imagemagick.org/script/command-line-options.php#gravity]
|
52
|
+
# * extent[http://www.imagemagick.org/script/command-line-options.php#extent]
|
53
|
+
# * quality[http://www.imagemagick.org/script/command-line-options.php#quality]
|
54
|
+
def resize_image(size)
|
55
|
+
@image.combine_options do |commands|
|
56
|
+
commands.send("auto-orient".to_sym)
|
57
|
+
commands.strip
|
58
|
+
commands.resize "#{size}^"
|
59
|
+
commands.gravity "center"
|
60
|
+
commands.extent size
|
61
|
+
commands.quality options[:output_quality]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# This was placed in a separate method largely to facilitate debugging
|
68
|
+
# and profiling.
|
69
|
+
def convert_image
|
70
|
+
return if @image[:format] == options[:convert_to]
|
71
|
+
@image.format(options[:convert_to])
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'stringio'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'zlib'
|
5
|
+
|
6
|
+
module HasImage
|
7
|
+
|
8
|
+
# Filesystem storage for the HasImage gem. The methods that HasImage inserts
|
9
|
+
# into ActiveRecord models only depend on the public methods in this class, so
|
10
|
+
# it should be reasonably straightforward to implement a different storage
|
11
|
+
# mechanism for Amazon AWS, Photobucket, DBFile, SFTP, or whatever you want.
|
12
|
+
class Storage
|
13
|
+
|
14
|
+
attr_accessor :image_data, :options, :temp_file
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
# Stolen from {Jamis Buck}[http://www.37signals.com/svn/archives2/id_partitioning.php].
|
19
|
+
def partitioned_path(id, *args)
|
20
|
+
("%08d" % id).scan(/..../) + args
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generates a 4-6 character random file name to use for the image and its
|
24
|
+
# thumbnails. This is done to avoid having files with unfortunate names.
|
25
|
+
# On one of my sites users frequently upload images with Arabic names, and
|
26
|
+
# they end up being hard to manipulate on the command line. This also
|
27
|
+
# helps prevent a possibly undesirable sitation where the uploaded images
|
28
|
+
# have offensive names.
|
29
|
+
def random_file_name
|
30
|
+
Zlib.crc32(Time.now.to_s + rand(10e10).to_s).to_s(36)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
# The constuctor should be invoked with the options set by has_image.
|
36
|
+
def initialize(options) # :nodoc:
|
37
|
+
@options = options
|
38
|
+
end
|
39
|
+
|
40
|
+
# The image data can be anything that inherits from IO. If you pass in an
|
41
|
+
# instance of Tempfile, it will be used directly without being copied to
|
42
|
+
# a new temp file.
|
43
|
+
def image_data=(image_data)
|
44
|
+
raise StorageError.new if image_data.blank?
|
45
|
+
if image_data.is_a?(Tempfile)
|
46
|
+
@temp_file = image_data
|
47
|
+
else
|
48
|
+
image_data.rewind
|
49
|
+
@temp_file = Tempfile.new 'has_image_data_%s' % Storage.random_file_name
|
50
|
+
@temp_file.write(image_data.read)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Is uploaded file smaller than the allowed minimum?
|
55
|
+
def image_too_small?
|
56
|
+
@temp_file.open if @temp_file.closed?
|
57
|
+
@temp_file.size < options[:min_size]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Is uploaded file larger than the allowed maximum?
|
61
|
+
def image_too_big?
|
62
|
+
@temp_file.open if @temp_file.closed?
|
63
|
+
@temp_file.size > options[:max_size]
|
64
|
+
end
|
65
|
+
|
66
|
+
# A tip of the hat to attachment_fu.
|
67
|
+
alias uploaded_data= image_data=
|
68
|
+
|
69
|
+
# A tip of the hat to attachment_fu.
|
70
|
+
alias uploaded_data image_data
|
71
|
+
|
72
|
+
# Invokes the processor to resize the image(s) and the installs them to
|
73
|
+
# the appropriate directory.
|
74
|
+
def install_images(id)
|
75
|
+
random_name = Storage.random_file_name
|
76
|
+
install_main_image(id, random_name)
|
77
|
+
install_thumbnails(id, random_name) if !options[:thumbnails].empty?
|
78
|
+
return random_name
|
79
|
+
ensure
|
80
|
+
@temp_file.close! if !@temp_file.closed?
|
81
|
+
@temp_file = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# Gets the "web" path for an image. For example:
|
85
|
+
#
|
86
|
+
# /photos/0000/0001/3er0zs.jpg
|
87
|
+
def public_path_for(object, thumbnail = nil)
|
88
|
+
filesystem_path_for(object, thumbnail).gsub(options[:base_path], '')
|
89
|
+
end
|
90
|
+
|
91
|
+
# Deletes the images and directory that contains them.
|
92
|
+
def remove_images(id)
|
93
|
+
FileUtils.rm_r path_for(id)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Is the uploaded file within the min and max allowed sizes?
|
97
|
+
def valid?
|
98
|
+
!(image_too_small? || image_too_big?)
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
|
103
|
+
# Gets the extension to append to the image. Transforms "jpeg" to "jpg."
|
104
|
+
def extension
|
105
|
+
options[:convert_to].to_s.downcase.gsub("jpeg", "jpg")
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# File name, plus thumbnail suffix, plus extension. For example:
|
111
|
+
#
|
112
|
+
# file_name_for("abc123", :thumb)
|
113
|
+
#
|
114
|
+
# gives you:
|
115
|
+
#
|
116
|
+
# "abc123_thumb.jpg"
|
117
|
+
#
|
118
|
+
#
|
119
|
+
def file_name_for(*args)
|
120
|
+
"%s.%s" % [args.compact.join("_"), extension]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Gets the full local filesystem path for an image. For example:
|
124
|
+
#
|
125
|
+
# /var/sites/example.com/production/public/photos/0000/0001/3er0zs.jpg
|
126
|
+
def filesystem_path_for(object, thumbnail = nil)
|
127
|
+
File.join(path_for(object.id), file_name_for(object.has_image_file, thumbnail))
|
128
|
+
end
|
129
|
+
|
130
|
+
# Write the main image to the install directory - probably somewhere under
|
131
|
+
# RAILS_ROOT/public.
|
132
|
+
def install_main_image(id, name)
|
133
|
+
FileUtils.mkdir_p path_for(id)
|
134
|
+
main = processor.resize(@temp_file, @options[:resize_to])
|
135
|
+
main.write(File.join(path_for(id), file_name_for(name)))
|
136
|
+
main.tempfile.close!
|
137
|
+
end
|
138
|
+
|
139
|
+
# Write the thumbnails to the install directory - probably somewhere under
|
140
|
+
# RAILS_ROOT/public.
|
141
|
+
def install_thumbnails(id, name)
|
142
|
+
FileUtils.mkdir_p path_for(id)
|
143
|
+
path = File.join(path_for(id), file_name_for(name))
|
144
|
+
options[:thumbnails].each do |thumb_name, size|
|
145
|
+
thumb = processor.resize(path, size)
|
146
|
+
thumb.write(File.join(path_for(id), file_name_for(name, thumb_name)))
|
147
|
+
thumb.tempfile.close!
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Get the full path for the id. For example:
|
152
|
+
#
|
153
|
+
# /var/sites/example.org/production/public/photos/0000/0001
|
154
|
+
def path_for(id)
|
155
|
+
File.join(options[:base_path], options[:path_prefix], Storage.partitioned_path(id))
|
156
|
+
end
|
157
|
+
|
158
|
+
# Instantiates the processor using the options set in my contructor (if
|
159
|
+
# not already instantiated), stores it in an instance variable, and
|
160
|
+
# returns it.
|
161
|
+
def processor
|
162
|
+
@processor ||= Processor.new(options)
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module HasImage
|
2
|
+
|
3
|
+
# Some helpers to make working with HasImage models in views a little
|
4
|
+
# easier.
|
5
|
+
module ViewHelpers
|
6
|
+
|
7
|
+
# Wraps the image_tag helper from Rails. Instead of passing the path to
|
8
|
+
# an image, you can pass any object that uses HasImage. The options can
|
9
|
+
# include the name of one of your thumbnails, for example:
|
10
|
+
#
|
11
|
+
# image_tag_for(@photo)
|
12
|
+
# image_tag_for(@photo, :thumb => :square)
|
13
|
+
#
|
14
|
+
# If your object uses fixed dimensions (i.e., "200x200" as opposed to
|
15
|
+
# "200x200>"), then the height and width properties will automatically be
|
16
|
+
# added to the resulting img tag unless you explicitly specify the size in
|
17
|
+
# the options.
|
18
|
+
#
|
19
|
+
# All arguments other than :thumb will simply be passed along to the Rails
|
20
|
+
# image_tag helper without modification.
|
21
|
+
#
|
22
|
+
# See also: HasImage::ModelInstanceMethods#public_path
|
23
|
+
def image_tag_for(object, options = {})
|
24
|
+
thumb = options.delete(:thumb)
|
25
|
+
if !options[:size]
|
26
|
+
if thumb
|
27
|
+
size = object.class.thumbnails[thumb.to_sym]
|
28
|
+
options[:size] = size if size =~ /\A[\d]*x[\d]*\Z/
|
29
|
+
else
|
30
|
+
size = object.class.resize_to
|
31
|
+
options[:size] = size if size =~ /\A[\d]*x[\d]*\Z/
|
32
|
+
end
|
33
|
+
end
|
34
|
+
image_tag(object.public_path(thumb), options)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/has_image.rb
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'has_image/processor'
|
2
|
+
require 'has_image/storage'
|
3
|
+
require 'has_image/view_helpers'
|
4
|
+
|
5
|
+
# = HasImage
|
6
|
+
#
|
7
|
+
# HasImage allows Ruby on Rails applications to have attached images. It is very
|
8
|
+
# small and lightweight: it only requires one column ("has_image_file") in your
|
9
|
+
# model to store the uploaded image's file name.
|
10
|
+
#
|
11
|
+
# HasImage is, by design, very simplistic: It only supports using a filesystem
|
12
|
+
# for storage, and only supports
|
13
|
+
# MiniMagick[http://github.com/probablycorey/mini_magick] as an image processor.
|
14
|
+
# However, its code is very small, clean and hackable, so adding support for
|
15
|
+
# other backends or processors should be fairly easy.
|
16
|
+
#
|
17
|
+
# HasImage works best for sites that want to show image galleries with
|
18
|
+
# fixed-size thumbnails. It uses ImageMagick's
|
19
|
+
# crop[http://www.imagemagick.org/script/command-line-options.php#crop] and
|
20
|
+
# {center
|
21
|
+
# gravity}[http://www.imagemagick.org/script/command-line-options.php#gravity]
|
22
|
+
# functions to produce thumbnails that generally look acceptable, unless the
|
23
|
+
# image is a panorama, or the subject matter is close to one of the margins,
|
24
|
+
# etc. For most sites where people upload pictures of themselves or their pets
|
25
|
+
# the generated thumbnails will look good almost all the time.
|
26
|
+
#
|
27
|
+
# It's pretty easy to change the image processing / resizing code; you can just
|
28
|
+
# override HasImage::Processor#resize_image to do what you wish:
|
29
|
+
#
|
30
|
+
# module HasImage::
|
31
|
+
# class Processor
|
32
|
+
# def resize_image(size)
|
33
|
+
# @image.combine_options do |commands|
|
34
|
+
# commands.my_custom_resizing_goes_here
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# Compared to attachment_fu, HasImage has advantages and disadvantages.
|
41
|
+
#
|
42
|
+
# = Advantages:
|
43
|
+
#
|
44
|
+
# * Simpler, smaller, more easily hackable codebase - and specialized for
|
45
|
+
# images only.
|
46
|
+
# * Installable via Ruby Gems. This makes version dependencies easy when using
|
47
|
+
# Rails 2.1.
|
48
|
+
# * Creates only one database record per image.
|
49
|
+
# * Has built-in facilities for making distortion-free, fixed-size thumbnails.
|
50
|
+
# * Doesn't regenerate the thumbnails every time you save your model. This means
|
51
|
+
# you can easily use it, for example, inside a Member model to store member
|
52
|
+
# avatars.
|
53
|
+
#
|
54
|
+
# = Disadvantages:
|
55
|
+
#
|
56
|
+
# * Doesn't save image dimensions. However, if you're using fixed-sized images,
|
57
|
+
# this is not a problem because you can just read the size from MyModel.thumbnails[:my_size]
|
58
|
+
# * No support for AWS or DBFile storage, only filesystem.
|
59
|
+
# * Only supports MiniMagick[http://github.com/probablycorey/mini_magick/tree] as an image processor, no RMagick, GD, CoreImage,
|
60
|
+
# etc.
|
61
|
+
# * No support for anything other than image attachments.
|
62
|
+
# * Not as popular as attachment_fu, which means fewer bug reports, and
|
63
|
+
# probably more bugs. Use at your own risk!
|
64
|
+
module HasImage
|
65
|
+
|
66
|
+
class ProcessorError < StandardError ; end
|
67
|
+
class StorageError < StandardError ; end
|
68
|
+
class FileTooBigError < StorageError ; end
|
69
|
+
class FileTooSmallError < StorageError ; end
|
70
|
+
|
71
|
+
class << self
|
72
|
+
|
73
|
+
def included(base) # :nodoc:
|
74
|
+
base.extend(ClassMethods)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Enables has_image functionality. You probably don't need to ever invoke
|
78
|
+
# this.
|
79
|
+
def enable # :nodoc:
|
80
|
+
return if ActiveRecord::Base.respond_to? :has_image
|
81
|
+
ActiveRecord::Base.send(:include, HasImage)
|
82
|
+
return if ActionView::Base.respond_to? :image_tag_for
|
83
|
+
ActionView::Base.send(:include, ViewHelpers)
|
84
|
+
end
|
85
|
+
|
86
|
+
# If you're invoking this method, you need to pass in the class for which
|
87
|
+
# you want to get default options; this is used to determine the path where
|
88
|
+
# the images will be stored in the file system. Take a look at
|
89
|
+
# HasImage::ClassMethods#has_image to see examples of how to set the options
|
90
|
+
# in your model.
|
91
|
+
#
|
92
|
+
# This method is called by your model when you call has_image. It's
|
93
|
+
# placed here rather than in the model's class methods to make it easier
|
94
|
+
# to access for testing. Unless you're working on the code, it's unlikely
|
95
|
+
# you'll ever need to invoke this method.
|
96
|
+
#
|
97
|
+
# * :resize_to => "200x200",
|
98
|
+
# * :thumbnails => {},
|
99
|
+
# * :max_size => 12.megabytes,
|
100
|
+
# * :min_size => 4.kilobytes,
|
101
|
+
# * :path_prefix => klass.to_s.tableize,
|
102
|
+
# * :base_path => File.join(RAILS_ROOT, 'public'),
|
103
|
+
# * :convert_to => "JPEG",
|
104
|
+
# * :output_quality => "85",
|
105
|
+
# * :invalid_image_message => "Can't process the image.",
|
106
|
+
# * :image_too_small_message => "The image is too small.",
|
107
|
+
# * :image_too_big_message => "The image is too big.",
|
108
|
+
def default_options_for(klass)
|
109
|
+
{
|
110
|
+
:resize_to => "200x200",
|
111
|
+
:thumbnails => {},
|
112
|
+
:max_size => 12.megabytes,
|
113
|
+
:min_size => 4.kilobytes,
|
114
|
+
:path_prefix => klass.to_s.tableize,
|
115
|
+
:base_path => File.join(RAILS_ROOT, 'public'),
|
116
|
+
:convert_to => "JPEG",
|
117
|
+
:output_quality => "85",
|
118
|
+
:invalid_image_message => "Can't process the image.",
|
119
|
+
:image_too_small_message => "The image is too small.",
|
120
|
+
:image_too_big_message => "The image is too big."
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
module ClassMethods
|
127
|
+
# To use HasImage with a Rails model, all you have to do is add a column
|
128
|
+
# named "has_image_file." For configuration defaults, you might want to take
|
129
|
+
# a look at the default options specified in HasImage#default_options_for.
|
130
|
+
# The different setting options are described below.
|
131
|
+
#
|
132
|
+
# Options:
|
133
|
+
# * <tt>:resize_to</tt> - Dimensions to resize to. This should be an ImageMagick {geometry string}[http://www.imagemagick.org/script/command-line-options.php#resize]. Fixed sizes are recommended.
|
134
|
+
# * <tt>:thumbnails</tt> - A hash of thumbnail names and dimensions. The dimensions should be ImageMagick {geometry strings}[http://www.imagemagick.org/script/command-line-options.php#resize]. Fixed sized are recommended.
|
135
|
+
# * <tt>:min_size</tt> - Minimum file size allowed. It's recommended that you set this size in kilobytes.
|
136
|
+
# * <tt>:max_size</tt> - Maximum file size allowed. It's recommended that you set this size in megabytes.
|
137
|
+
# * <tt>:base_path</tt> - Where to install the images. You should probably leave this alone, except for tests.
|
138
|
+
# * <tt>:path_prefix</tt> - Where to install the images, relative to basepath. You should probably leave this alone.
|
139
|
+
# * <tt>:convert_to</tt> - An ImageMagick format to convert images to. Recommended formats: JPEG, PNG, GIF.
|
140
|
+
# * <tt>:output_quality</tt> - Image output quality passed to ImageMagick.
|
141
|
+
# * <tt>:invalid_image_message</tt> - The message that will be shown when the image data can't be processed.
|
142
|
+
# * <tt>:image_too_small_message</tt> - The message that will be shown when the image file is too small. You should ideally set this to something that tells the user what the minimum is.
|
143
|
+
# * <tt>:image_too_big_message</tt> - The message that will be shown when the image file is too big. You should ideally set this to something that tells the user what the maximum is.
|
144
|
+
#
|
145
|
+
# Examples:
|
146
|
+
# has_image # uses all default options
|
147
|
+
# has_image :resize_to "800x800", :thumbnails => {:square => "150x150"}
|
148
|
+
# has_image :resize_to "100x150", :max_size => 500.kilobytes
|
149
|
+
# has_image :invalid_image_message => "No se puede procesar la imagen."
|
150
|
+
def has_image(options = {})
|
151
|
+
options.assert_valid_keys(:resize_to, :thumbnails, :max_size, :min_size,
|
152
|
+
:path_prefix, :base_path, :convert_to, :output_quality,
|
153
|
+
:invalid_image_message, :image_too_big_message, :image_too_small_message)
|
154
|
+
options = HasImage.default_options_for(self).merge(options)
|
155
|
+
class_inheritable_accessor :has_image_options
|
156
|
+
write_inheritable_attribute(:has_image_options, options)
|
157
|
+
|
158
|
+
after_create :install_images
|
159
|
+
after_save :update_images
|
160
|
+
after_destroy :remove_images
|
161
|
+
|
162
|
+
validate_on_create :image_data_valid?
|
163
|
+
|
164
|
+
include ModelInstanceMethods
|
165
|
+
extend ModelClassMethods
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
module ModelInstanceMethods
|
172
|
+
|
173
|
+
# Sets the uploaded image data. Image data can be an instance of Tempfile,
|
174
|
+
# or an instance of any class than inherits from IO.
|
175
|
+
def image_data=(image_data)
|
176
|
+
return if image_data.blank?
|
177
|
+
storage.image_data = image_data
|
178
|
+
end
|
179
|
+
|
180
|
+
# Is the image data a file that ImageMagick can process, and is it within
|
181
|
+
# the allowed minimum and maximum sizes?
|
182
|
+
def image_data_valid?
|
183
|
+
return if !storage.temp_file
|
184
|
+
if storage.image_too_big?
|
185
|
+
errors.add_to_base(self.class.has_image_options[:image_too_big_message])
|
186
|
+
elsif storage.image_too_small?
|
187
|
+
errors.add_to_base(self.class.has_image_options[:image_too_small_message])
|
188
|
+
elsif !HasImage::Processor.valid?(storage.temp_file)
|
189
|
+
errors.add_to_base(self.class.has_image_options[:invalid_image_message])
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Gets the "web path" for the image, or optionally, its thumbnail.
|
194
|
+
def public_path(thumbnail = nil)
|
195
|
+
storage.public_path_for(self, thumbnail)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Deletes the image from the storage.
|
199
|
+
def remove_images
|
200
|
+
return if has_image_file.blank?
|
201
|
+
storage.remove_images(self.id)
|
202
|
+
rescue Errno::ENOENT
|
203
|
+
logger.warn("Could not delete files for #{self.class.to_s} #{to_param}")
|
204
|
+
end
|
205
|
+
|
206
|
+
# Creates new images and removes the old ones when image_data has been
|
207
|
+
# set.
|
208
|
+
def update_images
|
209
|
+
return if storage.temp_file.blank?
|
210
|
+
storage.remove_images(self.id)
|
211
|
+
update_attribute(:has_image_file, storage.install_images(self.id))
|
212
|
+
end
|
213
|
+
|
214
|
+
# Processes and installs the image and its thumbnails.
|
215
|
+
def install_images
|
216
|
+
return if !storage.temp_file
|
217
|
+
update_attribute(:has_image_file, storage.install_images(self.id))
|
218
|
+
end
|
219
|
+
|
220
|
+
# Gets an instance of the underlying storage functionality. See
|
221
|
+
# HasImage::Storage.
|
222
|
+
def storage
|
223
|
+
@storage ||= HasImage::Storage.new(has_image_options)
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
module ModelClassMethods
|
229
|
+
|
230
|
+
# Get the hash of thumbnails set by the options specified when invoking
|
231
|
+
# HasImage::ClassMethods#has_image.
|
232
|
+
def thumbnails
|
233
|
+
has_image_options[:thumbnails]
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
if defined?(Rails) and defined?(ActiveRecord) and defined?(ActionController)
|
241
|
+
HasImage.enable
|
242
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'test_helper.rb'
|
2
|
+
|
3
|
+
class StorageTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def teardown
|
6
|
+
@temp_file.close if @temp_file
|
7
|
+
FileUtils.rm_rf(File.dirname(__FILE__) + '/../tmp')
|
8
|
+
end
|
9
|
+
|
10
|
+
def temp_file(fixture)
|
11
|
+
@temp_file = Tempfile.new('test')
|
12
|
+
@temp_file.write(File.new(File.dirname(__FILE__) + "/../test_rails/fixtures/#{fixture}", "r").read)
|
13
|
+
return @temp_file
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_detect_valid_image
|
17
|
+
assert HasImage::Processor.valid?(File.dirname(__FILE__) + "/../test_rails/fixtures/image.jpg")
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_detect_valid_image_from_tmp_file
|
21
|
+
assert HasImage::Processor.valid?(temp_file("image.jpg"))
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_detect_invalid_image
|
25
|
+
assert !HasImage::Processor.valid?(File.dirname(__FILE__) + "/../test_rails/fixtures/bad_image.jpg")
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_detect_invalid_image_from_tmp_file
|
29
|
+
assert !HasImage::Processor.valid?(temp_file("bad_image.jpg"))
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_resize
|
33
|
+
@processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
|
34
|
+
assert @processor.resize(temp_file("image.jpg"), "100x100")
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_resize_and_convert
|
38
|
+
@processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
|
39
|
+
assert @processor.resize(temp_file("image.png"), "100x100")
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_resize_should_fail_with_bad_image
|
43
|
+
@processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
|
44
|
+
assert_raises HasImage::ProcessorError do
|
45
|
+
@processor.resize(temp_file("bad_image.jpg"), "100x100")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'test_helper.rb'
|
2
|
+
|
3
|
+
class StorageTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
FileUtils.rm_rf(File.dirname(__FILE__) + '/../tmp')
|
10
|
+
@temp_file.close! if @temp_file && !@temp_file.closed?
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_options
|
14
|
+
HasImage.default_options_for("tests").merge(
|
15
|
+
:base_path => File.join(File.dirname(__FILE__), '..', 'tmp')
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_partitioned_path
|
20
|
+
assert_equal(["0001", "2345"], HasImage::Storage.partitioned_path("12345"))
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_random_file_name
|
24
|
+
assert_match(/[a-z0-9]{4,6}/i, HasImage::Storage.random_file_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_path_for
|
28
|
+
@storage = HasImage::Storage.new(default_options)
|
29
|
+
assert_match(/\/tmp\/tests\/0000\/0001/, @storage.send(:path_for, 1))
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_public_path_for
|
33
|
+
@storage = HasImage::Storage.new(default_options)
|
34
|
+
pic = stub(:has_image_file => "mypic", :id => 1)
|
35
|
+
assert_equal "/tests/0000/0001/mypic_square.jpg", @storage.public_path_for(pic, :square)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_filename_for
|
39
|
+
@storage = HasImage::Storage.new(default_options)
|
40
|
+
assert_equal "test.jpg", @storage.send(:file_name_for, "test")
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_set_data_from_file
|
44
|
+
@storage = HasImage::Storage.new(default_options)
|
45
|
+
@file = File.new(File.dirname(__FILE__) + "/../test_rails/fixtures/image.jpg", "r")
|
46
|
+
@storage.image_data = @file
|
47
|
+
assert @storage.temp_file.size > 0
|
48
|
+
assert_equal Zlib.crc32(@file.read), Zlib.crc32(@storage.temp_file.read)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_set_data_from_tempfile
|
52
|
+
@storage = HasImage::Storage.new(default_options)
|
53
|
+
@storage.image_data = temp_file("image.jpg")
|
54
|
+
assert @storage.temp_file.size > 0
|
55
|
+
assert_equal Zlib.crc32(@storage.temp_file.read), Zlib.crc32(@temp_file.read)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_install_and_remove_images
|
59
|
+
@storage = HasImage::Storage.new(default_options)
|
60
|
+
@storage.image_data = temp_file("image.jpg")
|
61
|
+
assert @storage.install_images(1)
|
62
|
+
assert @storage.remove_images(1)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_image_not_too_small
|
66
|
+
@storage = HasImage::Storage.new(default_options.merge(:min_size => 1.kilobyte))
|
67
|
+
@storage.image_data = temp_file("image.jpg")
|
68
|
+
assert !@storage.image_too_small?
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_image_too_small
|
72
|
+
@storage = HasImage::Storage.new(default_options.merge(:min_size => 1.gigabyte))
|
73
|
+
@storage.image_data = temp_file("image.jpg")
|
74
|
+
assert @storage.image_too_small?
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_image_too_big
|
78
|
+
@storage = HasImage::Storage.new(default_options.merge(:max_size => 1.kilobyte))
|
79
|
+
@storage.image_data = temp_file("image.jpg")
|
80
|
+
assert @storage.image_too_big?
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_image_not_too_big
|
84
|
+
@storage = HasImage::Storage.new(default_options.merge(:max_size => 1.gigabyte))
|
85
|
+
@storage.image_data = temp_file("image.jpg")
|
86
|
+
assert !@storage.image_too_big?
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def temp_file(fixture)
|
92
|
+
file = File.new(File.dirname(__FILE__) + "/../test_rails/fixtures/#{fixture}", "r")
|
93
|
+
@temp_file = Tempfile.new("test")
|
94
|
+
@temp_file.write(file.read)
|
95
|
+
return @temp_file
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
data/test_rails/pic.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PicTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
Pic.has_image_options = HasImage.default_options_for(Pic)
|
7
|
+
Pic.has_image_options[:base_path] = File.join(RAILS_ROOT, '/tmp')
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
FileUtils.rm_rf(File.join(RAILS_ROOT, 'tmp', 'pics'))
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_should_be_valid
|
15
|
+
@pic = Pic.new(:image_data => fixture_file_upload("/image.jpg", "image/jpeg"))
|
16
|
+
assert @pic.valid? , "#{@pic.errors.full_messages.to_sentence}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_should_be_too_big
|
20
|
+
Pic.has_image_options[:max_size] = 1.kilobyte
|
21
|
+
@pic = Pic.new(:image_data => fixture_file_upload("/image.jpg", "image/jpeg"))
|
22
|
+
assert !@pic.valid?
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_should_be_too_small
|
26
|
+
Pic.has_image_options[:min_size] = 1.gigabyte
|
27
|
+
@pic = Pic.new(:image_data => fixture_file_upload("/image.jpg", "image/jpeg"))
|
28
|
+
assert !@pic.valid?
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_invalid_image_detected
|
32
|
+
@pic = Pic.new(:image_data => fixture_file_upload("/bad_image.jpg", "image/jpeg"))
|
33
|
+
assert !@pic.valid?
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_create
|
37
|
+
@pic = Pic.new(:image_data => fixture_file_upload("/image.jpg", "image/jpeg"))
|
38
|
+
assert @pic.save!
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_update
|
42
|
+
@pic = Pic.new(:image_data => fixture_file_upload("/image.jpg", "image/jpeg"))
|
43
|
+
@pic.save!
|
44
|
+
@pic.image_data = fixture_file_upload("/image.png", "image/png")
|
45
|
+
assert @pic.save!
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_create_model_without_setting_image_data
|
49
|
+
assert Pic.new.save!
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_destroy_model_without_no_images
|
53
|
+
@pic = Pic.new
|
54
|
+
@pic.save!
|
55
|
+
assert @pic.destroy
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_destroy_model_with_images_already_deleted_from_filesystem
|
59
|
+
@pic = Pic.new
|
60
|
+
@pic.save!
|
61
|
+
@pic.update_attribute(:has_image_file, "test")
|
62
|
+
assert @pic.destroy
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_create_with_png
|
66
|
+
Pic.has_image_options[:min_size] = 1
|
67
|
+
@pic = Pic.new(:image_data => fixture_file_upload("/image.png", "image/png"))
|
68
|
+
assert @pic.save!
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_multiple_calls_to_valid_doesnt_blow_away_temp_image
|
72
|
+
Pic.has_image_options[:min_size] = 1
|
73
|
+
@pic = Pic.new(:image_data => fixture_file_upload("/image.png", "image/png"))
|
74
|
+
@pic.valid?
|
75
|
+
assert @pic.valid?
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
3
|
+
ENV['RAILS_ENV'] = 'test'
|
4
|
+
|
5
|
+
require 'test/unit'
|
6
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '/../../../../config/environment.rb'))
|
7
|
+
require 'active_record/fixtures'
|
8
|
+
require 'action_controller/test_process'
|
9
|
+
|
10
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
11
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
12
|
+
|
13
|
+
db_adapter = ENV['DB']
|
14
|
+
|
15
|
+
# no db passed, try one of these fine config-free DBs before bombing.
|
16
|
+
db_adapter ||=
|
17
|
+
begin
|
18
|
+
require 'rubygems'
|
19
|
+
require 'sqlite3'
|
20
|
+
'sqlite3'
|
21
|
+
rescue MissingSourceFile
|
22
|
+
begin
|
23
|
+
require 'sqlite'
|
24
|
+
'sqlite'
|
25
|
+
rescue MissingSourceFile
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if db_adapter.nil?
|
30
|
+
raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite3 or Sqlite."
|
31
|
+
end
|
32
|
+
|
33
|
+
ActiveRecord::Base.establish_connection(config[db_adapter])
|
34
|
+
|
35
|
+
load(File.dirname(__FILE__) + "/schema.rb")
|
36
|
+
|
37
|
+
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures"
|
38
|
+
$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
|
39
|
+
|
40
|
+
class Test::Unit::TestCase #:nodoc:
|
41
|
+
include ActionController::TestProcess
|
42
|
+
def create_fixtures(*table_names)
|
43
|
+
if block_given?
|
44
|
+
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
|
45
|
+
else
|
46
|
+
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
self.use_transactional_fixtures = true
|
51
|
+
self.use_instantiated_fixtures = false
|
52
|
+
|
53
|
+
end
|
54
|
+
require 'pic'
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_image
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Norman Clarke
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-07-23 00:00:00 -03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: HasImage is a Ruby on Rails gem/plugin that allows you to attach images to ActiveRecord models.
|
17
|
+
email: norman@randomba.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- CHANGELOG
|
25
|
+
- FAQ
|
26
|
+
files:
|
27
|
+
- CHANGELOG
|
28
|
+
- FAQ
|
29
|
+
- MIT-LICENSE
|
30
|
+
- README
|
31
|
+
- init.rb
|
32
|
+
- lib/has_image.rb
|
33
|
+
- lib/has_image/processor.rb
|
34
|
+
- lib/has_image/storage.rb
|
35
|
+
- lib/has_image/view_helpers.rb
|
36
|
+
- Rakefile
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://randomba.org
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --main
|
42
|
+
- README
|
43
|
+
- --inline-source
|
44
|
+
- --line-numbers
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.2.0
|
63
|
+
signing_key:
|
64
|
+
specification_version: 2
|
65
|
+
summary: Lets you attach images with thumbnails to active record models.
|
66
|
+
test_files:
|
67
|
+
- test_rails/database.yml
|
68
|
+
- test_rails/fixtures/bad_image.jpg
|
69
|
+
- test_rails/fixtures/image.jpg
|
70
|
+
- test_rails/fixtures/image.png
|
71
|
+
- test_rails/pic.rb
|
72
|
+
- test_rails/pic_test.rb
|
73
|
+
- test_rails/schema.rb
|
74
|
+
- test_rails/test_helper.rb
|
75
|
+
- test/processor_test.rb
|
76
|
+
- test/storage_test.rb
|