Proper debugging in rails and starting on adding pictures

By ‘proper’ i mean ‘as opposed to sticking in debug text’. Radrails seems to be the way to go, i’ve been recommended to use it as an eclipse plugin. I’ve downloaded the latest version of Eclipse (3.3) and am using its plugin installer system to install radrails. It all seems very nice and easy so far.

Still installing – in the meantime i fixed that bug! It seems like a real rails gotcha – here’s the explanation from a mail i sent to the brighton ruby group telling them about it:

It turns out this was to do with the placement of the ‘end’ tag for my form. Stripping out all of the formatting and irrelevant stuff, the structure of the ‘edit_story’ page was this:

<% form_tag :action => ‘create_story’, :id => @ story.id do %>
<%= text_field ‘story’, ‘title’, “size” => 80 %>
<%= text_field ‘story’, ‘summary’, “size” => 80 %>
<%= text_field ‘story’, ‘tags’, “size” => 80 %>
<%= submit_tag “Submit story” %>
<%= button_to “Add a picture”, :action => “new_picture”, :id => @ story.id %>
<%= button_to “Add a movie”, :action => “new_movie”, :id => @story.id %>
<% end %>

It seems to be the case that because the add picture/movie buttons were enclosed in the form ‘block’, they acted as submit buttons, and rails was ignoring their arguments. If i moved the end tag to after the submit button, everything works fine.

I just found out a nice way of dealing with this off railsforum.com, actually: Instead of putting redirection logic into the view, you can do it all in the controller by making *every* button a submit button, and then looking at the value of params[:commit] (which holds the displayed name of the button that was pressed (eg “Add a picture”) to decide what to do next. That makes it really easy to fit controller logic around button presses, eg, in “create_story”, which the form calls when submitted:

def create_story
@story = Story.find(params[:id])
#need to update @story with the contents of the fields
@story.user_id = session[:user].id
if @story.update_attributes(params[:story])
flash[:notice] = “Updated story.”
else
flash[:notice] = “Sorry, your story was not able to be updated.”
end

case params[:commit]
when “Submit story”
@story.complete = 1
@story.points = 1
@story.added_at = DateTime.now.to_s
if @story.save
flash[:notice] = “Saved story as a completed story.”
else
flash[:warning] = “Error: couldn’t save story!”
end
redirect_to :action => “show”, :id => @story.article_id
when “Add a picture”
redirect_to :action => “new_picture”, :id => @story
end
end

This is a really nice technique i think – keep logic in controllers i say 🙂

OK – did the same for movies. Now all the flow logic is there, all i need to do is to work out how to upload and display pictures and movies. I did a picture uploading tutorial before, so i should be able to use the same technique for pictures. Let’s just worry about pictures for now anyway.

Oops, forgot i was supposed to be getting set up with an IDE for debugging.  Let’s do that while we’re at a nice stage where nothing is broken for once, and before adding a load of new stuff.

…time passes…

Got that all set up now, but with one problem – breakpoints don’t stop the debugger.  Apart from that it looks fine, i can see all the debug info scrolling past as i go round the site.  Asked the guy who wrote the installation instructions for help, on his blog page.  In the meantime, photos:

Looked at that photo holder tutorial i did and it seems pretty simple.  In the db,  we store the content type (image/jpg), the filename (no path) and a blob with the data.

To display the photo, we say (in the view of course)

image_tag(“/photo_admin/code_image/#{photo.id}”, :alt => “Image”

Image tag is a rails helper.  code_image is a controller method that gets the photo (using the given id) and calls another rails method “send_data” with the object, the type, the filename and the ‘disposition’ (which can either be ‘inline’ for “shown in the document” or “attachment” for “downloadable” (the default).

Heres the send_data documentation:

send_data(data, options = {})

Send binary data to the user as a file download. May set content type, apparent file name, and specify whether to show data inline or download as an attachment.

Options:

  • :filename – Suggests a filename for the browser to use.
  • :type – specifies an HTTP content type. Defaults to ‘application/octet-stream’.
  • :disposition – specifies whether the file will be shown inline or downloaded. Valid values are ‘inline’ and ‘attachment’ (default).
  • :status – specifies the status code to send with the response. Defaults to ‘200 OK’.

So, send_data takes some data, then the options hash.  The example usage for pictures is

send_data image.data, :type => image.content_type, :disposition => 'inline'

That’s pretty much what the photo_admin controller does as well.

So, that’s how to display images: what about getting them in?

Well, the process is actually crashing at the moment:  the form for getting the data off the user is different to the ones i’ve been using so far:

<h1>New photo</h1>

<% form_for(:photo, @photo, :url=>{:action=>’create’}, :html=>{:multipart=>true}) do |form| %>
<%= form.file_field :image_file %>
<%= form.text_field :description %>
<%= submit_tag “Create” %>
<% end %>

<%= link_to ‘Back’, :action => ‘list’ %>

This uses the form_for method, and also has a file_field, which i’ve not used before.  According to the tutorial,

It takes the same options as the text_field, the object assigned to the template (in this case, form) and the method for accessing a specific attribute (in this case image_file) Have you spotted the first anomaly yet? No? Well let’s see: we’ve used :description in the text_field to enter our photo’s description, and that’s great, because we have a corresponding description field in our database table. But we’ve used image_file to upload our image data, and there’s no corresponding image_file field in our photos table. So what’s going on?

It turns out that when uploading a file, you don’t get receive just one string, such as the binary image data, but a more complex object containing not just the file’s binary data but its filename and content-type (such as image/jpg or image/gif). These are all stored together in :image_file, so what we’ll need to do is extract the data and assign it the correct model attributes. The best place to do that is in the photo model, in app/models/photo.rb

Ahh…so, file_field returns a hash called :image_file (we specified this name), which contains the data we need in various fields.  The photo model has the responsibility of pulling the values out and assigning them.

So, we go into the photo model and add a method called “image_file=”.  This pulls the values out, processes them where necessary, and puts them into ‘self’:

  1. class Photo < ActiveRecord::Base
  2.   def image_file=(input_data)
  3.     self.filename = input_data.original_filename
  4.     self.content_type = input_data.content_type.chomp
  5.     self.binary_data = input_data.read
  6.   end
  7. end

Here, we take the contents of image_file and we use three methods to extract the data and assign it to the model attributes that match our database table: the methods are original_filename, content_type and read.

original_filename gives you surprisingly enough the original filename of the file
content_type provides you with the content-type of the data, such as whether it is an image/gif or an audio/wav which is useful for validation
read lets you actually get at the binary data

The chomp method at the end of content_type simply removes any extraneous newline characters, to make it neater.

 

I’m still not really getting what’s going on here – the model method seems to be called at the first line of the controller’s create method:

@photo= Photo.new(params[:photo])

 

It must be getting pulled out of params and then called, but i don’t really understand the process.   Ah well, it seems to work anyway.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s