Rails文件上传利器——paperclip笔记

[b]Github[url]http://wiki.github.com/thoughtbot/paperclip/[/url]
Rdoc[url]http://rdoc.info/projects/thoughtbot/paperclip[/url]
Tutorial[url]http://jimneath.org/2008/04/17/paperclip-attaching-files-in-rails/[/url]
Goodbye attachment_fu, hello Paperclip:[url="http://thewebfellas.com/blog/2008/11/2/goodbye-attachment_fu-hello-paperclip"]Goodbye attachment_fu, hello Paperclip[/url]
Protecting your Paperclip downloads:[url="http://thewebfellas.com/blog/2009/8/29/protecting-your-paperclip-downloads"]Protecting your Paperclip downloads[/url]


[/b][b]安装:[/b]
script/plugin install git://github.com/thoughtbot/paperclip.git


[b]依赖:[/b]
paperclip依赖于[url="http://www.imagemagick.org/"]ImageMagick[/url]
大部分linux包管理器(如apt yum portage等)中应该都能找到这个软件包。

如果准备用ImageMagick为pdf文件做后期处理,需要安装ghostscript。ghostscript并不总是随着ImageMagick一起安装,可能需要单独安装。

[b]用法:[/b]
执行
script/generate paperclip User avatar

将生成如下migration:
class AddAvatarColumnsToUser < ActiveRecord::Migration
def self.up
add_column :users, :avatar_file_name, :string
add_column :users, :avatar_content_type, :string
add_column :users, :avatar_file_size, :integer
add_column :users, :avatar_updated_at, :datetime
end

def self.down
remove_column :users, :avatar_file_name
remove_column :users, :avatar_content_type
remove_column :users, :avatar_file_size
remove_column :users, :avatar_updated_at
end
end

修改User模型:
class User < ActiveRecord::Base
has_attached_file :avatar,
:styles => { :medium => "300x300>",
:thumb => "100x100>" }
end

表单:
<% form_for :user, :html => { :multipart => true } do |form| %>
<%= form.file_field :avatar %>
<% end %>

Controller:
def create
@user = User.create( params[:user] )
end

show页面:
<%= image_tag @user.avatar.url %>
<%= image_tag @user.avatar.url(:medium) %>
<%= image_tag @user.avatar.url(:thumb) %>


在Console里可以这样上传文件,同样可以把这些代码写在migration或者seeds.rb中:
user = User.first
File.open('path/to/image.png') { |photo_file| user.photo = photo_file }
user.save

