AJAX file uploads in Rails using attachment_fu and responds_to_parent2
2014-03-10 20:50
375 查看
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.
three that I’ve used over the past two years are:
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.
acts_as_attachment – written by
Rick Olson, it does everything that file_column can, but with a cleaner and extensible code base.
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.
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.
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.
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.
RMagick/ImageMagick:
Mac OS X,
Linux, and
Windows
FreeImage/image_science:
Mac OS X, Linux
To install minimagick:
To install attachment_fu:
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. Thethree that I’ve used over the past two years are:
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.
acts_as_attachment – written by
Rick Olson, it does everything that file_column can, but with a cleaner and extensible code base.
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: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.
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.
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/[/code]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 (widthandheightattributes) and thumbnailing (parent_idandthumbnailattributes).
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
Thehas_attachment(oracts_as_attachmentmethod for those not using attachment_fu) adds a lot of useful methods such asimage?to determine if the file is an image, andpublic_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:imageto 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_sizeand:max_sizeoptions.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. Usespublic/#{table_name}by default for the filesystem, and just#{table_name}for the S3 backend. Setting this sets the:storageto:file_system.storage– Use:file_systemto 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_attachmentensures thatsize,content_typeandfilenameare present and checks against the options given tohas_attachment; in our case the original should be no larger than 1 megabyte.
To enable multipart file uploads, we need to setmultipart => trueas a form option innew.rhtml. Theuploaded_datafile input field is used by attachment_fu to store the file contents in an attribute so that attachment_fu can do its magic when theuploaded_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 arake db:migrateto 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 modifyassets_controllerto only display originals by checking if theparent_idattribute isnil. 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 endStep 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 theform_fortag to aremote_form_for<% remote_form_for(:asset, :url => assets_path, :html => { :multipart => true }) do |f| %>
Addformat.jsto thecreateaction 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 acreate.rjsfile 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), :method => :delete, :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 adestroy.rjsfile to remove the deleted file form the list.page.remove "asset_#{@asset.id}"
In the controller you also need to addformat.jsto thedeleteaction.
Keep our form views DRY (optional)
We should also make the file upload form contents into a partial and use it innew.rhtmlas well asindex.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, :url => assets_path, :html => { :multipart => true }) do |form| %> <%= render(:partial => '/assets/form', :object => form)%> <% end %>
Add the form toindex.rhtml<% remote_form_for(:asset, :url => assets_path, :html => { :multipart => true }) do |form| %> <%= render(:partial => '/assets/form', :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 theindex.rhtmlto use aform_fortag. 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 usedisplay:noneor 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 yourcreateaction: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, "assets", :partial => 'assets/list_item', :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 thecreate.rjsfile.
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 deletedStep 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 yourroutes.rbfile.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_parent 1
- 信息增益(information gain)
- 人工智能和机器学习领域大牛
- POJ1218THE DRUNK JAILER 快速和一般方法两种解法
- jqery 对页面中的文档下载和email链接增加对应的图片
- STL第三章-pair的使用方法
- storm中,ack与fail信息的处理
- [NM 状态机2] Container状态机详解
- _itemFailedToPlayToEnd: { kind = 1; new = 2; old = 0; }
- jmail.dll的使用
- gtk main loop gmainloop
- SharePoint Debug - Failed to load resource: the server responded with a status of 500
- paip.获取地理位置根据Ip
- paip.获取地理位置根据Ip
- 最近贴图有点问题,然后用到一个工具叫mediainfo的解决了问题
- 利用Mail实时监测服务器程序状态
- Failure [INSTALL_PARSE_FAIL…
- Failed to load li…
- about gtk main loop
- 关于Rails的错误提示 Rails flash error不消失