dm-paperclip-r3 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +26 -0
- data/README.rdoc +118 -0
- data/Rakefile +104 -0
- data/init.rb +1 -0
- data/lib/dm-paperclip/attachment.rb +416 -0
- data/lib/dm-paperclip/callback_compatability.rb +33 -0
- data/lib/dm-paperclip/geometry.rb +117 -0
- data/lib/dm-paperclip/interpolations.rb +123 -0
- data/lib/dm-paperclip/iostream.rb +46 -0
- data/lib/dm-paperclip/processor.rb +49 -0
- data/lib/dm-paperclip/storage.rb +257 -0
- data/lib/dm-paperclip/thumbnail.rb +70 -0
- data/lib/dm-paperclip/upfile.rb +47 -0
- data/lib/dm-paperclip/validations.rb +124 -0
- data/lib/dm-paperclip.rb +369 -0
- data/tasks/paperclip_tasks.rake +38 -0
- data/test/attachment_test.rb +361 -0
- data/test/fixtures/12k.png +0 -0
- data/test/fixtures/50x50.png +0 -0
- data/test/fixtures/5k.png +0 -0
- data/test/fixtures/bad.png +1 -0
- data/test/fixtures/text.txt +0 -0
- data/test/geometry_test.rb +142 -0
- data/test/helper.rb +72 -0
- data/test/integration_test.rb +396 -0
- data/test/iostream_test.rb +78 -0
- data/test/paperclip_test.rb +163 -0
- data/test/storage_test.rb +324 -0
- data/test/thumbnail_test.rb +147 -0
- metadata +181 -0
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
LICENSE
|
3
|
+
|
4
|
+
The MIT License
|
5
|
+
|
6
|
+
Copyright (c) 2008 Jon Yurek and thoughtbot, inc.
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
10
|
+
in the Software without restriction, including without limitation the rights
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
13
|
+
furnished to do so, subject to the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be included in
|
16
|
+
all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
24
|
+
THE SOFTWARE.
|
25
|
+
|
26
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
=DataMapper Paperclip R3
|
2
|
+
|
3
|
+
"DM-Paperclip R3" is based on Ken Robertson's wonderful DM-Paperclip and includes patches for compatability with rails > 3.0.0.
|
4
|
+
|
5
|
+
DM-Paperclip is a port of Thoughtbot's Paperclip plugin to work with DataMapper. This plugin is fully compatible with
|
6
|
+
the original ActiveRecord-oriented Paperclip. You could take an existing ActiveRecord database and use it with DataMapper.
|
7
|
+
The module also includes updates validation handling and automatic including of the necessary 'property' fields into
|
8
|
+
your model.
|
9
|
+
|
10
|
+
To use it within your models, you need to ensure the three database fields are included. They are {name}_file_name,
|
11
|
+
{name}_content_type, and {name}_file_size. The first two are strings, the final _file_size column is an integer. So
|
12
|
+
if your user model has an avatar field, then you would add avatar_file_name, avatar_content_type, and avatar_file_size.
|
13
|
+
|
14
|
+
As with the original Paperclip plugin, it allows processing of thumbnails at the time the record is saved though ImageMagick.
|
15
|
+
It processes the thumbnails through the command-line applications instead of using RMagick.
|
16
|
+
|
17
|
+
See the documentation for the +has_attached_file+ method for options.
|
18
|
+
|
19
|
+
==Code
|
20
|
+
|
21
|
+
The code DM-Paperclip-R3 is available at Github:
|
22
|
+
|
23
|
+
git clone git://github.com/joelwreed/dm-paperclip.git
|
24
|
+
|
25
|
+
It is regularly updated to keep in sync with the latest from Thoughtbot.
|
26
|
+
|
27
|
+
Releases are tagged within the repository and versioned the same as the original model. You can also get the latest release
|
28
|
+
packaged as a gem through Rubyforge:
|
29
|
+
|
30
|
+
sudo gem install dm-paperclip-r3
|
31
|
+
|
32
|
+
==Usage
|
33
|
+
|
34
|
+
In your model:
|
35
|
+
|
36
|
+
class User
|
37
|
+
include DataMapper::Resource
|
38
|
+
include Paperclip::Resource
|
39
|
+
property :id, Serial
|
40
|
+
property :username, String
|
41
|
+
has_attached_file :avatar,
|
42
|
+
:styles => { :medium => "300x300>",
|
43
|
+
:thumb => "100x100>" }
|
44
|
+
end
|
45
|
+
|
46
|
+
You will need to add an initializer to configure Paperclip. If on Rails, can add a config/initializers/paperclip.rb, on Merb
|
47
|
+
can use config/init.rb and add it to the Merb::BootLoader.after_app_loads section. Can also use environment configs, rackup
|
48
|
+
file, Rake task, wherever.
|
49
|
+
|
50
|
+
Paperclip.configure do |config|
|
51
|
+
config.root = Rails.root # the application root to anchor relative urls (defaults to Dir.pwd)
|
52
|
+
config.env = Rails.env # server env support, defaults to ENV['RACK_ENV'] or 'development'
|
53
|
+
config.use_dm_validations = true # validate attachment sizes and such, defaults to false
|
54
|
+
config.processors_path = 'lib/pc' # relative path to look for processors, defaults to 'lib/paperclip_processors'
|
55
|
+
end
|
56
|
+
|
57
|
+
Your database will need to add four columns, avatar_file_name (varchar), avatar_content_type (varchar), and
|
58
|
+
avatar_file_size (integer), and avatar_updated_at (datetime). You can either add these manually, auto-
|
59
|
+
migrate, or use the following migration:
|
60
|
+
|
61
|
+
migration( 1, :add_user_paperclip_fields ) do
|
62
|
+
up do
|
63
|
+
modify_table :users do
|
64
|
+
add_column :avatar_file_name, "varchar(255)"
|
65
|
+
add_column :avatar_content_type, "varchar(255)"
|
66
|
+
add_column :avatar_file_size, "integer"
|
67
|
+
add_column :avatar_updated_at, "datetime"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
down do
|
71
|
+
modify_table :users do
|
72
|
+
drop_columns :avatar_file_name, :avatar_content_type, :avatar_file_size, :avatar_updated_at
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
In your edit and new views:
|
78
|
+
|
79
|
+
<% form_for @user, { :action => url(:user), :multipart => true } do %>
|
80
|
+
<%= file_field :name => 'avatar' %>
|
81
|
+
<% end %>
|
82
|
+
|
83
|
+
In your controller:
|
84
|
+
|
85
|
+
def create
|
86
|
+
...
|
87
|
+
@user.avatar = params[:avatar]
|
88
|
+
end
|
89
|
+
|
90
|
+
In your show view:
|
91
|
+
|
92
|
+
<%= image_tag @user.avatar.url %>
|
93
|
+
<%= image_tag @user.avatar.url(:medium) %>
|
94
|
+
<%= image_tag @user.avatar.url(:thumb) %>
|
95
|
+
|
96
|
+
The following validations are available:
|
97
|
+
|
98
|
+
validates_attachment_presence :avatar
|
99
|
+
validates_attachment_content_type :avatar, :content_type => "image/png"
|
100
|
+
validates_attachment_size :avatar, :in => 1..10240
|
101
|
+
validates_attachment_thumbnails :avatar
|
102
|
+
|
103
|
+
In order to use validations, you must have loaded the 'dm-validations' gem into your app
|
104
|
+
(available as a part of dm-more). If the gem isn't loaded before DM-Paperclip is loaded,
|
105
|
+
the validation methods will be excluded. You will also need to include DataMapper::Validate
|
106
|
+
into your mode:
|
107
|
+
|
108
|
+
class User
|
109
|
+
include DataMapper::Resource
|
110
|
+
include DataMapper::Validate
|
111
|
+
include Paperclip::Resource
|
112
|
+
property :id, Serial
|
113
|
+
property :username, String
|
114
|
+
has_attached_file :avatar,
|
115
|
+
:styles => { :medium => "300x300>",
|
116
|
+
:thumb => "100x100>" }
|
117
|
+
validates_attachment_size :avatar, :in => 1..5120
|
118
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
|
6
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
|
7
|
+
require 'dm-core'
|
8
|
+
require 'dm-validations'
|
9
|
+
require 'dm-paperclip-r3'
|
10
|
+
|
11
|
+
desc 'Default: run unit tests.'
|
12
|
+
task :default => [:clean, :test]
|
13
|
+
|
14
|
+
# Test tasks
|
15
|
+
desc 'Test the DM-Paperclip-R3 library.'
|
16
|
+
Rake::TestTask.new(:test) do |t|
|
17
|
+
t.libs << 'dm-paperclip-r3'
|
18
|
+
t.pattern = 'test/**/*_test.rb'
|
19
|
+
t.verbose = true
|
20
|
+
end
|
21
|
+
|
22
|
+
# Console
|
23
|
+
desc "Open an irb session preloaded with this library"
|
24
|
+
task :console do
|
25
|
+
sh "irb -rubygems -r dm-validations -r dm-migrations -r ./lib/dm-paperclip.rb"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Rdoc
|
29
|
+
desc 'Generate documentation for the paperclip plugin.'
|
30
|
+
Rake::RDocTask.new(:doc) do |rdoc|
|
31
|
+
rdoc.rdoc_dir = 'doc'
|
32
|
+
rdoc.title = 'DM-Paperclip-R3'
|
33
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
34
|
+
rdoc.rdoc_files.include('README.rdoc')
|
35
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
36
|
+
end
|
37
|
+
|
38
|
+
# Code coverage
|
39
|
+
task :coverage do
|
40
|
+
system("rm -fr coverage")
|
41
|
+
system("rcov test/test_*.rb")
|
42
|
+
system("open coverage/index.html")
|
43
|
+
end
|
44
|
+
|
45
|
+
# Clean house
|
46
|
+
desc 'Clean up files.'
|
47
|
+
task :clean do |t|
|
48
|
+
FileUtils.rm_rf "doc"
|
49
|
+
FileUtils.rm_rf "coverage"
|
50
|
+
FileUtils.rm_rf "tmp"
|
51
|
+
FileUtils.rm_rf "pkg"
|
52
|
+
FileUtils.rm_rf "log"
|
53
|
+
end
|
54
|
+
|
55
|
+
spec = Gem::Specification.new do |s|
|
56
|
+
s.name = "dm-paperclip-r3"
|
57
|
+
s.version = Paperclip::VERSION
|
58
|
+
s.author = "Joel Reed"
|
59
|
+
s.email = "joelwreed@gmail.com"
|
60
|
+
s.homepage = "https://github.com/joelwreed/dm-paperclip"
|
61
|
+
s.platform = Gem::Platform::RUBY
|
62
|
+
s.summary = "File attachments as attributes for DataMapper, based on the original Paperclip by Jon Yurek at Thoughtbot"
|
63
|
+
s.files = FileList["README.rdoc",
|
64
|
+
"LICENSE",
|
65
|
+
"Rakefile",
|
66
|
+
"init.rb",
|
67
|
+
"{lib,tasks,test}/**/*"].to_a
|
68
|
+
s.require_path = "lib"
|
69
|
+
s.test_files = FileList["test/**/test_*.rb"].to_a
|
70
|
+
s.has_rdoc = true
|
71
|
+
s.extra_rdoc_files = ["README.rdoc"]
|
72
|
+
s.rdoc_options << '--line-numbers' << '--inline-source'
|
73
|
+
s.requirements << "ImageMagick"
|
74
|
+
s.requirements << "data_mapper"
|
75
|
+
end
|
76
|
+
|
77
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
78
|
+
pkg.need_tar = true
|
79
|
+
end
|
80
|
+
|
81
|
+
desc 'Generate gemspec'
|
82
|
+
task :gemspec do
|
83
|
+
File.open("#{spec.name}.gemspec", 'w') { |f| f.puts(spec.to_ruby) }
|
84
|
+
end
|
85
|
+
|
86
|
+
WIN32 = (PLATFORM =~ /win32|cygwin/) rescue nil
|
87
|
+
SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
|
88
|
+
|
89
|
+
desc "Install #{spec.name} #{spec.version}"
|
90
|
+
task :install => [ :package ] do
|
91
|
+
sh "#{SUDO} gem install pkg/#{spec.name}-#{spec.version} --no-update-sources", :verbose => false
|
92
|
+
end
|
93
|
+
|
94
|
+
desc "Release new version"
|
95
|
+
task :release => [:test, :gem] do
|
96
|
+
require 'rubygems'
|
97
|
+
require 'rubyforge'
|
98
|
+
r = RubyForge.new
|
99
|
+
r.login
|
100
|
+
r.add_release spec.rubyforge_project,
|
101
|
+
spec.name,
|
102
|
+
spec.version,
|
103
|
+
File.join("pkg", "#{spec.name}-#{spec.version}.gem")
|
104
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'lib', 'dm-paperclip')
|
@@ -0,0 +1,416 @@
|
|
1
|
+
module 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
|
+
include IOStream
|
7
|
+
|
8
|
+
def self.default_options
|
9
|
+
@default_options ||= {
|
10
|
+
:url => "/system/:attachment/:id/:style/:filename",
|
11
|
+
:path => ":rails_root/public:url",
|
12
|
+
:styles => {},
|
13
|
+
:default_url => "/:attachment/:style/missing.png",
|
14
|
+
:default_style => :original,
|
15
|
+
:validations => [],
|
16
|
+
:storage => :filesystem,
|
17
|
+
:whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails]
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :name, :instance, :styles, :default_style, :convert_options, :queued_for_write, :options
|
22
|
+
|
23
|
+
# Creates an Attachment object. +name+ is the name of the attachment,
|
24
|
+
# +instance+ is the ActiveRecord object instance it's attached to, and
|
25
|
+
# +options+ is the same as the hash passed to +has_attached_file+.
|
26
|
+
def initialize name, instance, options = {}
|
27
|
+
@name = name
|
28
|
+
@instance = instance
|
29
|
+
|
30
|
+
options = self.class.default_options.merge(options)
|
31
|
+
|
32
|
+
@url = options[:url]
|
33
|
+
@url = @url.call(self) if @url.is_a?(Proc)
|
34
|
+
@path = options[:path]
|
35
|
+
@path = @path.call(self) if @path.is_a?(Proc)
|
36
|
+
@styles = options[:styles]
|
37
|
+
@styles = @styles.call(self) if @styles.is_a?(Proc)
|
38
|
+
@default_url = options[:default_url]
|
39
|
+
@validations = options[:validations]
|
40
|
+
@default_style = options[:default_style]
|
41
|
+
@storage = options[:storage]
|
42
|
+
@whiny = options[:whiny_thumbnails] || options[:whiny]
|
43
|
+
@convert_options = options[:convert_options] || {}
|
44
|
+
@processors = options[:processors] || [:thumbnail]
|
45
|
+
@options = options
|
46
|
+
@queued_for_delete = []
|
47
|
+
@queued_for_write = {}
|
48
|
+
@errors = {}
|
49
|
+
@validation_errors = nil
|
50
|
+
@dirty = false
|
51
|
+
|
52
|
+
normalize_style_definition
|
53
|
+
initialize_storage
|
54
|
+
end
|
55
|
+
|
56
|
+
# What gets called when you call instance.attachment = File. It clears
|
57
|
+
# errors, assigns attributes, processes the file, and runs validations. It
|
58
|
+
# also queues up the previous file for deletion, to be flushed away on
|
59
|
+
# #save of its host. In addition to form uploads, you can also assign
|
60
|
+
# another Paperclip attachment:
|
61
|
+
# new_user.avatar = old_user.avatar
|
62
|
+
# If the file that is assigned is not valid, the processing (i.e.
|
63
|
+
# thumbnailing, etc) will NOT be run.
|
64
|
+
def assign uploaded_file
|
65
|
+
|
66
|
+
ensure_required_accessors!
|
67
|
+
|
68
|
+
if uploaded_file.is_a?(Paperclip::Attachment)
|
69
|
+
uploaded_file = uploaded_file.to_file(:original)
|
70
|
+
close_uploaded_file = uploaded_file.respond_to?(:close)
|
71
|
+
end
|
72
|
+
|
73
|
+
return nil unless valid_assignment?(uploaded_file)
|
74
|
+
|
75
|
+
uploaded_file.binmode if uploaded_file.respond_to? :binmode
|
76
|
+
self.clear
|
77
|
+
|
78
|
+
return nil if uploaded_file.nil?
|
79
|
+
|
80
|
+
if uploaded_file.respond_to?(:[])
|
81
|
+
uploaded_file = uploaded_file.to_mash
|
82
|
+
|
83
|
+
@queued_for_write[:original] = to_tempfile(uploaded_file)
|
84
|
+
instance_write(:file_name, uploaded_file['filename'].strip.gsub(/[^\w\d\.\-]+/, '_'))
|
85
|
+
instance_write(:content_type, uploaded_file['content_type'] ? uploaded_file['content_type'].strip : uploaded_file['tempfile'].content_type.to_s.strip)
|
86
|
+
instance_write(:file_size, uploaded_file['size'] ? uploaded_file['size'].to_i : uploaded_file['tempfile'].size.to_i)
|
87
|
+
instance_write(:updated_at, Time.now)
|
88
|
+
else
|
89
|
+
@queued_for_write[:original] = uploaded_file.to_tempfile
|
90
|
+
instance_write(:file_name, uploaded_file.original_filename.strip.gsub(/[^\w\d\.\-]+/, '_'))
|
91
|
+
instance_write(:content_type, uploaded_file.content_type.to_s.strip)
|
92
|
+
instance_write(:file_size, uploaded_file.size.to_i)
|
93
|
+
instance_write(:updated_at, Time.now)
|
94
|
+
end
|
95
|
+
|
96
|
+
@dirty = true
|
97
|
+
|
98
|
+
post_process if valid?
|
99
|
+
|
100
|
+
# Reset the file size if the original file was reprocessed.
|
101
|
+
instance_write(:file_size, @queued_for_write[:original].size.to_i)
|
102
|
+
|
103
|
+
ensure
|
104
|
+
uploaded_file.close if close_uploaded_file
|
105
|
+
validate
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the public URL of the attachment, with a given style. Note that
|
109
|
+
# this does not necessarily need to point to a file that your web server
|
110
|
+
# can access and can point to an action in your app, if you need fine
|
111
|
+
# grained security. This is not recommended if you don't need the
|
112
|
+
# security, however, for performance reasons. set
|
113
|
+
# include_updated_timestamp to false if you want to stop the attachment
|
114
|
+
# update time appended to the url
|
115
|
+
def url style = default_style, include_updated_timestamp = true
|
116
|
+
the_url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style)
|
117
|
+
include_updated_timestamp && updated_at ? [the_url, updated_at.to_time.to_i].compact.join(the_url.include?("?") ? "&" : "?") : the_url
|
118
|
+
end
|
119
|
+
|
120
|
+
# Returns the path of the attachment as defined by the :path option. If the
|
121
|
+
# file is stored in the filesystem the path refers to the path of the file
|
122
|
+
# on disk. If the file is stored in S3, the path is the "key" part of the
|
123
|
+
# URL, and the :bucket option refers to the S3 bucket.
|
124
|
+
def path style = default_style
|
125
|
+
original_filename.nil? ? nil : interpolate(@path, style)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Alias to +url+
|
129
|
+
def to_s style = nil
|
130
|
+
url(style)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns true if there are no errors on this attachment.
|
134
|
+
def valid?
|
135
|
+
validate
|
136
|
+
errors.empty?
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns an array containing the errors on this attachment.
|
140
|
+
def errors
|
141
|
+
@errors
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns true if there are changes that need to be saved.
|
145
|
+
def dirty?
|
146
|
+
@dirty
|
147
|
+
end
|
148
|
+
|
149
|
+
# Saves the file, if there are no errors. If there are, it flushes them to
|
150
|
+
# the instance's errors and returns false, cancelling the save.
|
151
|
+
def save
|
152
|
+
if valid?
|
153
|
+
flush_deletes
|
154
|
+
flush_writes
|
155
|
+
@dirty = false
|
156
|
+
true
|
157
|
+
else
|
158
|
+
flush_errors
|
159
|
+
false
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Clears out the attachment. Has the same effect as previously assigning
|
164
|
+
# nil to the attachment. Does NOT save. If you wish to clear AND save,
|
165
|
+
# use #destroy.
|
166
|
+
def clear
|
167
|
+
queue_existing_for_delete
|
168
|
+
@errors = {}
|
169
|
+
@validation_errors = nil
|
170
|
+
end
|
171
|
+
|
172
|
+
# Destroys the attachment. Has the same effect as previously assigning
|
173
|
+
# nil to the attachment *and saving*. This is permanent. If you wish to
|
174
|
+
# wipe out the existing attachment but not save, use #clear.
|
175
|
+
def destroy
|
176
|
+
clear
|
177
|
+
save
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns the name of the file as originally assigned, and lives in the
|
181
|
+
# <attachment>_file_name attribute of the model.
|
182
|
+
def original_filename
|
183
|
+
instance_read(:file_name)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns the size of the file as originally assigned, and lives in the
|
187
|
+
# <attachment>_file_size attribute of the model.
|
188
|
+
def size
|
189
|
+
instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns the content_type of the file as originally assigned, and lives
|
193
|
+
# in the <attachment>_content_type attribute of the model.
|
194
|
+
def content_type
|
195
|
+
instance_read(:content_type)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Returns the last modified time of the file as originally assigned, and
|
199
|
+
# lives in the <attachment>_updated_at attribute of the model.
|
200
|
+
def updated_at
|
201
|
+
instance_read(:updated_at)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Paths and URLs can have a number of variables interpolated into them
|
205
|
+
# to vary the storage location based on name, id, style, class, etc.
|
206
|
+
# This method is a deprecated access into supplying and retrieving these
|
207
|
+
# interpolations. Future access should use either Paperclip.interpolates
|
208
|
+
# or extend the Paperclip::Interpolations module directly.
|
209
|
+
def self.interpolations
|
210
|
+
warn('[DEPRECATION] Paperclip::Attachment.interpolations is deprecated ' +
|
211
|
+
'and will be removed from future versions. ' +
|
212
|
+
'Use Paperclip.interpolates instead')
|
213
|
+
Paperclip::Interpolations
|
214
|
+
end
|
215
|
+
|
216
|
+
# This method really shouldn't be called that often. It's expected use is
|
217
|
+
# in the paperclip:refresh rake task and that's it. It will regenerate all
|
218
|
+
# thumbnails forcefully, by reobtaining the original file and going through
|
219
|
+
# the post-process again.
|
220
|
+
def reprocess!
|
221
|
+
new_original = Tempfile.new("paperclip-reprocess")
|
222
|
+
new_original.binmode
|
223
|
+
if old_original = to_file(:original)
|
224
|
+
new_original.write( old_original.read )
|
225
|
+
new_original.rewind
|
226
|
+
|
227
|
+
@queued_for_write = { :original => new_original }
|
228
|
+
post_process
|
229
|
+
|
230
|
+
old_original.close if old_original.respond_to?(:close)
|
231
|
+
|
232
|
+
save
|
233
|
+
else
|
234
|
+
true
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns true if a file has been assigned.
|
239
|
+
def file?
|
240
|
+
!original_filename.blank?
|
241
|
+
end
|
242
|
+
|
243
|
+
# Writes the attachment-specific attribute on the instance. For example,
|
244
|
+
# instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
|
245
|
+
# "avatar_file_name" field (assuming the attachment is called avatar).
|
246
|
+
def instance_write(attr, value)
|
247
|
+
setter = :"#{name}_#{attr}="
|
248
|
+
responds = instance.respond_to?(setter)
|
249
|
+
self.instance_variable_set("@_#{setter.to_s.chop}", value)
|
250
|
+
instance.send(setter, value) if responds || attr.to_s == "file_name"
|
251
|
+
end
|
252
|
+
|
253
|
+
# Reads the attachment-specific attribute on the instance. See instance_write
|
254
|
+
# for more details.
|
255
|
+
def instance_read(attr)
|
256
|
+
getter = :"#{name}_#{attr}"
|
257
|
+
responds = instance.respond_to?(getter)
|
258
|
+
cached = self.instance_variable_get("@_#{getter}")
|
259
|
+
return cached if cached
|
260
|
+
instance.send(getter) if responds || attr.to_s == "file_name"
|
261
|
+
end
|
262
|
+
|
263
|
+
private
|
264
|
+
|
265
|
+
def ensure_required_accessors! #:nodoc:
|
266
|
+
%w(file_name).each do |field|
|
267
|
+
unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=")
|
268
|
+
raise PaperclipError.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'")
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def log message #:nodoc:
|
274
|
+
Paperclip.log(message)
|
275
|
+
end
|
276
|
+
|
277
|
+
def valid_assignment? file #:nodoc:
|
278
|
+
if file.is_a?(Hash) || (defined?(Mash) && file.is_a?(Mash))
|
279
|
+
file[:filename] || file['filename']
|
280
|
+
else
|
281
|
+
file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def validate #:nodoc:
|
286
|
+
unless @validation_errors
|
287
|
+
@validation_errors = @validations.inject({}) do |errors, validation|
|
288
|
+
name, options = validation
|
289
|
+
errors[name] = send(:"validate_#{name}", options) if allow_validation?(options)
|
290
|
+
errors
|
291
|
+
end
|
292
|
+
@validation_errors.reject!{|k,v| v == nil }
|
293
|
+
@errors.merge!(@validation_errors)
|
294
|
+
end
|
295
|
+
@validation_errors
|
296
|
+
end
|
297
|
+
|
298
|
+
def allow_validation? options #:nodoc:
|
299
|
+
(options[:if].nil? || check_guard(options[:if])) && (options[:unless].nil? || !check_guard(options[:unless]))
|
300
|
+
end
|
301
|
+
|
302
|
+
def check_guard guard #:nodoc:
|
303
|
+
if guard.respond_to? :call
|
304
|
+
guard.call(instance)
|
305
|
+
elsif ! guard.blank?
|
306
|
+
instance.send(guard.to_s)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def validate_size options #:nodoc:
|
311
|
+
if file? && !options[:range].include?(size.to_i)
|
312
|
+
options[:message].gsub(/:min/, options[:min].to_s).gsub(/:max/, options[:max].to_s)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def validate_presence options #:nodoc:
|
317
|
+
options[:message] unless file?
|
318
|
+
end
|
319
|
+
|
320
|
+
def validate_content_type options #:nodoc:
|
321
|
+
valid_types = [options[:content_type]].flatten
|
322
|
+
unless original_filename.blank?
|
323
|
+
unless valid_types.blank?
|
324
|
+
content_type = instance_read(:content_type)
|
325
|
+
unless valid_types.any?{|t| content_type.nil? || t === content_type }
|
326
|
+
options[:message] || "is not one of the allowed file types."
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def normalize_style_definition #:nodoc:
|
333
|
+
@styles.each do |name, args|
|
334
|
+
unless args.is_a? Hash
|
335
|
+
dimensions, format = [args, nil].flatten[0..1]
|
336
|
+
format = nil if format.blank?
|
337
|
+
@styles[name] = {
|
338
|
+
:processors => @processors,
|
339
|
+
:geometry => dimensions,
|
340
|
+
:format => format,
|
341
|
+
:whiny => @whiny,
|
342
|
+
:convert_options => extra_options_for(name)
|
343
|
+
}
|
344
|
+
else
|
345
|
+
@styles[name] = {
|
346
|
+
:processors => @processors,
|
347
|
+
:whiny => @whiny,
|
348
|
+
:convert_options => extra_options_for(name)
|
349
|
+
}.merge(@styles[name])
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def solidify_style_definitions #:nodoc:
|
355
|
+
@styles.each do |name, args|
|
356
|
+
@styles[name][:geometry] = @styles[name][:geometry].call(instance) if @styles[name][:geometry].respond_to?(:call)
|
357
|
+
@styles[name][:processors] = @styles[name][:processors].call(instance) if @styles[name][:processors].respond_to?(:call)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def initialize_storage #:nodoc:
|
362
|
+
@storage_module = Paperclip::Storage.const_get(@storage.to_s.capitalize)
|
363
|
+
self.extend(@storage_module)
|
364
|
+
end
|
365
|
+
|
366
|
+
def extra_options_for(style) #:nodoc:
|
367
|
+
all_options = convert_options[:all]
|
368
|
+
all_options = all_options.call(instance) if all_options.respond_to?(:call)
|
369
|
+
style_options = convert_options[style]
|
370
|
+
style_options = style_options.call(instance) if style_options.respond_to?(:call)
|
371
|
+
|
372
|
+
[ style_options, all_options ].compact.join(" ")
|
373
|
+
end
|
374
|
+
|
375
|
+
def post_process #:nodoc:
|
376
|
+
return if @queued_for_write[:original].nil?
|
377
|
+
solidify_style_definitions
|
378
|
+
post_process_styles
|
379
|
+
end
|
380
|
+
|
381
|
+
def post_process_styles #:nodoc:
|
382
|
+
@styles.each do |name, args|
|
383
|
+
begin
|
384
|
+
raise RuntimeError.new("Style #{name} has no processors defined.") if args[:processors].blank?
|
385
|
+
@queued_for_write[name] = args[:processors].inject(@queued_for_write[:original]) do |file, processor|
|
386
|
+
Paperclip.processor(processor).make(file, args, self)
|
387
|
+
end
|
388
|
+
rescue PaperclipError => e
|
389
|
+
log("An error was received while processing: #{e.inspect}")
|
390
|
+
(@errors[:processing] ||= []) << e.message if @whiny
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def interpolate pattern, style = default_style #:nodoc:
|
396
|
+
Paperclip::Interpolations.interpolate(pattern, self, style)
|
397
|
+
end
|
398
|
+
|
399
|
+
def queue_existing_for_delete #:nodoc:
|
400
|
+
return unless file?
|
401
|
+
@queued_for_delete += [:original, *@styles.keys].uniq.map do |style|
|
402
|
+
path(style) if exists?(style)
|
403
|
+
end.compact
|
404
|
+
instance_write(:file_name, nil)
|
405
|
+
instance_write(:content_type, nil)
|
406
|
+
instance_write(:file_size, nil)
|
407
|
+
instance_write(:updated_at, nil)
|
408
|
+
end
|
409
|
+
|
410
|
+
def flush_errors #:nodoc:
|
411
|
+
@errors.each do |error, message|
|
412
|
+
[message].flatten.each {|m| instance.errors.add(name, m) }
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module 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
|
33
|
+
end
|