利用paperclip实现图片上传
现在rails上最火的两大上传图片插件是fleximage与paperclip。如果单是处理图片,一气呵成的话,当然是fleximage,但如果还要上其他mp3,flv等附件,做成多态关联,那就选paperclip。嘛,在一般的功能上,paperclip还是比老一辈的上传插件要优胜不少,如什么acts_as_attachment,attachment_fu,还是更轻量化的file_column。
安装支持
本插件要有Rmagick与ImageMagick的外部支持,安装请参照我的另一篇文章。
安装paperclip
ruby script/plugin install git://github.com/thoughtbot/paperclip.git |
打造主体框架
这里涉及到两个模块,User与Photo。 我们是利用user_id来区分相册。
mysql > create database album_development; rails album -d mysql cd album ruby script/plugin install git://github.com/technoweenie/restful-authentication.git ruby script/generate authenticated user sessions ruby script/generate scaffold Photo user :belongs_to is_avatar :boolean |
自己配置config目录下的database.yml的用户名与密码。
接着下来的一步非常关键,我们要给Photo添加上传附件的能力。
ruby script/generate paperclip Photo image rake db :migrate |
我们把附件的名字命名为image,这样Paperclip就会给我们Photo模型增加四个前缀为<attachment>_(我们刚才给予的附件的名字)的属性(<attachment> _file_name , <attachment> _file_size ,<attachment> _content_type ,与<attachment> _updated_at),也就是image_file_name,image_file_size,image_content_type与image_updated_at。
删除public目录下的index.html,并添加路由规则:
map.root :users |
修改users_controller,添加index action
def index; end |
添加对应视图
<%= link_to "相册" ,photos_path %> |
修改_user_bar.html.erb
<div id= "user_bar" > <% if logged_in? %> <%= link_to "注销" ,logout_path, :title => "注销" %> <%= link_to "欢迎,<strong>#{current_user.login}</strong>" ,current_user %> <% else %> <%= link_to "登录" , login_path, :title => "登录" %> <%= link_to "注册" , signup_path, :title => "注册" %> <% end %> </div> |
添加全局模板application.html.erb与全局助手layout_helper.rb
<! DOCTYPE html> <html dir= "ltr" lang= "en-US" > <head> <meta charset= "utf-8" > <!-- simplified version; works on legacy browsers --> <%= javascript_include_tag :defaults %> <title><%= h( yield ( :title ) || controller.action_name ) %></title> <%= stylesheet_link_tag 'blueprint' , 'application' %> <%= yield ( :head ) %> </head> <body> <div id= "container" > <%- flash. each do |name, msg| -%> <%= content_tag :div , msg, :class => "#{name}" %> <%- end -%> <%- if show_title? -%> <h1><%=h yield ( :title ) %></h1> <%- end -%> <%= render :partial => "users/user_bar" %> <%= yield %> </div> </body> </html> |
module LayoutHelper def title(page_title, show_title = true ) @content_for_title = page_title.to_s @show_title = show_title end def show_title? @show_title end end |
修改application_controller
class ApplicationController < ActionController::Base include AuthenticatedSystem #令views能够调用各自的视图助手(helper)里的方法 helper :all #令以下方法能在所有helper,views,controller中调用(来自restful-authentication插件) helper_method :logged_in ?, :current_user #开启反CSRF (Cross-Site Request Forgery)攻击保护 protect_from_forgery #过滤敏感字段 filter_parameter_logging :password , :password_confirmation #发生错误时自动重定向页面 rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found protected def record_not_found render :file => File .join( RAILS_ROOT , 'public' , '404.html' ), :status => 404 end end |
启动服务器,新建一名为司徒正美的用户,我们需要用其ID。

修改Photo模块,实现上传能力
新建_form.html.erb
<% form_for @photo , :html => { :multipart => true } do |f| %> <%= f.error_messages %> <% if logged_in? %> <%= f.hidden_field :user_id , :value => current_user.id %> <% end %> <p> <%= f.label :is_avatar %><br /> <%= f.check_box :is_avatar %> </p> <p> <%= f.file_field :image %> </p> <p> <button type= "submit" ><%= button_name %></button> </p> <% end %> |
修改new.html.erb
<% title "上传图片" %> <%= render :partial => 'form' , :locals => { :button_name => "上传" } %> <%= link_to 'Back' , photos_path, :class => "button" %> |
修改show.html.erb
<div class = "figure" > <%= image_tag @photo .image.url , :alt => "被GFW和谐了!" %> <div class = "legend" >所有人:<%=h @photo .user.login %>;是否为头像:<%= @photo .is_avatar %></div> </div> <%= link_to '编辑' , [ :edit , @photo ], :class => "button" %> <%= link_to '返回' , photos_path, :class => "button" %> |
修改photo.rb
class Photo < ActiveRecord::Base belongs_to :user has_attached_file :image end |
这样它就可以运行了,非常简单!

