summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--app/controllers/assets_controller.rb88
-rw-r--r--app/helpers/assets_helper.rb2
-rw-r--r--app/models/asset.rb10
-rw-r--r--app/views/admin/_menu.html.erb1
-rw-r--r--app/views/assets/edit.html.erb12
-rw-r--r--app/views/assets/index.html.erb20
-rw-r--r--app/views/assets/new.html.erb17
-rw-r--r--app/views/assets/show.html.erb10
-rw-r--r--app/views/layouts/assets.html.erb17
-rw-r--r--config/routes.rb2
-rw-r--r--db/migrate/20090424085851_create_assets.rb16
-rw-r--r--test/fixtures/assets.yml7
-rw-r--r--test/functional/assets_controller_test.rb45
-rw-r--r--test/unit/asset_test.rb8
-rw-r--r--test/unit/helpers/assets_helper_test.rb4
-rw-r--r--vendor/plugins/paperclip/LICENSE26
-rw-r--r--vendor/plugins/paperclip/README.rdoc172
-rw-r--r--vendor/plugins/paperclip/Rakefile77
-rw-r--r--vendor/plugins/paperclip/generators/paperclip/USAGE5
-rw-r--r--vendor/plugins/paperclip/generators/paperclip/paperclip_generator.rb27
-rw-r--r--vendor/plugins/paperclip/generators/paperclip/templates/paperclip_migration.rb.erb19
-rw-r--r--vendor/plugins/paperclip/init.rb1
-rw-r--r--vendor/plugins/paperclip/lib/paperclip.rb318
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/attachment.rb403
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/callback_compatability.rb33
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/geometry.rb115
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/iostream.rb58
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/matchers.rb4
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb49
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb66
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb48
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb83
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/processor.rb48
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/storage.rb236
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/thumbnail.rb70
-rw-r--r--vendor/plugins/paperclip/lib/paperclip/upfile.rb48
-rw-r--r--vendor/plugins/paperclip/paperclip.gemspec40
-rw-r--r--vendor/plugins/paperclip/shoulda_macros/paperclip.rb68
-rw-r--r--vendor/plugins/paperclip/tasks/paperclip_tasks.rake79
-rw-r--r--vendor/plugins/paperclip/test/.gitignore1
-rw-r--r--vendor/plugins/paperclip/test/attachment_test.rb742
-rw-r--r--vendor/plugins/paperclip/test/fixtures/12k.pngbin0 -> 12093 bytes
-rw-r--r--vendor/plugins/paperclip/test/fixtures/50x50.pngbin0 -> 1615 bytes
-rw-r--r--vendor/plugins/paperclip/test/fixtures/5k.pngbin0 -> 4456 bytes
-rw-r--r--vendor/plugins/paperclip/test/fixtures/bad.png1
-rw-r--r--vendor/plugins/paperclip/test/fixtures/text.txt0
-rw-r--r--vendor/plugins/paperclip/test/fixtures/twopage.pdfbin0 -> 8775 bytes
-rw-r--r--vendor/plugins/paperclip/test/geometry_test.rb168
-rw-r--r--vendor/plugins/paperclip/test/helper.rb82
-rw-r--r--vendor/plugins/paperclip/test/integration_test.rb481
-rw-r--r--vendor/plugins/paperclip/test/iostream_test.rb71
-rw-r--r--vendor/plugins/paperclip/test/matchers/have_attached_file_matcher_test.rb21
-rw-r--r--vendor/plugins/paperclip/test/matchers/validate_attachment_content_type_matcher_test.rb30
-rw-r--r--vendor/plugins/paperclip/test/matchers/validate_attachment_presence_matcher_test.rb21
-rw-r--r--vendor/plugins/paperclip/test/matchers/validate_attachment_size_matcher_test.rb50
-rw-r--r--vendor/plugins/paperclip/test/paperclip_test.rb233
-rw-r--r--vendor/plugins/paperclip/test/processor_test.rb10
-rw-r--r--vendor/plugins/paperclip/test/storage_test.rb277
-rw-r--r--vendor/plugins/paperclip/test/thumbnail_test.rb177
60 files changed, 4721 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index b8d3ec2..ed05711 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,7 @@ lib/chaos_calendar/Makefile
11lib/chaos_calendar/ical_occurrences.bundle 11lib/chaos_calendar/ical_occurrences.bundle
12lib/chaos_calendar/ical_occurrences.o 12lib/chaos_calendar/ical_occurrences.o
13lib/chaos_calendar/ical_occurrences_wrap.c 13lib/chaos_calendar/ical_occurrences_wrap.c
14lib/chaos_calendar/ical_occurrences_wrap.o \ No newline at end of file 14lib/chaos_calendar/ical_occurrences_wrap.o
15db/authors.csv
16public/system
17vendor/plugins/paperclip/tmp
diff --git a/app/controllers/assets_controller.rb b/app/controllers/assets_controller.rb
new file mode 100644
index 0000000..4734a54
--- /dev/null
+++ b/app/controllers/assets_controller.rb
@@ -0,0 +1,88 @@
1class AssetsController < ApplicationController
2
3 layout 'admin'
4
5 # GET /assets
6 # GET /assets.xml
7 def index
8 @assets = Asset.all
9
10 respond_to do |format|
11 format.html # index.html.erb
12 format.xml { render :xml => @assets }
13 end
14 end
15
16 # GET /assets/1
17 # GET /assets/1.xml
18 def show
19 @asset = Asset.find(params[:id])
20
21 respond_to do |format|
22 format.html # show.html.erb
23 format.xml { render :xml => @asset }
24 end
25 end
26
27 # GET /assets/new
28 # GET /assets/new.xml
29 def new
30 @asset = Asset.new
31
32 respond_to do |format|
33 format.html # new.html.erb
34 format.xml { render :xml => @asset }
35 end
36 end
37
38 # GET /assets/1/edit
39 def edit
40 @asset = Asset.find(params[:id])
41 end
42
43 # POST /assets
44 # POST /assets.xml
45 def create
46 @asset = Asset.new(params[:asset])
47
48 respond_to do |format|
49 if @asset.save
50 flash[:notice] = 'Asset was successfully created.'
51 format.html { redirect_to(@asset) }
52 format.xml { render :xml => @asset, :status => :created, :location => @asset }
53 else
54 format.html { render :action => "new" }
55 format.xml { render :xml => @asset.errors, :status => :unprocessable_entity }
56 end
57 end
58 end
59
60 # PUT /assets/1
61 # PUT /assets/1.xml
62 def update
63 @asset = Asset.find(params[:id])
64
65 respond_to do |format|
66 if @asset.update_attributes(params[:asset])
67 flash[:notice] = 'Asset was successfully updated.'
68 format.html { redirect_to(@asset) }
69 format.xml { head :ok }
70 else
71 format.html { render :action => "edit" }
72 format.xml { render :xml => @asset.errors, :status => :unprocessable_entity }
73 end
74 end
75 end
76
77 # DELETE /assets/1
78 # DELETE /assets/1.xml
79 def destroy
80 @asset = Asset.find(params[:id])
81 @asset.destroy
82
83 respond_to do |format|
84 format.html { redirect_to(assets_url) }
85 format.xml { head :ok }
86 end
87 end
88end
diff --git a/app/helpers/assets_helper.rb b/app/helpers/assets_helper.rb
new file mode 100644
index 0000000..9824bb4
--- /dev/null
+++ b/app/helpers/assets_helper.rb
@@ -0,0 +1,2 @@
1module AssetsHelper
2end
diff --git a/app/models/asset.rb b/app/models/asset.rb
new file mode 100644
index 0000000..c9c6296
--- /dev/null
+++ b/app/models/asset.rb
@@ -0,0 +1,10 @@
1class Asset < ActiveRecord::Base
2 has_attached_file(
3 :upload,
4 :styles => {
5 :normal => "450x450",
6 :medium => "300x300",
7 :thumb => "100x100",
8 }
9 )
10end
diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb
index 8f48822..5ae4307 100644
--- a/app/views/admin/_menu.html.erb
+++ b/app/views/admin/_menu.html.erb
@@ -2,4 +2,5 @@
2 2
3<%= link_to 'Overview', :controller => :admin, :action => 'index' %> 3<%= link_to 'Overview', :controller => :admin, :action => 'index' %>
4<%= link_to 'Nodes', nodes_path, selected?('nodes') %> 4<%= link_to 'Nodes', nodes_path, selected?('nodes') %>
5<%= link_to 'Assets', assets_path, selected?('assets') %>
5<%= link_to 'User', users_path, selected?('users') %> &gt; \ No newline at end of file 6<%= link_to 'User', users_path, selected?('users') %> &gt; \ No newline at end of file
diff --git a/app/views/assets/edit.html.erb b/app/views/assets/edit.html.erb
new file mode 100644
index 0000000..d60db94
--- /dev/null
+++ b/app/views/assets/edit.html.erb
@@ -0,0 +1,12 @@
1<h1>Editing asset</h1>
2
3<% form_for(@asset) do |f| %>
4 <%= f.error_messages %>
5
6 <p>
7 <%= f.submit 'Update' %>
8 </p>
9<% end %>
10
11<%= link_to 'Show', @asset %> |
12<%= link_to 'Back', assets_path %> \ No newline at end of file
diff --git a/app/views/assets/index.html.erb b/app/views/assets/index.html.erb
new file mode 100644
index 0000000..c9be684
--- /dev/null
+++ b/app/views/assets/index.html.erb
@@ -0,0 +1,20 @@
1<% content_for :subnavigation do %>
2 <%= link_to 'New asset', new_asset_path %>
3<% end %>
4
5
6<table>
7 <tr>
8 </tr>
9
10<% @assets.each do |asset| %>
11 <tr>
12 <td><%= image_tag asset.upload.url(:thumb) %></td>
13 <td><%= link_to asset.name, asset.upload.url %></td>
14 <td><%= asset.upload.content_type %></td>
15 <td><%= link_to 'Show', asset %></td>
16 <td><%= link_to 'Edit', edit_asset_path(asset) %></td>
17 <td><%= link_to 'Destroy', asset, :confirm => 'Are you sure?', :method => :delete %></td>
18 </tr>
19<% end %>
20</table> \ No newline at end of file
diff --git a/app/views/assets/new.html.erb b/app/views/assets/new.html.erb
new file mode 100644
index 0000000..366488f
--- /dev/null
+++ b/app/views/assets/new.html.erb
@@ -0,0 +1,17 @@
1<h1>New asset</h1>
2
3<% form_for(@asset, :html => { :multipart => true }) do |f| %>
4 <%= f.error_messages %>
5
6 <p>
7 <%= f.label :name %><br />
8 <%= f.text_field :name %>
9 </p>
10
11 <p>
12 <%= f.file_field :upload %>
13 <%= f.submit 'Create' %>
14 </p>
15<% end %>
16
17<%= link_to 'Back', assets_path %> \ No newline at end of file
diff --git a/app/views/assets/show.html.erb b/app/views/assets/show.html.erb
new file mode 100644
index 0000000..dd657d3
--- /dev/null
+++ b/app/views/assets/show.html.erb
@@ -0,0 +1,10 @@
1<% content_for :subnavigation do %>
2 <%= link_to 'Edit', edit_asset_path(@asset) %>
3 <%= link_to 'Back', assets_path %>
4<% end %>
5
6
7
8<%= image_tag @asset.upload.url(:medium) %><br />
9<%= @asset.upload.content_type %><br />
10<%= "#{@asset.upload.size/1024} KB" %><br /> \ No newline at end of file
diff --git a/app/views/layouts/assets.html.erb b/app/views/layouts/assets.html.erb
new file mode 100644
index 0000000..9d629c8
--- /dev/null
+++ b/app/views/layouts/assets.html.erb
@@ -0,0 +1,17 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
4<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
5<head>
6 <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
7 <title>Assets: <%= controller.action_name %></title>
8 <%= stylesheet_link_tag 'scaffold' %>
9</head>
10<body>
11
12<p style="color: green"><%= flash[:notice] %></p>
13
14<%= yield %>
15
16</body>
17</html>
diff --git a/config/routes.rb b/config/routes.rb
index 38f508a..e96ea13 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,6 @@
1ActionController::Routing::Routes.draw do |map| 1ActionController::Routing::Routes.draw do |map|
2 map.resources :assets
3
2 4
3 5
4 map.filter :locale 6 map.filter :locale
diff --git a/db/migrate/20090424085851_create_assets.rb b/db/migrate/20090424085851_create_assets.rb
new file mode 100644
index 0000000..c7f1796
--- /dev/null
+++ b/db/migrate/20090424085851_create_assets.rb
@@ -0,0 +1,16 @@
1class CreateAssets < ActiveRecord::Migration
2 def self.up
3 create_table :assets do |t|
4 t.string :name
5 t.string :upload_file_name
6 t.string :upload_content_type
7 t.integer :upload_file_size
8 t.datetime :upload_updated_at
9 t.timestamps
10 end
11 end
12
13 def self.down
14 drop_table :assets
15 end
16end
diff --git a/test/fixtures/assets.yml b/test/fixtures/assets.yml
new file mode 100644
index 0000000..5bf0293
--- /dev/null
+++ b/test/fixtures/assets.yml
@@ -0,0 +1,7 @@
1# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
3# one:
4# column: value
5#
6# two:
7# column: value
diff --git a/test/functional/assets_controller_test.rb b/test/functional/assets_controller_test.rb
new file mode 100644
index 0000000..7b838cc
--- /dev/null
+++ b/test/functional/assets_controller_test.rb
@@ -0,0 +1,45 @@
1require 'test_helper'
2
3class AssetsControllerTest < ActionController::TestCase
4 test "should get index" do
5 get :index
6 assert_response :success
7 assert_not_nil assigns(:assets)
8 end
9
10 test "should get new" do
11 get :new
12 assert_response :success
13 end
14
15 test "should create asset" do
16 assert_difference('Asset.count') do
17 post :create, :asset => { }
18 end
19
20 assert_redirected_to asset_path(assigns(:asset))
21 end
22
23 test "should show asset" do
24 get :show, :id => assets(:one).to_param
25 assert_response :success
26 end
27
28 test "should get edit" do
29 get :edit, :id => assets(:one).to_param
30 assert_response :success
31 end
32
33 test "should update asset" do
34 put :update, :id => assets(:one).to_param, :asset => { }
35 assert_redirected_to asset_path(assigns(:asset))
36 end
37
38 test "should destroy asset" do
39 assert_difference('Asset.count', -1) do
40 delete :destroy, :id => assets(:one).to_param
41 end
42
43 assert_redirected_to assets_path
44 end
45end
diff --git a/test/unit/asset_test.rb b/test/unit/asset_test.rb
new file mode 100644
index 0000000..58aba76
--- /dev/null
+++ b/test/unit/asset_test.rb
@@ -0,0 +1,8 @@
1require 'test_helper'
2
3class AssetTest < ActiveSupport::TestCase
4 # Replace this with your real tests.
5 test "the truth" do
6 assert true
7 end
8end
diff --git a/test/unit/helpers/assets_helper_test.rb b/test/unit/helpers/assets_helper_test.rb
new file mode 100644
index 0000000..ae50bff
--- /dev/null
+++ b/test/unit/helpers/assets_helper_test.rb
@@ -0,0 +1,4 @@
1require 'test_helper'
2
3class AssetsHelperTest < ActionView::TestCase
4end
diff --git a/vendor/plugins/paperclip/LICENSE b/vendor/plugins/paperclip/LICENSE
new file mode 100644
index 0000000..299b9ed
--- /dev/null
+++ b/vendor/plugins/paperclip/LICENSE
@@ -0,0 +1,26 @@
1
2LICENSE
3
4The MIT License
5
6Copyright (c) 2008 Jon Yurek and thoughtbot, inc.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy
9of this software and associated documentation files (the "Software"), to deal
10in the Software without restriction, including without limitation the rights
11to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12copies of the Software, and to permit persons to whom the Software is
13furnished to do so, subject to the following conditions:
14
15The above copyright notice and this permission notice shall be included in
16all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24THE SOFTWARE.
25
26
diff --git a/vendor/plugins/paperclip/README.rdoc b/vendor/plugins/paperclip/README.rdoc
new file mode 100644
index 0000000..85fcf4b
--- /dev/null
+++ b/vendor/plugins/paperclip/README.rdoc
@@ -0,0 +1,172 @@
1=Paperclip
2
3Paperclip is intended as an easy file attachment library for ActiveRecord. The
4intent behind it was to keep setup as easy as possible and to treat files as
5much like other attributes as possible. This means they aren't saved to their
6final locations on disk, nor are they deleted if set to nil, until
7ActiveRecord::Base#save is called. It manages validations based on size and
8presence, if required. It can transform its assigned image into thumbnails if
9needed, and the prerequisites are as simple as installing ImageMagick (which,
10for most modern Unix-based systems, is as easy as installing the right
11packages). Attached files are saved to the filesystem and referenced in the
12browser by an easily understandable specification, which has sensible and
13useful defaults.
14
15See the documentation for +has_attached_file+ in Paperclip::ClassMethods for
16more detailed options.
17
18==Quick Start
19
20In your model:
21
22 class User < ActiveRecord::Base
23 has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
24 end
25
26In your migrations:
27
28 class AddAvatarColumnsToUser < ActiveRecord::Migration
29 def self.up
30 add_column :users, :avatar_file_name, :string
31 add_column :users, :avatar_content_type, :string
32 add_column :users, :avatar_file_size, :integer
33 add_column :users, :avatar_updated_at, :datetime
34 end
35
36 def self.down
37 remove_column :users, :avatar_file_name
38 remove_column :users, :avatar_content_type
39 remove_column :users, :avatar_file_size
40 remove_column :users, :avatar_updated_at
41 end
42 end
43
44In your edit and new views:
45
46 <% form_for :user, @user, :url => user_path, :html => { :multipart => true } do |form| %>
47 <%= form.file_field :avatar %>
48 <% end %>
49
50In your controller:
51
52 def create
53 @user = User.create( params[:user] )
54 end
55
56In your show view:
57
58 <%= image_tag @user.avatar.url %>
59 <%= image_tag @user.avatar.url(:medium) %>
60 <%= image_tag @user.avatar.url(:thumb) %>
61
62==Usage
63
64The basics of paperclip are quite simple: Declare that your model has an
65attachment with the has_attached_file method, and give it a name. Paperclip
66will wrap up up to four attributes (all prefixed with that attachment's name,
67so you can have multiple attachments per model if you wish) and give the a
68friendly front end. The attributes are <attachment>_file_name,
69<attachment>_file_size, <attachment>_content_type, and <attachment>_updated_at.
70Only <attachment>_file_name is required for paperclip to operate. More
71information about the options to has_attached_file is available in the
72documentation of Paperclip::ClassMethods.
73
74Attachments can be validated with Paperclip's validation methods,
75validates_attachment_presence, validates_attachment_content_type, and
76validates_attachment_size.
77
78==Storage
79
80The files that are assigned as attachments are, by default, placed in the
81directory specified by the :path option to has_attached_file. By default, this
82location is
83":rails_root/public/system/:attachment/:id/:style/:basename.:extension". This
84location was chosen because on standard Capistrano deployments, the
85public/system directory is symlinked to the app's shared directory, meaning it
86will survive between deployments. For example, using that :path, you may have a
87file at
88
89 /data/myapp/releases/20081229172410/public/system/avatars/13/small/my_pic.png
90
91NOTE: This is a change from previous versions of Paperclip, but is overall a
92safer choice for the defaul file store.
93
94You may also choose to store your files using Amazon's S3 service. You can find
95more information about S3 storage at the description for
96Paperclip::Storage::S3.
97
98Files on the local filesystem (and in the Rails app's public directory) will be
99available to the internet at large. If you require access control, it's
100possible to place your files in a different location. You will need to change
101both the :path and :url options in order to make sure the files are unavailable
102to the public. Both :path and :url allow the same set of interpolated
103variables.
104
105==Post Processing
106
107Paperclip supports an extendible selection of post-processors. When you define
108a set of styles for an attachment, by default it is expected that those
109"styles" are actually "thumbnails". However, you can do more than just
110thumbnail images. By defining a subclass of Paperclip::Processor, you can
111perform any processing you want on the files that are attached. Any file in
112your Rails app's lib/paperclip_processors directory is automatically loaded by
113paperclip, allowing you to easily define custom processors. You can specify a
114processor with the :processors option to has_attached_file:
115
116 has_attached_file :scan, :styles => { :text => { :quality => :better } },
117 :processors => [:ocr]
118
119This would load the hypothetical class Paperclip::Ocr, which would have the
120hash "{ :quality => :better }" passed to it along with the uploaded file. For
121more information about defining processors, see Paperclip::Processor.
122
123The default processor is Paperclip::Thumbnail. For backwards compatability
124reasons, you can pass a single geometry string or an array containing a
125geometry and a format, which the file will be converted to, like so:
126
127 has_attached_file :avatar, :styles => { :thumb => ["32x32#", :png] }
128
129This will convert the "thumb" style to a 32x32 square in png format, regardless
130of what was uploaded. If the format is not specified, it is kept the same (i.e.
131jpgs will remain jpgs).
132
133Multiple processors can be specified, and they will be invoked in the order
134they are defined in the :processors array. Each successive processor will
135be given the result of the previous processor's execution. All processors will
136receive the same parameters, which are what you define in the :styles hash.
137For example, assuming we had this definition:
138
139 has_attached_file :scan, :styles => { :text => { :quality => :better } },
140 :processors => [:rotator, :ocr]
141
142then both the :rotator processor and the :ocr processor would receive the
143options "{ :quality => :better }". This parameter may not mean anything to one
144or more or the processors, and they are free to ignore it.
145
146==Events
147
148Before and after the Post Processing step, Paperclip calls back to the model
149with a few callbacks, allowing the model to change or cancel the processing
150step. The callbacks are "before_post_process" and "after_post_process" (which
151are called before and after the processing of each attachment), and the
152attachment-specific "before_<attachment>_post_process" and
153"after_<attachment>_post_process". The callbacks are intended to be as close to
154normal ActiveRecord callbacks as possible, so if you return false (specifically
155- returning nil is not the same) in a before_ filter, the post processing step
156will halt. Returning false in an after_ filter will not halt anything, but you
157can access the model and the attachment if necessary.
158
159NOTE: Post processing will not even *start* if the attachment is not valid
160according to the validations. Your callbacks (and processors) will only be
161called with valid attachments.
162
163==Contributing
164
165If you'd like to contribute a feature or bugfix: Thanks! To make sure your
166fix/feature has a high chance of being included, please read the following
167guidelines:
168
1691. Ask on the mailing list, or post a ticket in Lighthouse.
1702. Make sure there are tests! We will not accept any patch that is not tested.
171 It's a rare time when explicit tests aren't needed. If you have questions
172 about writing tests for paperclip, please ask the mailing list.
diff --git a/vendor/plugins/paperclip/Rakefile b/vendor/plugins/paperclip/Rakefile
new file mode 100644
index 0000000..91f1687
--- /dev/null
+++ b/vendor/plugins/paperclip/Rakefile
@@ -0,0 +1,77 @@
1require 'rake'
2require 'rake/testtask'
3require 'rake/rdoctask'
4
5$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
6require 'paperclip'
7
8desc 'Default: run unit tests.'
9task :default => [:clean, :test]
10
11desc 'Test the paperclip plugin.'
12Rake::TestTask.new(:test) do |t|
13 t.libs << 'lib' << 'profile'
14 t.pattern = 'test/**/*_test.rb'
15 t.verbose = true
16end
17
18desc 'Start an IRB session with all necessary files required.'
19task :shell do |t|
20 chdir File.dirname(__FILE__)
21 exec 'irb -I lib/ -I lib/paperclip -r rubygems -r active_record -r tempfile -r init'
22end
23
24desc 'Generate documentation for the paperclip plugin.'
25Rake::RDocTask.new(:rdoc) do |rdoc|
26 rdoc.rdoc_dir = 'doc'
27 rdoc.title = 'Paperclip'
28 rdoc.options << '--line-numbers' << '--inline-source'
29 rdoc.rdoc_files.include('README*')
30 rdoc.rdoc_files.include('lib/**/*.rb')
31end
32
33desc 'Update documentation on website'
34task :sync_docs => 'rdoc' do
35 `rsync -ave ssh doc/ dev@dev.thoughtbot.com:/home/dev/www/dev.thoughtbot.com/paperclip`
36end
37
38desc 'Clean up files.'
39task :clean do |t|
40 FileUtils.rm_rf "doc"
41 FileUtils.rm_rf "tmp"
42 FileUtils.rm_rf "pkg"
43 FileUtils.rm "test/debug.log" rescue nil
44 FileUtils.rm "test/paperclip.db" rescue nil
45end
46
47spec = Gem::Specification.new do |s|
48 s.name = "paperclip"
49 s.version = Paperclip::VERSION
50 s.author = "Jon Yurek"
51 s.email = "jyurek@thoughtbot.com"
52 s.homepage = "http://www.thoughtbot.com/projects/paperclip"
53 s.platform = Gem::Platform::RUBY
54 s.summary = "File attachments as attributes for ActiveRecord"
55 s.files = FileList["README*",
56 "LICENSE",
57 "Rakefile",
58 "init.rb",
59 "{generators,lib,tasks,test,shoulda_macros}/**/*"].to_a
60 s.require_path = "lib"
61 s.test_files = FileList["test/**/test_*.rb"].to_a
62 s.rubyforge_project = "paperclip"
63 s.has_rdoc = true
64 s.extra_rdoc_files = FileList["README*"].to_a
65 s.rdoc_options << '--line-numbers' << '--inline-source'
66 s.requirements << "ImageMagick"
67 s.add_runtime_dependency 'right_aws'
68 s.add_development_dependency 'thoughtbot-shoulda'
69 s.add_development_dependency 'mocha'
70end
71
72desc "Generate a gemspec file for GitHub"
73task :gemspec do
74 File.open("#{spec.name}.gemspec", 'w') do |f|
75 f.write spec.to_ruby
76 end
77end
diff --git a/vendor/plugins/paperclip/generators/paperclip/USAGE b/vendor/plugins/paperclip/generators/paperclip/USAGE
new file mode 100644
index 0000000..2d611d7
--- /dev/null
+++ b/vendor/plugins/paperclip/generators/paperclip/USAGE
@@ -0,0 +1,5 @@
1Usage:
2
3 script/generate paperclip Class attachment1 (attachment2 ...)
4
5This will create a migration that will add the proper columns to your class's table. \ No newline at end of file
diff --git a/vendor/plugins/paperclip/generators/paperclip/paperclip_generator.rb b/vendor/plugins/paperclip/generators/paperclip/paperclip_generator.rb
new file mode 100644
index 0000000..77ee5a2
--- /dev/null
+++ b/vendor/plugins/paperclip/generators/paperclip/paperclip_generator.rb
@@ -0,0 +1,27 @@
1class PaperclipGenerator < Rails::Generator::NamedBase
2 attr_accessor :attachments, :migration_name
3
4 def initialize(args, options = {})
5 super
6 @class_name, @attachments = args[0], args[1..-1]
7 end
8
9 def manifest
10 file_name = generate_file_name
11 @migration_name = file_name.camelize
12 record do |m|
13 m.migration_template "paperclip_migration.rb.erb",
14 File.join('db', 'migrate'),
15 :migration_file_name => file_name
16 end
17 end
18
19 private
20
21 def generate_file_name
22 names = attachments.map{|a| a.underscore }
23 names = names[0..-2] + ["and", names[-1]] if names.length > 1
24 "add_attachments_#{names.join("_")}_to_#{@class_name.underscore}"
25 end
26
27end
diff --git a/vendor/plugins/paperclip/generators/paperclip/templates/paperclip_migration.rb.erb b/vendor/plugins/paperclip/generators/paperclip/templates/paperclip_migration.rb.erb
new file mode 100644
index 0000000..eebb0e5
--- /dev/null
+++ b/vendor/plugins/paperclip/generators/paperclip/templates/paperclip_migration.rb.erb
@@ -0,0 +1,19 @@
1class <%= migration_name %> < ActiveRecord::Migration
2 def self.up
3<% attachments.each do |attachment| -%>
4 add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name, :string
5 add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_content_type, :string
6 add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_size, :integer
7 add_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at, :datetime
8<% end -%>
9 end
10
11 def self.down
12<% attachments.each do |attachment| -%>
13 remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_name
14 remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_content_type
15 remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_file_size
16 remove_column :<%= class_name.underscore.camelize.tableize %>, :<%= attachment %>_updated_at
17<% end -%>
18 end
19end
diff --git a/vendor/plugins/paperclip/init.rb b/vendor/plugins/paperclip/init.rb
new file mode 100644
index 0000000..5a07dda
--- /dev/null
+++ b/vendor/plugins/paperclip/init.rb
@@ -0,0 +1 @@
require File.join(File.dirname(__FILE__), "lib", "paperclip")
diff --git a/vendor/plugins/paperclip/lib/paperclip.rb b/vendor/plugins/paperclip/lib/paperclip.rb
new file mode 100644
index 0000000..74c3d79
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip.rb
@@ -0,0 +1,318 @@
1# Paperclip allows file attachments that are stored in the filesystem. All graphical
2# transformations are done using the Graphics/ImageMagick command line utilities and
3# are stored in Tempfiles until the record is saved. Paperclip does not require a
4# separate model for storing the attachment's information, instead adding a few simple
5# columns to your table.
6#
7# Author:: Jon Yurek
8# Copyright:: Copyright (c) 2008 thoughtbot, inc.
9# License:: MIT License (http://www.opensource.org/licenses/mit-license.php)
10#
11# Paperclip defines an attachment as any file, though it makes special considerations
12# for image files. You can declare that a model has an attached file with the
13# +has_attached_file+ method:
14#
15# class User < ActiveRecord::Base
16# has_attached_file :avatar, :styles => { :thumb => "100x100" }
17# end
18#
19# user = User.new
20# user.avatar = params[:user][:avatar]
21# user.avatar.url
22# # => "/users/avatars/4/original_me.jpg"
23# user.avatar.url(:thumb)
24# # => "/users/avatars/4/thumb_me.jpg"
25#
26# See the +has_attached_file+ documentation for more details.
27
28require 'tempfile'
29require 'paperclip/upfile'
30require 'paperclip/iostream'
31require 'paperclip/geometry'
32require 'paperclip/processor'
33require 'paperclip/thumbnail'
34require 'paperclip/storage'
35require 'paperclip/attachment'
36if defined? RAILS_ROOT
37 Dir.glob(File.join(File.expand_path(RAILS_ROOT), "lib", "paperclip_processors", "*.rb")).each do |processor|
38 require processor
39 end
40end
41
42# The base module that gets included in ActiveRecord::Base. See the
43# documentation for Paperclip::ClassMethods for more useful information.
44module Paperclip
45
46 VERSION = "2.2.8"
47
48 class << self
49 # Provides configurability to Paperclip. There are a number of options available, such as:
50 # * whiny_thumbnails: Will raise an error if Paperclip cannot process thumbnails of
51 # an uploaded image. Defaults to true.
52 # * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors
53 # log levels, etc. Defaults to true.
54 # * command_path: Defines the path at which to find the command line
55 # programs if they are not visible to Rails the system's search path. Defaults to
56 # nil, which uses the first executable found in the user's search path.
57 # * image_magick_path: Deprecated alias of command_path.
58 def options
59 @options ||= {
60 :whiny_thumbnails => true,
61 :image_magick_path => nil,
62 :command_path => nil,
63 :log => true,
64 :swallow_stderr => true
65 }
66 end
67
68 def path_for_command command #:nodoc:
69 if options[:image_magick_path]
70 warn("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead")
71 end
72 path = [options[:command_path] || options[:image_magick_path], command].compact
73 File.join(*path)
74 end
75
76 def interpolates key, &block
77 Paperclip::Attachment.interpolations[key] = block
78 end
79
80 # The run method takes a command to execute and a string of parameters
81 # that get passed to it. The command is prefixed with the :command_path
82 # option from Paperclip.options. If you have many commands to run and
83 # they are in different paths, the suggested course of action is to
84 # symlink them so they are all in the same directory.
85 #
86 # If the command returns with a result code that is not one of the
87 # expected_outcodes, a PaperclipCommandLineError will be raised. Generally
88 # a code of 0 is expected, but a list of codes may be passed if necessary.
89 def run cmd, params = "", expected_outcodes = 0
90 command = %Q<#{%Q[#{path_for_command(cmd)} #{params}].gsub(/\s+/, " ")}>
91 command = "#{command} 2>#{bit_bucket}" if Paperclip.options[:swallow_stderr]
92 output = `#{command}`
93 unless [expected_outcodes].flatten.include?($?.exitstatus)
94 raise PaperclipCommandLineError, "Error while running #{cmd}"
95 end
96 output
97 end
98
99 def bit_bucket #:nodoc:
100 File.exists?("/dev/null") ? "/dev/null" : "NUL"
101 end
102
103 def included base #:nodoc:
104 base.extend ClassMethods
105 unless base.respond_to?(:define_callbacks)
106 base.send(:include, Paperclip::CallbackCompatability)
107 end
108 end
109
110 def processor name #:nodoc:
111 name = name.to_s.camelize
112 processor = Paperclip.const_get(name)
113 unless processor.ancestors.include?(Paperclip::Processor)
114 raise PaperclipError.new("Processor #{name} was not found")
115 end
116 processor
117 end
118 end
119
120 class PaperclipError < StandardError #:nodoc:
121 end
122
123 class PaperclipCommandLineError < StandardError #:nodoc:
124 end
125
126 class NotIdentifiedByImageMagickError < PaperclipError #:nodoc:
127 end
128
129 module ClassMethods
130 # +has_attached_file+ gives the class it is called on an attribute that maps to a file. This
131 # is typically a file stored somewhere on the filesystem and has been uploaded by a user.
132 # The attribute returns a Paperclip::Attachment object which handles the management of
133 # that file. The intent is to make the attachment as much like a normal attribute. The
134 # thumbnails will be created when the new file is assigned, but they will *not* be saved
135 # until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is
136 # called on it, the attachment will *not* be deleted until +save+ is called. See the
137 # Paperclip::Attachment documentation for more specifics. There are a number of options
138 # you can set to change the behavior of a Paperclip attachment:
139 # * +url+: The full URL of where the attachment is publically accessible. This can just
140 # as easily point to a directory served directly through Apache as it can to an action
141 # that can control permissions. You can specify the full domain and path, but usually
142 # just an absolute path is sufficient. The leading slash *must* be included manually for
143 # absolute paths. The default value is
144 # "/system/:attachment/:id/:style/:basename.:extension". See
145 # Paperclip::Attachment#interpolate for more information on variable interpolaton.
146 # :url => "/:class/:attachment/:id/:style_:basename.:extension"
147 # :url => "http://some.other.host/stuff/:class/:id_:extension"
148 # * +default_url+: The URL that will be returned if there is no attachment assigned.
149 # This field is interpolated just as the url is. The default value is
150 # "/:attachment/:style/missing.png"
151 # has_attached_file :avatar, :default_url => "/images/default_:style_avatar.png"
152 # User.new.avatar_url(:small) # => "/images/default_small_avatar.png"
153 # * +styles+: A hash of thumbnail styles and their geometries. You can find more about
154 # geometry strings at the ImageMagick website
155 # (http://www.imagemagick.org/script/command-line-options.php#resize). Paperclip
156 # also adds the "#" option (e.g. "50x50#"), which will resize the image to fit maximally
157 # inside the dimensions and then crop the rest off (weighted at the center). The
158 # default value is to generate no thumbnails.
159 # * +default_style+: The thumbnail style that will be used by default URLs.
160 # Defaults to +original+.
161 # has_attached_file :avatar, :styles => { :normal => "100x100#" },
162 # :default_style => :normal
163 # user.avatar.url # => "/avatars/23/normal_me.png"
164 # * +whiny_thumbnails+: Will raise an error if Paperclip cannot post_process an uploaded file due
165 # to a command line error. This will override the global setting for this attachment.
166 # Defaults to true.
167 # * +convert_options+: When creating thumbnails, use this free-form options
168 # field to pass in various convert command options. Typical options are "-strip" to
169 # remove all Exif data from the image (save space for thumbnails and avatars) or
170 # "-depth 8" to specify the bit depth of the resulting conversion. See ImageMagick
171 # convert documentation for more options: (http://www.imagemagick.org/script/convert.php)
172 # Note that this option takes a hash of options, each of which correspond to the style
173 # of thumbnail being generated. You can also specify :all as a key, which will apply
174 # to all of the thumbnails being generated. If you specify options for the :original,
175 # it would be best if you did not specify destructive options, as the intent of keeping
176 # the original around is to regenerate all the thumbnails when requirements change.
177 # has_attached_file :avatar, :styles => { :large => "300x300", :negative => "100x100" }
178 # :convert_options => {
179 # :all => "-strip",
180 # :negative => "-negate"
181 # }
182 # * +storage+: Chooses the storage backend where the files will be stored. The current
183 # choices are :filesystem and :s3. The default is :filesystem. Make sure you read the
184 # documentation for Paperclip::Storage::Filesystem and Paperclip::Storage::S3
185 # for backend-specific options.
186 def has_attached_file name, options = {}
187 include InstanceMethods
188
189 write_inheritable_attribute(:attachment_definitions, {}) if attachment_definitions.nil?
190 attachment_definitions[name] = {:validations => {}}.merge(options)
191
192 after_save :save_attached_files
193 before_destroy :destroy_attached_files
194
195 define_callbacks :before_post_process, :after_post_process
196 define_callbacks :"before_#{name}_post_process", :"after_#{name}_post_process"
197
198 define_method name do |*args|
199 a = attachment_for(name)
200 (args.length > 0) ? a.to_s(args.first) : a
201 end
202
203 define_method "#{name}=" do |file|
204 attachment_for(name).assign(file)
205 end
206
207 define_method "#{name}?" do
208 attachment_for(name).file?
209 end
210
211 validates_each(name) do |record, attr, value|
212 attachment = record.attachment_for(name)
213 attachment.send(:flush_errors) unless attachment.valid?
214 end
215 end
216
217 # Places ActiveRecord-style validations on the size of the file assigned. The
218 # possible options are:
219 # * +in+: a Range of bytes (i.e. +1..1.megabyte+),
220 # * +less_than+: equivalent to :in => 0..options[:less_than]
221 # * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
222 # * +message+: error message to display, use :min and :max as replacements
223 def validates_attachment_size name, options = {}
224 min = options[:greater_than] || (options[:in] && options[:in].first) || 0
225 max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
226 range = (min..max)
227 message = options[:message] || "file size must be between :min and :max bytes."
228
229 attachment_definitions[name][:validations][:size] = lambda do |attachment, instance|
230 if attachment.file? && !range.include?(attachment.size.to_i)
231 message.gsub(/:min/, min.to_s).gsub(/:max/, max.to_s)
232 end
233 end
234 end
235
236 # Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true.
237 def validates_attachment_thumbnails name, options = {}
238 attachment_definitions[name][:whiny_thumbnails] = true
239 end
240
241 # Places ActiveRecord-style validations on the presence of a file.
242 def validates_attachment_presence name, options = {}
243 message = options[:message] || "must be set."
244 attachment_definitions[name][:validations][:presence] = lambda do |attachment, instance|
245 message unless attachment.file?
246 end
247 end
248
249 # Places ActiveRecord-style validations on the content type of the file
250 # assigned. The possible options are:
251 # * +content_type+: Allowed content types. Can be a single content type
252 # or an array. Each type can be a String or a Regexp. It should be
253 # noted that Internet Explorer upload files with content_types that you
254 # may not expect. For example, JPEG images are given image/pjpeg and
255 # PNGs are image/x-png, so keep that in mind when determining how you
256 # match. Allows all by default.
257 # * +message+: The message to display when the uploaded file has an invalid
258 # content type.
259 # NOTE: If you do not specify an [attachment]_content_type field on your
260 # model, content_type validation will work _ONLY upon assignment_ and
261 # re-validation after the instance has been reloaded will always succeed.
262 def validates_attachment_content_type name, options = {}
263 attachment_definitions[name][:validations][:content_type] = lambda do |attachment, instance|
264 valid_types = [options[:content_type]].flatten
265
266 unless attachment.original_filename.blank?
267 unless valid_types.blank?
268 content_type = attachment.instance_read(:content_type)
269 unless valid_types.any?{|t| content_type.nil? || t === content_type }
270 options[:message] || "is not one of the allowed file types."
271 end
272 end
273 end
274 end
275 end
276
277 # Returns the attachment definitions defined by each call to
278 # has_attached_file.
279 def attachment_definitions
280 read_inheritable_attribute(:attachment_definitions)
281 end
282 end
283
284 module InstanceMethods #:nodoc:
285 def attachment_for name
286 @_paperclip_attachments ||= {}
287 @_paperclip_attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name])
288 end
289
290 def each_attachment
291 self.class.attachment_definitions.each do |name, definition|
292 yield(name, attachment_for(name))
293 end
294 end
295
296 def save_attached_files
297 logger.info("[paperclip] Saving attachments.")
298 each_attachment do |name, attachment|
299 attachment.send(:save)
300 end
301 end
302
303 def destroy_attached_files
304 logger.info("[paperclip] Deleting attachments.")
305 each_attachment do |name, attachment|
306 attachment.send(:queue_existing_for_delete)
307 attachment.send(:flush_deletes)
308 end
309 end
310 end
311
312end
313
314# Set it all up.
315if Object.const_defined?("ActiveRecord")
316 ActiveRecord::Base.send(:include, Paperclip)
317 File.send(:include, Paperclip::Upfile)
318end
diff --git a/vendor/plugins/paperclip/lib/paperclip/attachment.rb b/vendor/plugins/paperclip/lib/paperclip/attachment.rb
new file mode 100644
index 0000000..897c67e
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/attachment.rb
@@ -0,0 +1,403 @@
1module Paperclip
2 # The Attachment class manages the files for a given attachment. It saves
3 # when the model saves, deletes when the model is destroyed, and processes
4 # the file upon assignment.
5 class Attachment
6
7 def self.default_options
8 @default_options ||= {
9 :url => "/system/:attachment/:id/:style/:basename.:extension",
10 :path => ":rails_root/public/system/:attachment/:id/:style/:basename.:extension",
11 :styles => {},
12 :default_url => "/:attachment/:style/missing.png",
13 :default_style => :original,
14 :validations => {},
15 :storage => :filesystem
16 }
17 end
18
19 attr_reader :name, :instance, :styles, :default_style, :convert_options, :queued_for_write
20
21 # Creates an Attachment object. +name+ is the name of the attachment,
22 # +instance+ is the ActiveRecord object instance it's attached to, and
23 # +options+ is the same as the hash passed to +has_attached_file+.
24 def initialize name, instance, options = {}
25 @name = name
26 @instance = instance
27
28 options = self.class.default_options.merge(options)
29
30 @url = options[:url]
31 @url = @url.call(self) if @url.is_a?(Proc)
32 @path = options[:path]
33 @path = @path.call(self) if @path.is_a?(Proc)
34 @styles = options[:styles]
35 @styles = @styles.call(self) if @styles.is_a?(Proc)
36 @default_url = options[:default_url]
37 @validations = options[:validations]
38 @default_style = options[:default_style]
39 @storage = options[:storage]
40 @whiny = options[:whiny_thumbnails]
41 @convert_options = options[:convert_options] || {}
42 @processors = options[:processors] || [:thumbnail]
43 @options = options
44 @queued_for_delete = []
45 @queued_for_write = {}
46 @errors = {}
47 @validation_errors = nil
48 @dirty = false
49
50 normalize_style_definition
51 initialize_storage
52 end
53
54 # What gets called when you call instance.attachment = File. It clears
55 # errors, assigns attributes, processes the file, and runs validations. It
56 # also queues up the previous file for deletion, to be flushed away on
57 # #save of its host. In addition to form uploads, you can also assign
58 # another Paperclip attachment:
59 # new_user.avatar = old_user.avatar
60 # If the file that is assigned is not valid, the processing (i.e.
61 # thumbnailing, etc) will NOT be run.
62 def assign uploaded_file
63 %w(file_name).each do |field|
64 unless @instance.class.column_names.include?("#{name}_#{field}")
65 raise PaperclipError.new("#{@instance.class} model does not have required column '#{name}_#{field}'")
66 end
67 end
68
69 if uploaded_file.is_a?(Paperclip::Attachment)
70 uploaded_file = uploaded_file.to_file(:original)
71 close_uploaded_file = uploaded_file.respond_to?(:close)
72 end
73
74 return nil unless valid_assignment?(uploaded_file)
75
76 uploaded_file.binmode if uploaded_file.respond_to? :binmode
77 self.clear
78
79 return nil if uploaded_file.nil?
80
81 @queued_for_write[:original] = uploaded_file.to_tempfile
82 instance_write(:file_name, uploaded_file.original_filename.strip.gsub(/[^\w\d\.\-]+/, '_'))
83 instance_write(:content_type, uploaded_file.content_type.to_s.strip)
84 instance_write(:file_size, uploaded_file.size.to_i)
85 instance_write(:updated_at, Time.now)
86
87 @dirty = true
88
89 post_process if valid?
90
91 # Reset the file size if the original file was reprocessed.
92 instance_write(:file_size, @queued_for_write[:original].size.to_i)
93 ensure
94 uploaded_file.close if close_uploaded_file
95 validate
96 end
97
98 # Returns the public URL of the attachment, with a given style. Note that
99 # this does not necessarily need to point to a file that your web server
100 # can access and can point to an action in your app, if you need fine
101 # grained security. This is not recommended if you don't need the
102 # security, however, for performance reasons. set
103 # include_updated_timestamp to false if you want to stop the attachment
104 # update time appended to the url
105 def url style = default_style, include_updated_timestamp = true
106 url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
107 include_updated_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
108 end
109
110 # Returns the path of the attachment as defined by the :path option. If the
111 # file is stored in the filesystem the path refers to the path of the file
112 # on disk. If the file is stored in S3, the path is the "key" part of the
113 # URL, and the :bucket option refers to the S3 bucket.
114 def path style = nil #:nodoc:
115 original_filename.nil? ? nil : interpolate(@path, style)
116 end
117
118 # Alias to +url+
119 def to_s style = nil
120 url(style)
121 end
122
123 # Returns true if there are no errors on this attachment.
124 def valid?
125 validate
126 errors.empty?
127 end
128
129 # Returns an array containing the errors on this attachment.
130 def errors
131 @errors
132 end
133
134 # Returns true if there are changes that need to be saved.
135 def dirty?
136 @dirty
137 end
138
139 # Saves the file, if there are no errors. If there are, it flushes them to
140 # the instance's errors and returns false, cancelling the save.
141 def save
142 if valid?
143 flush_deletes
144 flush_writes
145 @dirty = false
146 true
147 else
148 flush_errors
149 false
150 end
151 end
152
153 # Clears out the attachment. Has the same effect as previously assigning
154 # nil to the attachment. Does NOT save. If you wish to clear AND save,
155 # use #destroy.
156 def clear
157 queue_existing_for_delete
158 @errors = {}
159 @validation_errors = nil
160 end
161
162 # Destroys the attachment. Has the same effect as previously assigning
163 # nil to the attachment *and saving*. This is permanent. If you wish to
164 # wipe out the existing attachment but not save, use #clear.
165 def destroy
166 clear
167 save
168 end
169
170 # Returns the name of the file as originally assigned, and lives in the
171 # <attachment>_file_name attribute of the model.
172 def original_filename
173 instance_read(:file_name)
174 end
175
176 # Returns the size of the file as originally assigned, and lives in the
177 # <attachment>_file_size attribute of the model.
178 def size
179 instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
180 end
181
182 # Returns the content_type of the file as originally assigned, and lives
183 # in the <attachment>_content_type attribute of the model.
184 def content_type
185 instance_read(:content_type)
186 end
187
188 # Returns the last modified time of the file as originally assigned, and
189 # lives in the <attachment>_updated_at attribute of the model.
190 def updated_at
191 time = instance_read(:updated_at)
192 time && time.to_i
193 end
194
195 # A hash of procs that are run during the interpolation of a path or url.
196 # A variable of the format :name will be replaced with the return value of
197 # the proc named ":name". Each lambda takes the attachment and the current
198 # style as arguments. This hash can be added to with your own proc if
199 # necessary.
200 def self.interpolations
201 @interpolations ||= {
202 :rails_root => lambda{|attachment,style| RAILS_ROOT },
203 :rails_env => lambda{|attachment,style| RAILS_ENV },
204 :class => lambda do |attachment,style|
205 attachment.instance.class.name.underscore.pluralize
206 end,
207 :basename => lambda do |attachment,style|
208 attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
209 end,
210 :extension => lambda do |attachment,style|
211 ((style = attachment.styles[style]) && style[:format]) ||
212 File.extname(attachment.original_filename).gsub(/^\.+/, "")
213 end,
214 :id => lambda{|attachment,style| attachment.instance.id },
215 :id_partition => lambda do |attachment, style|
216 ("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
217 end,
218 :attachment => lambda{|attachment,style| attachment.name.to_s.downcase.pluralize },
219 :style => lambda{|attachment,style| style || attachment.default_style },
220 }
221 end
222
223 # This method really shouldn't be called that often. It's expected use is
224 # in the paperclip:refresh rake task and that's it. It will regenerate all
225 # thumbnails forcefully, by reobtaining the original file and going through
226 # the post-process again.
227 def reprocess!
228 new_original = Tempfile.new("paperclip-reprocess")
229 new_original.binmode
230 if old_original = to_file(:original)
231 new_original.write( old_original.read )
232 new_original.rewind
233
234 @queued_for_write = { :original => new_original }
235 post_process
236
237 old_original.close if old_original.respond_to?(:close)
238
239 save
240 else
241 true
242 end
243 end
244
245 # Returns true if a file has been assigned.
246 def file?
247 !original_filename.blank?
248 end
249
250 # Writes the attachment-specific attribute on the instance. For example,
251 # instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
252 # "avatar_file_name" field (assuming the attachment is called avatar).
253 def instance_write(attr, value)
254 setter = :"#{name}_#{attr}="
255 responds = instance.respond_to?(setter)
256 self.instance_variable_set("@_#{setter.to_s.chop}", value)
257 instance.send(setter, value) if responds || attr.to_s == "file_name"
258 end
259
260 # Reads the attachment-specific attribute on the instance. See instance_write
261 # for more details.
262 def instance_read(attr)
263 getter = :"#{name}_#{attr}"
264 responds = instance.respond_to?(getter)
265 cached = self.instance_variable_get("@_#{getter}")
266 return cached if cached
267 instance.send(getter) if responds || attr.to_s == "file_name"
268 end
269
270 private
271
272 def logger #:nodoc:
273 instance.logger
274 end
275
276 def log message #:nodoc:
277 logger.info("[paperclip] #{message}") if logging?
278 end
279
280 def logging? #:nodoc:
281 Paperclip.options[:log]
282 end
283
284 def valid_assignment? file #:nodoc:
285 file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
286 end
287
288 def validate #:nodoc:
289 unless @validation_errors
290 @validation_errors = @validations.inject({}) do |errors, validation|
291 name, block = validation
292 errors[name] = block.call(self, instance) if block
293 errors
294 end
295 @validation_errors.reject!{|k,v| v == nil }
296 @errors.merge!(@validation_errors)
297 end
298 @validation_errors
299 end
300
301 def normalize_style_definition #:nodoc:
302 @styles.each do |name, args|
303 unless args.is_a? Hash
304 dimensions, format = *args
305 @styles[name] = {
306 :processors => @processors,
307 :geometry => dimensions,
308 :format => format,
309 :whiny => @whiny,
310 :convert_options => extra_options_for(name)
311 }
312 else
313 @styles[name] = {
314 :processors => @processors,
315 :whiny => @whiny,
316 :convert_options => extra_options_for(name)
317 }.merge(@styles[name])
318 end
319 end
320 end
321
322 def solidify_style_definitions #:nodoc:
323 @styles.each do |name, args|
324 @styles[name][:geometry] = @styles[name][:geometry].call(instance) if @styles[name][:geometry].respond_to?(:call)
325 @styles[name][:processors] = @styles[name][:processors].call(instance) if @styles[name][:processors].respond_to?(:call)
326 end
327 end
328
329 def initialize_storage #:nodoc:
330 @storage_module = Paperclip::Storage.const_get(@storage.to_s.capitalize)
331 self.extend(@storage_module)
332 end
333
334 def extra_options_for(style) #:nodoc:
335 all_options = convert_options[:all]
336 all_options = all_options.call(instance) if all_options.respond_to?(:call)
337 style_options = convert_options[style]
338 style_options = style_options.call(instance) if style_options.respond_to?(:call)
339
340 [ style_options, all_options ].compact.join(" ")
341 end
342
343 def post_process #:nodoc:
344 return if @queued_for_write[:original].nil?
345 solidify_style_definitions
346 return if fire_events(:before)
347 post_process_styles
348 return if fire_events(:after)
349 end
350
351 def fire_events(which)
352 return true if callback(:"#{which}_post_process") == false
353 return true if callback(:"#{which}_#{name}_post_process") == false
354 end
355
356 def callback which #:nodoc:
357 instance.run_callbacks(which, @queued_for_write){|result, obj| result == false }
358 end
359
360 def post_process_styles
361 @styles.each do |name, args|
362 begin
363 raise RuntimeError.new("Style #{name} has no processors defined.") if args[:processors].blank?
364 @queued_for_write[name] = args[:processors].inject(@queued_for_write[:original]) do |file, processor|
365 Paperclip.processor(processor).make(file, args, self)
366 end
367 rescue PaperclipError => e
368 log("An error was received while processing: #{e.inspect}")
369 (@errors[:processing] ||= []) << e.message if @whiny
370 end
371 end
372 end
373
374 def interpolate pattern, style = default_style #:nodoc:
375 interpolations = self.class.interpolations.sort{|a,b| a.first.to_s <=> b.first.to_s }
376 interpolations.reverse.inject( pattern.dup ) do |result, interpolation|
377 tag, blk = interpolation
378 result.gsub(/:#{tag}/) do |match|
379 blk.call( self, style )
380 end
381 end
382 end
383
384 def queue_existing_for_delete #:nodoc:
385 return unless file?
386 @queued_for_delete += [:original, *@styles.keys].uniq.map do |style|
387 path(style) if exists?(style)
388 end.compact
389 instance_write(:file_name, nil)
390 instance_write(:content_type, nil)
391 instance_write(:file_size, nil)
392 instance_write(:updated_at, nil)
393 end
394
395 def flush_errors #:nodoc:
396 @errors.each do |error, message|
397 [message].flatten.each {|m| instance.errors.add(name, m) }
398 end
399 end
400
401 end
402end
403
diff --git a/vendor/plugins/paperclip/lib/paperclip/callback_compatability.rb b/vendor/plugins/paperclip/lib/paperclip/callback_compatability.rb
new file mode 100644
index 0000000..10b08fc
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/callback_compatability.rb
@@ -0,0 +1,33 @@
1module Paperclip
2 # This module is intended as a compatability shim for the differences in
3 # callbacks between Rails 2.0 and Rails 2.1.
4 module CallbackCompatability
5 def self.included(base)
6 base.extend(ClassMethods)
7 base.send(:include, InstanceMethods)
8 end
9
10 module ClassMethods
11 # The implementation of this method is taken from the Rails 1.2.6 source,
12 # from rails/activerecord/lib/active_record/callbacks.rb, line 192.
13 def define_callbacks(*args)
14 args.each do |method|
15 self.class_eval <<-"end_eval"
16 def self.#{method}(*callbacks, &block)
17 callbacks << block if block_given?
18 write_inheritable_array(#{method.to_sym.inspect}, callbacks)
19 end
20 end_eval
21 end
22 end
23 end
24
25 module InstanceMethods
26 # The callbacks in < 2.1 don't worry about the extra options or the
27 # block, so just run what we have available.
28 def run_callbacks(meth, opts = nil, &blk)
29 callback(meth)
30 end
31 end
32 end
33end
diff --git a/vendor/plugins/paperclip/lib/paperclip/geometry.rb b/vendor/plugins/paperclip/lib/paperclip/geometry.rb
new file mode 100644
index 0000000..7fbe038
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/geometry.rb
@@ -0,0 +1,115 @@
1module Paperclip
2
3 # Defines the geometry of an image.
4 class Geometry
5 attr_accessor :height, :width, :modifier
6
7 # Gives a Geometry representing the given height and width
8 def initialize width = nil, height = nil, modifier = nil
9 @height = height.to_f
10 @width = width.to_f
11 @modifier = modifier
12 end
13
14 # Uses ImageMagick to determing the dimensions of a file, passed in as either a
15 # File or path.
16 def self.from_file file
17 file = file.path if file.respond_to? "path"
18 geometry = begin
19 Paperclip.run("identify", %Q[-format "%wx%h" "#{file}"[0]])
20 rescue PaperclipCommandLineError
21 ""
22 end
23 parse(geometry) ||
24 raise(NotIdentifiedByImageMagickError.new("#{file} is not recognized by the 'identify' command."))
25 end
26
27 # Parses a "WxH" formatted string, where W is the width and H is the height.
28 def self.parse string
29 if match = (string && string.match(/\b(\d*)x?(\d*)\b([\>\<\#\@\%^!])?/))
30 Geometry.new(*match[1,3])
31 end
32 end
33
34 # True if the dimensions represent a square
35 def square?
36 height == width
37 end
38
39 # True if the dimensions represent a horizontal rectangle
40 def horizontal?
41 height < width
42 end
43
44 # True if the dimensions represent a vertical rectangle
45 def vertical?
46 height > width
47 end
48
49 # The aspect ratio of the dimensions.
50 def aspect
51 width / height
52 end
53
54 # Returns the larger of the two dimensions
55 def larger
56 [height, width].max
57 end
58
59 # Returns the smaller of the two dimensions
60 def smaller
61 [height, width].min
62 end
63
64 # Returns the width and height in a format suitable to be passed to Geometry.parse
65 def to_s
66 s = ""
67 s << width.to_i.to_s if width > 0
68 s << "x#{height.to_i}" if height > 0
69 s << modifier.to_s
70 s
71 end
72
73 # Same as to_s
74 def inspect
75 to_s
76 end
77
78 # Returns the scaling and cropping geometries (in string-based ImageMagick format)
79 # neccessary to transform this Geometry into the Geometry given. If crop is true,
80 # then it is assumed the destination Geometry will be the exact final resolution.
81 # In this case, the source Geometry is scaled so that an image containing the
82 # destination Geometry would be completely filled by the source image, and any
83 # overhanging image would be cropped. Useful for square thumbnail images. The cropping
84 # is weighted at the center of the Geometry.
85 def transformation_to dst, crop = false
86 if crop
87 ratio = Geometry.new( dst.width / self.width, dst.height / self.height )
88 scale_geometry, scale = scaling(dst, ratio)
89 crop_geometry = cropping(dst, ratio, scale)
90 else
91 scale_geometry = dst.to_s
92 end
93
94 [ scale_geometry, crop_geometry ]
95 end
96
97 private
98
99 def scaling dst, ratio
100 if ratio.horizontal? || ratio.square?
101 [ "%dx" % dst.width, ratio.width ]
102 else
103 [ "x%d" % dst.height, ratio.height ]
104 end
105 end
106
107 def cropping dst, ratio, scale
108 if ratio.horizontal? || ratio.square?
109 "%dx%d+%d+%d" % [ dst.width, dst.height, 0, (self.height * scale - dst.height) / 2 ]
110 else
111 "%dx%d+%d+%d" % [ dst.width, dst.height, (self.width * scale - dst.width) / 2, 0 ]
112 end
113 end
114 end
115end
diff --git a/vendor/plugins/paperclip/lib/paperclip/iostream.rb b/vendor/plugins/paperclip/lib/paperclip/iostream.rb
new file mode 100644
index 0000000..74e2014
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/iostream.rb
@@ -0,0 +1,58 @@
1# Provides method that can be included on File-type objects (IO, StringIO, Tempfile, etc) to allow stream copying
2# and Tempfile conversion.
3module IOStream
4
5 # Returns a Tempfile containing the contents of the readable object.
6 def to_tempfile
7 tempfile = Tempfile.new("stream")
8 tempfile.binmode
9 self.stream_to(tempfile)
10 end
11
12 # Copies one read-able object from one place to another in blocks, obviating the need to load
13 # the whole thing into memory. Defaults to 8k blocks. If this module is included in both
14 # StringIO and Tempfile, then either can have its data copied anywhere else without typing
15 # worries or memory overhead worries. Returns a File if a String is passed in as the destination
16 # and returns the IO or Tempfile as passed in if one is sent as the destination.
17 def stream_to path_or_file, in_blocks_of = 8192
18 dstio = case path_or_file
19 when String then File.new(path_or_file, "wb+")
20 when IO then path_or_file
21 when Tempfile then path_or_file
22 end
23 buffer = ""
24 self.rewind
25 while self.read(in_blocks_of, buffer) do
26 dstio.write(buffer)
27 end
28 dstio.rewind
29 dstio
30 end
31end
32
33class IO #:nodoc:
34 include IOStream
35end
36
37%w( Tempfile StringIO ).each do |klass|
38 if Object.const_defined? klass
39 Object.const_get(klass).class_eval do
40 include IOStream
41 end
42 end
43end
44
45# Corrects a bug in Windows when asking for Tempfile size.
46if defined? Tempfile
47 class Tempfile
48 def size
49 if @tmpfile
50 @tmpfile.fsync
51 @tmpfile.flush
52 @tmpfile.stat.size
53 else
54 0
55 end
56 end
57 end
58end
diff --git a/vendor/plugins/paperclip/lib/paperclip/matchers.rb b/vendor/plugins/paperclip/lib/paperclip/matchers.rb
new file mode 100644
index 0000000..ca24b5e
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/matchers.rb
@@ -0,0 +1,4 @@
1require 'paperclip/matchers/have_attached_file_matcher'
2require 'paperclip/matchers/validate_attachment_presence_matcher'
3require 'paperclip/matchers/validate_attachment_content_type_matcher'
4require 'paperclip/matchers/validate_attachment_size_matcher'
diff --git a/vendor/plugins/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb b/vendor/plugins/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb
new file mode 100644
index 0000000..da0dd8b
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb
@@ -0,0 +1,49 @@
1module Paperclip
2 module Shoulda
3 module Matchers
4 def have_attached_file name
5 HaveAttachedFileMatcher.new(name)
6 end
7
8 class HaveAttachedFileMatcher
9 def initialize attachment_name
10 @attachment_name = attachment_name
11 end
12
13 def matches? subject
14 @subject = subject
15 responds? && has_column? && included?
16 end
17
18 def failure_message
19 "Should have an attachment named #{@attachment_name}"
20 end
21
22 def negative_failure_message
23 "Should not have an attachment named #{@attachment_name}"
24 end
25
26 def description
27 "have an attachment named #{@attachment_name}"
28 end
29
30 protected
31
32 def responds?
33 methods = @subject.instance_methods
34 methods.include?("#{@attachment_name}") &&
35 methods.include?("#{@attachment_name}=") &&
36 methods.include?("#{@attachment_name}?")
37 end
38
39 def has_column?
40 @subject.column_names.include?("#{@attachment_name}_file_name")
41 end
42
43 def included?
44 @subject.ancestors.include?(Paperclip::InstanceMethods)
45 end
46 end
47 end
48 end
49end
diff --git a/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb
new file mode 100644
index 0000000..b4e97fd
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb
@@ -0,0 +1,66 @@
1module Paperclip
2 module Shoulda
3 module Matchers
4 def validate_attachment_content_type name
5 ValidateAttachmentContentTypeMatcher.new(name)
6 end
7
8 class ValidateAttachmentContentTypeMatcher
9 def initialize attachment_name
10 @attachment_name = attachment_name
11 end
12
13 def allowing *types
14 @allowed_types = types.flatten
15 self
16 end
17
18 def rejecting *types
19 @rejected_types = types.flatten
20 self
21 end
22
23 def matches? subject
24 @subject = subject
25 @allowed_types && @rejected_types &&
26 allowed_types_allowed? && rejected_types_rejected?
27 end
28
29 def failure_message
30 "Content types #{@allowed_types.join(", ")} should be accepted" +
31 " and #{@rejected_types.join(", ")} rejected by #{@attachment_name}"
32 end
33
34 def negative_failure_message
35 "Content types #{@allowed_types.join(", ")} should be rejected" +
36 " and #{@rejected_types.join(", ")} accepted by #{@attachment_name}"
37 end
38
39 def description
40 "validate the content types allowed on attachment #{@attachment_name}"
41 end
42
43 protected
44
45 def allow_types?(types)
46 types.all? do |type|
47 file = StringIO.new(".")
48 file.content_type = type
49 attachment = @subject.new.attachment_for(@attachment_name)
50 attachment.assign(file)
51 attachment.errors[:content_type].nil?
52 end
53 end
54
55 def allowed_types_allowed?
56 allow_types?(@allowed_types)
57 end
58
59 def rejected_types_rejected?
60 not allow_types?(@rejected_types)
61 end
62 end
63 end
64 end
65end
66
diff --git a/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb
new file mode 100644
index 0000000..61dc0ea
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb
@@ -0,0 +1,48 @@
1module Paperclip
2 module Shoulda
3 module Matchers
4 def validate_attachment_presence name
5 ValidateAttachmentPresenceMatcher.new(name)
6 end
7
8 class ValidateAttachmentPresenceMatcher
9 def initialize attachment_name
10 @attachment_name = attachment_name
11 end
12
13 def matches? subject
14 @subject = subject
15 error_when_not_valid? && no_error_when_valid?
16 end
17
18 def failure_message
19 "Attachment #{@attachment_name} should be required"
20 end
21
22 def negative_failure_message
23 "Attachment #{@attachment_name} should not be required"
24 end
25
26 def description
27 "require presence of attachment #{@attachment_name}"
28 end
29
30 protected
31
32 def error_when_not_valid?
33 @attachment = @subject.new.send(@attachment_name)
34 @attachment.assign(nil)
35 not @attachment.errors[:presence].nil?
36 end
37
38 def no_error_when_valid?
39 @file = StringIO.new(".")
40 @attachment = @subject.new.send(@attachment_name)
41 @attachment.assign(@file)
42 @attachment.errors[:presence].nil?
43 end
44 end
45 end
46 end
47end
48
diff --git a/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb
new file mode 100644
index 0000000..f84c479
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb
@@ -0,0 +1,83 @@
1module Paperclip
2 module Shoulda
3 module Matchers
4 def validate_attachment_size name
5 ValidateAttachmentSizeMatcher.new(name)
6 end
7
8 class ValidateAttachmentSizeMatcher
9 def initialize attachment_name
10 @attachment_name = attachment_name
11 @low, @high = 0, (1.0/0)
12 end
13
14 def less_than size
15 @high = size
16 self
17 end
18
19 def greater_than size
20 @low = size
21 self
22 end
23
24 def in range
25 @low, @high = range.first, range.last
26 self
27 end
28
29 def matches? subject
30 @subject = subject
31 lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
32 end
33
34 def failure_message
35 "Attachment #{@attachment_name} must be between #{@low} and #{@high} bytes"
36 end
37
38 def negative_failure_message
39 "Attachment #{@attachment_name} cannot be between #{@low} and #{@high} bytes"
40 end
41
42 def description
43 "validate the size of attachment #{@attachment_name}"
44 end
45
46 protected
47
48 def override_method object, method, &replacement
49 (class << object; self; end).class_eval do
50 define_method(method, &replacement)
51 end
52 end
53
54 def passes_validation_with_size(new_size)
55 file = StringIO.new(".")
56 override_method(file, :size){ new_size }
57 attachment = @subject.new.attachment_for(@attachment_name)
58 attachment.assign(file)
59 attachment.errors[:size].nil?
60 end
61
62 def lower_than_low?
63 not passes_validation_with_size(@low - 1)
64 end
65
66 def higher_than_low?
67 passes_validation_with_size(@low + 1)
68 end
69
70 def lower_than_high?
71 return true if @high == (1.0/0)
72 passes_validation_with_size(@high - 1)
73 end
74
75 def higher_than_high?
76 return true if @high == (1.0/0)
77 not passes_validation_with_size(@high + 1)
78 end
79 end
80 end
81 end
82end
83
diff --git a/vendor/plugins/paperclip/lib/paperclip/processor.rb b/vendor/plugins/paperclip/lib/paperclip/processor.rb
new file mode 100644
index 0000000..9082bfd
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/processor.rb
@@ -0,0 +1,48 @@
1module Paperclip
2 # Paperclip processors allow you to modify attached files when they are
3 # attached in any way you are able. Paperclip itself uses command-line
4 # programs for its included Thumbnail processor, but custom processors
5 # are not required to follow suit.
6 #
7 # Processors are required to be defined inside the Paperclip module and
8 # are also required to be a subclass of Paperclip::Processor. There are
9 # only two methods you must implement to properly be a subclass:
10 # #initialize and #make. Initialize's arguments are the file that will
11 # be operated on (which is an instance of File), and a hash of options
12 # that were defined in has_attached_file's style hash.
13 #
14 # All #make needs to do is return an instance of File (Tempfile is
15 # acceptable) which contains the results of the processing.
16 #
17 # See Paperclip.run for more information about using command-line
18 # utilities from within Processors.
19 class Processor
20 attr_accessor :file, :options, :attachment
21
22 def initialize file, options = {}, attachment = nil
23 @file = file
24 @options = options
25 @attachment = attachment
26 end
27
28 def make
29 end
30
31 def self.make file, options = {}, attachment = nil
32 new(file, options, attachment).make
33 end
34 end
35
36 # Due to how ImageMagick handles its image format conversion and how Tempfile
37 # handles its naming scheme, it is necessary to override how Tempfile makes
38 # its names so as to allow for file extensions. Idea taken from the comments
39 # on this blog post:
40 # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
41 class Tempfile < ::Tempfile
42 # Replaces Tempfile's +make_tmpname+ with one that honors file extensions.
43 def make_tmpname(basename, n)
44 extension = File.extname(basename)
45 sprintf("%s,%d,%d%s", File.basename(basename, extension), $$, n, extension)
46 end
47 end
48end
diff --git a/vendor/plugins/paperclip/lib/paperclip/storage.rb b/vendor/plugins/paperclip/lib/paperclip/storage.rb
new file mode 100644
index 0000000..58913ad
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/storage.rb
@@ -0,0 +1,236 @@
1module Paperclip
2 module Storage
3
4 # The default place to store attachments is in the filesystem. Files on the local
5 # filesystem can be very easily served by Apache without requiring a hit to your app.
6 # They also can be processed more easily after they've been saved, as they're just
7 # normal files. There is one Filesystem-specific option for has_attached_file.
8 # * +path+: The location of the repository of attachments on disk. This can (and, in
9 # almost all cases, should) be coordinated with the value of the +url+ option to
10 # allow files to be saved into a place where Apache can serve them without
11 # hitting your app. Defaults to
12 # ":rails_root/public/:attachment/:id/:style/:basename.:extension"
13 # By default this places the files in the app's public directory which can be served
14 # directly. If you are using capistrano for deployment, a good idea would be to
15 # make a symlink to the capistrano-created system directory from inside your app's
16 # public directory.
17 # See Paperclip::Attachment#interpolate for more information on variable interpolaton.
18 # :path => "/var/app/attachments/:class/:id/:style/:basename.:extension"
19 module Filesystem
20 def self.extended base
21 end
22
23 def exists?(style = default_style)
24 if original_filename
25 File.exist?(path(style))
26 else
27 false
28 end
29 end
30
31 # Returns representation of the data of the file assigned to the given
32 # style, in the format most representative of the current storage.
33 def to_file style = default_style
34 @queued_for_write[style] || (File.new(path(style), 'rb') if exists?(style))
35 end
36 alias_method :to_io, :to_file
37
38 def flush_writes #:nodoc:
39 @queued_for_write.each do |style, file|
40 file.close
41 FileUtils.mkdir_p(File.dirname(path(style)))
42 logger.info("[paperclip] saving #{path(style)}")
43 FileUtils.mv(file.path, path(style))
44 FileUtils.chmod(0644, path(style))
45 end
46 @queued_for_write = {}
47 end
48
49 def flush_deletes #:nodoc:
50 @queued_for_delete.each do |path|
51 begin
52 logger.info("[paperclip] deleting #{path}")
53 FileUtils.rm(path) if File.exist?(path)
54 rescue Errno::ENOENT => e
55 # ignore file-not-found, let everything else pass
56 end
57 begin
58 while(true)
59 path = File.dirname(path)
60 FileUtils.rmdir(path)
61 end
62 rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR
63 # Stop trying to remove parent directories
64 rescue SystemCallError => e
65 logger.info("[paperclip] There was an unexpected error while deleting directories: #{e.class}")
66 # Ignore it
67 end
68 end
69 @queued_for_delete = []
70 end
71 end
72
73 # Amazon's S3 file hosting service is a scalable, easy place to store files for
74 # distribution. You can find out more about it at http://aws.amazon.com/s3
75 # There are a few S3-specific options for has_attached_file:
76 # * +s3_credentials+: Takes a path, a File, or a Hash. The path (or File) must point
77 # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
78 # gives you. You can 'environment-space' this just like you do to your
79 # database.yml file, so different environments can use different accounts:
80 # development:
81 # access_key_id: 123...
82 # secret_access_key: 123...
83 # test:
84 # access_key_id: abc...
85 # secret_access_key: abc...
86 # production:
87 # access_key_id: 456...
88 # secret_access_key: 456...
89 # This is not required, however, and the file may simply look like this:
90 # access_key_id: 456...
91 # secret_access_key: 456...
92 # In which case, those access keys will be used in all environments. You can also
93 # put your bucket name in this file, instead of adding it to the code directly.
94 # This is useful when you want the same account but a different bucket for
95 # development versus production.
96 # * +s3_permissions+: This is a String that should be one of the "canned" access
97 # policies that S3 provides (more information can be found here:
98 # http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html#RESTCannedAccessPolicies)
99 # The default for Paperclip is "public-read".
100 # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
101 # 'http' or 'https'. Defaults to 'http' when your :s3_permissions are 'public-read' (the
102 # default), and 'https' when your :s3_permissions are anything else.
103 # * +s3_headers+: A hash of headers such as {'Expires' => 1.year.from_now.httpdate}
104 # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
105 # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
106 # Paperclip will attempt to create it. The bucket name will not be interpolated.
107 # You can define the bucket as a Proc if you want to determine it's name at runtime.
108 # Paperclip will call that Proc with attachment as the only argument.
109 # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
110 # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
111 # link in the +url+ entry for more information about S3 domains and buckets.
112 # * +url+: There are three options for the S3 url. You can choose to have the bucket's name
113 # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
114 # Lastly, you can specify a CNAME (which requires the CNAME to be specified as
115 # :s3_alias_url. You can read more about CNAMEs and S3 at
116 # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html
117 # Normally, this won't matter in the slightest and you can leave the default (which is
118 # path-style, or :s3_path_url). But in some cases paths don't work and you need to use
119 # the domain-style (:s3_domain_url). Anything else here will be treated like path-style.
120 # NOTE: If you use a CNAME for use with CloudFront, you can NOT specify https as your
121 # :s3_protocol; This is *not supported* by S3/CloudFront. Finally, when using the host
122 # alias, the :bucket parameter is ignored, as the hostname is used as the bucket name
123 # by S3.
124 # * +path+: This is the key under the bucket in which the file will be stored. The
125 # URL will be constructed from the bucket and the path. This is what you will want
126 # to interpolate. Keys should be unique, like filenames, and despite the fact that
127 # S3 (strictly speaking) does not support directories, you can still use a / to
128 # separate parts of your file name.
129 module S3
130 def self.extended base
131 require 'right_aws'
132 base.instance_eval do
133 @s3_credentials = parse_credentials(@options[:s3_credentials])
134 @bucket = @options[:bucket] || @s3_credentials[:bucket]
135 @bucket = @bucket.call(self) if @bucket.is_a?(Proc)
136 @s3_options = @options[:s3_options] || {}
137 @s3_permissions = @options[:s3_permissions] || 'public-read'
138 @s3_protocol = @options[:s3_protocol] || (@s3_permissions == 'public-read' ? 'http' : 'https')
139 @s3_headers = @options[:s3_headers] || {}
140 @s3_host_alias = @options[:s3_host_alias]
141 @url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/)
142 end
143 base.class.interpolations[:s3_alias_url] = lambda do |attachment, style|
144 "#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
145 end
146 base.class.interpolations[:s3_path_url] = lambda do |attachment, style|
147 "#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
148 end
149 base.class.interpolations[:s3_domain_url] = lambda do |attachment, style|
150 "#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
151 end
152 end
153
154 def s3
155 @s3 ||= RightAws::S3.new(@s3_credentials[:access_key_id],
156 @s3_credentials[:secret_access_key],
157 @s3_options)
158 end
159
160 def s3_bucket
161 @s3_bucket ||= s3.bucket(@bucket, true, @s3_permissions)
162 end
163
164 def bucket_name
165 @bucket
166 end
167
168 def s3_host_alias
169 @s3_host_alias
170 end
171
172 def parse_credentials creds
173 creds = find_credentials(creds).stringify_keys
174 (creds[ENV['RAILS_ENV']] || creds).symbolize_keys
175 end
176
177 def exists?(style = default_style)
178 s3_bucket.key(path(style)) ? true : false
179 end
180
181 def s3_protocol
182 @s3_protocol
183 end
184
185 # Returns representation of the data of the file assigned to the given
186 # style, in the format most representative of the current storage.
187 def to_file style = default_style
188 @queued_for_write[style] || s3_bucket.key(path(style))
189 end
190 alias_method :to_io, :to_file
191
192 def flush_writes #:nodoc:
193 @queued_for_write.each do |style, file|
194 begin
195 logger.info("[paperclip] saving #{path(style)}")
196 key = s3_bucket.key(path(style))
197 key.data = file
198 key.put(nil, @s3_permissions, {'Content-type' => instance_read(:content_type)}.merge(@s3_headers))
199 rescue RightAws::AwsError => e
200 raise
201 end
202 end
203 @queued_for_write = {}
204 end
205
206 def flush_deletes #:nodoc:
207 @queued_for_delete.each do |path|
208 begin
209 logger.info("[paperclip] deleting #{path}")
210 if file = s3_bucket.key(path)
211 file.delete
212 end
213 rescue RightAws::AwsError
214 # Ignore this.
215 end
216 end
217 @queued_for_delete = []
218 end
219
220 def find_credentials creds
221 case creds
222 when File
223 YAML.load_file(creds.path)
224 when String
225 YAML.load_file(creds)
226 when Hash
227 creds
228 else
229 raise ArgumentError, "Credentials are not a path, file, or hash."
230 end
231 end
232 private :find_credentials
233
234 end
235 end
236end
diff --git a/vendor/plugins/paperclip/lib/paperclip/thumbnail.rb b/vendor/plugins/paperclip/lib/paperclip/thumbnail.rb
new file mode 100644
index 0000000..2178a9c
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/thumbnail.rb
@@ -0,0 +1,70 @@
1module Paperclip
2 # Handles thumbnailing images that are uploaded.
3 class Thumbnail < Processor
4
5 attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options
6
7 # Creates a Thumbnail object set to work on the +file+ given. It
8 # will attempt to transform the image into one defined by +target_geometry+
9 # which is a "WxH"-style string. +format+ will be inferred from the +file+
10 # unless specified. Thumbnail creation will raise no errors unless
11 # +whiny+ is true (which it is, by default. If +convert_options+ is
12 # set, the options will be appended to the convert command upon image conversion
13 def initialize file, options = {}, attachment = nil
14 super
15 geometry = options[:geometry]
16 @file = file
17 @crop = geometry[-1,1] == '#'
18 @target_geometry = Geometry.parse geometry
19 @current_geometry = Geometry.from_file @file
20 @convert_options = options[:convert_options]
21 @whiny = options[:whiny].nil? ? true : options[:whiny]
22 @format = options[:format]
23
24 @current_format = File.extname(@file.path)
25 @basename = File.basename(@file.path, @current_format)
26 end
27
28 # Returns true if the +target_geometry+ is meant to crop.
29 def crop?
30 @crop
31 end
32
33 # Returns true if the image is meant to make use of additional convert options.
34 def convert_options?
35 not @convert_options.blank?
36 end
37
38 # Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
39 # that contains the new image.
40 def make
41 src = @file
42 dst = Tempfile.new([@basename, @format].compact.join("."))
43 dst.binmode
44
45 command = <<-end_command
46 "#{ File.expand_path(src.path) }[0]"
47 #{ transformation_command }
48 "#{ File.expand_path(dst.path) }"
49 end_command
50
51 begin
52 success = Paperclip.run("convert", command.gsub(/\s+/, " "))
53 rescue PaperclipCommandLineError
54 raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if @whiny
55 end
56
57 dst
58 end
59
60 # Returns the command ImageMagick's +convert+ needs to transform the image
61 # into the thumbnail.
62 def transformation_command
63 scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
64 trans = "-resize \"#{scale}\""
65 trans << " -crop \"#{crop}\" +repage" if crop
66 trans << " #{convert_options}" if convert_options?
67 trans
68 end
69 end
70end
diff --git a/vendor/plugins/paperclip/lib/paperclip/upfile.rb b/vendor/plugins/paperclip/lib/paperclip/upfile.rb
new file mode 100644
index 0000000..f0c8a44
--- /dev/null
+++ b/vendor/plugins/paperclip/lib/paperclip/upfile.rb
@@ -0,0 +1,48 @@
1module Paperclip
2 # The Upfile module is a convenience module for adding uploaded-file-type methods
3 # to the +File+ class. Useful for testing.
4 # user.avatar = File.new("test/test_avatar.jpg")
5 module Upfile
6
7 # Infer the MIME-type of the file from the extension.
8 def content_type
9 type = (self.path.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
10 case type
11 when %r"jpe?g" then "image/jpeg"
12 when %r"tiff?" then "image/tiff"
13 when %r"png", "gif", "bmp" then "image/#{type}"
14 when "txt" then "text/plain"
15 when %r"html?" then "text/html"
16 when "csv", "xml", "css", "js" then "text/#{type}"
17 else "application/x-#{type}"
18 end
19 end
20
21 # Returns the file's normal name.
22 def original_filename
23 File.basename(self.path)
24 end
25
26 # Returns the size of the file.
27 def size
28 File.size(self)
29 end
30 end
31end
32
33if defined? StringIO
34 class StringIO
35 attr_accessor :original_filename, :content_type
36 def original_filename
37 @original_filename ||= "stringio.txt"
38 end
39 def content_type
40 @content_type ||= "text/plain"
41 end
42 end
43end
44
45class File #:nodoc:
46 include Paperclip::Upfile
47end
48
diff --git a/vendor/plugins/paperclip/paperclip.gemspec b/vendor/plugins/paperclip/paperclip.gemspec
new file mode 100644
index 0000000..12ce4c2
--- /dev/null
+++ b/vendor/plugins/paperclip/paperclip.gemspec
@@ -0,0 +1,40 @@
1# -*- encoding: utf-8 -*-
2
3Gem::Specification.new do |s|
4 s.name = %q{paperclip}
5 s.version = "2.2.8"
6
7 s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8 s.authors = ["Jon Yurek"]
9 s.date = %q{2009-04-02}
10 s.email = %q{jyurek@thoughtbot.com}
11 s.extra_rdoc_files = ["README.rdoc"]
12 s.files = ["README.rdoc", "LICENSE", "Rakefile", "init.rb", "generators/paperclip", "generators/paperclip/paperclip_generator.rb", "generators/paperclip/templates", "generators/paperclip/templates/paperclip_migration.rb.erb", "generators/paperclip/USAGE", "lib/paperclip", "lib/paperclip/attachment.rb", "lib/paperclip/callback_compatability.rb", "lib/paperclip/geometry.rb", "lib/paperclip/iostream.rb", "lib/paperclip/matchers", "lib/paperclip/matchers/have_attached_file_matcher.rb", "lib/paperclip/matchers/validate_attachment_content_type_matcher.rb", "lib/paperclip/matchers/validate_attachment_presence_matcher.rb", "lib/paperclip/matchers/validate_attachment_size_matcher.rb", "lib/paperclip/matchers.rb", "lib/paperclip/processor.rb", "lib/paperclip/storage.rb", "lib/paperclip/thumbnail.rb", "lib/paperclip/upfile.rb", "lib/paperclip.rb", "tasks/paperclip_tasks.rake", "test/attachment_test.rb", "test/database.yml", "test/debug.log", "test/fixtures", "test/fixtures/12k.png", "test/fixtures/50x50.png", "test/fixtures/5k.png", "test/fixtures/bad.png", "test/fixtures/s3.yml", "test/fixtures/text.txt", "test/fixtures/twopage.pdf", "test/geometry_test.rb", "test/helper.rb", "test/integration_test.rb", "test/iostream_test.rb", "test/matchers", "test/matchers/have_attached_file_matcher_test.rb", "test/matchers/validate_attachment_content_type_matcher_test.rb", "test/matchers/validate_attachment_presence_matcher_test.rb", "test/matchers/validate_attachment_size_matcher_test.rb", "test/paperclip_test.rb", "test/processor_test.rb", "test/s3.yml", "test/storage_test.rb", "test/thumbnail_test.rb", "test/tmp", "test/tmp/storage.txt", "shoulda_macros/paperclip.rb"]
13 s.has_rdoc = true
14 s.homepage = %q{http://www.thoughtbot.com/projects/paperclip}
15 s.rdoc_options = ["--line-numbers", "--inline-source"]
16 s.require_paths = ["lib"]
17 s.requirements = ["ImageMagick"]
18 s.rubyforge_project = %q{paperclip}
19 s.rubygems_version = %q{1.3.1}
20 s.summary = %q{File attachments as attributes for ActiveRecord}
21
22 if s.respond_to? :specification_version then
23 current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24 s.specification_version = 2
25
26 if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27 s.add_runtime_dependency(%q<right_aws>, [">= 0"])
28 s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
29 s.add_development_dependency(%q<mocha>, [">= 0"])
30 else
31 s.add_dependency(%q<right_aws>, [">= 0"])
32 s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
33 s.add_dependency(%q<mocha>, [">= 0"])
34 end
35 else
36 s.add_dependency(%q<right_aws>, [">= 0"])
37 s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
38 s.add_dependency(%q<mocha>, [">= 0"])
39 end
40end
diff --git a/vendor/plugins/paperclip/shoulda_macros/paperclip.rb b/vendor/plugins/paperclip/shoulda_macros/paperclip.rb
new file mode 100644
index 0000000..d690357
--- /dev/null
+++ b/vendor/plugins/paperclip/shoulda_macros/paperclip.rb
@@ -0,0 +1,68 @@
1require 'paperclip/matchers'
2
3module Paperclip
4 # =Paperclip Shoulda Macros
5 #
6 # These macros are intended for use with shoulda, and will be included into
7 # your tests automatically. All of the macros use the standard shoulda
8 # assumption that the name of the test is based on the name of the model
9 # you're testing (that is, UserTest is the test for the User model), and
10 # will load that class for testing purposes.
11 module Shoulda
12 include Matchers
13 # This will test whether you have defined your attachment correctly by
14 # checking for all the required fields exist after the definition of the
15 # attachment.
16 def should_have_attached_file name
17 klass = self.name.gsub(/Test$/, '').constantize
18 matcher = have_attached_file name
19 should matcher.description do
20 assert_accepts(matcher, klass)
21 end
22 end
23
24 # Tests for validations on the presence of the attachment.
25 def should_validate_attachment_presence name
26 klass = self.name.gsub(/Test$/, '').constantize
27 matcher = validate_attachment_presence name
28 should matcher.description do
29 assert_accepts(matcher, klass)
30 end
31 end
32
33 # Tests that you have content_type validations specified. There are two
34 # options, :valid and :invalid. Both accept an array of strings. The
35 # strings should be a list of content types which will pass and fail
36 # validation, respectively.
37 def should_validate_attachment_content_type name, options = {}
38 klass = self.name.gsub(/Test$/, '').constantize
39 valid = [options[:valid]].flatten
40 invalid = [options[:invalid]].flatten
41 matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid)
42 should matcher.description do
43 assert_accepts(matcher, klass)
44 end
45 end
46
47 # Tests to ensure that you have file size validations turned on. You
48 # can pass the same options to this that you can to
49 # validate_attachment_file_size - :less_than, :greater_than, and :in.
50 # :less_than checks that a file is less than a certain size, :greater_than
51 # checks that a file is more than a certain size, and :in takes a Range or
52 # Array which specifies the lower and upper limits of the file size.
53 def should_validate_attachment_size name, options = {}
54 klass = self.name.gsub(/Test$/, '').constantize
55 min = options[:greater_than] || (options[:in] && options[:in].first) || 0
56 max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
57 range = (min..max)
58 matcher = validate_attachment_size(name).in(range)
59 should matcher.description do
60 assert_accepts(matcher, klass)
61 end
62 end
63 end
64end
65
66class Test::Unit::TestCase #:nodoc:
67 extend Paperclip::Shoulda
68end
diff --git a/vendor/plugins/paperclip/tasks/paperclip_tasks.rake b/vendor/plugins/paperclip/tasks/paperclip_tasks.rake
new file mode 100644
index 0000000..23e4c11
--- /dev/null
+++ b/vendor/plugins/paperclip/tasks/paperclip_tasks.rake
@@ -0,0 +1,79 @@
1def obtain_class
2 class_name = ENV['CLASS'] || ENV['class']
3 raise "Must specify CLASS" unless class_name
4 @klass = Object.const_get(class_name)
5end
6
7def obtain_attachments
8 name = ENV['ATTACHMENT'] || ENV['attachment']
9 raise "Class #{@klass.name} has no attachments specified" unless @klass.respond_to?(:attachment_definitions)
10 if !name.blank? && @klass.attachment_definitions.keys.include?(name)
11 [ name ]
12 else
13 @klass.attachment_definitions.keys
14 end
15end
16
17def for_all_attachments
18 klass = obtain_class
19 names = obtain_attachments
20 ids = klass.connection.select_values(klass.send(:construct_finder_sql, :select => 'id'))
21
22 ids.each do |id|
23 instance = klass.find(id)
24 names.each do |name|
25 result = if instance.send("#{ name }?")
26 yield(instance, name)
27 else
28 true
29 end
30 print result ? "." : "x"; $stdout.flush
31 end
32 end
33 puts " Done."
34end
35
36namespace :paperclip do
37 desc "Refreshes both metadata and thumbnails."
38 task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"]
39
40 namespace :refresh do
41 desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT)."
42 task :thumbnails => :environment do
43 errors = []
44 for_all_attachments do |instance, name|
45 result = instance.send(name).reprocess!
46 errors << [instance.id, instance.errors] unless instance.errors.blank?
47 result
48 end
49 errors.each{|e| puts "#{e.first}: #{e.last.full_messages.inspect}" }
50 end
51
52 desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)."
53 task :metadata => :environment do
54 for_all_attachments do |instance, name|
55 if file = instance.send(name).to_file
56 instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip)
57 instance.send("#{name}_content_type=", file.content_type.strip)
58 instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size")
59 instance.save(false)
60 else
61 true
62 end
63 end
64 end
65 end
66
67 desc "Cleans out invalid attachments. Useful after you've added new validations."
68 task :clean => :environment do
69 for_all_attachments do |instance, name|
70 instance.send(name).send(:validate)
71 if instance.send(name).valid?
72 true
73 else
74 instance.send("#{name}=", nil)
75 instance.save
76 end
77 end
78 end
79end
diff --git a/vendor/plugins/paperclip/test/.gitignore b/vendor/plugins/paperclip/test/.gitignore
new file mode 100644
index 0000000..b14c548
--- /dev/null
+++ b/vendor/plugins/paperclip/test/.gitignore
@@ -0,0 +1 @@
debug.log
diff --git a/vendor/plugins/paperclip/test/attachment_test.rb b/vendor/plugins/paperclip/test/attachment_test.rb
new file mode 100644
index 0000000..61ab2a2
--- /dev/null
+++ b/vendor/plugins/paperclip/test/attachment_test.rb
@@ -0,0 +1,742 @@
1require 'test/helper'
2
3class Dummy
4 # This is a dummy class
5end
6
7class AttachmentTest < Test::Unit::TestCase
8 context "Attachment default_options" do
9 setup do
10 rebuild_model
11 @old_default_options = Paperclip::Attachment.default_options.dup
12 @new_default_options = @old_default_options.merge({
13 :path => "argle/bargle",
14 :url => "fooferon",
15 :default_url => "not here.png"
16 })
17 end
18
19 teardown do
20 Paperclip::Attachment.default_options.merge! @old_default_options
21 end
22
23 should "be overrideable" do
24 Paperclip::Attachment.default_options.merge!(@new_default_options)
25 @new_default_options.keys.each do |key|
26 assert_equal @new_default_options[key],
27 Paperclip::Attachment.default_options[key]
28 end
29 end
30
31 context "without an Attachment" do
32 setup do
33 @dummy = Dummy.new
34 end
35
36 should "return false when asked exists?" do
37 assert !@dummy.avatar.exists?
38 end
39 end
40
41 context "on an Attachment" do
42 setup do
43 @dummy = Dummy.new
44 @attachment = @dummy.avatar
45 end
46
47 Paperclip::Attachment.default_options.keys.each do |key|
48 should "be the default_options for #{key}" do
49 assert_equal @old_default_options[key],
50 @attachment.instance_variable_get("@#{key}"),
51 key
52 end
53 end
54
55 context "when redefined" do
56 setup do
57 Paperclip::Attachment.default_options.merge!(@new_default_options)
58 @dummy = Dummy.new
59 @attachment = @dummy.avatar
60 end
61
62 Paperclip::Attachment.default_options.keys.each do |key|
63 should "be the new default_options for #{key}" do
64 assert_equal @new_default_options[key],
65 @attachment.instance_variable_get("@#{key}"),
66 key
67 end
68 end
69 end
70 end
71 end
72
73 context "An attachment with similarly named interpolations" do
74 setup do
75 rebuild_model :path => ":id.omg/:id-bbq/:idwhat/:id_partition.wtf"
76 @dummy = Dummy.new
77 @dummy.stubs(:id).returns(1024)
78 @file = File.new(File.join(File.dirname(__FILE__),
79 "fixtures",
80 "5k.png"), 'rb')
81 @dummy.avatar = @file
82 end
83
84 teardown { @file.close }
85
86 should "make sure that they are interpolated correctly" do
87 assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path
88 end
89 end
90
91 context "An attachment with a :rails_env interpolation" do
92 setup do
93 @rails_env = "blah"
94 @id = 1024
95 rebuild_model :path => ":rails_env/:id.png"
96 @dummy = Dummy.new
97 @dummy.stubs(:id).returns(@id)
98 @file = StringIO.new(".")
99 @dummy.avatar = @file
100 end
101
102 should "return the proper path" do
103 temporary_rails_env(@rails_env) {
104 assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path
105 }
106 end
107 end
108
109 context "An attachment with :convert_options" do
110 setup do
111 rebuild_model :styles => {
112 :thumb => "100x100",
113 :large => "400x400"
114 },
115 :convert_options => {
116 :all => "-do_stuff",
117 :thumb => "-thumbnailize"
118 }
119 @dummy = Dummy.new
120 @dummy.avatar
121 end
122
123 should "report the correct options when sent #extra_options_for(:thumb)" do
124 assert_equal "-thumbnailize -do_stuff", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
125 end
126
127 should "report the correct options when sent #extra_options_for(:large)" do
128 assert_equal "-do_stuff", @dummy.avatar.send(:extra_options_for, :large)
129 end
130
131 before_should "call extra_options_for(:thumb/:large)" do
132 Paperclip::Attachment.any_instance.expects(:extra_options_for).with(:thumb)
133 Paperclip::Attachment.any_instance.expects(:extra_options_for).with(:large)
134 end
135 end
136
137 context "An attachment with :convert_options that is a proc" do
138 setup do
139 rebuild_model :styles => {
140 :thumb => "100x100",
141 :large => "400x400"
142 },
143 :convert_options => {
144 :all => lambda{|i| i.all },
145 :thumb => lambda{|i| i.thumb }
146 }
147 Dummy.class_eval do
148 def all; "-all"; end
149 def thumb; "-thumb"; end
150 end
151 @dummy = Dummy.new
152 @dummy.avatar
153 end
154
155 should "report the correct options when sent #extra_options_for(:thumb)" do
156 assert_equal "-thumb -all", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
157 end
158
159 should "report the correct options when sent #extra_options_for(:large)" do
160 assert_equal "-all", @dummy.avatar.send(:extra_options_for, :large)
161 end
162
163 before_should "call extra_options_for(:thumb/:large)" do
164 Paperclip::Attachment.any_instance.expects(:extra_options_for).with(:thumb)
165 Paperclip::Attachment.any_instance.expects(:extra_options_for).with(:large)
166 end
167 end
168
169 context "An attachment with :path that is a proc" do
170 setup do
171 rebuild_model :path => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
172
173 @file = File.new(File.join(File.dirname(__FILE__),
174 "fixtures",
175 "5k.png"), 'rb')
176 @dummyA = Dummy.new(:other => 'a')
177 @dummyA.avatar = @file
178 @dummyB = Dummy.new(:other => 'b')
179 @dummyB.avatar = @file
180 end
181
182 teardown { @file.close }
183
184 should "return correct path" do
185 assert_equal "path/a.png", @dummyA.avatar.path
186 assert_equal "path/b.png", @dummyB.avatar.path
187 end
188 end
189
190 context "An attachment with :styles that is a proc" do
191 setup do
192 rebuild_model :styles => lambda{ |attachment| {:thumb => "50x50#", :large => "400x400"} }
193
194 @attachment = Dummy.new.avatar
195 end
196
197 should "have the correct geometry" do
198 assert_equal "50x50#", @attachment.styles[:thumb][:geometry]
199 end
200 end
201
202 context "An attachment with :url that is a proc" do
203 setup do
204 rebuild_model :url => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
205
206 @file = File.new(File.join(File.dirname(__FILE__),
207 "fixtures",
208 "5k.png"), 'rb')
209 @dummyA = Dummy.new(:other => 'a')
210 @dummyA.avatar = @file
211 @dummyB = Dummy.new(:other => 'b')
212 @dummyB.avatar = @file
213 end
214
215 teardown { @file.close }
216
217 should "return correct url" do
218 assert_equal "path/a.png", @dummyA.avatar.url(:original, false)
219 assert_equal "path/b.png", @dummyB.avatar.url(:original, false)
220 end
221 end
222
223 geometry_specs = [
224 [ lambda{|z| "50x50#" }, :png ],
225 lambda{|z| "50x50#" },
226 { :geometry => lambda{|z| "50x50#" } }
227 ]
228 geometry_specs.each do |geometry_spec|
229 context "An attachment geometry like #{geometry_spec}" do
230 setup do
231 rebuild_model :styles => { :normal => geometry_spec }
232 @attachment = Dummy.new.avatar
233 end
234
235 should "not run the procs immediately" do
236 assert_kind_of Proc, @attachment.styles[:normal][:geometry]
237 end
238
239 context "when assigned" do
240 setup do
241 @file = StringIO.new(".")
242 @attachment.assign(@file)
243 end
244
245 should "have the correct geometry" do
246 assert_equal "50x50#", @attachment.styles[:normal][:geometry]
247 end
248 end
249 end
250 end
251
252 context "An attachment with both 'normal' and hash-style styles" do
253 setup do
254 rebuild_model :styles => {
255 :normal => ["50x50#", :png],
256 :hash => { :geometry => "50x50#", :format => :png }
257 }
258 @dummy = Dummy.new
259 @attachment = @dummy.avatar
260 end
261
262 [:processors, :whiny, :convert_options, :geometry, :format].each do |field|
263 should "have the same #{field} field" do
264 assert_equal @attachment.styles[:normal][field], @attachment.styles[:hash][field]
265 end
266 end
267 end
268
269 context "An attachment with :processors that is a proc" do
270 setup do
271 rebuild_model :styles => { :normal => '' }, :processors => lambda { |a| [ :test ] }
272 @attachment = Dummy.new.avatar
273 end
274
275 should "not run the proc immediately" do
276 assert_kind_of Proc, @attachment.styles[:normal][:processors]
277 end
278
279 context "when assigned" do
280 setup do
281 @attachment.assign(StringIO.new("."))
282 end
283
284 should "have the correct processors" do
285 assert_equal [ :test ], @attachment.styles[:normal][:processors]
286 end
287 end
288 end
289
290 context "An attachment with erroring processor" do
291 setup do
292 rebuild_model :processor => [:thumbnail], :styles => { :small => '' }, :whiny_thumbnails => true
293 @dummy = Dummy.new
294 Paperclip::Thumbnail.expects(:make).raises(Paperclip::PaperclipError, "cannot be processed.")
295 @file = StringIO.new("...")
296 @file.stubs(:to_tempfile).returns(@file)
297 @dummy.avatar = @file
298 end
299
300 should "correctly forward processing error message to the instance" do
301 @dummy.valid?
302 assert_contains @dummy.errors.full_messages, "Avatar cannot be processed."
303 end
304 end
305
306 context "An attachment with multiple processors" do
307 setup do
308 class Paperclip::Test < Paperclip::Processor; end
309 @style_params = { :once => {:one => 1, :two => 2} }
310 rebuild_model :processors => [:thumbnail, :test], :styles => @style_params
311 @dummy = Dummy.new
312 @file = StringIO.new("...")
313 @file.stubs(:to_tempfile).returns(@file)
314 Paperclip::Test.stubs(:make).returns(@file)
315 Paperclip::Thumbnail.stubs(:make).returns(@file)
316 end
317
318 context "when assigned" do
319 setup { @dummy.avatar = @file }
320
321 before_should "call #make on all specified processors" do
322 expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => nil, :convert_options => ""})
323 Paperclip::Thumbnail.expects(:make).with(@file, expected_params, @dummy.avatar).returns(@file)
324 Paperclip::Test.expects(:make).with(@file, expected_params, @dummy.avatar).returns(@file)
325 end
326
327 before_should "call #make with attachment passed as third argument" do
328 expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => nil, :convert_options => ""})
329 Paperclip::Test.expects(:make).with(@file, expected_params, @dummy.avatar).returns(@file)
330 end
331 end
332 end
333
334 context "An attachment with no processors defined" do
335 setup do
336 rebuild_model :processors => [], :styles => {:something => 1}
337 @dummy = Dummy.new
338 @file = StringIO.new("...")
339 end
340 should "raise when assigned to" do
341 assert_raises(RuntimeError){ @dummy.avatar = @file }
342 end
343 end
344
345 context "Assigning an attachment with post_process hooks" do
346 setup do
347 rebuild_model :styles => { :something => "100x100#" }
348 Dummy.class_eval do
349 before_avatar_post_process :do_before_avatar
350 after_avatar_post_process :do_after_avatar
351 before_post_process :do_before_all
352 after_post_process :do_after_all
353 def do_before_avatar; end
354 def do_after_avatar; end
355 def do_before_all; end
356 def do_after_all; end
357 end
358 @file = StringIO.new(".")
359 @file.stubs(:to_tempfile).returns(@file)
360 @dummy = Dummy.new
361 Paperclip::Thumbnail.stubs(:make).returns(@file)
362 @attachment = @dummy.avatar
363 end
364
365 should "call the defined callbacks when assigned" do
366 @dummy.expects(:do_before_avatar).with()
367 @dummy.expects(:do_after_avatar).with()
368 @dummy.expects(:do_before_all).with()
369 @dummy.expects(:do_after_all).with()
370 Paperclip::Thumbnail.expects(:make).returns(@file)
371 @dummy.avatar = @file
372 end
373
374 should "not cancel the processing if a before_post_process returns nil" do
375 @dummy.expects(:do_before_avatar).with().returns(nil)
376 @dummy.expects(:do_after_avatar).with()
377 @dummy.expects(:do_before_all).with().returns(nil)
378 @dummy.expects(:do_after_all).with()
379 Paperclip::Thumbnail.expects(:make).returns(@file)
380 @dummy.avatar = @file
381 end
382
383 should "cancel the processing if a before_post_process returns false" do
384 @dummy.expects(:do_before_avatar).never
385 @dummy.expects(:do_after_avatar).never
386 @dummy.expects(:do_before_all).with().returns(false)
387 @dummy.expects(:do_after_all).never
388 Paperclip::Thumbnail.expects(:make).never
389 @dummy.avatar = @file
390 end
391
392 should "cancel the processing if a before_avatar_post_process returns false" do
393 @dummy.expects(:do_before_avatar).with().returns(false)
394 @dummy.expects(:do_after_avatar).never
395 @dummy.expects(:do_before_all).with().returns(true)
396 @dummy.expects(:do_after_all).never
397 Paperclip::Thumbnail.expects(:make).never
398 @dummy.avatar = @file
399 end
400 end
401
402 context "Assigning an attachment" do
403 setup do
404 rebuild_model :styles => { :something => "100x100#" }
405 @file = StringIO.new(".")
406 @file.expects(:original_filename).returns("5k.png\n\n")
407 @file.expects(:content_type).returns("image/png\n\n")
408 @file.stubs(:to_tempfile).returns(@file)
409 @dummy = Dummy.new
410 Paperclip::Thumbnail.expects(:make).returns(@file)
411 @dummy.expects(:run_callbacks).with(:before_avatar_post_process, {:original => @file})
412 @dummy.expects(:run_callbacks).with(:before_post_process, {:original => @file})
413 @dummy.expects(:run_callbacks).with(:after_avatar_post_process, {:original => @file, :something => @file})
414 @dummy.expects(:run_callbacks).with(:after_post_process, {:original => @file, :something => @file})
415 @attachment = @dummy.avatar
416 @dummy.avatar = @file
417 end
418
419 should "strip whitespace from original_filename field" do
420 assert_equal "5k.png", @dummy.avatar.original_filename
421 end
422
423 should "strip whitespace from content_type field" do
424 assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
425 end
426 end
427
428 context "Attachment with strange letters" do
429 setup do
430 rebuild_model
431
432 @not_file = mock
433 @tempfile = mock
434 @not_file.stubs(:nil?).returns(false)
435 @not_file.expects(:size).returns(10)
436 @tempfile.expects(:size).returns(10)
437 @not_file.expects(:to_tempfile).returns(@tempfile)
438 @not_file.expects(:original_filename).returns("sheep_say_bæ.png\r\n")
439 @not_file.expects(:content_type).returns("image/png\r\n")
440
441 @dummy = Dummy.new
442 @attachment = @dummy.avatar
443 @attachment.expects(:valid_assignment?).with(@not_file).returns(true)
444 @attachment.expects(:queue_existing_for_delete)
445 @attachment.expects(:post_process)
446 @attachment.expects(:valid?).returns(true)
447 @attachment.expects(:validate)
448 @dummy.avatar = @not_file
449 end
450
451 should "remove strange letters and replace with underscore (_)" do
452 assert_equal "sheep_say_b_.png", @dummy.avatar.original_filename
453 end
454
455 end
456
457 context "An attachment" do
458 setup do
459 Paperclip::Attachment.default_options.merge!({
460 :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
461 })
462 FileUtils.rm_rf("tmp")
463 rebuild_model
464 @instance = Dummy.new
465 @attachment = Paperclip::Attachment.new(:avatar, @instance)
466 @file = File.new(File.join(File.dirname(__FILE__),
467 "fixtures",
468 "5k.png"), 'rb')
469 end
470
471 teardown { @file.close }
472
473 should "raise if there are not the correct columns when you try to assign" do
474 @other_attachment = Paperclip::Attachment.new(:not_here, @instance)
475 assert_raises(Paperclip::PaperclipError) do
476 @other_attachment.assign(@file)
477 end
478 end
479
480 should "return its default_url when no file assigned" do
481 assert @attachment.to_file.nil?
482 assert_equal "/avatars/original/missing.png", @attachment.url
483 assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
484 end
485
486 should "return nil as path when no file assigned" do
487 assert @attachment.to_file.nil?
488 assert_equal nil, @attachment.path
489 assert_equal nil, @attachment.path(:blah)
490 end
491
492 context "with a file assigned in the database" do
493 setup do
494 @attachment.stubs(:instance_read).with(:file_name).returns("5k.png")
495 @attachment.stubs(:instance_read).with(:content_type).returns("image/png")
496 @attachment.stubs(:instance_read).with(:file_size).returns(12345)
497 now = Time.now
498 Time.stubs(:now).returns(now)
499 @attachment.stubs(:instance_read).with(:updated_at).returns(Time.now)
500 end
501
502 should "return a correct url even if the file does not exist" do
503 assert_nil @attachment.to_file
504 assert_match %r{^/system/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
505 end
506
507 should "make sure the updated_at mtime is in the url if it is defined" do
508 assert_match %r{#{Time.now.to_i}$}, @attachment.url(:blah)
509 end
510
511 should "make sure the updated_at mtime is NOT in the url if false is passed to the url method" do
512 assert_no_match %r{#{Time.now.to_i}$}, @attachment.url(:blah, false)
513 end
514
515 context "with the updated_at field removed" do
516 setup do
517 @attachment.stubs(:instance_read).with(:updated_at).returns(nil)
518 end
519
520 should "only return the url without the updated_at when sent #url" do
521 assert_match "/avatars/#{@instance.id}/blah/5k.png", @attachment.url(:blah)
522 end
523 end
524
525 should "return the proper path when filename has a single .'s" do
526 assert_equal "./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.png", @attachment.path
527 end
528
529 should "return the proper path when filename has multiple .'s" do
530 @attachment.stubs(:instance_read).with(:file_name).returns("5k.old.png")
531 assert_equal "./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.old.png", @attachment.path
532 end
533
534 context "when expecting three styles" do
535 setup do
536 styles = {:styles => { :large => ["400x400", :png],
537 :medium => ["100x100", :gif],
538 :small => ["32x32#", :jpg]}}
539 @attachment = Paperclip::Attachment.new(:avatar,
540 @instance,
541 styles)
542 end
543
544 context "and assigned a file" do
545 setup do
546 now = Time.now
547 Time.stubs(:now).returns(now)
548 @attachment.assign(@file)
549 end
550
551 should "be dirty" do
552 assert @attachment.dirty?
553 end
554
555 context "and saved" do
556 setup do
557 @attachment.save
558 end
559
560 should "return the real url" do
561 file = @attachment.to_file
562 assert file
563 assert_match %r{^/system/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
564 assert_match %r{^/system/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
565 file.close
566 end
567
568 should "commit the files to disk" do
569 [:large, :medium, :small].each do |style|
570 io = @attachment.to_io(style)
571 assert File.exists?(io)
572 assert ! io.is_a?(::Tempfile)
573 io.close
574 end
575 end
576
577 should "save the files as the right formats and sizes" do
578 [[:large, 400, 61, "PNG"],
579 [:medium, 100, 15, "GIF"],
580 [:small, 32, 32, "JPEG"]].each do |style|
581 cmd = %Q[identify -format "%w %h %b %m" "#{@attachment.path(style.first)}"]
582 out = `#{cmd}`
583 width, height, size, format = out.split(" ")
584 assert_equal style[1].to_s, width.to_s
585 assert_equal style[2].to_s, height.to_s
586 assert_equal style[3].to_s, format.to_s
587 end
588 end
589
590 should "still have its #file attribute not be nil" do
591 assert ! (file = @attachment.to_file).nil?
592 file.close
593 end
594
595 context "and trying to delete" do
596 setup do
597 @existing_names = @attachment.styles.keys.collect do |style|
598 @attachment.path(style)
599 end
600 end
601
602 should "delete the files after assigning nil" do
603 @attachment.expects(:instance_write).with(:file_name, nil)
604 @attachment.expects(:instance_write).with(:content_type, nil)
605 @attachment.expects(:instance_write).with(:file_size, nil)
606 @attachment.expects(:instance_write).with(:updated_at, nil)
607 @attachment.assign nil
608 @attachment.save
609 @existing_names.each{|f| assert ! File.exists?(f) }
610 end
611
612 should "delete the files when you call #clear and #save" do
613 @attachment.expects(:instance_write).with(:file_name, nil)
614 @attachment.expects(:instance_write).with(:content_type, nil)
615 @attachment.expects(:instance_write).with(:file_size, nil)
616 @attachment.expects(:instance_write).with(:updated_at, nil)
617 @attachment.clear
618 @attachment.save
619 @existing_names.each{|f| assert ! File.exists?(f) }
620 end
621
622 should "delete the files when you call #delete" do
623 @attachment.expects(:instance_write).with(:file_name, nil)
624 @attachment.expects(:instance_write).with(:content_type, nil)
625 @attachment.expects(:instance_write).with(:file_size, nil)
626 @attachment.expects(:instance_write).with(:updated_at, nil)
627 @attachment.destroy
628 @existing_names.each{|f| assert ! File.exists?(f) }
629 end
630 end
631 end
632 end
633 end
634
635 end
636
637 context "when trying a nonexistant storage type" do
638 setup do
639 rebuild_model :storage => :not_here
640 end
641
642 should "not be able to find the module" do
643 assert_raise(NameError){ Dummy.new.avatar }
644 end
645 end
646 end
647
648 context "An attachment with only a avatar_file_name column" do
649 setup do
650 ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
651 table.column :avatar_file_name, :string
652 end
653 rebuild_class
654 @dummy = Dummy.new
655 @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
656 end
657
658 teardown { @file.close }
659
660 should "not error when assigned an attachment" do
661 assert_nothing_raised { @dummy.avatar = @file }
662 end
663
664 should "return the time when sent #avatar_updated_at" do
665 now = Time.now
666 Time.stubs(:now).returns(now)
667 @dummy.avatar = @file
668 assert now, @dummy.avatar.updated_at
669 end
670
671 should "return nil when reloaded and sent #avatar_updated_at" do
672 @dummy.save
673 @dummy.reload
674 assert_nil @dummy.avatar.updated_at
675 end
676
677 should "return the right value when sent #avatar_file_size" do
678 @dummy.avatar = @file
679 assert_equal @file.size, @dummy.avatar.size
680 end
681
682 context "and avatar_updated_at column" do
683 setup do
684 ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp
685 rebuild_class
686 @dummy = Dummy.new
687 end
688
689 should "not error when assigned an attachment" do
690 assert_nothing_raised { @dummy.avatar = @file }
691 end
692
693 should "return the right value when sent #avatar_updated_at" do
694 now = Time.now
695 Time.stubs(:now).returns(now)
696 @dummy.avatar = @file
697 assert_equal now.to_i, @dummy.avatar.updated_at
698 end
699 end
700
701 context "and avatar_content_type column" do
702 setup do
703 ActiveRecord::Base.connection.add_column :dummies, :avatar_content_type, :string
704 rebuild_class
705 @dummy = Dummy.new
706 end
707
708 should "not error when assigned an attachment" do
709 assert_nothing_raised { @dummy.avatar = @file }
710 end
711
712 should "return the right value when sent #avatar_content_type" do
713 @dummy.avatar = @file
714 assert_equal "image/png", @dummy.avatar.content_type
715 end
716 end
717
718 context "and avatar_file_size column" do
719 setup do
720 ActiveRecord::Base.connection.add_column :dummies, :avatar_file_size, :integer
721 rebuild_class
722 @dummy = Dummy.new
723 end
724
725 should "not error when assigned an attachment" do
726 assert_nothing_raised { @dummy.avatar = @file }
727 end
728
729 should "return the right value when sent #avatar_file_size" do
730 @dummy.avatar = @file
731 assert_equal @file.size, @dummy.avatar.size
732 end
733
734 should "return the right value when saved, reloaded, and sent #avatar_file_size" do
735 @dummy.avatar = @file
736 @dummy.save
737 @dummy = Dummy.find(@dummy.id)
738 assert_equal @file.size, @dummy.avatar.size
739 end
740 end
741 end
742end
diff --git a/vendor/plugins/paperclip/test/fixtures/12k.png b/vendor/plugins/paperclip/test/fixtures/12k.png
new file mode 100644
index 0000000..f819d45
--- /dev/null
+++ b/vendor/plugins/paperclip/test/fixtures/12k.png
Binary files differ
diff --git a/vendor/plugins/paperclip/test/fixtures/50x50.png b/vendor/plugins/paperclip/test/fixtures/50x50.png
new file mode 100644
index 0000000..63f5646
--- /dev/null
+++ b/vendor/plugins/paperclip/test/fixtures/50x50.png
Binary files differ
diff --git a/vendor/plugins/paperclip/test/fixtures/5k.png b/vendor/plugins/paperclip/test/fixtures/5k.png
new file mode 100644
index 0000000..75d9f04
--- /dev/null
+++ b/vendor/plugins/paperclip/test/fixtures/5k.png
Binary files differ
diff --git a/vendor/plugins/paperclip/test/fixtures/bad.png b/vendor/plugins/paperclip/test/fixtures/bad.png
new file mode 100644
index 0000000..7ba4f07
--- /dev/null
+++ b/vendor/plugins/paperclip/test/fixtures/bad.png
@@ -0,0 +1 @@
This is not an image.
diff --git a/vendor/plugins/paperclip/test/fixtures/text.txt b/vendor/plugins/paperclip/test/fixtures/text.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/vendor/plugins/paperclip/test/fixtures/text.txt
diff --git a/vendor/plugins/paperclip/test/fixtures/twopage.pdf b/vendor/plugins/paperclip/test/fixtures/twopage.pdf
new file mode 100644
index 0000000..0c34a51
--- /dev/null
+++ b/vendor/plugins/paperclip/test/fixtures/twopage.pdf
Binary files differ
diff --git a/vendor/plugins/paperclip/test/geometry_test.rb b/vendor/plugins/paperclip/test/geometry_test.rb
new file mode 100644
index 0000000..134372d
--- /dev/null
+++ b/vendor/plugins/paperclip/test/geometry_test.rb
@@ -0,0 +1,168 @@
1require 'test/helper'
2
3class GeometryTest < Test::Unit::TestCase
4 context "Paperclip::Geometry" do
5 should "correctly report its given dimensions" do
6 assert @geo = Paperclip::Geometry.new(1024, 768)
7 assert_equal 1024, @geo.width
8 assert_equal 768, @geo.height
9 end
10
11 should "set height to 0 if height dimension is missing" do
12 assert @geo = Paperclip::Geometry.new(1024)
13 assert_equal 1024, @geo.width
14 assert_equal 0, @geo.height
15 end
16
17 should "set width to 0 if width dimension is missing" do
18 assert @geo = Paperclip::Geometry.new(nil, 768)
19 assert_equal 0, @geo.width
20 assert_equal 768, @geo.height
21 end
22
23 should "be generated from a WxH-formatted string" do
24 assert @geo = Paperclip::Geometry.parse("800x600")
25 assert_equal 800, @geo.width
26 assert_equal 600, @geo.height
27 end
28
29 should "be generated from a xH-formatted string" do
30 assert @geo = Paperclip::Geometry.parse("x600")
31 assert_equal 0, @geo.width
32 assert_equal 600, @geo.height
33 end
34
35 should "be generated from a Wx-formatted string" do
36 assert @geo = Paperclip::Geometry.parse("800x")
37 assert_equal 800, @geo.width
38 assert_equal 0, @geo.height
39 end
40
41 should "be generated from a W-formatted string" do
42 assert @geo = Paperclip::Geometry.parse("800")
43 assert_equal 800, @geo.width
44 assert_equal 0, @geo.height
45 end
46
47 should "ensure the modifier is nil if not present" do
48 assert @geo = Paperclip::Geometry.parse("123x456")
49 assert_nil @geo.modifier
50 end
51
52 ['>', '<', '#', '@', '%', '^', '!', nil].each do |mod|
53 should "ensure the modifier #{mod.inspect} is preserved" do
54 assert @geo = Paperclip::Geometry.parse("123x456#{mod}")
55 assert_equal mod, @geo.modifier
56 assert_equal "123x456#{mod}", @geo.to_s
57 end
58 end
59
60 ['>', '<', '#', '@', '%', '^', '!', nil].each do |mod|
61 should "ensure the modifier #{mod.inspect} is preserved with no height" do
62 assert @geo = Paperclip::Geometry.parse("123x#{mod}")
63 assert_equal mod, @geo.modifier
64 assert_equal "123#{mod}", @geo.to_s
65 end
66 end
67
68 should "make sure the modifier gets passed during transformation_to" do
69 assert @src = Paperclip::Geometry.parse("123x456")
70 assert @dst = Paperclip::Geometry.parse("123x456>")
71 assert_equal "123x456>", @src.transformation_to(@dst).to_s
72 end
73
74 should "generate correct ImageMagick formatting string for W-formatted string" do
75 assert @geo = Paperclip::Geometry.parse("800")
76 assert_equal "800", @geo.to_s
77 end
78
79 should "generate correct ImageMagick formatting string for Wx-formatted string" do
80 assert @geo = Paperclip::Geometry.parse("800x")
81 assert_equal "800", @geo.to_s
82 end
83
84 should "generate correct ImageMagick formatting string for xH-formatted string" do
85 assert @geo = Paperclip::Geometry.parse("x600")
86 assert_equal "x600", @geo.to_s
87 end
88
89 should "generate correct ImageMagick formatting string for WxH-formatted string" do
90 assert @geo = Paperclip::Geometry.parse("800x600")
91 assert_equal "800x600", @geo.to_s
92 end
93
94 should "be generated from a file" do
95 file = File.join(File.dirname(__FILE__), "fixtures", "5k.png")
96 file = File.new(file, 'rb')
97 assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) }
98 assert @geo.height > 0
99 assert @geo.width > 0
100 end
101
102 should "be generated from a file path" do
103 file = File.join(File.dirname(__FILE__), "fixtures", "5k.png")
104 assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) }
105 assert @geo.height > 0
106 assert @geo.width > 0
107 end
108
109 should "not generate from a bad file" do
110 file = "/home/This File Does Not Exist.omg"
111 assert_raise(Paperclip::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) }
112 end
113
114 [['vertical', 900, 1440, true, false, false, 1440, 900, 0.625],
115 ['horizontal', 1024, 768, false, true, false, 1024, 768, 1.3333],
116 ['square', 100, 100, false, false, true, 100, 100, 1]].each do |args|
117 context "performing calculations on a #{args[0]} viewport" do
118 setup do
119 @geo = Paperclip::Geometry.new(args[1], args[2])
120 end
121
122 should "#{args[3] ? "" : "not"} be vertical" do
123 assert_equal args[3], @geo.vertical?
124 end
125
126 should "#{args[4] ? "" : "not"} be horizontal" do
127 assert_equal args[4], @geo.horizontal?
128 end
129
130 should "#{args[5] ? "" : "not"} be square" do
131 assert_equal args[5], @geo.square?
132 end
133
134 should "report that #{args[6]} is the larger dimension" do
135 assert_equal args[6], @geo.larger
136 end
137
138 should "report that #{args[7]} is the smaller dimension" do
139 assert_equal args[7], @geo.smaller
140 end
141
142 should "have an aspect ratio of #{args[8]}" do
143 assert_in_delta args[8], @geo.aspect, 0.0001
144 end
145 end
146 end
147
148 [[ [1000, 100], [64, 64], "x64", "64x64+288+0" ],
149 [ [100, 1000], [50, 950], "x950", "50x950+22+0" ],
150 [ [100, 1000], [50, 25], "50x", "50x25+0+237" ]]. each do |args|
151 context "of #{args[0].inspect} and given a Geometry #{args[1].inspect} and sent transform_to" do
152 setup do
153 @geo = Paperclip::Geometry.new(*args[0])
154 @dst = Paperclip::Geometry.new(*args[1])
155 @scale, @crop = @geo.transformation_to @dst, true
156 end
157
158 should "be able to return the correct scaling transformation geometry #{args[2]}" do
159 assert_equal args[2], @scale
160 end
161
162 should "be able to return the correct crop transformation geometry #{args[3]}" do
163 assert_equal args[3], @crop
164 end
165 end
166 end
167 end
168end
diff --git a/vendor/plugins/paperclip/test/helper.rb b/vendor/plugins/paperclip/test/helper.rb
new file mode 100644
index 0000000..3e7e6a1
--- /dev/null
+++ b/vendor/plugins/paperclip/test/helper.rb
@@ -0,0 +1,82 @@
1require 'rubygems'
2require 'test/unit'
3gem 'thoughtbot-shoulda', ">= 2.9.0"
4require 'shoulda'
5require 'mocha'
6require 'tempfile'
7
8gem 'sqlite3-ruby'
9
10require 'active_record'
11require 'active_support'
12begin
13 require 'ruby-debug'
14rescue LoadError
15 puts "ruby-debug not loaded"
16end
17
18ROOT = File.join(File.dirname(__FILE__), '..')
19RAILS_ROOT = ROOT
20
21$LOAD_PATH << File.join(ROOT, 'lib')
22$LOAD_PATH << File.join(ROOT, 'lib', 'paperclip')
23
24require File.join(ROOT, 'lib', 'paperclip.rb')
25
26require 'shoulda_macros/paperclip'
27
28ENV['RAILS_ENV'] ||= 'test'
29
30FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
31config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
32ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
33ActiveRecord::Base.establish_connection(config['test'])
34
35def reset_class class_name
36 ActiveRecord::Base.send(:include, Paperclip)
37 Object.send(:remove_const, class_name) rescue nil
38 klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
39 klass.class_eval{ include Paperclip }
40 klass
41end
42
43def reset_table table_name, &block
44 block ||= lambda{ true }
45 ActiveRecord::Base.connection.create_table :dummies, {:force => true}, &block
46end
47
48def modify_table table_name, &block
49 ActiveRecord::Base.connection.change_table :dummies, &block
50end
51
52def rebuild_model options = {}
53 ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
54 table.column :other, :string
55 table.column :avatar_file_name, :string
56 table.column :avatar_content_type, :string
57 table.column :avatar_file_size, :integer
58 table.column :avatar_updated_at, :datetime
59 end
60 rebuild_class options
61end
62
63def rebuild_class options = {}
64 ActiveRecord::Base.send(:include, Paperclip)
65 Object.send(:remove_const, "Dummy") rescue nil
66 Object.const_set("Dummy", Class.new(ActiveRecord::Base))
67 Dummy.class_eval do
68 include Paperclip
69 has_attached_file :avatar, options
70 end
71end
72
73def temporary_rails_env(new_env)
74 old_env = defined?(RAILS_ENV) ? RAILS_ENV : nil
75 silence_warnings do
76 Object.const_set("RAILS_ENV", new_env)
77 end
78 yield
79 silence_warnings do
80 Object.const_set("RAILS_ENV", old_env)
81 end
82end
diff --git a/vendor/plugins/paperclip/test/integration_test.rb b/vendor/plugins/paperclip/test/integration_test.rb
new file mode 100644
index 0000000..f7014ac
--- /dev/null
+++ b/vendor/plugins/paperclip/test/integration_test.rb
@@ -0,0 +1,481 @@
1require 'test/helper'
2
3class IntegrationTest < Test::Unit::TestCase
4 context "Many models at once" do
5 setup do
6 rebuild_model
7 @file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
8 300.times do |i|
9 Dummy.create! :avatar => @file
10 end
11 end
12
13 should "not exceed the open file limit" do
14 assert_nothing_raised do
15 dummies = Dummy.find(:all)
16 dummies.each { |dummy| dummy.avatar }
17 end
18 end
19 end
20
21 context "An attachment" do
22 setup do
23 rebuild_model :styles => { :thumb => "50x50#" }
24 @dummy = Dummy.new
25 @file = File.new(File.join(File.dirname(__FILE__),
26 "fixtures",
27 "5k.png"), 'rb')
28 @dummy.avatar = @file
29 assert @dummy.save
30 end
31
32 teardown { @file.close }
33
34 should "create its thumbnails properly" do
35 assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:thumb)}"`
36 end
37
38 context "redefining its attachment styles" do
39 setup do
40 Dummy.class_eval do
41 has_attached_file :avatar, :styles => { :thumb => "150x25#" }
42 has_attached_file :avatar, :styles => { :thumb => "150x25#", :dynamic => lambda { |a| '50x50#' } }
43 end
44 @d2 = Dummy.find(@dummy.id)
45 @d2.avatar.reprocess!
46 @d2.save
47 end
48
49 should "create its thumbnails properly" do
50 assert_match /\b150x25\b/, `identify "#{@dummy.avatar.path(:thumb)}"`
51 assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:dynamic)}"`
52 end
53 end
54 end
55
56 context "A model that modifies its original" do
57 setup do
58 rebuild_model :styles => { :original => "2x2#" }
59 @dummy = Dummy.new
60 @file = File.new(File.join(File.dirname(__FILE__),
61 "fixtures",
62 "5k.png"), 'rb')
63 @dummy.avatar = @file
64 end
65
66 should "report the file size of the processed file and not the original" do
67 assert_not_equal @file.size, @dummy.avatar.size
68 end
69
70 teardown { @file.close }
71 end
72
73 context "A model with attachments scoped under an id" do
74 setup do
75 rebuild_model :styles => { :large => "100x100",
76 :medium => "50x50" },
77 :path => ":rails_root/tmp/:id/:attachments/:style.:extension"
78 @dummy = Dummy.new
79 @file = File.new(File.join(File.dirname(__FILE__),
80 "fixtures",
81 "5k.png"), 'rb')
82 @dummy.avatar = @file
83 end
84
85 teardown { @file.close }
86
87 context "when saved" do
88 setup do
89 @dummy.save
90 @saved_path = @dummy.avatar.path(:large)
91 end
92
93 should "have a large file in the right place" do
94 assert File.exists?(@dummy.avatar.path(:large))
95 end
96
97 context "and deleted" do
98 setup do
99 @dummy.avatar.clear
100 @dummy.save
101 end
102
103 should "not have a large file in the right place anymore" do
104 assert ! File.exists?(@saved_path)
105 end
106
107 should "not have its next two parent directories" do
108 assert ! File.exists?(File.dirname(@saved_path))
109 assert ! File.exists?(File.dirname(File.dirname(@saved_path)))
110 end
111
112 before_should "not die if an unexpected SystemCallError happens" do
113 FileUtils.stubs(:rmdir).raises(Errno::EPIPE)
114 end
115 end
116 end
117 end
118
119 context "A model with no attachment validation" do
120 setup do
121 rebuild_model :styles => { :large => "300x300>",
122 :medium => "100x100",
123 :thumb => ["32x32#", :gif] },
124 :default_style => :medium,
125 :url => "/:attachment/:class/:style/:id/:basename.:extension",
126 :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
127 @dummy = Dummy.new
128 end
129
130 should "have its definition return false when asked about whiny_thumbnails" do
131 assert ! Dummy.attachment_definitions[:avatar][:whiny_thumbnails]
132 end
133
134 context "when validates_attachment_thumbnails is called" do
135 setup do
136 Dummy.validates_attachment_thumbnails :avatar
137 end
138
139 should "have its definition return true when asked about whiny_thumbnails" do
140 assert_equal true, Dummy.attachment_definitions[:avatar][:whiny_thumbnails]
141 end
142 end
143
144 context "redefined to have attachment validations" do
145 setup do
146 rebuild_model :styles => { :large => "300x300>",
147 :medium => "100x100",
148 :thumb => ["32x32#", :gif] },
149 :whiny_thumbnails => true,
150 :default_style => :medium,
151 :url => "/:attachment/:class/:style/:id/:basename.:extension",
152 :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
153 end
154
155 should "have its definition return true when asked about whiny_thumbnails" do
156 assert_equal true, Dummy.attachment_definitions[:avatar][:whiny_thumbnails]
157 end
158 end
159 end
160
161 context "A model with no convert_options setting" do
162 setup do
163 rebuild_model :styles => { :large => "300x300>",
164 :medium => "100x100",
165 :thumb => ["32x32#", :gif] },
166 :default_style => :medium,
167 :url => "/:attachment/:class/:style/:id/:basename.:extension",
168 :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
169 @dummy = Dummy.new
170 end
171
172 should "have its definition return nil when asked about convert_options" do
173 assert ! Dummy.attachment_definitions[:avatar][:convert_options]
174 end
175
176 context "redefined to have convert_options setting" do
177 setup do
178 rebuild_model :styles => { :large => "300x300>",
179 :medium => "100x100",
180 :thumb => ["32x32#", :gif] },
181 :convert_options => "-strip -depth 8",
182 :default_style => :medium,
183 :url => "/:attachment/:class/:style/:id/:basename.:extension",
184 :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
185 end
186
187 should "have its definition return convert_options value when asked about convert_options" do
188 assert_equal "-strip -depth 8", Dummy.attachment_definitions[:avatar][:convert_options]
189 end
190 end
191 end
192
193 context "A model with a filesystem attachment" do
194 setup do
195 rebuild_model :styles => { :large => "300x300>",
196 :medium => "100x100",
197 :thumb => ["32x32#", :gif] },
198 :whiny_thumbnails => true,
199 :default_style => :medium,
200 :url => "/:attachment/:class/:style/:id/:basename.:extension",
201 :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
202 @dummy = Dummy.new
203 @file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
204 @bad_file = File.new(File.join(FIXTURES_DIR, "bad.png"), 'rb')
205
206 assert @dummy.avatar = @file
207 assert @dummy.valid?
208 assert @dummy.save
209 end
210
211 should "write and delete its files" do
212 [["434x66", :original],
213 ["300x46", :large],
214 ["100x15", :medium],
215 ["32x32", :thumb]].each do |geo, style|
216 cmd = %Q[identify -format "%wx%h" "#{@dummy.avatar.path(style)}"]
217 assert_equal geo, `#{cmd}`.chomp, cmd
218 end
219
220 saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) }
221
222 @d2 = Dummy.find(@dummy.id)
223 assert_equal "100x15", `identify -format "%wx%h" "#{@d2.avatar.path}"`.chomp
224 assert_equal "434x66", `identify -format "%wx%h" "#{@d2.avatar.path(:original)}"`.chomp
225 assert_equal "300x46", `identify -format "%wx%h" "#{@d2.avatar.path(:large)}"`.chomp
226 assert_equal "100x15", `identify -format "%wx%h" "#{@d2.avatar.path(:medium)}"`.chomp
227 assert_equal "32x32", `identify -format "%wx%h" "#{@d2.avatar.path(:thumb)}"`.chomp
228
229 @dummy.avatar = "not a valid file but not nil"
230 assert_equal File.basename(@file.path), @dummy.avatar_file_name
231 assert @dummy.valid?
232 assert @dummy.save
233
234 saved_paths.each do |p|
235 assert File.exists?(p)
236 end
237
238 @dummy.avatar.clear
239 assert_nil @dummy.avatar_file_name
240 assert @dummy.valid?
241 assert @dummy.save
242
243 saved_paths.each do |p|
244 assert ! File.exists?(p)
245 end
246
247 @d2 = Dummy.find(@dummy.id)
248 assert_nil @d2.avatar_file_name
249 end
250
251 should "work exactly the same when new as when reloaded" do
252 @d2 = Dummy.find(@dummy.id)
253
254 assert_equal @dummy.avatar_file_name, @d2.avatar_file_name
255 [:thumb, :medium, :large, :original].each do |style|
256 assert_equal @dummy.avatar.path(style), @d2.avatar.path(style)
257 end
258
259 saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) }
260
261 @d2.avatar.clear
262 assert @d2.save
263
264 saved_paths.each do |p|
265 assert ! File.exists?(p)
266 end
267 end
268
269 should "know the difference between good files, bad files, and not files" do
270 expected = @dummy.avatar.to_file
271 @dummy.avatar = "not a file"
272 assert @dummy.valid?
273 assert_equal expected.path, @dummy.avatar.path
274 expected.close
275
276 @dummy.avatar = @bad_file
277 assert ! @dummy.valid?
278 end
279
280 should "know the difference between good files, bad files, and not files when validating" do
281 Dummy.validates_attachment_presence :avatar
282 @d2 = Dummy.find(@dummy.id)
283 @d2.avatar = @file
284 assert @d2.valid?, @d2.errors.full_messages.inspect
285 @d2.avatar = @bad_file
286 assert ! @d2.valid?
287 end
288
289 should "be able to reload without saving and not have the file disappear" do
290 @dummy.avatar = @file
291 assert @dummy.save
292 @dummy.avatar.clear
293 assert_nil @dummy.avatar_file_name
294 @dummy.reload
295 assert_equal "5k.png", @dummy.avatar_file_name
296 end
297
298 context "that is assigned its file from another Paperclip attachment" do
299 setup do
300 @dummy2 = Dummy.new
301 @file2 = File.new(File.join(FIXTURES_DIR, "12k.png"), 'rb')
302 assert @dummy2.avatar = @file2
303 @dummy2.save
304 end
305
306 should "work when assigned a file" do
307 assert_not_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`,
308 `identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"`
309
310 assert @dummy.avatar = @dummy2.avatar
311 @dummy.save
312 assert_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`,
313 `identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"`
314 end
315 end
316
317 end
318
319 context "A model with an attachments association and a Paperclip attachment" do
320 setup do
321 Dummy.class_eval do
322 has_many :attachments, :class_name => 'Dummy'
323 end
324
325 @dummy = Dummy.new
326 @dummy.avatar = File.new(File.join(File.dirname(__FILE__),
327 "fixtures",
328 "5k.png"), 'rb')
329 end
330
331 should "should not error when saving" do
332 assert_nothing_raised do
333 @dummy.save!
334 end
335 end
336 end
337
338 if ENV['S3_TEST_BUCKET']
339 def s3_files_for attachment
340 [:thumb, :medium, :large, :original].inject({}) do |files, style|
341 data = `curl "#{attachment.url(style)}" 2>/dev/null`.chomp
342 t = Tempfile.new("paperclip-test")
343 t.binmode
344 t.write(data)
345 t.rewind
346 files[style] = t
347 files
348 end
349 end
350
351 def s3_headers_for attachment, style
352 `curl --head "#{attachment.url(style)}" 2>/dev/null`.split("\n").inject({}) do |h,head|
353 split_head = head.chomp.split(/\s*:\s*/, 2)
354 h[split_head.first.downcase] = split_head.last unless split_head.empty?
355 h
356 end
357 end
358
359 context "A model with an S3 attachment" do
360 setup do
361 rebuild_model :styles => { :large => "300x300>",
362 :medium => "100x100",
363 :thumb => ["32x32#", :gif] },
364 :storage => :s3,
365 :whiny_thumbnails => true,
366 # :s3_options => {:logger => Logger.new(StringIO.new)},
367 :s3_credentials => File.new(File.join(File.dirname(__FILE__), "s3.yml")),
368 :default_style => :medium,
369 :bucket => ENV['S3_TEST_BUCKET'],
370 :path => ":class/:attachment/:id/:style/:basename.:extension"
371 @dummy = Dummy.new
372 @file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
373 @bad_file = File.new(File.join(FIXTURES_DIR, "bad.png"), 'rb')
374
375 assert @dummy.avatar = @file
376 assert @dummy.valid?
377 assert @dummy.save
378
379 @files_on_s3 = s3_files_for @dummy.avatar
380 end
381
382 should "write and delete its files" do
383 [["434x66", :original],
384 ["300x46", :large],
385 ["100x15", :medium],
386 ["32x32", :thumb]].each do |geo, style|
387 cmd = %Q[identify -format "%wx%h" "#{@files_on_s3[style].path}"]
388 assert_equal geo, `#{cmd}`.chomp, cmd
389 end
390
391 @d2 = Dummy.find(@dummy.id)
392 @d2_files = s3_files_for @d2.avatar
393 [["434x66", :original],
394 ["300x46", :large],
395 ["100x15", :medium],
396 ["32x32", :thumb]].each do |geo, style|
397 cmd = %Q[identify -format "%wx%h" "#{@d2_files[style].path}"]
398 assert_equal geo, `#{cmd}`.chomp, cmd
399 end
400
401 @dummy.avatar = "not a valid file but not nil"
402 assert_equal File.basename(@file.path), @dummy.avatar_file_name
403 assert @dummy.valid?
404 assert @dummy.save
405
406 saved_keys = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.to_file(s) }
407
408 saved_keys.each do |key|
409 assert key.exists?
410 end
411
412 @dummy.avatar.clear
413 assert_nil @dummy.avatar_file_name
414 assert @dummy.valid?
415 assert @dummy.save
416
417 saved_keys.each do |key|
418 assert ! key.exists?
419 end
420
421 @d2 = Dummy.find(@dummy.id)
422 assert_nil @d2.avatar_file_name
423 end
424
425 should "work exactly the same when new as when reloaded" do
426 @d2 = Dummy.find(@dummy.id)
427
428 assert_equal @dummy.avatar_file_name, @d2.avatar_file_name
429 [:thumb, :medium, :large, :original].each do |style|
430 assert_equal @dummy.avatar.to_file(style).to_s, @d2.avatar.to_file(style).to_s
431 end
432
433 saved_keys = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.to_file(s) }
434
435 @d2.avatar.clear
436 assert @d2.save
437
438 saved_keys.each do |key|
439 assert ! key.exists?
440 end
441 end
442
443 should "know the difference between good files, bad files, not files, and nil" do
444 expected = @dummy.avatar.to_file
445 @dummy.avatar = "not a file"
446 assert @dummy.valid?
447 assert_equal expected.full_name, @dummy.avatar.to_file.full_name
448
449 @dummy.avatar = @bad_file
450 assert ! @dummy.valid?
451 @dummy.avatar = nil
452 assert @dummy.valid?
453
454 Dummy.validates_attachment_presence :avatar
455 @d2 = Dummy.find(@dummy.id)
456 @d2.avatar = @file
457 assert @d2.valid?
458 @d2.avatar = @bad_file
459 assert ! @d2.valid?
460 @d2.avatar = nil
461 assert ! @d2.valid?
462 end
463
464 should "be able to reload without saving and not have the file disappear" do
465 @dummy.avatar = @file
466 assert @dummy.save
467 @dummy.avatar = nil
468 assert_nil @dummy.avatar_file_name
469 @dummy.reload
470 assert_equal "5k.png", @dummy.avatar_file_name
471 end
472
473 should "have the right content type" do
474 headers = s3_headers_for(@dummy.avatar, :original)
475 p headers
476 assert_equal 'image/png', headers['content-type']
477 end
478 end
479 end
480end
481
diff --git a/vendor/plugins/paperclip/test/iostream_test.rb b/vendor/plugins/paperclip/test/iostream_test.rb
new file mode 100644
index 0000000..97030b5
--- /dev/null
+++ b/vendor/plugins/paperclip/test/iostream_test.rb
@@ -0,0 +1,71 @@
1require 'test/helper'
2
3class IOStreamTest < Test::Unit::TestCase
4 context "IOStream" do
5 should "be included in IO, File, Tempfile, and StringIO" do
6 [IO, File, Tempfile, StringIO].each do |klass|
7 assert klass.included_modules.include?(IOStream), "Not in #{klass}"
8 end
9 end
10 end
11
12 context "A file" do
13 setup do
14 @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
15 end
16
17 teardown { @file.close }
18
19 context "that is sent #stream_to" do
20
21 context "and given a String" do
22 setup do
23 FileUtils.mkdir_p(File.join(ROOT, 'tmp'))
24 assert @result = @file.stream_to(File.join(ROOT, 'tmp', 'iostream.string.test'))
25 end
26
27 should "return a File" do
28 assert @result.is_a?(File)
29 end
30
31 should "contain the same data as the original file" do
32 @file.rewind; @result.rewind
33 assert_equal @file.read, @result.read
34 end
35 end
36
37 context "and given a Tempfile" do
38 setup do
39 tempfile = Tempfile.new('iostream.test')
40 tempfile.binmode
41 assert @result = @file.stream_to(tempfile)
42 end
43
44 should "return a Tempfile" do
45 assert @result.is_a?(Tempfile)
46 end
47
48 should "contain the same data as the original file" do
49 @file.rewind; @result.rewind
50 assert_equal @file.read, @result.read
51 end
52 end
53
54 end
55
56 context "that is sent #to_tempfile" do
57 setup do
58 assert @tempfile = @file.to_tempfile
59 end
60
61 should "convert it to a Tempfile" do
62 assert @tempfile.is_a?(Tempfile)
63 end
64
65 should "have the Tempfile contain the same data as the file" do
66 @file.rewind; @tempfile.rewind
67 assert_equal @file.read, @tempfile.read
68 end
69 end
70 end
71end
diff --git a/vendor/plugins/paperclip/test/matchers/have_attached_file_matcher_test.rb b/vendor/plugins/paperclip/test/matchers/have_attached_file_matcher_test.rb
new file mode 100644
index 0000000..b29ec37
--- /dev/null
+++ b/vendor/plugins/paperclip/test/matchers/have_attached_file_matcher_test.rb
@@ -0,0 +1,21 @@
1require 'test/helper'
2
3class HaveAttachedFileMatcherTest < Test::Unit::TestCase
4 context "have_attached_file" do
5 setup do
6 @dummy_class = reset_class "Dummy"
7 reset_table "dummies"
8 @matcher = self.class.have_attached_file(:avatar)
9 end
10
11 should "reject a class with no attachment" do
12 assert_rejects @matcher, @dummy_class
13 end
14
15 should "accept a class with an attachment" do
16 modify_table("dummies"){|d| d.string :avatar_file_name }
17 @dummy_class.has_attached_file :avatar
18 assert_accepts @matcher, @dummy_class
19 end
20 end
21end
diff --git a/vendor/plugins/paperclip/test/matchers/validate_attachment_content_type_matcher_test.rb b/vendor/plugins/paperclip/test/matchers/validate_attachment_content_type_matcher_test.rb
new file mode 100644
index 0000000..241eb5f
--- /dev/null
+++ b/vendor/plugins/paperclip/test/matchers/validate_attachment_content_type_matcher_test.rb
@@ -0,0 +1,30 @@
1require 'test/helper'
2
3class ValidateAttachmentContentTypeMatcherTest < Test::Unit::TestCase
4 context "validate_attachment_content_type" do
5 setup do
6 reset_table("dummies") do |d|
7 d.string :avatar_file_name
8 end
9 @dummy_class = reset_class "Dummy"
10 @dummy_class.has_attached_file :avatar
11 @matcher = self.class.validate_attachment_content_type(:avatar).
12 allowing(%w(image/png image/jpeg)).
13 rejecting(%w(audio/mp3 application/octet-stream))
14 end
15
16 should "reject a class with no validation" do
17 assert_rejects @matcher, @dummy_class
18 end
19
20 should "reject a class with a validation that doesn't match" do
21 @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{audio/.*}
22 assert_rejects @matcher, @dummy_class
23 end
24
25 should "accept a class with a validation" do
26 @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{image/.*}
27 assert_accepts @matcher, @dummy_class
28 end
29 end
30end
diff --git a/vendor/plugins/paperclip/test/matchers/validate_attachment_presence_matcher_test.rb b/vendor/plugins/paperclip/test/matchers/validate_attachment_presence_matcher_test.rb
new file mode 100644
index 0000000..860a760
--- /dev/null
+++ b/vendor/plugins/paperclip/test/matchers/validate_attachment_presence_matcher_test.rb
@@ -0,0 +1,21 @@
1require 'test/helper'
2
3class ValidateAttachmentPresenceMatcherTest < Test::Unit::TestCase
4 context "validate_attachment_presence" do
5 setup do
6 reset_table("dummies"){|d| d.string :avatar_file_name }
7 @dummy_class = reset_class "Dummy"
8 @dummy_class.has_attached_file :avatar
9 @matcher = self.class.validate_attachment_presence(:avatar)
10 end
11
12 should "reject a class with no validation" do
13 assert_rejects @matcher, @dummy_class
14 end
15
16 should "accept a class with a validation" do
17 @dummy_class.validates_attachment_presence :avatar
18 assert_accepts @matcher, @dummy_class
19 end
20 end
21end
diff --git a/vendor/plugins/paperclip/test/matchers/validate_attachment_size_matcher_test.rb b/vendor/plugins/paperclip/test/matchers/validate_attachment_size_matcher_test.rb
new file mode 100644
index 0000000..7e4c9b4
--- /dev/null
+++ b/vendor/plugins/paperclip/test/matchers/validate_attachment_size_matcher_test.rb
@@ -0,0 +1,50 @@
1require 'test/helper'
2
3class ValidateAttachmentSizeMatcherTest < Test::Unit::TestCase
4 context "validate_attachment_size" do
5 setup do
6 reset_table("dummies") do |d|
7 d.string :avatar_file_name
8 end
9 @dummy_class = reset_class "Dummy"
10 @dummy_class.has_attached_file :avatar
11 end
12
13 context "of limited size" do
14 setup{ @matcher = self.class.validate_attachment_size(:avatar).in(256..1024) }
15
16 should "reject a class with no validation" do
17 assert_rejects @matcher, @dummy_class
18 end
19
20 should "reject a class with a validation that's too high" do
21 @dummy_class.validates_attachment_size :avatar, :in => 256..2048
22 assert_rejects @matcher, @dummy_class
23 end
24
25 should "reject a class with a validation that's too low" do
26 @dummy_class.validates_attachment_size :avatar, :in => 0..1024
27 assert_rejects @matcher, @dummy_class
28 end
29
30 should "accept a class with a validation that matches" do
31 @dummy_class.validates_attachment_size :avatar, :in => 256..1024
32 assert_accepts @matcher, @dummy_class
33 end
34 end
35
36 context "validates_attachment_size with infinite range" do
37 setup{ @matcher = self.class.validate_attachment_size(:avatar) }
38
39 should "accept a class with an upper limit" do
40 @dummy_class.validates_attachment_size :avatar, :less_than => 1
41 assert_accepts @matcher, @dummy_class
42 end
43
44 should "accept a class with no upper limit" do
45 @dummy_class.validates_attachment_size :avatar, :greater_than => 1
46 assert_accepts @matcher, @dummy_class
47 end
48 end
49 end
50end
diff --git a/vendor/plugins/paperclip/test/paperclip_test.rb b/vendor/plugins/paperclip/test/paperclip_test.rb
new file mode 100644
index 0000000..8365649
--- /dev/null
+++ b/vendor/plugins/paperclip/test/paperclip_test.rb
@@ -0,0 +1,233 @@
1require 'test/helper'
2
3class PaperclipTest < Test::Unit::TestCase
4 [:image_magick_path, :convert_path].each do |path|
5 context "Calling Paperclip.run with an #{path} specified" do
6 setup do
7 Paperclip.options[:image_magick_path] = nil
8 Paperclip.options[:convert_path] = nil
9 Paperclip.options[path] = "/usr/bin"
10 end
11
12 should "execute the right command" do
13 Paperclip.expects(:path_for_command).with("convert").returns("/usr/bin/convert")
14 Paperclip.expects(:bit_bucket).returns("/dev/null")
15 Paperclip.expects(:"`").with("/usr/bin/convert one.jpg two.jpg 2>/dev/null")
16 Paperclip.run("convert", "one.jpg two.jpg")
17 end
18 end
19 end
20
21 context "Calling Paperclip.run with no path specified" do
22 setup do
23 Paperclip.options[:image_magick_path] = nil
24 Paperclip.options[:convert_path] = nil
25 end
26
27 should "execute the right command" do
28 Paperclip.expects(:path_for_command).with("convert").returns("convert")
29 Paperclip.expects(:bit_bucket).returns("/dev/null")
30 Paperclip.expects(:"`").with("convert one.jpg two.jpg 2>/dev/null")
31 Paperclip.run("convert", "one.jpg two.jpg")
32 end
33 end
34
35 should "raise when sent #processor and the name of a class that exists but isn't a subclass of Processor" do
36 assert_raises(Paperclip::PaperclipError){ Paperclip.processor(:attachment) }
37 end
38
39 should "raise when sent #processor and the name of a class that doesn't exist" do
40 assert_raises(NameError){ Paperclip.processor(:boogey_man) }
41 end
42
43 should "return a class when sent #processor and the name of a class under Paperclip" do
44 assert_equal ::Paperclip::Thumbnail, Paperclip.processor(:thumbnail)
45 end
46
47 context "Paperclip.bit_bucket" do
48 context "on systems without /dev/null" do
49 setup do
50 File.expects(:exists?).with("/dev/null").returns(false)
51 end
52
53 should "return 'NUL'" do
54 assert_equal "NUL", Paperclip.bit_bucket
55 end
56 end
57
58 context "on systems with /dev/null" do
59 setup do
60 File.expects(:exists?).with("/dev/null").returns(true)
61 end
62
63 should "return '/dev/null'" do
64 assert_equal "/dev/null", Paperclip.bit_bucket
65 end
66 end
67 end
68
69 context "An ActiveRecord model with an 'avatar' attachment" do
70 setup do
71 rebuild_model :path => "tmp/:class/omg/:style.:extension"
72 @file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
73 end
74
75 teardown { @file.close }
76
77 should "not error when trying to also create a 'blah' attachment" do
78 assert_nothing_raised do
79 Dummy.class_eval do
80 has_attached_file :blah
81 end
82 end
83 end
84
85 context "that is attr_protected" do
86 setup do
87 Dummy.class_eval do
88 attr_protected :avatar
89 end
90 @dummy = Dummy.new
91 end
92
93 should "not assign the avatar on mass-set" do
94 @dummy.attributes = { :other => "I'm set!",
95 :avatar => @file }
96
97 assert_equal "I'm set!", @dummy.other
98 assert ! @dummy.avatar?
99 end
100
101 should "still allow assigment on normal set" do
102 @dummy.other = "I'm set!"
103 @dummy.avatar = @file
104
105 assert_equal "I'm set!", @dummy.other
106 assert @dummy.avatar?
107 end
108 end
109
110 context "with a subclass" do
111 setup do
112 class ::SubDummy < Dummy; end
113 end
114
115 should "be able to use the attachment from the subclass" do
116 assert_nothing_raised do
117 @subdummy = SubDummy.create(:avatar => @file)
118 end
119 end
120
121 should "be able to see the attachment definition from the subclass's class" do
122 assert_equal "tmp/:class/omg/:style.:extension", SubDummy.attachment_definitions[:avatar][:path]
123 end
124
125 teardown do
126 Object.send(:remove_const, "SubDummy") rescue nil
127 end
128 end
129
130 should "have an #avatar method" do
131 assert Dummy.new.respond_to?(:avatar)
132 end
133
134 should "have an #avatar= method" do
135 assert Dummy.new.respond_to?(:avatar=)
136 end
137
138 context "that is valid" do
139 setup do
140 @dummy = Dummy.new
141 @dummy.avatar = @file
142 end
143
144 should "be valid" do
145 assert @dummy.valid?
146 end
147
148 context "then has a validation added that makes it invalid" do
149 setup do
150 assert @dummy.save
151 Dummy.class_eval do
152 validates_attachment_content_type :avatar, :content_type => ["text/plain"]
153 end
154 @dummy2 = Dummy.find(@dummy.id)
155 end
156
157 should "be invalid when reloaded" do
158 assert ! @dummy2.valid?, @dummy2.errors.inspect
159 end
160
161 should "be able to call #valid? twice without having duplicate errors" do
162 @dummy2.avatar.valid?
163 first_errors = @dummy2.avatar.errors
164 @dummy2.avatar.valid?
165 assert_equal first_errors, @dummy2.avatar.errors
166 end
167 end
168 end
169
170 def self.should_validate validation, options, valid_file, invalid_file
171 context "with #{validation} validation and #{options.inspect} options" do
172 setup do
173 Dummy.send(:"validates_attachment_#{validation}", :avatar, options)
174 @dummy = Dummy.new
175 end
176 context "and assigning nil" do
177 setup do
178 @dummy.avatar = nil
179 @dummy.valid?
180 end
181 if validation == :presence
182 should "have an error on the attachment" do
183 assert @dummy.errors.on(:avatar)
184 end
185 else
186 should "not have an error on the attachment" do
187 assert_nil @dummy.errors.on(:avatar)
188 end
189 end
190 end
191 context "and assigned a valid file" do
192 setup do
193 @dummy.avatar = valid_file
194 @dummy.valid?
195 end
196 should "not have an error when assigned a valid file" do
197 assert ! @dummy.avatar.errors.key?(validation)
198 end
199 should "not have an error on the attachment" do
200 assert_nil @dummy.errors.on(:avatar)
201 end
202 end
203 context "and assigned an invalid file" do
204 setup do
205 @dummy.avatar = invalid_file
206 @dummy.valid?
207 end
208 should "have an error when assigned a valid file" do
209 assert_not_nil @dummy.avatar.errors[validation]
210 end
211 should "have an error on the attachment" do
212 assert @dummy.errors.on(:avatar)
213 end
214 end
215 end
216 end
217
218 [[:presence, {}, "5k.png", nil],
219 [:size, {:in => 1..10240}, nil, "12k.png"],
220 [:size, {:less_than => 10240}, "5k.png", "12k.png"],
221 [:size, {:greater_than => 8096}, "12k.png", "5k.png"],
222 [:content_type, {:content_type => "image/png"}, "5k.png", "text.txt"],
223 [:content_type, {:content_type => "text/plain"}, "text.txt", "5k.png"],
224 [:content_type, {:content_type => %r{image/.*}}, "5k.png", "text.txt"]].each do |args|
225 validation, options, valid_file, invalid_file = args
226 valid_file &&= File.open(File.join(FIXTURES_DIR, valid_file), "rb")
227 invalid_file &&= File.open(File.join(FIXTURES_DIR, invalid_file), "rb")
228
229 should_validate validation, options, valid_file, invalid_file
230 end
231
232 end
233end
diff --git a/vendor/plugins/paperclip/test/processor_test.rb b/vendor/plugins/paperclip/test/processor_test.rb
new file mode 100644
index 0000000..a05f0a9
--- /dev/null
+++ b/vendor/plugins/paperclip/test/processor_test.rb
@@ -0,0 +1,10 @@
1require 'test/helper'
2
3class ProcessorTest < Test::Unit::TestCase
4 should "instantiate and call #make when sent #make to the class" do
5 processor = mock
6 processor.expects(:make).with()
7 Paperclip::Processor.expects(:new).with(:one, :two, :three).returns(processor)
8 Paperclip::Processor.make(:one, :two, :three)
9 end
10end
diff --git a/vendor/plugins/paperclip/test/storage_test.rb b/vendor/plugins/paperclip/test/storage_test.rb
new file mode 100644
index 0000000..e2ed8a4
--- /dev/null
+++ b/vendor/plugins/paperclip/test/storage_test.rb
@@ -0,0 +1,277 @@
1require 'test/helper'
2
3class StorageTest < Test::Unit::TestCase
4 context "Parsing S3 credentials" do
5 setup do
6 rebuild_model :storage => :s3,
7 :bucket => "testing",
8 :s3_credentials => {:not => :important}
9
10 @dummy = Dummy.new
11 @avatar = @dummy.avatar
12
13 @current_env = ENV['RAILS_ENV']
14 end
15
16 teardown do
17 ENV['RAILS_ENV'] = @current_env
18 end
19
20 should "get the correct credentials when RAILS_ENV is production" do
21 ENV['RAILS_ENV'] = 'production'
22 assert_equal({:key => "12345"},
23 @avatar.parse_credentials('production' => {:key => '12345'},
24 :development => {:key => "54321"}))
25 end
26
27 should "get the correct credentials when RAILS_ENV is development" do
28 ENV['RAILS_ENV'] = 'development'
29 assert_equal({:key => "54321"},
30 @avatar.parse_credentials('production' => {:key => '12345'},
31 :development => {:key => "54321"}))
32 end
33
34 should "return the argument if the key does not exist" do
35 ENV['RAILS_ENV'] = "not really an env"
36 assert_equal({:test => "12345"}, @avatar.parse_credentials(:test => "12345"))
37 end
38 end
39
40 context "" do
41 setup do
42 rebuild_model :storage => :s3,
43 :s3_credentials => {},
44 :bucket => "bucket",
45 :path => ":attachment/:basename.:extension",
46 :url => ":s3_path_url"
47 @dummy = Dummy.new
48 @dummy.avatar = StringIO.new(".")
49 end
50
51 should "return a url based on an S3 path" do
52 assert_match %r{^http://s3.amazonaws.com/bucket/avatars/stringio.txt}, @dummy.avatar.url
53 end
54 end
55 context "" do
56 setup do
57 rebuild_model :storage => :s3,
58 :s3_credentials => {},
59 :bucket => "bucket",
60 :path => ":attachment/:basename.:extension",
61 :url => ":s3_domain_url"
62 @dummy = Dummy.new
63 @dummy.avatar = StringIO.new(".")
64 end
65
66 should "return a url based on an S3 subdomain" do
67 assert_match %r{^http://bucket.s3.amazonaws.com/avatars/stringio.txt}, @dummy.avatar.url
68 end
69 end
70 context "" do
71 setup do
72 rebuild_model :storage => :s3,
73 :s3_credentials => {
74 :production => { :bucket => "prod_bucket" },
75 :development => { :bucket => "dev_bucket" }
76 },
77 :s3_host_alias => "something.something.com",
78 :path => ":attachment/:basename.:extension",
79 :url => ":s3_alias_url"
80 @dummy = Dummy.new
81 @dummy.avatar = StringIO.new(".")
82 end
83
84 should "return a url based on the host_alias" do
85 assert_match %r{^http://something.something.com/avatars/stringio.txt}, @dummy.avatar.url
86 end
87 end
88
89 context "Parsing S3 credentials with a bucket in them" do
90 setup do
91 rebuild_model :storage => :s3,
92 :s3_credentials => {
93 :production => { :bucket => "prod_bucket" },
94 :development => { :bucket => "dev_bucket" }
95 }
96 @dummy = Dummy.new
97 end
98
99 should "get the right bucket in production", :before => lambda{ ENV.expects(:[]).returns('production') } do
100 assert_equal "prod_bucket", @dummy.avatar.bucket_name
101 end
102
103 should "get the right bucket in development", :before => lambda{ ENV.expects(:[]).returns('development') } do
104 assert_equal "dev_bucket", @dummy.avatar.bucket_name
105 end
106 end
107
108 context "An attachment with S3 storage" do
109 setup do
110 rebuild_model :storage => :s3,
111 :bucket => "testing",
112 :path => ":attachment/:style/:basename.:extension",
113 :s3_credentials => {
114 'access_key_id' => "12345",
115 'secret_access_key' => "54321"
116 }
117 end
118
119 should "be extended by the S3 module" do
120 assert Dummy.new.avatar.is_a?(Paperclip::Storage::S3)
121 end
122
123 should "not be extended by the Filesystem module" do
124 assert ! Dummy.new.avatar.is_a?(Paperclip::Storage::Filesystem)
125 end
126
127 context "when assigned" do
128 setup do
129 @file = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
130 @dummy = Dummy.new
131 @dummy.avatar = @file
132 end
133
134 teardown { @file.close }
135
136 should "not get a bucket to get a URL" do
137 @dummy.avatar.expects(:s3).never
138 @dummy.avatar.expects(:s3_bucket).never
139 assert_match %r{^http://s3\.amazonaws\.com/testing/avatars/original/5k\.png}, @dummy.avatar.url
140 end
141
142 context "and saved" do
143 setup do
144 @s3_mock = stub
145 @bucket_mock = stub
146 RightAws::S3.expects(:new).with("12345", "54321", {}).returns(@s3_mock)
147 @s3_mock.expects(:bucket).with("testing", true, "public-read").returns(@bucket_mock)
148 @key_mock = stub
149 @bucket_mock.expects(:key).returns(@key_mock)
150 @key_mock.expects(:data=)
151 @key_mock.expects(:put).with(nil, 'public-read', 'Content-type' => 'image/png')
152 @dummy.save
153 end
154
155 should "succeed" do
156 assert true
157 end
158 end
159
160 context "and remove" do
161 setup do
162 @s3_mock = stub
163 @bucket_mock = stub
164 RightAws::S3.expects(:new).with("12345", "54321", {}).returns(@s3_mock)
165 @s3_mock.expects(:bucket).with("testing", true, "public-read").returns(@bucket_mock)
166 @key_mock = stub
167 @bucket_mock.expects(:key).at_least(2).returns(@key_mock)
168 @key_mock.expects(:delete)
169 @dummy.destroy_attached_files
170 end
171
172 should "succeed" do
173 assert true
174 end
175 end
176 end
177 end
178
179 context "An attachment with S3 storage and bucket defined as a Proc" do
180 setup do
181 rebuild_model :storage => :s3,
182 :bucket => lambda { |attachment| "bucket_#{attachment.instance.other}" },
183 :s3_credentials => {:not => :important}
184 end
185
186 should "get the right bucket name" do
187 assert "bucket_a", Dummy.new(:other => 'a').avatar.bucket_name
188 assert "bucket_b", Dummy.new(:other => 'b').avatar.bucket_name
189 end
190 end
191
192 context "An attachment with S3 storage and specific s3 headers set" do
193 setup do
194 rebuild_model :storage => :s3,
195 :bucket => "testing",
196 :path => ":attachment/:style/:basename.:extension",
197 :s3_credentials => {
198 'access_key_id' => "12345",
199 'secret_access_key' => "54321"
200 },
201 :s3_headers => {'Cache-Control' => 'max-age=31557600'}
202 end
203
204 context "when assigned" do
205 setup do
206 @file = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
207 @dummy = Dummy.new
208 @dummy.avatar = @file
209 end
210
211 teardown { @file.close }
212
213 context "and saved" do
214 setup do
215 @s3_mock = stub
216 @bucket_mock = stub
217 RightAws::S3.expects(:new).with("12345", "54321", {}).returns(@s3_mock)
218 @s3_mock.expects(:bucket).with("testing", true, "public-read").returns(@bucket_mock)
219 @key_mock = stub
220 @bucket_mock.expects(:key).returns(@key_mock)
221 @key_mock.expects(:data=)
222 @key_mock.expects(:put).with(nil,
223 'public-read',
224 'Content-type' => 'image/png',
225 'Cache-Control' => 'max-age=31557600')
226 @dummy.save
227 end
228
229 should "succeed" do
230 assert true
231 end
232 end
233 end
234 end
235
236 unless ENV["S3_TEST_BUCKET"].blank?
237 context "Using S3 for real, an attachment with S3 storage" do
238 setup do
239 rebuild_model :styles => { :thumb => "100x100", :square => "32x32#" },
240 :storage => :s3,
241 :bucket => ENV["S3_TEST_BUCKET"],
242 :path => ":class/:attachment/:id/:style.:extension",
243 :s3_credentials => File.new(File.join(File.dirname(__FILE__), "s3.yml"))
244
245 Dummy.delete_all
246 @dummy = Dummy.new
247 end
248
249 should "be extended by the S3 module" do
250 assert Dummy.new.avatar.is_a?(Paperclip::Storage::S3)
251 end
252
253 context "when assigned" do
254 setup do
255 @file = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
256 @dummy.avatar = @file
257 end
258
259 teardown { @file.close }
260
261 should "still return a Tempfile when sent #to_io" do
262 assert_equal Tempfile, @dummy.avatar.to_io.class
263 end
264
265 context "and saved" do
266 setup do
267 @dummy.save
268 end
269
270 should "be on S3" do
271 assert true
272 end
273 end
274 end
275 end
276 end
277end
diff --git a/vendor/plugins/paperclip/test/thumbnail_test.rb b/vendor/plugins/paperclip/test/thumbnail_test.rb
new file mode 100644
index 0000000..624e7fa
--- /dev/null
+++ b/vendor/plugins/paperclip/test/thumbnail_test.rb
@@ -0,0 +1,177 @@
1require 'test/helper'
2
3class ThumbnailTest < Test::Unit::TestCase
4
5 context "A Paperclip Tempfile" do
6 setup do
7 @tempfile = Paperclip::Tempfile.new("file.jpg")
8 end
9
10 should "have its path contain a real extension" do
11 assert_equal ".jpg", File.extname(@tempfile.path)
12 end
13
14 should "be a real Tempfile" do
15 assert @tempfile.is_a?(::Tempfile)
16 end
17 end
18
19 context "Another Paperclip Tempfile" do
20 setup do
21 @tempfile = Paperclip::Tempfile.new("file")
22 end
23
24 should "not have an extension if not given one" do
25 assert_equal "", File.extname(@tempfile.path)
26 end
27
28 should "still be a real Tempfile" do
29 assert @tempfile.is_a?(::Tempfile)
30 end
31 end
32
33 context "An image" do
34 setup do
35 @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
36 end
37
38 teardown { @file.close }
39
40 [["600x600>", "434x66"],
41 ["400x400>", "400x61"],
42 ["32x32<", "434x66"]
43 ].each do |args|
44 context "being thumbnailed with a geometry of #{args[0]}" do
45 setup do
46 @thumb = Paperclip::Thumbnail.new(@file, :geometry => args[0])
47 end
48
49 should "start with dimensions of 434x66" do
50 cmd = %Q[identify -format "%wx%h" "#{@file.path}"]
51 assert_equal "434x66", `#{cmd}`.chomp
52 end
53
54 should "report the correct target geometry" do
55 assert_equal args[0], @thumb.target_geometry.to_s
56 end
57
58 context "when made" do
59 setup do
60 @thumb_result = @thumb.make
61 end
62
63 should "be the size we expect it to be" do
64 cmd = %Q[identify -format "%wx%h" "#{@thumb_result.path}"]
65 assert_equal args[1], `#{cmd}`.chomp
66 end
67 end
68 end
69 end
70
71 context "being thumbnailed at 100x50 with cropping" do
72 setup do
73 @thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x50#")
74 end
75
76 should "report its correct current and target geometries" do
77 assert_equal "100x50#", @thumb.target_geometry.to_s
78 assert_equal "434x66", @thumb.current_geometry.to_s
79 end
80
81 should "report its correct format" do
82 assert_nil @thumb.format
83 end
84
85 should "have whiny turned on by default" do
86 assert @thumb.whiny
87 end
88
89 should "have convert_options set to nil by default" do
90 assert_equal nil, @thumb.convert_options
91 end
92
93 should "send the right command to convert when sent #make" do
94 Paperclip.expects(:"`").with do |arg|
95 arg.match %r{convert\s+"#{File.expand_path(@thumb.file.path)}\[0\]"\s+-resize\s+\"x50\"\s+-crop\s+\"100x50\+114\+0\"\s+\+repage\s+".*?"}
96 end
97 @thumb.make
98 end
99
100 should "create the thumbnail when sent #make" do
101 dst = @thumb.make
102 assert_match /100x50/, `identify "#{dst.path}"`
103 end
104 end
105
106 context "being thumbnailed with convert options set" do
107 setup do
108 @thumb = Paperclip::Thumbnail.new(@file,
109 :geometry => "100x50#",
110 :convert_options => "-strip -depth 8")
111 end
112
113 should "have convert_options value set" do
114 assert_equal "-strip -depth 8", @thumb.convert_options
115 end
116
117 should "send the right command to convert when sent #make" do
118 Paperclip.expects(:"`").with do |arg|
119 arg.match %r{convert\s+"#{File.expand_path(@thumb.file.path)}\[0\]"\s+-resize\s+"x50"\s+-crop\s+"100x50\+114\+0"\s+\+repage\s+-strip\s+-depth\s+8\s+".*?"}
120 end
121 @thumb.make
122 end
123
124 should "create the thumbnail when sent #make" do
125 dst = @thumb.make
126 assert_match /100x50/, `identify "#{dst.path}"`
127 end
128
129 context "redefined to have bad convert_options setting" do
130 setup do
131 @thumb = Paperclip::Thumbnail.new(@file,
132 :geometry => "100x50#",
133 :convert_options => "-this-aint-no-option")
134 end
135
136 should "error when trying to create the thumbnail" do
137 assert_raises(Paperclip::PaperclipError) do
138 @thumb.make
139 end
140 end
141 end
142 end
143 end
144
145 context "A multipage PDF" do
146 setup do
147 @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "twopage.pdf"), 'rb')
148 end
149
150 teardown { @file.close }
151
152 should "start with two pages with dimensions 612x792" do
153 cmd = %Q[identify -format "%wx%h" "#{@file.path}"]
154 assert_equal "612x792"*2, `#{cmd}`.chomp
155 end
156
157 context "being thumbnailed at 100x100 with cropping" do
158 setup do
159 @thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x100#", :format => :png)
160 end
161
162 should "report its correct current and target geometries" do
163 assert_equal "100x100#", @thumb.target_geometry.to_s
164 assert_equal "612x792", @thumb.current_geometry.to_s
165 end
166
167 should "report its correct format" do
168 assert_equal :png, @thumb.format
169 end
170
171 should "create the thumbnail when sent #make" do
172 dst = @thumb.make
173 assert_match /100x100/, `identify "#{dst.path}"`
174 end
175 end
176 end
177end