但上面的代码在windows平台下会有问题:
[quote]identify: no decode delegate for this image format `C:/DOCUME~1/ROB~1.CML/LOCALS~1/Temp/stream.2692.0'.[/quote]
解决办法是在open方法中加上参数'b',设置为binary(二进制)模式:
user = User.first
File.open('path/to/image.png', 'rb') { |photo_file| user.photo = photo_file }
user.save


假如model中有如下代码:
has_attached_file :photo, 
:styles => { :original => '250x250>',
:small => '50x50' }

并且你上传了一个1600x1600大小的图片,这个图片将被压缩为250x250大小,保存为orignial(原始图片),而真正的原始图片将被删除。

style也可以是一个proc,proc执行的字符串结果将被当作style。这可以让用户来指定一个photo_width和photo_height值来动态设置style:
has_attached_file :photo, 
:styles => { :original => '250x250>',
:small => '50x50',
:custom => Proc.new { |instance| "#{instance.photo_width}x#{instance.photo_height}>" }


[b]关于style中的"#"和">"我做了一下试验:[/b]

使用#时,paperclip是完全按照指定的大小来切割图片,如果为一张200x80的图片指定style为'100x100#',得到的结果是先把该图片按比例放大到高度为100,也就是250x100,然后取正中间的100x100那部分。

使用>时,paperclip则是直接将图片按比例缩小,比如一张图片尺寸为200x80,指定的style为"100x100>",结果生成的图片则会是100x40。这是图片本身的高[b]或者[/b]宽大于指定的样式的情况。如果图片的高和宽都小于或等于指定的样式,paperclip则不会对图片做任何改动。

不使用#和>时,paperclip的动作和使用>时差不多。区别在于,当图片的高和宽都小于指定的样式时,paperclip会将其按比例放大。例如200x80的图片经过paperclip用"300x300"的style处理后,结果是一张300x120大小的图片。

也就是说,#会让整个指定范围填充满图片,但不能保证显示出完整的图片内容,有可能只是图片的一部分;>会保证整个图片的完整性,但不保证能够填充满整个指定范围;不使用#和>的情况下,整个图片的完整性得以保证,同时还可以保证图片的宽[b]或者[/b]高(其中一边)能达到指定的长度。

[b]改名、设置存放路径以及访问路径[/b]

paperclip允许为上传的文件指定文件名和存放路径以及访问路径。paperclip提供了2个选项:
:path和:url
其中:path是用来指定图片的存放路径的,:url是用来指定访问路径的,不必将访问路径直接指向存放路径,可以将其指向一个Controller的action,再通过action来返回图片,以便控制访问权限等。(参考[url="http://thewebfellas.com/blog/2009/8/29/protecting-your-paperclip-downloads"]Protecting your Paperclip downloads[/url])

这两个选项都可以使用占位符,默认值是这样的:
:url => "/:attachment/:id/:style/:basename.:extension"
:path => ":rails_root/public/:attachment/:id/:style/:basename.:extension"

可用的占位符有:
[quote][b]:rails_root[/b]
The path to the Rails application.(当前Rails程序的路径,等同于RAILS_PATH的值。)
[b]:rails_env[/b]
The current environment (e.g. development, production)(当前运行环境,如:development,production)
[b]:class[/b]
The class name of the model that the attachment is part of, underscored and pluralised for your convenience.(文件拥有者的类名的复数形式,多个单词间以下划线连接。)
[b]:basename[/b]
The name of the originally uploaded file without its extension.(上传的文件原始的名字,不带扩展名。)
[b]:extension[/b]
The file extension of the originally uploaded file.(上传的文件的扩展名)
[b]:id[/b]
The ID of the model that the attachment is part of.(文件拥有者的id)
[b]:id_partition[/b]
The same as :id but formatted as a string using ID partitioning.(和:id一样,但结果是用[url="http://www.37signals.com/svn/archives2/id_partitioning.php"]ID partitioning[/url]格式化过的。)
[b]:attachment[/b]
The name of the attachment attribute (defined in the call to has_attached_file) downcased and pluralised for your enjoyment.(文件作为拥有者的属性的属性名,也就是跟在has_attached_file后面的那个名字,小写、复数形式。)
[b]:style[/b]
The current style of the attachment file being processed (e.g. in the ‘discarding an uploaded image‘ example above the :style would be one of ‘original’ or ‘small’) (文件被以某种样式处理的样式名,该选项用于图片处理。)[/quote]

刚才在使用中又有一点小发现:如果指定了:url而没有指定:path,paperclip会自动把文件存放到:url对应的path中去。但是如果指定了:path却没有指定:url,paperclip则继续使用默认的:url,即:url => "/:attachment/:id/:style/:basename.:extension"。

[b]
自定义占位符:[/b]

很简单,比如要添加一个叫:symbol的占位符:
Paperclip::Attachment.interpolations[:symbol] = proc do |attachment, style|
attachment.instance.symbol.downcase
end

[color=gray]代码中的attachment.instance将会返回文件拥有者的对象。[/color]
完了就可以这样使用:
has_attached_file :logo,
:url => '/:attachment/:symbol/:style/:basename.:extension',
:path => ':rails_root/public/:attachment/:symbol/:style/:basename.:extension '


[b]删除、更新文件:[/b]
如果要删除文件,只要将文件拥有者的这个文件属性值设置为nil然后保存即可。重新为其上传一份文件将会覆盖原来的文件,更新文件操作就像更新一般的属性一样简单。如果更新的时候没有上传任何文件,那原文件仍然存在,不会被删除。估计Goodbye attachment_fu, hello Papercli那篇文章写得比较早了,看了一下日期是2008-11-02,文中有这么一句“Things get a little trickier if you want to be able to delete an existing attachment without replacing it using an update action.”,说的是如果在更新操作的时候,没有上传文件,则原文件会被删除,并且后面附有解决办法。但经过我试验(2010-02-26),结果和作者所说的不一样。


[b]验证:[/b]
简单的直接贴一段代码:
  validates_attachment_presence :mp3
validates_attachment_content_type :mp3,
:content_type => [ 'application/mp3', 'application/x-mp3', 'audio/mpeg', 'audio/mp3' ],
:allow_nil => true
validates_attachment_size :mp3, :less_than => 10.megabytes


paperclip的rake tasks:
[quote][b]rake paperclip:clean[/b] # Cleans out invalid attachments.
[b]rake paperclip:refresh[/b] # Refreshes both metadata and thumbnails.
[b]rake paperclip:refresh:metadata [/b] # Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT).
[b]rake paperclip:refresh:thumbnails[/b] # Regenerates thumbnails for a given CLASS (and optional ATTACHMENT).[/quote]

[color=red][b]注:当前版本(2.3.1.1)的validates_attachment_size中,:allow_nil选项没有效果[/b][/color],搜索到这个issues:
[url]http://github.com/thoughtbot/paperclip/issues/issue/134[/url]
暂时就先照着这上面改吧。
把最后一行代码:
validates_inclusion_of :"#{name}_file_size",
:in => range,
:message => message,
:if => options[:if],
:unless => options[:unless]

换成:
validates_inclusion_of :"#{name}_file_size", options.merge(:in => range, :message => message)


结尾部分跑题一下:
[align=center]==========================================跑题开始==========================================[/align]

资料中提到[color=red][b]mass-assignment[/b][/color]([url="http://railscasts.com/episodes/26-hackers-love-mass-assignment"]这里有个视频[/url]说明了mass-assignment是怎么回事,以及rails中的解决办法),查了一下,意思大概是这样的:比如你有一个User model,开放给用户填写的只有name、password、email等字段,但是实际上User model里还有一个admin字段,为boolean类型,现某controller中有如下代码:
@user = User.create params[:user]

假如某恶意用户提交了一个如下的params:
{:user=>{:name=>'hacker', :admin => true}}

那么此用户将获得管理员的权限。

Rails提供了一个attr_protected和一个attr_accessible方法,前者起一个黑名单的作用,后者则起到一个白名单的作用。现在假设有一个User model,只有name一个字段,在不使用这两个方法的情况下,提交{:user=>{:name=>'hacker'}},看看后台的输出:
[quote]Parameters: {"authenticity_token"=>"DNUkfJi/S5WK8XrRu3ADsfz+z/RVNBFODCm3OcgUKs8=", "user"=>{"name"=>"hacker"}}[/quote]
这个字段被提交到了服务器,并且:
INSERT INTO "users" ("name", "created_at", "updated_at") VALUES('hackerr', '2010-02-25 02:15:55', '2010-02-25 02:15:55')

这个字段被保存到了数据库中。
现在假如我在User model里添加这么一句:
attr_protected :name

再次提交相同的内容,后台的输出:[quote]{"authenticity_token"=>"DNUkfJi/S5WK8XrRu3ADsfz+z/RVNBFODCm3OcgUKs8=", "user"=>{"name"=>"hacker"}}[/quote]
还有一句:
[quote]WARNING: Can't mass-assign these protected attributes: name[/quote]
并且执行的SQL变成:
INSERT INTO "users" ("name", "created_at", "updated_at") VALUES(NULL, '2010-02-25 02:20:20', '2010-02-25 02:20:20')

这时候,只能在Controller里手动指定@user.name的值了:
@user.name = "xx";@user.save


[b][url="http://andyhu1007.iteye.com/"]andyhu1007[/url]的一系列Rails安全相关译文,原文是rails官方guides:
[url]http://www.iteye.com/topic/378105[/url]
[url]http://www.iteye.com/topic/378559[/url]
[url]http://www.iteye.com/topic/378622[/url][/b]
[align=center]==========================================跑题结束==========================================[/align]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值