Ruby's Louvre

每天学习一点点算法

导航

利用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环境是绝对没有问题的。

posted on 2009-07-21 16:21  司徒正美  阅读(3787)  评论(0编辑  收藏  举报