In this walkthrough,I go through the available options and an example using attachment_fu to handle file uploads and image thumbnailing,and responds_to_parent to implement the iframe remoting pattern to work around javascript’s security restrictions on file system access.

You can also download the complete example.

This is an outdated article. I will be updating with a new article soon.

Step 1. Choose a file upload plugin

Sure,you can write one yourself (or bake the code directly into your app),but unless you have specific requirements you should take a look at what’s available. Even if you do have a good excuse,you can learn from the existing plugins or extend them. The three that I’ve used over the past two years are:

  1. file_column – the first file upload plugin available for Rails that I kNow of,it handles saving files to the filesystem,resizing of images,creation of thumbnails,and integration with rmagick; however it doesn’t seem to be in active development.
  2. acts_as_attachment – written by Rick Olson,it does everything that file_column can,but with a cleaner and extensible code base.
  3. attachment_fu – is a rewrite of acts_as_attachment adding a plugin architecture to extend it to add different image processors (image_science,mini_magick and rmagick are provided) and storage backends (database,file system,and Amazon S3). The only problem is you need Rails 1.2+

Recommendation: attachment_fu if you are using Rails 1.2+,otherwise acts_as_attachment.

Step 2. Determine which Image Processor you want to use.

attachment_fu supports three processors out of the Box:

  1. image_science – a light ruby wrapper around the FreeImage library,it can only be used to resize images. It used to have problems with image quality of thumbnails and PNG color profiles but these have recently been fixed.
  2. RMagick – a ruby wrapper around the ImageMagick/GraphicsMagick libraries,it provides a lot of advanced image processing features. It’s memory hungry though,and can max resource limits on some shared hosts causing your app to fail; it’s happened to me a few times on large images.
  3. minimagick – another wrapper around ImageMagick,however it resizes images using imagemagick’s mogrify command. If you are hitting resource limits on your host,minimagick is preferred over rmagick.

Recommendation: image_science if you only need image resizing and can handle the slightly inferior thumbnail quality,minimagick otherwise.

Step 3. Install image processors and attachment_fu

The installation process is quite long for the image processors,so I’ve just linked to them here:

  • RMagick/ImageMagick: Mac OS X, Linux,and Windows
  • FreeImage/image_science: Mac OS X,Linux
  • To install minimagick:
    sudo gem install mini_magick
  • To install attachment_fu:
    script/plugin install http://svn.techno-weenie.net/projects/plugins/attachment_fu/

Step 4. Add uploading to your code

I’ll use a restful model for our file uploads since it’s all the rage (here’s a good introduction). You can create a restful scaffold using the following command:

ruby script/generate scaffold_resource asset filename:string content_type:string size:integer width:integer height:integer parent_id:integer thumbnail:string created_at:datetime

This will create the controllers,models,views and a migration. I’ve included support for saving image properties (width and height attributes) and thumbnailing (parent_id and thumbnail attributes).

Here is the resulting migration if you want to do it manually:

class CreateAssets < ActiveRecord::Migration
  def self.up
    create_table :assets do |t|
      t.column :filename,:string
      t.column :content_type,:string
      t.column :size,:integer
      t.column :width,:integer
      t.column :height,:integer
      t.column :parent_id,:integer
      t.column :thumbnail,:string
      t.column :created_at,:datetime
    end    
  end  def self.down
    drop_table :assets
  end
end

In the model,it’s really a one liner to add file upload features.

class Asset < ActiveRecord::Base  has_attachment  :storage => :file_system,:max_size => 1.megabytes,:thumbnails => { :thumb => '80x80>',:tiny => '40x40>' },:processor => :MiniMagick # attachment_fu looks in this order: ImageScience,Rmagick,MiniMagick  validates_as_attachment # ok two lines if you want to do validation,and why wouldn't you?
end

The has_attachment (or acts_as_attachment method for those not using attachment_fu) adds a lot of useful methods such as image? to determine if the file is an image,and public_filename(thumbnail=nil) to retrieve the filename for the original or thumbnail. I usually add methods to determine other file types such as movies,music,and documents.