通过url上传图片
ruby script/generate migration AddImageRemoteUrlToPhoto image_remote_url :string rake db :migrate |
修改Photo模型
require 'open-uri' class Photo < ActiveRecord::Base belongs_to :user has_attached_file :image attr_accessor :image_url before_validation :download_remote_image , :if => :image_url_provided ? validates_presence_of :image_remote_url , :if => :image_url_provided ?, :message => '地址不合法' private def image_url_provided? ! self .image_url.blank? end def download_remote_image self .image = do_download_remote_image self .image_remote_url = image_url end def do_download_remote_image io = open( URI .parse(image_url)) def io.original_filename; base_uri.path.split( '/' ).last; end io.original_filename.blank? ? nil : io rescue # catch url errors with validations instead of exceptions (Errno::ENOENT, OpenURI::HTTPError, etc...) end end |
修改_form.html.erb
<% form_for @photo , :html => { :multipart => true } do |f| %> <%= f.error_messages %> <% if logged_in? %> <%= f.hidden_field :user_id , :value => current_user.id %> <% end %> <% if action_name == "new" %> <p> <%= f.label :is_avatar , "是否作为头像" %> <%= f.check_box :is_avatar %> </p> <% end %> <p> <%= f.file_field :image %><br /> 或者通过 URL <%= f.text_field :image_url %> </p> <p> <button type= "submit" ><%= button_name %></button> </p> <% end %> |

一样上传成功!

添加多种样式与验证
#…………………… has_attached_file :image , :default_url => "/images/rails.png" , :styles => { :thumb => "100x100#" , :gallery => "150x150>" , :avatar => "200x200>" } #使用这个就不能删除图片了 #validates_attachment_presence :image validates_attachment_size :image , :less_than => 5 .megabytes validates_attachment_content_type :image , :content_type => [ 'image/gif' , 'image/png' , 'image/x-png' , 'image/jpeg' , 'image/pjpeg' , 'image/jpg' ] #…………………… |
删除图片
由于paperclip默认是把上传的东西保存在硬盘中的,调用destroy action只能删除数据库的数据,但不能删除其关的图片。因此我们需要在其模型中添加删除图片的逻辑。
#=============================其他代码===================== #=============================删除图片===================== def delete_image=(value) @delete_image = !value.to_i.zero? end def delete_image !! @delete_image end alias_method :delete_image ?, :delete_image before_validation :clear_image def clear_image self .image = nil if delete_image? && !image.dirty? end #===============================其他代码=================== |
修改_form.html.erb
<% form_for @photo , :html => { :multipart => true } do |f| %> <%= f.error_messages %> <% if logged_in? %> <%= f.hidden_field :user_id , :value => current_user.id %> <% end %> <% if action_name == "new" %> <p> <%= f.label :is_avatar , "是否作为头像" %> <%= f.check_box :is_avatar %> </p> <% end %> <p> <%= f.file_field :image %><br /> 或者通过 URL <%= f.text_field :image_url %> </p> <%- unless @photo .new_record? || ! @photo .image? -%> <div> <%= image_tag( @photo .image.url( :gallery ), :alt => 'Photo' , :title => '当前图片' ) %> <p> <%= f.label( :delete_image , '删除图片' ) %> <%= f.check_box( :delete_image ) %> </p> </div> <%- end -%> <p> <button type= "submit" ><%= button_name %></button> </p> <% end %> |
修改edit.html.erb
<% title "编辑图片" %> <%= render :partial => 'form' , :locals => { :button_name => "更新" } %> <%= link_to '大图' , @photo , :class => "button" %> <%= link_to '返回' , photos_path, :class => "button" %> |
修改update action
def update @photo = Photo.find(params[ :id ]) if @photo .update_attributes(params[ :photo ]) message = @photo .delete_image?? "删除图片成功!" : "更新图片成功!" flash[ :notice ] = message redirect_to( @photo ) else render :action => "edit" end end |
<h1>Listing photos</h1> <table> <tr> <th>所有人</th> <th>是否作为头像</th> <th>预览</th> </tr> <% @photos . each do |photo| %> <tr> <td><%=h photo.user.login %></td> <td><%= photo.is_avatar? "是" : "否" %></td> <td><%= image_tag photo.image.url( :thumb ) , :alt => "被GFW和谐了!" %></td> <td><%= link_to 'Show' , photo %></td> <td><%= link_to 'Edit' , edit_photo_path(photo) %></td> <td><%= link_to 'Destroy' , photo, :confirm => 'Are you sure?' , :method => :delete %></td> </tr> <% end %> </table> <br /> <%= link_to 'New photo' , new_photo_path %> |

最后,paperclip是对window不友好的,google讨论组中最常见的问题是“is not recognized by the 'identify' command”错误,不过我在写这篇博文时还是没遇见过。它在LINUX环境是绝对没有问题的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义