p{border: solid 1px silver; padding: 5px;}. See also the Rails cookbook: "Sending and receiving files":http://manuals.rubyonrails.com/read/chapter/56

The form part of the "new" template:

<pre><code><form action="create" method="post" enctype="multipart/form-data">
  <p>
    <b>Name:</b><br />
    <%= text_field "person", "name" %>
  </p>

  <p>
    <b>Picture:</b><br />
    <input type="file" name="person[picture]" />
  </p>

  <p><input type="submit" name="Save" /></p>
</form></code></pre>

The controller:

<pre><code>class AddressbookController < ApplicationController
  def new
    # not really needed since the template doesn't rely on any data
  end

  def create
    post = Post.save(@params["person"])
    redirect_to :action => "show", :id => post.id
  end
end</code></pre>

The model:

<pre><code>class Post < ActiveRecord::Base
  def self.save(person)
    File.open("pictures/#{person['name']}/picture.jpg", "w") { |f| f.write(person['picture'].read) }
  end
end</code></pre>

To get the original name of the uploaded file, use <code>person['picture'].original_filename</code>. There is also a <code>person['picture'].content_type</code> method. (These are buried somewhere in the depths of <code>cgi.rb</code>)

*Note for Windows:* to avoid corrupting binary files, you must call File.open in binary mode. Change the "w" flag to "wb", like this:

<pre><code>File.open("pictures/#{person['name']}/picture.jpg", "wb") { |f| f.write(person['picture'].read) }</code></pre>

*truncated files?*
If you have trouble with zero-length files written by this code, try calling <pre><code>person['picture'].rewind</code></pre> before writing the file.

*Files uploaded from Internet Explorer*
Internet Explorer prepends the original path of a file to the filename sent, so the <code>original_filename</code> routine will return something like <code>C:\Documents and Files\user_name\Pictures\My File.jpg</code> instead of just <code>My File.jpg</code>.  To deal with this, make sure you write a <code>sanitize</code> method, perhaps called via <code>:before_save</code>, to remove the path and any illegal characters from the filename.  

An example <code>sanitize</code> method (this is not perfect!):

<pre><code>private
    def sanitize_filename(value)
        # get only the filename, not the whole path
        just_filename = File.basename(value.gsub('\\\\', '/')) 
        # replace all none alphanumeric, underscore or periods with underscore
        @filename = just_filename.gsub(/[^\w\.\-]/,'_') 
    end</code></pre>

h2. CGI class documentation

h3. From the "CGI class documentation":http://www.ruby-doc.org/stdlib/libdoc/cgi/rdoc/classes/CGI.html

<blockquote> <h4>Multipart requests</h4>

If a request’s method is POST and its content type is multipart/form-data, then it may contain uploaded files. These are stored by the \QueryExtension module in the parameters of the request. The parameter name is the name attribute of the file input field, as usual. However, the value is not a string, but an IO object, either an IOString for small files, or a Tempfile for larger ones. This object also has the additional singleton methods: 
<dl>
<dt>local_path():</dt><dd>the path of the uploaded file on the local filesystem </dd>
<dt>original_filename():</dt><dd>the name of the file on the client computer </dd>
<dt>content_type():</dt><dd>the content type of the file<dd>
</dl></blockquote>

h2. How to upload files directly into the database

*The view* is almost the same as above, but I changed the field's name to @tmp_file@ since it's not going to be stored anywhere permanently.

*The model* for me is just the plain model without any specific methods at the moment. Oh and the field 'picture' in the DB should be a binary field (for example BLOB in \MySQL)

*The controller*:

<pre><code>def create
  @params['person']['filename'] = @params['person']['tmp_file'].original_filename.gsub(/[^a-zA-Z0-9.]/, '_') # This makes sure filenames are sane
  @params['person']['picture'] = @params['person']['tmp_file'].read
  @params['person'].delete('tmp_file') # let's remove the field from the hash, because there's no such field in the DB anyway.
  @person = Person.new(@params['person'])
  # then the basic if @person.save ... like in TutorialFramingOut
</code></pre>

%{color:blue}In the above, the file contents gets inserted into the hash as a string - ie. it's read to memory. Is that the best we can do? Can we not pass a file-reference or similar and via that get the file contents streamed from the filesystem to the DB?%

h2. How to download files from the database

Here's a piece of code that'll help you download the picture from the DB directly (modified from HowtoSendFiles rev=1)

<pre><code>  @entry = Person.find(@params['id'])
  @response.headers['Pragma'] = ' '
  @response.headers['Cache-Control'] = ' '
  @response.headers['Content-type'] = 'application/octet-stream'
  @response.headers['Content-Disposition'] = "attachment; filename=#{@person.filename}" 
  @response.headers['Accept-Ranges'] = 'bytes'
  @response.headers['Content-Length'] = @person.picture.length
  @response.headers['Content-Transfer-Encoding'] = 'binary'
  @response.headers['Content-Description'] = 'File Transfer'
  render_text @person.picture</code></pre>

Alternately, you can do the much simpler:

<pre><code>  @person = Person.find(@params['id'])
  send_data @person.picture, :filename => @person.filename, :type => "image/jpeg"
</code></pre>

If you want to display the image inline (ie, in a page), use this send_data instead:

<pre><code>  send_data @person.picture, :filename => @person.filename, :type => "image/jpeg", :disposition => "inline"</code></pre>

Here's the api documentation for "send_file":http://rails.rubyonrails.com/classes/ActionController/Base.html#M000089 and "send_data":http://rails.rubyonrails.com/classes/ActionController/Base.html#M000090

[This stuff won't work in lighttpd due to a bug in lighttpd and file uploads.  That was many versions ago, however.  It may work now.  --[[what-a-day|what-a-day]]]

h2. File save variation

The uploaded file will be a  "TempFile":http://www.ruby-doc.org/core/classes/Tempfile.html -like object (if over 10kb in size). These can be copied by the filesystem instead of being read and processed through Ruby.

<pre><code>  def picture=(picture)
    FileUtils.copy picture.local_path, "pictures/#{name}/picture.jpg"
  end</code></pre>

h2. Using caching to reduce database load and bandwidth usage

See HowtoUseHTTPCaching

h2. Checking that the user provided a file:

This can be done via picture.size == 0

h2. Uploading files to a SQLite database

You can't store things with null bytes into BLOBs yet. Until the "new adapter":http://www.jamisbuck.org/jamis/blog.cgi/programming/ruby is out, use Base64 or some similar binary->ascii coding system to make it not contain null bytes.

h2. Testing a file upload

In order to test a file being uploaded you have to mirror what cgi.rb is doing with a multipart post. Unfortunately what it does is quite long and complex, this code takes a file on your system, and turns it into what normally comes out of cgi.rb. Obviously ymmv with the methods

<pre><code>
  def uploaded_file(path, content_type, filename)
    t = Tempfile.new(filename);
    FileUtils.copy_file(path, t.path)
    (class << t; self; end).class_eval do
      alias local_path path
      define_method(:original_filename) {filename}
      define_method(:content_type) {content_type}
    end
    return t
  end
</code></pre>

Usage:
<pre><code> 
uploaded_file("#{File.expand_path(RAILS_ROOT)}/test/mocks/testing/stock.jpg", 'image/jpeg', 'stock.jpg')
</code></pre>

h2. Another approach

There is another example at 
http://www.albert.bagasie.com/RailsTips

h2. Mime-types

No mention of mime-types here. You could try the "MIME::Types":http://www.halostatue.ca/ruby/MIME__Types.html  library for Ruby

category:Howto