The options available are:

  • content_type – Allowed content types. Allows all by default. Use :image to allow all standard image types.
  • min_size – Minimum size allowed. 1 byte is the default.
  • max_size – Maximum size allowed. 1.megabyte is the default.
  • size – Range of sizes allowed. (1..1.megabyte) is the default. This overrides the :min_size and :max_size options.
  • resize_to – Used by RMagick to resize images. Pass either an array of width/height,or a geometry string.
  • thumbnails – Specifies a set of thumbnails to generate. This accepts a hash of filename suffixes and RMagick resizing options.
  • thumbnail_class – Set what class to use for thumbnails. This attachment class is used by default.
  • path_prefix – path to store the uploaded files. Uses public/#{table_name} by default for the filesystem,and just #{table_name} for the S3 backend. Setting this sets the :storage to :file_system.
  • storage – Use :file_system to specify the attachment data is stored with the file system. Defaults to :db_system.

In the above we’re storing the files in the file system and are adding two thumbnails if it’s an image: one called ‘thumb’ no bigger than 80×80 pixels,and the other called ‘tiny’. By default,these will be stored in the same directory as the original: /public/assets/nnnn/mmmm/ with their thumbnail name as a suffix. To show them in the view,we just do the following: <%= image_tag(image.public_filename(:thumb)) %>

validates_as_attachment ensures that size,content_type and filename are present and checks against the options given to has_attachment; in our case the original should be no larger than 1 megabyte.

To enable multipart file uploads,we need to set multipart => true as a form option in new.rhtml. The uploaded_data file input field is used by attachment_fu to store the file contents in an attribute so that attachment_fu can do its magic when the uploaded_data= method is called.

<%= error_messages_for :asset %>
<% form_for(:asset,:url => assets_path,:html => { :multipart => true }) do |form| %>
  <p>
    <label for="uploaded_data">Upload a file:</label>
    <%= form.file_field :uploaded_data %>
  </p>
  <p>
    <%= submit_tag "Create" %>
  </p>
<% end %>

We’ll also pretty up the index code. We want to show a thumbnail if the file is an image,otherwise just the name:

<h1>Listing assets</h1>
<ul id="assets">
<% @assets.each do |asset| %>
<li id="asset_<%= asset.id %>">
<% if asset.image? %>
<%= link_to(image_tag(asset.public_filename(:thumb))) %><br />
<% end %>
<%= link_to(asset.filename,asset_path(asset)) %> (<%= link_to "Delete",asset_path(asset),:method => :delete,:confirm => "are you sure?"%>)
</li>
<% end %>
</ul><br /><%= link_to 'New asset',new_asset_path %>

Don’t forget to do a rake db:migrate to add the assets table. At this stage you can start your server and go to http://localhost:3000/assets/new to add a new file. After being redirected back to the index page you’ll notice that thumbnails are showing in our index with the originals. To get rid of this,we can modify assets_controller to only display originals by checking if the parent_id attribute is nil. attachment_fu also allows you to store thumbnails into a different model,which would make this step unnecessary.

  def index
    @assets = Asset.find(:all,:conditions => {:parent_id => nil},:order => 'created_at DESC')
    respond_to do |format|
      format.html # index.rhtml
      format.xml  { render :xml => @assets.to_xml }
    end
  end

Step 5. AJAX it

Let’s try and AJAX our file uploads. The current user flow is:

  • go to index page
  • click on “new file” link
  • choose a file and submit the form
  • get redirected to index.

What we want to happen is to have all that occur on the index page,with no page refreshes. normally you would do the following:

Add the Javascript prototype/scriptaculous libraries into your layout.

<%= javascript_include_tag :defaults %>

Change the form_for tag to a remote_form_for

<% remote_form_for(:asset,:html => { :multipart => true }) do |f| %>

