Michael Kovacs' photos More of Michael Kovacs' photos
Recommend Me Cable Car Software logo

Thursday, August 31, 2006

Rails realities part 19 (Relation assignments)

Many folks out there may not know this but you can set relations on AR objects in more than one way. Unless there's another I'm missing I know of 2 ways:
  1. parent.child = Child.find(:first)

  2. parent.child_id = Child.find(:first).id

When you save the parent object after each of the above statements you'll have created the parent-child relationship in the database. The second way is probably not as well known as the first, but it is very useful when updating a HABTM relationship.
Allow me to demonstrate...

In your view you have:

< %parent.children.each do |child|%>
< %=check_box_tag 'parent[child_ids][]', child.id, child_selected%> Child value < %=child.to_s%>
< %end%>

When you submit a form containing this field tag you'll receive all in your parameter set:
{'parent' => {'child_ids' => [2, 3, 7]}}

With which you can simply update the parent like so:

And all of your related HABTM objects will be set and updated once you save the parent object.

With that groundwork laid have a look at the following code:
parent = Parent.new
parent.child = Child.find(1)
parent.child_id = 3

What do you think will be saved in the database for this relation? The child with the id 1 or 3?

Of course the answer is 1. Huh? Don't get it? Yeah me neither. I stumbled across this bug, or inconsistency today while working on reassigning a related object to a new value.

I wrote this small unit test to reproduce what I was seeing in my app. The usage of setting "child_id" will work as long as the related object is null. I haven't checked yet to see if this is a bug or not and will update if it is already known. I'm using rails 1.1.6.

Be careful out there!

Saturday, August 26, 2006

Rails realities part 18 (Multi-part parameter parsing)

Rails has the ability to automagically parse the date parameters that are created from using the various date_helper methods that create select inputs. But the problem is that in order to have that magic parsing happen you have to do a mass assignment of parameters to the object that contains the value.

Well sometimes you'd like to just grab the value of the date without having to do a mass parameter assignment on your object.

Here's an example of how the form parameters are passed in when using a built in rails date helper:
{"date(1i)"=>"2006", "date(2i)"=>"8", "date(3i)"}

Today's post provides some code extracted right out of ActiveRecord that will do this without having to do the mass assignment.

Anyway, here's a link to the code:

I did this in about 10 minutes so I make no claims about the robustness here. I tested it on one date select group and it worked fine. Corrections and improvements are definitely welcomed but this seems like a good candidate to be extracted into a helper within rails.

Tuesday, August 22, 2006

IE blows... news at 11

I recently had need to go back to a page whose URL was dynamically constructed and decided to use the HTTP_REFERER header variable that gets passed in on an HTTP request. Well turns out IE refuses to participate in such craziness.

This is apparently a known issue with IE. I never knew this because, well, I never had occasion to try using this variable. Oh well, maybe it's fixed in 7.0. Nevertheless looks like I'll be using the session for tracking this info.

Thursday, August 17, 2006

Rails realities part 17 (Prefetching and using :joins)

Been awhile since I've posted but I actually have some cool stuff to talk about shortly when I get more time as I've been quite busy. But today is a quick post about using finders, namely prefetching and joins. Not much is written on the topic or when you might want to use some of the various options on on a find method.

First off the problem statement. We have a page where we display a list of Foo objects. We also have a relation on Foo called 'notes'. What we want is to pre fetch the related notes objects that have not been read.
We'll start off building our query from ground zero.

So the query starts off like this:

@foos = Foo.find(:all)
This simply gives us our list of all foos.

Next we move up to including the 'notes' relation
@foos = Foo.find(:all, :include => :notes)
OK so now we're prefetching notes, but we're getting all of them and we only want the ones that have not been read. How are we going to do that? Conditions? Let's try that:
@foos = Foo.find(:all, :include => :notes, :conditions => ['notes.read = 0'])
What this will return is the list of Foo objects that have unread notes. Hmm... not what we want. We want all Foo objects but we want to prefetch the ones that have not been read.

OK how about this clever hack... We create a new relation on Foo for the purpose of fetching related objects that meet this criteria? Note: This is NOT an ideal solution but in the interest of simply getting it working sometimes you shoot first and ask questions later. So we add the following to Foo and modify our finder query as follows:
class Foo
has_many :unread_notes, :conditions => 'read = 0'
@foos = Foo.find(:all, :include => :unread_notes)
Sure enough, this does what we want, but now we've got a relation on Foo whose sole purpose is life is to run this query. I mistakenly thought that the :joins parameter would do what I want but thusfar that's proven not to be true. So as of right now I have a dummy relation in my object so that I can prefetch intelligently. Is there a "correct" way to perform this in my finder query?

This page is powered by Blogger. Isn't yours?