Friday, 3 October 2008

file_column

file_column is a simple plugin that lets users upload images, which can then be displayed. There are alternatives out there; this was the first I found, but does the job so well I looked no further.

Download from here:
http://www.kanthak.net/opensource/file_column/

Install by dumping the files in vendor\plugins inside your project.

file_column does not work properly with more recent Rails. Modify file_column_help.rb
url = ""
url << request.relative_url_root.to_s << "/"
url << object.send("#{method}_options")[:base_url] << "/"


To
url = "/"
url << object.send("#{method}_options")[:base_url] << "/"


I think it allows for sub-domains and stuff like that, so is unnecessary in most cases. It fails as request (or @request) is nil. Why should that be?

Example

For a table called empire, with a column called image:

In the model, use something like this:
file_column :image, :magick => { :geometry => "200x100>" }


The magic bit uses RMagick to limit the size of the image.

In the new view, use something like this:
<% form_for(@empire, :html=> {:multipart=>true}) do f %>
<%= file_column_field "empire", "image" %>


In the show view, use something like this:
<%= unless @empire.image.nil?
image_tag url_for_file_column("empire", "image"), :align=>"right"
end %>


This will only display an image if one is entered in the database (though the file might still be missing). Note the align right option; this is not how it is documented; it should be done through a hash called option, but that did not work for me.

No changes in the controller.

Update: For Rails 2.2.2
Using this in Rails 2.2.2, I also had to make a change in file_column.rb, around line 619:
my_options = FileColumn::init_options(options,
Inflector.underscore(self.name).to_s,
attr.to_s)

Becomes:
my_options = FileColumn::init_options(options,
ActiveSupport::Inflector.underscore(self.name).to_s,
attr.to_s)


Update: Functional Testing
I found this quite a problem, with little guidance anywhere on the web as to what I should be doing. However, this is what I got working. The application is for a record of cylinder batches, each batch requiring a scanned certificate in PDF format.
test "should update cylinder_batch with file" do
login_as 'manager', @request
cb = CylinderBatch.find(:first)
filename = 'LittleBookOfRuby.pdf'
test_file = File.new("#{RAILS_ROOT}/test/fixtures/#{filename}")

put :update, :id => cb.id, :cylinder_batch => { :certificate => test_file}
assert File.exists? "#{RAILS_ROOT}/test/tmp/file_column/cylinder_batch/certificate/#{cb.id}/#{filename}"
assert_redirected_to cylinder_batch_path(assigns(:cylinder_batch))
cb2 = CylinderBatch.find cb.id
assert_equal "#{RAILS_ROOT}/test/tmp/file_column/cylinder_batch/certificate/#{cb.id}/#{filename}", cb2.certificate

get :delete_certificate, :id => cb.id
assert !File.exists?("#{RAILS_ROOT}/test/tmp/file_column/cylinder_batch/certificate/#{cb.id}/#{filename}")
assert_redirected_to cylinder_batch_path(assigns(:cylinder_batch))
cb2 = CylinderBatch.find cb.id
assert_nil cb2.certificate
end

The method is broken into three parts, the first setting up a few things, the second testing the update method with a new file, and the third part testing the delete_certificate method for removing a file. Usually I would only do one controller method per test method, but in this case one method sets up the other, while the other cleans up after the first, so this was more convenient.

I put a test file in the fixtures folders. This should be of the same type as you expect in your application. I tried using a YAML file, and while file_column would save the file, it did not update the table; I think file_column rejects file types it does not know. Also as a general point, be aware that file_column does not like anything besides letters, digits, underscores and hyphens in filenames.

In the parameters for the update method, I simply map the file to the appropriate column name. The file object contains the filename as well as the contents, and file_column can sort it all out. I then check that the file exists in the appropriate directory, the database has been updated correctly and the user redirected.

In the last section, after calling the delete_certificate method in the controller, I just check the file has gone, the database entry is nil and the redirect again. Simple. When you know how.

Struggling with Ruby: Contents Page

4 comments:

Steve Enzer said...

Thanks for posting that, you got me out of a real head-banger today!

wellsy said...
This comment has been removed by the author.
wellsy said...

Thanks for the post. Was struggling with this one too. The amendment to file_column_helper sorted it out for me. Much appreciated!

I've also found out you can then also call this from other locations by using 'item.image_relative_path', and adding the relative link at the start

J. Aaron Farr said...

Rather than delete the line altogether, you should change:

request.relative_url_root

to:

ActionController::Base.relative_url_root