Add format.js to the create action in the controller to handle AJAX requests:

  def create
    @asset = Asset.new(params[:asset])
    respond_to do |format|
      if @asset.save
        flash[:notice] = 'Asset was successfully created.'
        format.html { redirect_to asset_url(@asset) }
        format.xml  { head :created,:location => asset_url(@asset) }
        format.js
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @asset.errors.to_xml }
        format.js
      end
    end
  end

Make a create.rjs file to insert the asset at the bottom of your list:

page.insert_html :bottom,"assets",:partial => 'assets/list_item',:object => @asset
page.visual_effect :highlight,"asset_#{@asset.id}" 

Create a partial to show the image in the list

<li id="asset_<%= list_item.id %>">
<% if list_item.image? %>
<%= link_to(image_tag(list_item.public_filename(:thumb))) %><br />
<% end %>
<%= link_to(list_item.filename,asset_path(list_item))%> (<%= link_to_remote("Delete",{:url => asset_path(list_item),:confirm => "are you sure?"}) %>)
</li>

Add AJAX deletion (optional)

If you’ve noticed the changes in the prevIoUs code,I’ve added AJAX deletion of files as well. To enable this on the server we add a destroy.rjs file to remove the deleted file form the list.

page.remove "asset_#{@asset.id}" 

In the controller you also need to add format.js to the delete action.

Keep our form views DRY (optional)

We should also make the file upload form contents into a partial and use it in new.rhtml as well as index.rhtml.

_form.rhtml
  <p>
    <label for="uploaded_data">Upload a file:</label>
    <%= form.file_field :uploaded_data %>
  </p>
  <p>
    <%= submit_tag "Create" %>
  </p>
new.rhtml
<% form_for(:asset,:html => { :multipart => true }) do |form| %>
<%= render(:partial => '/assets/form',:object => form)%>
<% end %>

Add the form to index.rhtml

<% remote_form_for(:asset,:object => form) %>
<% end %>

Now that we have all our code in place,go back to the index page where you should be able to upload a new file using AJAX.

Unfortunately there is one problem. A security restriction with javascript prevents access to the filesystem. If you used validations for your asset model you would have gotten an error complaining about missing attributes. This is because only the filename is sent to the server,not the file itself. How can we solve this issue?

Step 6. Using iframes and responds_to_parent

To get around the AJAX/file upload problem we make use of the iframe remoting pattern. We need a hidden iframe and target our form’s action to that iframe. First,we change the index.rhtml to use a form_for tag. To get rails to process our action like an AJAX request we simply add a ”.js” extension to the form’s action. We then set the iframe to a 1×1 sized pixel so it doesn’t get shown. Don’t use display:none or your iframe will be hidden from your form and depending on your browser you will end up opening a new window,load the response in the main window,or download the server response.

<% form_for(:asset,:url =>formatted_assets_path(:format => 'js'),:html => { :multipart => true,:target => 'upload_frame'}) do |form| %>
<%= render(:partial => '/assets/form',:object => form) %>
<% end %>
<iframe id='upload_frame' name="upload_frame" style="width:1px;height:1px;border:0px" src="about:blank"></iframe>

To handle the form on the server,we can use Sean Treadway’s responds_to_parent plugin.

script/plugin install http://responds-to-parent.googlecode.com/svn/trunk/

This plugin makes it dead simple to send javascript back to the parent window,not the iframe itself. Add the following to your create action:

def create
  @asset = Asset.new(params[:asset])
  respond_to do |format|
    if @asset.save
      flash[:notice] = 'Asset was successfully created.'
      format.html { redirect_to asset_url(@asset) }
      format.xml  { head :created,:location => asset_url(@asset) }
      format.js do
        responds_to_parent do
          render :update do |page|
            page.insert_html :bottom,:object => @asset
            page.visual_effect :highlight,"asset_#{@asset.id}" 
          end
        end
      end
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @asset.errors.to_xml }
      format.js do
        responds_to_parent do
          render :update do |page|
              # update the page with an error message
          end
        end
      end
    end
  end
end

At this point you no longer need the create.rjs file.

