norman-has_image 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,19 @@
1
+ 2008-07-29 Norman Clarke <norman@randomba.org>
2
+
3
+ * Made image deletion nullify the "has_image_file" field.
4
+ * Added "has_image?" method to model instances.
5
+ * Fixed ENONENT error with record update when there are no images yet.
6
+ * Reverted thumbnail sorting feature - it's fast but makes terrible quality
7
+ thumbnails. It's just not worth it.
8
+
9
+ 2008-07-28 Norman Clarke <norman@randomba.org>
10
+
11
+ * Added sorted thumbnail processing. This improves thumbnail generation
12
+ speed by about 25% for 4.5 meg jpegs with 5 thumbnails.
13
+ * Fixed broken resize for non-fixed-width thumbnails.
14
+ * Added check for bad geometry strings.
15
+ * Added dependencies and Rubyforge project to gemspec, updated docs.
16
+
17
+ 2008-07-25 Norman Clarke <norman@randomba.org>
18
+
19
+ * First public release.
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,143 @@
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 has_image
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
+ == Hacking it
98
+
99
+ Don't like the way it makes images? Want to pipe the images through some
100
+ {crazy fast seam carving library written in
101
+ OCaml}[http://eigenclass.org/hiki/seam-carving-in-ocaml], or watermark them
102
+ with your corporate logo? Happiness is just a monkey-patch[http://en.wikipedia.org/wiki/Monkey_patch] away:
103
+
104
+ module HasImage
105
+ class Processor
106
+ def resize_image(size)
107
+ # your new-and-improved thumbnailer code goes here.
108
+ end
109
+ end
110
+ end
111
+
112
+ HasImage[http://github.com/norman/has_image] follows a philosophy of "{skinny
113
+ model, fat plugin}[http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model]."
114
+ This means that it tries to pollute your ActiveRecord model with as little
115
+ functionality as possible, so that in a sense, the model is acts like a
116
+ "controller" and the plugin like a "model" as regards the image handling
117
+ functionality. This makes it easier to test, hack, and reuse, because the
118
+ storage and processing functionality is largely independent of your model, and
119
+ of Rails.
120
+
121
+ My goal for HasImage[http://github.com/norman/has_image] is to keep it very
122
+ small. If you need *a lot* of functionality that's not here, instead of patching
123
+ this code, you will likely be better off using
124
+ attachment_fu[http://github.com/technoweenie/attachment_fu], which is much
125
+ more powerful, but also more complex.
126
+
127
+ == Bugs
128
+
129
+ Please report them on Lighthouse[http://randomba.lighthouseapp.com/projects/14674-has_image].
130
+
131
+ At the time of writing (July 2008),
132
+ HasImage[http://github.com/norman/has_image] is in its infancy. Your patches,
133
+ bug reports and withering criticism are more than welcome.
134
+
135
+ == Links
136
+
137
+ * {HasImage RDocs}[http://randomba.org/projects/has_image] (regenerated nightly)
138
+ * {HasImage on GitHub}[http://github.com/norman/has_image]
139
+ * {HasImage on Rubyforge}[http://rubyforge.org/projects/has-image/]
140
+ * {HasImage on Lighthouse}[http://randomba.lighthouseapp.com/projects/14674-has_image]
141
+
142
+ Copyright (c) 2008 {Norman Clarke}[mailto:norman@randomba.org], released under
143
+ 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,93 @@
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
+
12
+ # "The form of an {extended geometry
13
+ # string}[http://www.imagemagick.org/script/command-line-options.php?#resize] is
14
+ # <width>x<height>{+-}<xoffset>{+-}<yoffset>{%}{!}{<}{>}"
15
+ def geometry_string_valid?(string)
16
+ string =~ /\A[\d]*x[\d]*([+-][0-9][+-][0-9])?[%@!<>^]?\Z/
17
+ end
18
+
19
+ # Arg should be either a file, or a path. This runs ImageMagick's
20
+ # "identify" command and looks for an exit status indicating an error. If
21
+ # there is no error, then ImageMagick has identified the file as something
22
+ # it can work with and it will be converted to the desired output format.
23
+ def valid?(arg)
24
+ arg.close if arg.respond_to?(:close) && !arg.closed?
25
+ silence_stderr do
26
+ `identify #{arg.respond_to?(:path) ? arg.path : arg.to_s}`
27
+ $? == 0
28
+ end
29
+ end
30
+ end
31
+
32
+ # The constuctor should be invoked with the options set by has_image.
33
+ def initialize(options) # :nodoc:
34
+ @options = options
35
+ end
36
+
37
+ # Create the resized image, and transforms it to the desired output
38
+ # format if necessary. The size should be a valid ImageMagick {geometry
39
+ # string}[http://www.imagemagick.org/script/command-line-options.php#resize].
40
+ def resize(file, size)
41
+ unless Processor.geometry_string_valid?(size)
42
+ raise InvalidGeometryError.new('"%s" is not a valid ImageMagick geometry string' % size)
43
+ end
44
+ silence_stderr do
45
+ path = file.respond_to?(:path) ? file.path : file
46
+ file.close if file.respond_to?(:close) && !file.closed?
47
+ @image = MiniMagick::Image.from_file(path)
48
+ convert_image
49
+ resize_image(size)
50
+ return @image
51
+ end
52
+ rescue MiniMagick::MiniMagickError
53
+ raise ProcessorError.new("That doesn't look like an image file.")
54
+ end
55
+
56
+ # Image resizing is placed in a separate method for easy monkey-patching.
57
+ # This is intended to be invoked from resize, rather than directly.
58
+ # By default, the following ImageMagick functionality is invoked:
59
+ # * auto-orient[http://www.imagemagick.org/script/command-line-options.php#auto-orient]
60
+ # * strip[http://www.imagemagick.org/script/command-line-options.php#strip]
61
+ # * resize[http://www.imagemagick.org/script/command-line-options.php#resize]
62
+ # * gravity[http://www.imagemagick.org/script/command-line-options.php#gravity]
63
+ # * extent[http://www.imagemagick.org/script/command-line-options.php#extent]
64
+ # * quality[http://www.imagemagick.org/script/command-line-options.php#quality]
65
+ def resize_image(size)
66
+ @image.combine_options do |commands|
67
+ commands.send("auto-orient".to_sym)
68
+ commands.strip
69
+ # Fixed-dimension images
70
+ if size =~ /\A[\d]*x[\d]*!?\Z/
71
+ commands.resize "#{size}^"
72
+ commands.gravity "center"
73
+ commands.extent size
74
+ # Non-fixed-dimension images
75
+ else
76
+ commands.resize "#{size}"
77
+ end
78
+ commands.quality options[:output_quality]
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ # This was placed in a separate method largely to facilitate debugging
85
+ # and profiling.
86
+ def convert_image
87
+ return if @image[:format] == options[:convert_to]
88
+ @image.format(options[:convert_to])
89
+ end
90
+
91
+ end
92
+
93
+ 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,249 @@
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
+ class InvalidGeometryError < ProcessorError ; end
71
+
72
+ class << self
73
+
74
+ def included(base) # :nodoc:
75
+ base.extend(ClassMethods)
76
+ end
77
+
78
+ # Enables has_image functionality. You probably don't need to ever invoke
79
+ # this.
80
+ def enable # :nodoc:
81
+ return if ActiveRecord::Base.respond_to? :has_image
82
+ ActiveRecord::Base.send(:include, HasImage)
83
+ return if ActionView::Base.respond_to? :image_tag_for
84
+ ActionView::Base.send(:include, ViewHelpers)
85
+ end
86
+
87
+ # If you're invoking this method, you need to pass in the class for which
88
+ # you want to get default options; this is used to determine the path where
89
+ # the images will be stored in the file system. Take a look at
90
+ # HasImage::ClassMethods#has_image to see examples of how to set the options
91
+ # in your model.
92
+ #
93
+ # This method is called by your model when you call has_image. It's
94
+ # placed here rather than in the model's class methods to make it easier
95
+ # to access for testing. Unless you're working on the code, it's unlikely
96
+ # you'll ever need to invoke this method.
97
+ #
98
+ # * :resize_to => "200x200",
99
+ # * :thumbnails => {},
100
+ # * :max_size => 12.megabytes,
101
+ # * :min_size => 4.kilobytes,
102
+ # * :path_prefix => klass.to_s.tableize,
103
+ # * :base_path => File.join(RAILS_ROOT, 'public'),
104
+ # * :convert_to => "JPEG",
105
+ # * :output_quality => "85",
106
+ # * :invalid_image_message => "Can't process the image.",
107
+ # * :image_too_small_message => "The image is too small.",
108
+ # * :image_too_big_message => "The image is too big.",
109
+ def default_options_for(klass)
110
+ {
111
+ :resize_to => "200x200",
112
+ :thumbnails => {},
113
+ :max_size => 12.megabytes,
114
+ :min_size => 4.kilobytes,
115
+ :path_prefix => klass.to_s.tableize,
116
+ :base_path => File.join(RAILS_ROOT, 'public'),
117
+ :convert_to => "JPEG",
118
+ :output_quality => "85",
119
+ :invalid_image_message => "Can't process the image.",
120
+ :image_too_small_message => "The image is too small.",
121
+ :image_too_big_message => "The image is too big."
122
+ }
123
+ end
124
+
125
+ end
126
+
127
+ module ClassMethods
128
+ # To use HasImage with a Rails model, all you have to do is add a column
129
+ # named "has_image_file." For configuration defaults, you might want to take
130
+ # a look at the default options specified in HasImage#default_options_for.
131
+ # The different setting options are described below.
132
+ #
133
+ # Options:
134
+ # * <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.
135
+ # * <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.
136
+ # * <tt>:min_size</tt> - Minimum file size allowed. It's recommended that you set this size in kilobytes.
137
+ # * <tt>:max_size</tt> - Maximum file size allowed. It's recommended that you set this size in megabytes.
138
+ # * <tt>:base_path</tt> - Where to install the images. You should probably leave this alone, except for tests.
139
+ # * <tt>:path_prefix</tt> - Where to install the images, relative to basepath. You should probably leave this alone.
140
+ # * <tt>:convert_to</tt> - An ImageMagick format to convert images to. Recommended formats: JPEG, PNG, GIF.
141
+ # * <tt>:output_quality</tt> - Image output quality passed to ImageMagick.
142
+ # * <tt>:invalid_image_message</tt> - The message that will be shown when the image data can't be processed.
143
+ # * <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.
144
+ # * <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.
145
+ #
146
+ # Examples:
147
+ # has_image # uses all default options
148
+ # has_image :resize_to "800x800", :thumbnails => {:square => "150x150"}
149
+ # has_image :resize_to "100x150", :max_size => 500.kilobytes
150
+ # has_image :invalid_image_message => "No se puede procesar la imagen."
151
+ def has_image(options = {})
152
+ options.assert_valid_keys(:resize_to, :thumbnails, :max_size, :min_size,
153
+ :path_prefix, :base_path, :convert_to, :output_quality,
154
+ :invalid_image_message, :image_too_big_message, :image_too_small_message)
155
+ options = HasImage.default_options_for(self).merge(options)
156
+ class_inheritable_accessor :has_image_options
157
+ write_inheritable_attribute(:has_image_options, options)
158
+
159
+ after_create :install_images
160
+ after_save :update_images
161
+ after_destroy :remove_images
162
+
163
+ validate_on_create :image_data_valid?
164
+
165
+ include ModelInstanceMethods
166
+ extend ModelClassMethods
167
+
168
+ end
169
+
170
+ end
171
+
172
+ module ModelInstanceMethods
173
+
174
+ # Does the object have an image?
175
+ def has_image?
176
+ !has_image_file.blank?
177
+ end
178
+
179
+ # Sets the uploaded image data. Image data can be an instance of Tempfile,
180
+ # or an instance of any class than inherits from IO.
181
+ def image_data=(image_data)
182
+ return if image_data.blank?
183
+ storage.image_data = image_data
184
+ end
185
+
186
+ # Is the image data a file that ImageMagick can process, and is it within
187
+ # the allowed minimum and maximum sizes?
188
+ def image_data_valid?
189
+ return if !storage.temp_file
190
+ if storage.image_too_big?
191
+ errors.add_to_base(self.class.has_image_options[:image_too_big_message])
192
+ elsif storage.image_too_small?
193
+ errors.add_to_base(self.class.has_image_options[:image_too_small_message])
194
+ elsif !HasImage::Processor.valid?(storage.temp_file)
195
+ errors.add_to_base(self.class.has_image_options[:invalid_image_message])
196
+ end
197
+ end
198
+
199
+ # Gets the "web path" for the image, or optionally, its thumbnail.
200
+ def public_path(thumbnail = nil)
201
+ storage.public_path_for(self, thumbnail)
202
+ end
203
+
204
+ # Deletes the image from the storage.
205
+ def remove_images
206
+ return if has_image_file.blank?
207
+ storage.remove_images(self.id)
208
+ update_attribute(:has_image_file, nil)
209
+ rescue Errno::ENOENT
210
+ logger.warn("Could not delete files for #{self.class.to_s} #{to_param}")
211
+ end
212
+
213
+ # Creates new images and removes the old ones when image_data has been
214
+ # set.
215
+ def update_images
216
+ return if storage.temp_file.blank?
217
+ remove_images
218
+ update_attribute(:has_image_file, storage.install_images(self.id))
219
+ end
220
+
221
+ # Processes and installs the image and its thumbnails.
222
+ def install_images
223
+ return if !storage.temp_file
224
+ update_attribute(:has_image_file, storage.install_images(self.id))
225
+ end
226
+
227
+ # Gets an instance of the underlying storage functionality. See
228
+ # HasImage::Storage.
229
+ def storage
230
+ @storage ||= HasImage::Storage.new(has_image_options)
231
+ end
232
+
233
+ end
234
+
235
+ module ModelClassMethods
236
+
237
+ # Get the hash of thumbnails set by the options specified when invoking
238
+ # HasImage::ClassMethods#has_image.
239
+ def thumbnails
240
+ has_image_options[:thumbnails]
241
+ end
242
+
243
+ end
244
+
245
+ end
246
+
247
+ if defined?(Rails) and defined?(ActiveRecord) and defined?(ActionController)
248
+ HasImage.enable
249
+ end
@@ -0,0 +1,61 @@
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_with_invalid_geometry
33
+ @processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
34
+ assert_raises HasImage::InvalidGeometryError do
35
+ @processor.resize(temp_file("image.jpg"), "bad_geometry")
36
+ end
37
+ end
38
+
39
+ def test_resize_fixed
40
+ @processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
41
+ assert @processor.resize(temp_file("image.jpg"), "100x100")
42
+ end
43
+
44
+ def test_resize_unfixed
45
+ @processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
46
+ assert @processor.resize(temp_file("image.jpg"), "1024x768>")
47
+ end
48
+
49
+ def test_resize_and_convert
50
+ @processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
51
+ assert @processor.resize(temp_file("image.png"), "100x100")
52
+ end
53
+
54
+ def test_resize_should_fail_with_bad_image
55
+ @processor = HasImage::Processor.new({:convert_to => "JPEG", :output_quality => "85"})
56
+ assert_raises HasImage::ProcessorError do
57
+ @processor.resize(temp_file("bad_image.jpg"), "100x100")
58
+ end
59
+ end
60
+
61
+ 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
@@ -0,0 +1,3 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: ":memory:"
Binary file
Binary file
Binary file
data/test_rails/pic.rb ADDED
@@ -0,0 +1,3 @@
1
+ class Pic < ActiveRecord::Base
2
+ has_image
3
+ end
@@ -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,9 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+
3
+ create_table "pics", :force => true do |t|
4
+ t.string :has_image_file
5
+ t.datetime :created_at
6
+ t.datetime :updated_at
7
+ end
8
+
9
+ end
@@ -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,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: norman-has_image
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.5
5
+ platform: ruby
6
+ authors:
7
+ - Norman Clarke
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-29 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mini_magick
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.2.3
23
+ version:
24
+ description: HasImage is a Ruby on Rails gem/plugin that allows you to attach images to ActiveRecord models.
25
+ email: norman@randomba.org
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README
32
+ - CHANGELOG
33
+ - FAQ
34
+ files:
35
+ - CHANGELOG
36
+ - FAQ
37
+ - MIT-LICENSE
38
+ - README
39
+ - init.rb
40
+ - lib/has_image.rb
41
+ - lib/has_image/processor.rb
42
+ - lib/has_image/storage.rb
43
+ - lib/has_image/view_helpers.rb
44
+ - Rakefile
45
+ has_rdoc: true
46
+ homepage: http://randomba.org
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --main
50
+ - README
51
+ - --inline-source
52
+ - --line-numbers
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: has-image
70
+ rubygems_version: 1.2.0
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: Lets you attach images with thumbnails to active record models.
74
+ test_files:
75
+ - test_rails/database.yml
76
+ - test_rails/fixtures/bad_image.jpg
77
+ - test_rails/fixtures/image.jpg
78
+ - test_rails/fixtures/image.png
79
+ - test_rails/pic.rb
80
+ - test_rails/pic_test.rb
81
+ - test_rails/schema.rb
82
+ - test_rails/test_helper.rb
83
+ - test/processor_test.rb
84
+ - test/storage_test.rb