Now you should be able to get your index page and upload a file the AJAX way!

Step 7. Make it production ready

There are some more changes you need to make it production ready:

  • handling errors,
  • displaying error messages when uploading fails,
  • showing some Feedback to the user while the file is uploading or being deleted

Step 8. Bonus: making a file download by clicking on a link

Just add the following action to your assets controller; don’t forget to add the route to your routes.rb file.

  def download
    @asset = Asset.find(params[:id])
    send_file("#{RAILS_ROOT}/public"+@asset.public_filename,:disposition => 'attachment',:encoding => 'utf8',:type => @asset.content_type,:filename => URI.encode(@asset.filename)) 
  end

Update: 2007/05/23 Thanks to Geoff Buesing for pointing out that we can use formatted_routes.

Update: 2007/05/26 Updated a bug in the initial index.html example (thanks Benedikt!) and added a download link to the final example (see the first paragraph).

AJAX file uploads in Rails using attachment_fu and responds_to_parent2的更多相关文章

  1. HTML5新增form控件和表单属性实例代码详解

    这篇文章主要介绍了HTML5新增form控件和表单属性实例代码详解,需要的朋友可以参考下

  2. HTML5表单验证特性(知识点小结)

    这篇文章主要介绍了HTML5表单验证特性的一些知识点,本文通过实例代码截图的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. amazeui页面分析之登录页面的示例代码

    这篇文章主要介绍了amazeui页面分析之登录页面的示例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  4. ios – 如何修复pods许可证文件错误

    当我输入要么我为一个库获得以下内容:无法读取许可证文件/Users/me/Documents/project/myproject/company/myproduct/Pods/OHAttributedLabel/OHAttributedLabel/LICENSEforthespecOHAttributedLabel(3.5.3)有谁知道为什么?我被建议:无济于事解决方法TL;DRIt’sRuby2

  5. ios – Swift Eureka Form中的循环

    我正在构建一个Eureka表单,并希望在表单中放置一个循环来构建基于数组的步进器列表.我试图使用的代码是:但是,当我这样做时,我在StepperRow行上出现了一个错误:所以看起来Swift不再认为它在形式之内并且正在关注

  6. 应用程序关闭时的iOS任务

    我正在构建一个应用程序,通过ajax将文件上传到服务器.问题是用户很可能有时不会有互联网连接,并且客户希望在用户重新连接时安排ajax调用.这可能是用户在离线时安排文件上传并关闭应用程序.应用程序关闭时可以进行ajax调用吗?

  7. swift 上传图片和参数 upload image with params

    Alamofire.upload(urlRequest.0,urlRequest.1).progress{(bytesWritten,totalBytesWritten,totalBytesExpectedToWrite)inprintln("\(totalBytesWritten)/\(totalBytesExpectedToWrite)")}}

  8. Project Perfect让Swift在服务器端跑起来-让Perfect更Rails (五)

    让大家看看我对Perfect的改造。再说说Perfect的运行原理。其实当用户发送请求时,都是首先找到PerfectServerModuleInit()这个方法,根据指定规则去找对应的Handlers,之后通过Handlers的方法handleRequest去处理相对应的事务处理。其实handleRequest很接近我们的Controller,如果做成一个类似Rails的框架不是不可能的。首先我要扩展一下PerfectLib中的WebRequest和WebResponse这两个方法,针对WebReques

  9. swift – 使用PostgreSQL在Vapor 3中上传图片

    我正在关注这些家伙MartinLasek教程,现在我正在“图片上传”.似乎没有人能回答“如何上传iVapor3图像”的问题Db连接正常,所有其他值都保存.这是我的创建方法:和型号:}和叶子模板:我知道需要一种管理文件的方法和原始图像字节,但我怎么去那里?这使用多部分表单的自动解码:upload.leaf文件是:使用File类型可以访问上载文件的本地文件名以及文件数据.如果将其余的Question字段添加到ExampleUpload结构中,则可以使用该路径捕获整个表单的字段.

  10. android – Phonegap本地构建 – jquery ajax错误:readystate 0 responsetext status 0 statustext error

    解决方法您是否在索引文件中包含了内容安全元标记?

随机推荐

  1. xe-ajax-mock 前端虚拟服务

    最新版本见Github,点击查看历史版本基于XEAjax扩展的Mock虚拟服务插件;对于前后端分离的开发模式,ajax+mock使前端不再依赖后端接口开发效率更高。CDN使用script方式安装,XEAjaxMock会定义为全局变量生产环境请使用xe-ajax-mock.min.js,更小的压缩版本,可以带来更快的速度体验。

  2. vue 使用 xe-ajax

    安装完成后自动挂载在vue实例this.$ajaxCDN安装使用script方式安装,VXEAjax会定义为全局变量生产环境请使用vxe-ajax.min.js,更小的压缩版本,可以带来更快的速度体验。cdnjs获取最新版本点击浏览已发布的所有npm包源码unpkg获取最新版本点击浏览已发布的所有npm包源码AMD安装require.js安装示例ES6Module安装通过Vue.use()来全局安装示例./Home.vue

  3. AJAX POST数据中文乱码解决

    前端使用encodeURI进行编码后台java.net.URLDecoder进行解码编解码工具

  4. Koa2框架利用CORS完成跨域ajax请求

    实现跨域ajax请求的方式有很多,其中一个是利用CORS,而这个方法关键是在服务器端进行配置。本文仅对能够完成正常跨域ajax响应的,最基本的配置进行说明。这样OPTIONS请求就能够通过了。至此为止,相当于仅仅完成了预检,还没发送真正的请求呢。

  5. form提交时,ajax上传文件并更新到&lt;input&gt;中的value字段

  6. ajax的cache作用

    filePath="+escape;},error:{alert;}});解决方案:1.加cache:false2.url加随机数正常代码:网上高人解读:cache的作用就是第一次请求完毕之后,如果再次去请求,可以直接从缓存里面读取而不是再到服务器端读取。

  7. 浅谈ajax上传文件属性contentType = false

    默认值为contentType="application/x-www-form-urlencoded".在默认情况下,内容编码类型满足大多数情况。在这里,我们主要谈谈contentType=false.在使用ajax上传文件时:在其中先封装了一个formData对象,然后使用post方法将文件传给服务器。说到这,我们发现在JQueryajax()方法中我们使contentType=false,这不是冲突了吗?这就是因为当我们在form标签中设置了enctype=“multipart/form-data”,

  8. 909422229_ajaxFileUpload上传文件

    ajaxFileUpload.js很多同名的,因为做出来一个很容易。我上github搜AjaxFileUpload出来很多类似js。ajaxFileUpload是一个异步上传文件的jQuery插件传一个不知道什么版本的上来,以后不用到处找了。语法:$.ajaxFileUploadoptions参数说明:1、url上传处理程序地址。2,fileElementId需要上传的文件域的ID,即的ID。3,secureuri是否启用安全提交,默认为false。4,dataType服务器返回的数据类型。6,error

  9. AJAX-Cache:一款好用的Ajax缓存插件

    原文链接AJAX-Cache是什么Ajax是前端开发必不可少的数据获取手段,在频繁的异步请求业务中,我们往往需要利用“缓存”提升界面响应速度,减少网络资源占用。AJAX-Cache是一款jQuery缓存插件,可以为$.ajax()方法扩展缓存功能。

  10. jsf – Ajax update/render在已渲染属性的组件上不起作用

    我试图ajax更新一个有条件渲染的组件。我可以确保#{user}实际上是可用的。这是怎么引起的,我该如何解决呢?必须始终在ajax可以重新呈现之前呈现组件。Ajax正在使用JavaScriptdocument.getElementById()来查找需要更新的组件。但是如果JSF没有将组件放在第一位,那么JavaScript找不到要更新的内容。解决方案是简单地引用总是渲染的父组件。

返回
顶部