Sunday, August 6

Drill-down Filtering for AjaxScaffold

A couple weeks ago, I decided to start another little pet project of porting the simple little in-house project-tracking application I had written in Java for my current company over to Ruby on Rails. I did a little leg work and discovered that the AjaxScaffold plug-in offered to save me a lot of wheel reinventing. It's a slick little project and I highly recommend. However, it was missing a critical little feature set I required. I needed three little pieces of functionality tied to the columns of information displayed by the default "list" view:

1. I needed for the values in the columns, certain columns, to be hyperlinked, and when a user clicked on one of the hyperlinked values, the list would refresh/rerender itself and filter out all of the line items that didn't contain the clicked value for the associated column. For example, if I had a column named "Clients" and one of the values in that column was "Acme", then when I clicked on the Acme name the list would hide all of the other lines associated with other clients.

2. I needed for this click-to-filter feature to be reversible. For example, once you had drilled-down into only the Acme values from the Clients column, if you then again clicked on Acme the filter would undo and all of the other lines belonging to other clients would reappear.

3. Finally, I needed for the drill-downs to be aggregated. For example, once I had drilled-down into only the line items with Acme as the Client, I could also drill-down even farther by clicking on Bob in the Representatives column, leaving me with a list that contained only line items with Acme as the Client and Bob as the Representative. Likewise I could re-click Bob or Acme, in any order, to undo that particular column filter.

Clear as mud? I hope my explanation makes sense. Anyhow, AjaxScaffold does not, unfortunately, offer that functionality. Perhaps the author(s) will stumble across this essay and alleviate that situation in the near future. I'll keep my fingers crossed.

Since Ruby and Rails are just an off-hours hobby for me, I'm no expert in either, and I wasn't sure where to even start with this one, so I posted a plea for help over on the Google Groups dedicated to supporting this plug-in. The group doesn't appear to be very active, so I was shocked to get a response from Scott Brittain about ninety minutes later. Scott kindly shared with me his solution to #1 above, and that was all I needed to get the ball rolling. Less than twenty-four hours later, I managed to hack-out #2 and #3 as well.

This probably isn't going to make much sense to those of you that don't use or are unfamiliar with the guts of the code AjaxScaffold generates, but here's how I had to hack it to get the magic I desired.

Guided by Scott's initial clue, it starts in the model. Here is where you can define which values to use as columns in the list displayed to the user, as well as munge each column, which is exactly what I had to do. The setting in my model looks like this now:

@scaffold_columns = [
AjaxScaffold::ScaffoldColumn.new( self, {
:name => "Name",
:eval => "project.name"
} ),
AjaxScaffold::ScaffoldColumn.new( self, {
:name => "Assigned To",
:eval => drill_down_link( 'User', 'user_id' ),
:sort_sql => "projects.user_id"
} ),
AjaxScaffold::ScaffoldColumn.new( self, {
:name => "Deadline",
:eval => "project.deadline"
} ),
AjaxScaffold::ScaffoldColumn.new( self, {
:name => "Status",
:eval => drill_down_link( 'Status', 'status_id' ),
:sort_sql => "projects.status_id"
} ),
]

Most of that is standard AjaxScaffold code; the lines important to my enhancement are the two that look like this:

:eval => drill_down_link( 'User', 'user_id' ),

What's going to happen is that the AjaxScaffold framework is going to, as it renders each of the columns, call "eval" on the member of the ScaffoldColumn instance named, oddly enough "eval". So here is how I inject my magic, by passing in a block of code to be "eval'd". For the sake of brevity and cleanliness, I've defined this block elsewhere in a method named "drill_down_link", and here's what it looks like:

def Project.drill_down_link( model_name, column_name )
# TODO: reverse merge a default parameters hash to aggregate drilldowns
<<EOF
url_for_params = { :controller => 'projects', :action => 'list' }
[ :user_id, :status_id ].each { |param|
if params[param]
unless param.to_s.eql?( '#{column_name}' )
url_for_params.merge!( param => params[param] )
end
end
}
url_for_params.merge!( :#{column_name} => project.#{column_name} ) unless params[:#{column_name}]
link_to( #{model_name}.find( project.#{column_name} ).name, url_for( url_for_params ) )
EOF
end

Yeah. That's pretty ugly. Writing Ruby code that's going to be evaluated by other Ruby code, and worse yet I've got to inject model and variable names with substitutions using the "#{}" mechanism. It sends a chill down my spine, but I've not yet found a more elegant way around it.

So now when these special columns are "eval'd" during rendering of the list, they will display the appropriate value and hyperlink that value back to the listing passing in the parameters and values necessary to filter (or unfilter) the list, as well as propagating the values passed in by the other columns.

Note that this isn't very DRY as I've hard-coded the list of column values "[ :user_id, :status_id ]" here, and I will again in the controller, so there's some room for improvement. But for now, it all works, so I'm content, and I figured I'd go ahead and share it because it will likely not get touched again.

The only piece left is to actually apply the filtering to the list when it renders. That's done in the generated code for the controller in a method named enigmatically "component". Here's the code with my changes emphasized:

def component
@show_wrapper = true if @show_wrapper.nil?
@sort_sql = Project.scaffold_columns_hash[current_sort(params)].sort_sql rescue nil
@sort_by = @sort_sql.nil? ? "#{Project.table_name}.#{Project.primary_key} asc" : @sort_sql + " " + current_sort_direction(params)
clauses = ''
values = {}
[ :user_id, :status_id ].each { |param|
if params[param]
clauses << ( clauses.blank? ? '' : ' AND ' )
clauses << "#{param.to_s} = :#{param}"
values.merge!( param => params[param] )
end
}
conditions = values.empty? ? nil : [ clauses, values ]
# @paginator, @projects = paginate(:projects, :order => @sort_by, :per_page => default_per_page)
@paginator, @projects = paginate( :projects, :order => @sort_by, :per_page => 25, :conditions => conditions )

render :action => "component", :layout => false
end

What I do here is simply build-up a conditionals clause to be passed into the pagination method, and that's it! As you can see here I've hard-coded the parameter list again. Shame on me.

It's not pretty. It's not elegant. It's not DRY. And, it's probably not great Ruby either. But, it works, and perhaps somebody reading this will tell me how to clean it all up.

I am exceedingly grateful to the author(s) of AjaxScaffold and the helpful Scott Brittain from the forums. They, like Rails framework itself, have saved me countless hours of reinventing wheels. Thanks!

A little knowledge can be dangerous

<arturaz> any ideas how i can get @foo from :foo ?

<inono> magic

<TeflonTed> eval "@#{:foo.to_s}" ? :-)

<arturaz> eval's evil :)

<TeflonTed> magic is evil

Tuesday, July 18

The Longest Tease

Refactoring Rails has been hosting a "coming soon" banner for nine months since its announcement. Come on, people. It's time to deliver that baby. I can only be enticed for so long. For the love of all things holy, throw us a bone here!

Sunday, July 16

Foreign Keys in Rails in Three Easy Steps

1. Fix Your Migrations

The first thing you need to do is add the foreign key declarations to your migrations. There are several ways to do this. If you want to go old-school, you can hack the SQL directly into the migration file, but if you want to take a more classy (and DRY) route, I recommend Simon Harris' plug-in. The plug-in is very clever about figuring out the relationships automagically, but if necessary you can easily nudge it in the right direction.

2. Fix Your Fixtures

By default, Rails is going to load your fixtures alphabetically, and unless by some miracle that happens to be the correct order in which to load them without violating any referential integrity, you're out of luck. If you use Simon's plug-in, fixtures aren't going to present too much of a problem because it is smart enough to not apply foreign keys to the testing database. However, if you are like me, you're going to write some unit tests to ensure that the foreign keys are intact and operational, and all of those tests are going to fail. But don't fret. Again, there are a couple ways to solve this. The classiest of which is a little code hacking which lets you specify the order in which your fixtures should be loaded. (Will somebody please refactor this into a plug-in already!?) But if you want to go old-school and get your hands dirty, you can simply rename your fixture files, prefixing them with numerical values which coax Rails to load them in the correct order.

3. Fix Your Code

Rails and referential integrity don't always play nice together, so you're going to have to do a little massaging with your code. As I've ranted about here a few times before, one of the most frustrating is the way has_many handles the breaking of relationships. When you "delete" a relationship, Rails doesn't delete the record in the database, it simply nullifies one of the foreign keys, leaving an orphaned record. DHH noted in a response to one of my earlier rants that the reason for this is so the record can be reassigned, thus resetting the just-broken foreign key. However, if that happens to be a real foreign key in a database that enforces referential integrity, you're going to get a barf as the database will not let Rails set it to null. So, instead of deleting a relationship from a collection, you'll need to destroy the relationship itself. A minor syntactical semantic, but confusing and sometimes confounding nonetheless.

Conclusion

That's it in a nutshell. It's a bit of work to overcome a few little bumps in the road, and although it somewhat violates the Rails philosophy of database design, I think it's well worth it, and many of my colleagues tend to agree. As a shameless self promotion, note that I have whipped up a very crude plug-in to make has_many collections behave a little more intuitively (by my personal definition of intuition, of course) -- it should be backwards compatible so dropping it into your project shouldn't break anything.

Sunday, July 9

Home Office


Home Office, originally uploaded by trak3r.

Just testing the Flickr-Blogger integration.

Monday, July 3

OpenVPN is Da Bomb

Long story short, last winter our hosting provider (which happens to be the most incompetent company I've dealt with in the last decade; oh the stories I could tell) migrated our fully-managed system to their shiny new and improved facility, and in the process finagled a renegotiation of our contract which omitted a lot of the services that had been "standard" beforehand, like the virtual private network between our office and our servers.

When this oversight was brought to the attention of the paper pushers and check signers, our hosting provider explained to us that they'd be happy to provide the service to us for a mere few thousand dollars a month over our existing service charge. Now, you have to understand that we're a B2B company, and some of our clients are very finicky about security, and commonly audit the way we run our shop to make sure we don't represent a chink in their armor, so having a VPN is critical to our business. But, we're a small company, and that's a pretty large bite out of our bottom line, so we started looking at alternatives.

The first one we checked out was Hamachi. Hamachi is beautifully simplistic, but so simple that it lacked two critical features. First of all, it didn't run as a service, and thus stopped as soon as you logged out of the machine, and Heaven forbid your remote machine rebooted on you and you couldn't get back to it. Secondly, Windows Remote Desktop (a.k.a. Terminal Services) doesn't work over it. That last one is mind boggling.

So we next tried out OpenVPN. Not only did it have the two important features Hamachi lacked, but it also has a neat little trick that lets your connection act as if it's part of the remote network, acquiring an internal LAN IP address from the DHCP server and all. Everything works seamlessly: remote desktop, networked file shares, source control access, etc. I haven't tried, but I bet the printers work as well. On top of that, it handles multiple networks without breaking a sweat. I've got a VPN to my office, one to my production servers, and another to my personal server. Even as I bounce between the network cable to the wifi to dial-up, the service is able to reconnect and reconfigure itself. I've even been surprised by one cool side effect where I closed my laptop (putting it in sleep mode) while leaving a remote terminal open, then opening it back up when I got home and finding the terminal session still alive and kicking.

But -- there's always a but, isn't there? -- we had to pretty quickly remove OpenVPN from our production servers as it was causing a horrible periodic stalling of network traffic. Essentially, every ten minutes or so, the server would just cease to respond to network requests (HTTP requests were the most evident -- we serve about twelve per second). Then, after about one minute of not responding, it would suddenly burst back alive, handling any of the stalled network connections that hadn't timed out. This made our web-site unusable. Note that the HTTP traffic wasn't being served over the OpenVPN connection, nor was OpenVPN even active, it was just installed -- which seems to suggest to me it's a weird Windows driver issue, but I'm not the expert. Unfortunately, the experts can't or won't help as the ticket we opened has gone ignored.

Wednesday, June 28

DHH's Kool-Aid is Mighty Tasty!

As I suspected, I didn't have all the information. That's why I like to cover my ass with statements like:

"I hope that I'm wrong here and some noble Rails advocate will set me straight..."

And as luck would have it, The Man himself set me straight (or somebody doing a convincing job of impersonating him... where's OpenID when you need it?):

The "problem" with the code you show seems to be a misunderstanding of how associations in Rails work. A call like collector.possessions.delete(possession) is not MEANT to destroy the possession. You do that by calling either possession.destroy or collector.possessions[1].destroy. The call you're making reads like this "delete the possession from the collector's list of possessions (thus making you free to give the possession to someone else)". Considering that the method is named delete, though, I'll treat it as an honest mistake...

Hmm, that's not exactly what I would consider an intuitive API, but I'll give it a test run. First, let's see if I can really "destroy" that record. The code:
collector = Collector.find(19) 
possession = collector.possessions.find_by_collectible_id(72)
possession.destroy

And the query:
mysql> select * from possessions;
Empty set (0.00 sec)

Yup, it's gone! Score one for my misinterpretation of the API.

Now let's see about that giving the possession to someone else. The code:
collector = Collector.find(19) 
possession = collector.possessions.find_by_collectible_id(72)
collector.possessions.delete( possession )
anotherCollector = Collector.find(33)
anotherCollector.possessions << possession

(Yes, I recreated the record after the prior test -- I didn't just pass a nil from one collection to another) And the query:
mysql> select * from possessions;
+----+--------------+----------------+----------+
| id | collector_id | collectible_id | quantity |
+----+--------------+----------------+----------+
| 3 | 33 | 72 | NULL |
+----+--------------+----------------+----------+
1 row in set (0.00 sec)

Sure enough, it works just like David said. Who would have figured he'd know? ;-)

Now that I know I have to "destroy" my relationships instead of simply "deleting" them from their respective collections, I can accomplish what I'd hoped from my last rant, that being a clean database. However, I don't think this is very intuitive and I think it's a rather gaping hole in the design that when I do "delete" a relationship from a collection it becomes an orphaned record until such time as I "append" it to another collection, and if that time never comes (as it didn't in the example from my last post) I've got litter in my database. That's not a Good Thing(tm), and it could be prevented at the database level with foreign and/or composite keys if Rails' behavior wasn't to nullify one off the references. But, since that's the way Rails acts, I can't defy it at the database level without causing it to break or code around it, both of which completely deter from the beauty, simplicity, and speedy development we've come to expect from the framework.

So, in conclusion, it is workable, but it's not elegant. And, in my humble opinion, it's not intuitive and it's not The Right Way(tm). But there are no silver bullets or golden hammers out there, and when it comes to weighing pros to cons this con isn't nearly big enough (now that I know the work-around) to out-weigh all the pros.

Tuesday, June 27

Should we drink DHH's Kool-Aid?

David Heinemeier Hansson is a smart and charismatic guy, but that doesn't mean he's always right. I had always thought, apparently through ignorance, that the lack of support of foreign keys and composite keys in Ruby on Rails was due to its immaturity and was something that was on the horizon. But David set the record straight in his keynote at RailsConf -- he thinks they're old-school, unnecessary, and has no intention of supporting them. I believe his words were "Why should we bend to work with them? Let them bend to work with us!" That got me thinking.

I'm not one to make rash decisions. I'm always trying to look at an issue from all conceivable perspectives before weighing in on an opinion. So when I heard mister Heinemeier Hansson's shocking declaration, I had to stop and reconsider my enterprisey religion. I've been weaned on foreign keys and composite keys. It's been beaten into my brain that they are The Right Way (tm) to accomplish a Good Thing (tm). How could these rock-solid foundations of enlightened database design be so nonchalantly tossed aside? I was breaking out into a cold sweat.

Let's agree on an example schema before I get into the nitty gritty. I'm going to stick with the same objects and relationships I've used in my last dozen rants. We've got Collectors. Collectors possess Collectibles. Possessions store this relationship. The tables look like this (generated by a migration script):
Collectors
--------
ID
Name

Collectibles
--------
ID
Name

Possessions
--------
ID
Collector_ID
Collectible_ID
Quantity

Now my old-school enterprisey noggin immediately notices that:

1. Possessions.Collector_ID should be a foreign key to Collectors.ID

2. Possessions.Collectible_ID should be a foreign key to Collectibles.ID

3. Collector_ID and Collectible_ID in Possessions should be a composite key.

4. WTF is that ID column doing in Possessions!?

Let's tackle 1 and 2 first. Why should they be foreign keys? For data integrity! If the database doesn't enforce the reference between the two tables, naughty code might create records in the Possessions table that don't actually refer to existing Collectors or Collectibles. That would be bad.

What happens if you do in fact make them foreign keys[1]? Well, the most annoying thing is that your tests stop running because fixtures aren't smart enough to load in the proper order[2]. It tries to load dependent records before their dependencies. Not fun.

So for the sake of argument let's consider dropping the foreign keys. Wow, I just got a cold chill down my spine. I feel dirty just for having typed that sentence. But I'll press on...

As I've already mentioned, without the referential integrity we can get bogus records in this table. That irks an obsessive compulsive anal-retentive person like myself, but I'll concede that it's not the end of the world. The system can be coded to continue to operate with bad data. Shudder.

But there's another can of worms still to open, point number 3. If the Collector and Collectible IDs in Possessions are not a composite key, we can have multiple records representing the same relationship! That's very bad. But again, I'm going to try to be open-minded here. We can, at the very least, prevent that by making the two columns a "unique" constraint. That's still allowed within the Rails realm, isn't it? Well, not really, which brings us to point 4.

Rails, and DHH, believe that the one-primary-key-per-table world is a safe and workable place to be. This is why the Possessions table has a unique ID column. OK, I can live with that, I think. But here's where things start to get sticky (or stinky?) and I need to add some code here to demonstrate the problem. Here's the models:
class Collector < ActiveRecord::Base
has_many :possessions
has_many :collectibles, :through => :possessions
end

class Collectible < ActiveRecord::Base
end

class Possession < ActiveRecord::Base
belongs_to :collector
belongs_to :collectible
end

And here's a little test to create a Possession:
collector = Collector.find(19) 
collectible = Collectible.find(72)

possession = Possession.new
possession.collector_id = collector.id
possession.collectible_id = collectible.id
possession.save!

And here's what we have in the database:
mysql> select * from possessions;
+----+--------------+----------------+----------+
| id | collector_id | collectible_id | quantity |
+----+--------------+----------------+----------+
| 19 | 19 | 72 | NULL |
+----+--------------+----------------+----------+
1 row in set (0.00 sec)

So far so good. But what happens when we need to delete a Possession? Here's the code:
possession = collector.possessions.find_by_collectible_id(72)
collector.possessions.delete( possession )

And here's what we're left with in the database:
mysql> select * from possessions;
+----+--------------+----------------+----------+
| id | collector_id | collectible_id | quantity |
+----+--------------+----------------+----------+
| 19 | NULL | 72 | NULL |
+----+--------------+----------------+----------+
1 row in set (0.00 sec)

Gah! That's one of them orphaned records I warned you about. What if we run the whole test again? Will it reclaim that record?
mysql> select * from possessions;
+----+--------------+----------------+----------+
| id | collector_id | collectible_id | quantity |
+----+--------------+----------------+----------+
| 19 | NULL | 72 | NULL |
| 20 | NULL | 72 | NULL |
+----+--------------+----------------+----------+
2 rows in set (0.01 sec)

Nope! Now we've got two lost souls. If we'd applied that unique constraint I'd mentioned earlier to the un-foreign key columns, the database would have thrown a fit and Rails would have choked.

So what's going on here? Well, from my point of view, it seems that Rails not only dissuades me from designing my database with proper referential integrity but it also litters my tables with orphaned records. I hope that I'm wrong here and some noble Rails advocate will set me straight, but even if there is some way of correcting this behavior it's clearly not the default behavior and that goes against Rails' mantra of convention over configuration -- I shouldn't have to configure Rails to not break referential integrity and not clutter my tables with bogus data.

Josh Susser and I have been discussing this issue in our blogs and via e-mail -- and we should have discussed it at RailsConf but I was in burn-out mode and didn't have the cognitive energy -- but we're both attempting to build plug-ins to make better sense of this issue. Rather than collaborating on a single solution, we're tackling it from different angles, but one thing our solutions seems we have in common is that we both agree those orphaned records need to be deleted. In reading a copy of Josh's code (and I hope he doesn't mind me revealing this) I found the amusing comment:
# delete ??? - would need to call destroy on join model instance.

And that's exactly what I did in my plug-in:
[snip]
# the default way that rails handles this is by
# "setting their foreign keys to NULL"
# this would leave orphaned records in the database,
# and I'm not cool with that
# AND since my migrations set the foreign keys to "not null" (duh!)
# this breaks hard-core
# so what I should do here is look up the unique record ID for the relationship
# (aside: why the hell do they have unique IDs rather than compound keys!?)
# and delete the record outright
@reflection.klass.delete( wrapper.id )
# TODO: I need to remove the wrapped relationship from the superclass
# collection as well or it will be in an invalid state. As a quick hack I might
# be able to simply force a reload of it from the database.
[/snip]

At the moment I'm torn on the issue. Do I want to "bend" to the Rails way and just live with the lack of referential integrity and bogus data? Or do I want to try to make Rails bend to work The Right Way(tm)? Obviously I'm trying the latter, and so are other prominent Railists like Josh, which to me is a good sign that this is an important issue. Hell, even Dave Thomas complained about it in his keynote at RailsConf. Thankfully I'm not yet using Rails for any commercial work so it's all academic mental self-pleasure for now. But if I were put into a position where I had to make the call, it would be an incredibly grueling decision, and it shouldn't have to be.


Footnotes:

[1] You can add foreign keys manually in the migration script via raw SQL or use Simon Harris' plug-in. Note that his plug-in is clever enough to not apply the foreign keys to the testing database so your fixtures don't barf.

[2] There are hacks you can use to control the order of fixture loading if you really want/need it.

Sunday, June 25

RailsConf 2006

Apple should have sponsored this conference.

I submit for your shock and awe, Exhibit A:

http://flickr.com/photos/twylo/173895378/

And, Exhibit B:

http://flickr.com/photos/mintchaos/sets/72157594176520552/

Wednesday, June 21

Perfect Timing

My dead-tree copy of Rails Recipes just arrived, just the thing I'll need to pass the time while I'm flying to RailsConf tomorrow.

Sunday, June 11

10 Days 'til RailsConf 2006

Wow, that really snuck up on me. My attendence will likely inspire some new rants and raves here, but I won't be doing one of those play-by-play or day-in-review bloggings. I'm sure there will be more than enough of those to go around. There's already a list forming on the Wiki, which supposedly is going to be aggregated via a site/feed that doesn't yet exist (it currently links to an old stale RubyConf blog). That would be nice, although most of these contributors are probably already on RubyCorner, so I'll be getting two copies of each post (or three in the case of the blogs I subscribe to directly). But what I really look forward to is breaking down the blog barrier and meeting a lot of you in person. Take a good hard look at this face, commit it to memory, and when you see it in Chicago next week, come up and say "Hi Ted!" :-)

Tuesday, May 23

Automatic foreign keys in your migrations

A couple weeks back, Simon Harris blogged about a little plug-in he'd whipped up which makes your Ruby on Rails database migrations automatically guess and apply foreign key relationships. This was something I had been yearning for, so I jumped all over it. Unfortunately, it had a few problems:

- It didn't work with the Windows port of Ruby 1.8.4. Simon had developed and tested it on the OSX port of Ruby 1.8.4 and method aliasing -- for some reason -- isn't consistent between the two.

- It didn't work with MySQL. Simon had developed and tested it for PostgreSQL and the SQL syntax support differed slightly.

- It didn't handle column names that weren't clearly evident, so adhering to the Ruby on Rails philosophy, I recommended a little "configuration" to spice up the "convention".

- It choked on columns that looked like foreign keys but were not, attempting to create foreign keys to nonexistent tables.

- It didn't consistently properly pluralize table names.

Believe it or not, the set of migrations for my current pet project is complex enough to have exposed all of these issues.

So my correspondence with Simon graduated from comments on his blog to direct e-mails, and by last night we were lobbing chunks of code back and forth by the minute. We probably should have switched to some form of instant messaging, but we had our heads buried deep in the code. Due to the time difference (Simon is in Oslo at the moment, and I'm in South Florida) he was up until 1:30 in the morning (his time) working out the final kinks.

And by the grace of the Heavens, we finally got it perfect. My complex migrations run from beginning to end, perfectly applying every foreign key. This is my new favorite plug-in, and I'm proud to have helped it to fruition.

Simon has posted the latest and greatest version on RubyForge. Check it out!

Tuesday, May 2

has_many :helpers, recapped

Based on comments to my last post it's become apparent that readers haven't been able to follow my piecemeal posts and assemble them back into a coherent big picture, and that makes perfect sense as I don't do that for the blogs I read either, so I need to start composing my posts as stand-alone self-contained nuggets, providing context and back-story where appropriate. So with that in mind, I'm composing here a wrap-up state-of-the-union essay on my has_many helper plug-in.

First of all, I'll revisit why I created this helper utility. I have Collectors. I have Collectibles. Collectors possess Collectibles, modeled as Possessions. It looks like this in code:

class Collector < ActiveRecord::Base
has_many :possessions
end

class Possession < ActiveRecord::Base
belongs_to :collector
belongs_to :collectible
end

class Collectible < ActiveRecord::Base
end

Note that I'm not using has_and_belongs_to_many and I'm not using the "through" variation.

Now what I want to do is be able to add Collectibles to a Collector's Possessions like so:

collector = Collector.find( 123 )
collectible = Collectible.find( 456 )
collector.possessions << collectible

But, of course, that doesn't work with Rails. I will get the error:

ActiveRecord::AssociationTypeMismatch: Possession expected, got Collectible

Rails expects me to code it this way:

collector.possessions.create( :collectible_id => collectible.id )

I say pshaw to that, good sir! And so I decided to fix it.

First, like a good little Rails programmer, I adhered to the religion of Test-Driven Development and wrote a test for the way I thought it should work:

def test_has_many_extensions

collector_id = 1
collectible_id = 3

collector = Collector.find( collector_id )
collectible = Collectible.find( collectible_id )

# ensure we are starting with a clean slate
assert ! collector.possessions.include?( collectible )
# sanity check -- compare it with the old-fashioned way
assert collector.possessions.find_by_collectible_id( collectible.id ).nil?

# add the collectible to the collector's possessions
collector.possessions << collectible
# ensure that the collectible was added to the collector's possessions
assert collector.possessions.include?( collectible )
# sanity check -- compare it with the old-fashioned way
assert ! collector.possessions.find_by_collectible_id( collectible.id ).nil?

# try to re-add it -- should this break?
collector.possessions << collectible

# reinitialize the collector so we can...
collector = Collector.find( collector_id )

# ...ensure that the collectible possession mapping was saved to the database
assert collector.possessions.include?( collectible )
# sanity check; compare it with the old-fashioned way
assert ! collector.possessions.find_by_collectible_id( collectible.id ).nil?

# remove the collectible from the collector's possessions
collector.possessions.delete( collectible )
# ensure that it was removed
assert ! collector.possessions.include?( collectible )
# sanity check; compare it with the old-fashioned way
assert collector.possessions.find_by_collectible_id( collectible.id ).nil?

# re-remove it -- should this break?
collector.possessions.delete( collectible )

# reinitialize the collector so we can...
collector = Collector.find( collector_id )

# ...ensure that the collectible possession mapping was removed from the database
assert ! collector.possessions.include?( collectible )
# sanity check; compare it with the old-fashioned way
assert collector.possessions.find_by_collectible_id( collectible.id ).nil?

end

Then, I made it work with the following plug-in, which is available on RubyForge at http://rubyforge.org/projects/tedshasmanyhelp/:

module ActiveRecord
module Associations
class AssociationCollectible < AssociationProxy

def teds_guess_foreign_key( widget )
widget.class.to_s.downcase + '_id'
end

def teds_find_relationship_wrapper( widget )
conditions = "#{ teds_guess_foreign_key( widget ) } = #{ widget.id }"
find( :first, :conditions => conditions )
end

def include?( widget )
unless widget.instance_of?( @reflection.klass )
widget = teds_find_relationship_wrapper( widget )
end
super( widget )
end

alias_method :old_delete, :delete

def delete( widget )
unless widget.instance_of?( @reflection.klass )
widget = teds_find_relationship_wrapper( widget )
end
unless widget.nil? # I should probably raise an error
old_delete( widget )
end
end

alias_method :old_push, '<<'

def <<( widget )
if widget.instance_of?( @reflection.klass )
old_push( widget )
else
create( { teds_guess_foreign_key( widget ) => widget.id } )
end
end

end
end
end

Tuesday, April 25

Ted's Has Many Helpers is on Ruby Forge

This is my first endeavor into a publicly available chunk of code (if you don't count my old JavaWorld articles -- eight years after the fact I still get e-mails about them), so it's probably a good thing that I'm starting with a script that's currently about a dozen lines long ;-) I haven't quite grokked all of the Ruby Forge goodness so bear with me as I try to figure out how to get it all set up and make the code easily available. If you have any experience publishing Rails plug-ins as gems or svn:externals (which is what I'm doing at the moment) please feel free to share your wealth of knowledge. Thanks, enjoy, and please be merciful.

http://rubyforge.org/projects/tedshasmanyhelp/

Tuesday, April 18

has_many :continuing sagas

A few quick updates this morning.

First, I fixed a little boo boo in the implementation from my last post related to my deferral of the "find_by_widget_id" call. I was calling it on the wrong model, but returning the correct value oddly enough to fool my unit tests. Once I started using the tool in real code I discovered the error.

Secondly, Josh Susser has posted a write-up (a lot more eloquently) of exactly what I'm dealing with on his blog. He concludes his explanation with the claim, "I do actually have some ideas about how to add some more magic ... I'll have to save that for next time." Here's to hoping he doesn't wait too long to share.

Finally, I had to deploy a self-referential cross-reference model last night, one that links to itself. Essentially, a "friends" mapping that maps Collectors to Collectors. This, of course, broke my little hack as the "guessed" foreign key no longer worked. However, I think I might be able to dig it up since it is passed in to the "belongs_to" declaration of the mapping model. If I figure it out, I will of course post the new code.

So without further ado, here's the latest.

module ActiveRecord
module Associations
class AssociationCollection < AssociationProxy

def teds_guess_foreign_key( widget )
widget.class.to_s.downcase + '_id'
end

def teds_find_relationship_wrapper( widget )
conditions = "#{ teds_guess_foreign_key( widget ) } = #{ widget.id }"
find( :first, :conditions => conditions )
end

def include?( widget )
unless widget.instance_of?( @reflection.klass )
widget = teds_find_relationship_wrapper( widget )
end
super( widget ) # ! index( widget ).nil?
end

alias_method :old_delete, :delete

def delete( widget )
unless widget.instance_of?( @reflection.klass )
widget = teds_find_relationship_wrapper( widget )
end
unless widget.nil? # I should probably raise an error
old_delete( widget )
end
end

alias_method :old_push, '<<'

def <<( widget )
if widget.instance_of?( @reflection.klass )
old_push( widget )
else
create( { teds_guess_foreign_key( widget ) => widget.id } )
end
end

end
end
end

Sunday, April 16

Google Calendar - Oh so close!

It took me a few years to switch from Yahoo! Mail to Gmail as I patiently waited for Google to get their act together an add the feature allowing you to define the "From:" address as something other than your Gmail account. Yahoo! Mail had this for as long as I can remember, and it kept me as a loyal customer.

Sadly, it turns out that Google Calendar is history repeating itself. The calendar feature is the last and only thing I still rely on Yahoo! for and I'd glady switch to Google in a second if it supplied the features I need. But, alas, it does not. It has two major deficiencies:

1. It doesn't support the recurring schedule of "every other week". Most of the regular events on my calendar repeat in this manner, such as weekends with my daughter. A rather disappointing oversight on their part.

2. Per-event notification configuration. You can have all of your notifications via e-mail, or all via SMS, or both, but you can't have one come by e-mail (like a week early warning of an upcoming birthday) and another come by SMS (like 10 minutes before a meeting). Again, a frustrating limitation.

Slightly related to the latter, but not necessarily a deal killer, is the fact that you can only set notifications for events on your primary calendar. I set up a separate calendar for my company-related events, and another for just birthdays and anniversaries, then realized to my dismay that I couldn't set up notifications for any of them. Thankfully, it's easy to move events from one calendar to another.

Oddly enough, you can set a notification on an event in the primary calendar then move the event to another calendar and the notification icon remains, but I've not tested to see if the notification actually gets sent. That's an ugly hack.

My colleague assures me that they will fix these problems quickly, but based on how incredibly long I had to wait for them to match Yahoo! Mail's feature set, I'm not going to hold my breath.

Oh so close, Google. Oh so close.

Saturday, April 15

has_many :polishing

As I noted in the update to my last post, I had a little bug in my extension of the has_many feature that broke the old vanilla usage. The bug was in the way that "super" called from the "<<" override wasn't correctly/consistently deferring to the former implementation. I couldn't figure out why it didn't work, but I did figure out how to work around it. I just aliased the method name and deferred to the alias. Slick.

Oh, I also moved the extension into it's own file "lib/has_many_extensions.rb":

module ActiveRecord
module Associations
class AssociationCollection

def foreign_key( widget )
widget.class.to_s.downcase + '_id'
end

def find_relationship_wrapper( widget )
instance_eval "#{ @reflection.klass }.find_by_#{ foreign_key( widget ) }( #{ widget.id } )"
end

def include?( widget )
unless widget.instance_of?( @reflection.klass )
widget = find_relationship_wrapper( widget )
end
super( widget )
end

alias_method :old_delete, :delete

def delete( widget )
unless widget.instance_of?( @reflection.klass )
widget = find_relationship_wrapper( widget )
end
old_delete( widget )
end

alias_method :old_push, '<<'

def <<( widget )
if widget.instance_of?( @reflection.klass )
old_push( widget )
else
create( { foreign_key( widget ) => widget.id } )
end
end

end
end
end

Then I added the necessary "require" to the end of my "config/environment.rb" file:

require 'has_many_extensions'

Enjoy!

Friday, April 14

has_many :gets even better

Update: My redefinition of << below is breaking normal has_many relationships. I haven't yet figured out why, but when I do I'll post a fix.

As I alluded to in the update to my last post, after it had stewed in my noggin for a couple hours, I realized that I had grossly overcomplicated two issues.

First of all, I didn't need to "cheat" the system by overriding the has_many signature and passing in the "hint" of the model being mapped to (Collectible). I can very easily check it at run time. I know the model of the relationship mapping (Possession), so I can check to see if the passed in object is an instance of the mapping (Possession) and if not, assume it's the model to be mapped to (Collectible).

That leads me to the second drastic improvement. I don't need to define new methods! I can override the existing has_many helper methods, and defer to their overridden counterparts when the mapping model (Possession) is passed in, or apply my new magic when the model to be mapped (Collectible) is passed in.

So first of all I beefed up the unit test with lots of commenty goodness and followed each test of the new overridden methods with a sanity check using the old [ugly] way:

def test_has_many_extensions
collector_id = 1
collectible_id = 3
collector = Collector.find( collector_id )
collectible = Collectible.find( collectible_id )
# ensure we are starting with a clean slate
assert ! collector.possessions.include?( collectible )
# sanity check -- compare it with the old-fashioned way
assert collector.possessions.find_by_collectible_id( collectible.id ).nil?
# add the collectible to the collector's possessions
collector.possessions << collectible
# ensure that the collectible was added to the collector's possessions
assert collector.possessions.include?( collectible )
# sanity check -- compare it with the old-fashioned way
assert ! collector.possessions.find_by_collectible_id( collectible.id ).nil?
# try to re-add it -- should this break?
collector.possessions << collectible
# reinitialize the collector so we can...
collector = Collector.find( collector_id )
# ...ensure that the collectible possession mapping was saved to the database
assert collector.possessions.include?( collectible )
# sanity check; compare it with the old-fashioned way
assert ! collector.possessions.find_by_collectible_id( collectible.id ).nil?
# remove the collectible from the collector's possessions
collector.possessions.delete( collectible )
# ensure that it was removed
assert ! collector.possessions.include?( collectible )
# sanity check; compare it with the old-fashioned way
assert collector.possessions.find_by_collectible_id( collectible.id ).nil?
# re-remove it -- should this break?
collector.possessions.delete( collectible )
# reinitialize the collector so we can...
collector = Collector.find( collector_id )
# ...ensure that the collectible possession mapping was removed from the database
assert ! collector.possessions.include?( collectible )
# sanity check; compare it with the old-fashioned way
assert collector.possessions.find_by_collectible_id( collectible.id ).nil?
end

Then I revered the Model declaration back to good old vanilla Rails syntax:

class Collector < ActiveRecord::Base

has_many :possessions

end

Then I completely scrapped the hack to override the has_many declaration and beefed up the AssociationCollection hack by overriding the actual old-school methods and deferring to their overridden counterparts when appropriate:

module ActiveRecord
module Associations
class AssociationCollection

def foreign_key( widget )
widget.class.to_s.downcase + '_id'
end

def find_relationship_wrapper( widget )
instance_eval "#{ @reflection.klass }.find_by_#{ foreign_key( widget ) }( #{ widget.id } )"
end

def include?( widget )
unless widget.instance_of?( @reflection.klass )
widget = find_relationship_wrapper( widget )
end
super( widget )
end

alias_method :old_delete, :delete

def delete( widget )
unless widget.instance_of?( @reflection.klass )
widget = find_relationship_wrapper( widget )
end
old_delete( widget )
end

def <<( widget )
if widget.instance_of?( @reflection.klass )
super( widget )
else
create( { foreign_key( widget ) => widget.id } )
end
end

end
end
end

Now this is a piece of code I can be proud of! Feast your eyes on that puppy and try not to drool on your keyboard ;-) Ha! I'm high on Ruby on Rails, and I ain't never comin' down!

has_many :revelations

Update: On the drive home tonight, it dawned on me that there's two ways to drastically improve this little hack. I'm trying to make them work right now and I'll post a new entry revealing them as soon as possible.

It wasn't easy, and it's not pretty, but I did it! I managed to achieve my Holy Grail of has_many functionality. (Read my last two posts if you're not familiar with the problem I was having.) Here's how I did it.

In true TDD fashion, I decided on how I wanted to call the API first and wrote a test:

def test_has_many_extensions
collector = Collector.find(1)
collectible = Collectible.find(3)
assert ! collector.possessions._include?( collectible )

collector.possessions._add( collectible )
assert collector.possessions._include?( collectible )

collector = Collector.find(1)
assert collector.possessions._include?( collectible )

collector.possessions._delete( collectible )
assert ! collector.possessions._include?( collectible )

collector = Collector.find(1)
assert ! collector.possessions._include?( collectible )
end

As you can see, I simply took the existing common has_many helper method names (which normally take instances of the joining Model as an argument) and prefixed them with underscores then instead passed in instances of the Model to be mapped to/from.

This, in my mind, seems like a more logical and readable way of "conversing" with the API. I don't want to "add a Possession to a Collector's Possessions." I want to "add a Collectible to a Collector's Possessions." Get it?

So the first thing I had to do was delve into the guts of Rails and start trying to reverse-engineer how everything worked. That was painful. It was excruciating. I'm used to working with Eclipse at the office and navigating Java projects. Eclipse makes it easy to trace up and down call stacks, bookmark points of interest, etc. No such luck with Rails. Even though I was using RadRails (which is built on the Eclipse platform), it doesn't yet offer such niceties [and might not ever due to the complications of parsing and pre-processing a vibrantly dynamic scripting language like Ruby, but I'll keep my toes out of that flame pool for now].

I'll save you the gory details, not so much out of mercy but more from the fact that I couldn't retain it all. My head was swimming trying to keep it all together, and I never really grok'd it completely. But, what's important is that I figured out enough of it to extend it with the functionality I desired.

My new underscore-prefixed methods needed to be added to ActiveRecord::Associations::AssociationCollection, so I placed the following in my "config/environment.rb" file:

module ActiveRecord
module Associations
class AssociationCollection

def _add( widget )
create( { teds_foreign_key => widget.id } )
end

def mapping( widget )
instance_eval "#{ @reflection.klass }.find_by_#{ teds_foreign_key }( #{ widget.id } )"
end

def _delete( widget )
delete( mapping( widget ) )
end

def _include?( widget )
include?( mapping( widget ) )
end

end
end
end

If you're actually reading the code, you'll notice some scary stuff in there. First of note is the eval statement. I needed to make a call to a method that was defined at runtime by the framework, so I don't know beforehand what the name of that method is, and that method's name is based on the model being referenced, so it differs with context. Such is the paradoxical beauty and horror of Ruby. So I formatted the call I needed to make in a string using variables that would be populated at run-time to fill in the holes. This is how I get instances of the mapping Model necessary for passing in to the default has_many helper methods.

The second scary thing is the mysterious "teds_foreign_key" references. I tried, truly I did, but could not find any way in the existing has_many code to extract the foreign key column name. I know it's in there. It has to be in order for it do generate the proper database queries. But I couldn't find it. Undoubtedly it's buried in some abstraction of a library that creates the code for another library on the fly and eval's it all ;-) Perhaps some enlightened reader will set me straight. But, in the meantime, I had to cheat the system. I am passing in a clue to the foreign key from the Model declaration:

class Collector < ActiveRecord::Base

has_many :possessions, :referenced_class => Collectible

...

That "referenced_class" option is what I added to the has_many signature. I use it to deduce the foreign key (and not very elegantly, admittedly) so the aforementioned methods can use it. Here's how I accomplished that (also in my "config/environment.rb" file):

module ActiveRecord
module Associations
module ClassMethods

alias_method :old_has_many, :has_many

def has_many( association_id, options = {}, &extension )
if options[:referenced_class]
@@teds_foreign_key = options[:referenced_class].to_s.downcase + '_id'
end
old_has_many( association_id, options = {}, &extension )
end

def teds_foreign_key
@@teds_foreign_key
end

end
end
end

And that's it! I tested it from the console and everything worked fine; I queried the database after every step as a sanity check. And, of course, the unit test at the top of this post ran without error. I'm pretty fricken psyched about this. If you like it and it helps you out, please let me know. If you see some sort of problem with it, please also speak up. I'm certainly no Ruby or Rails expert [yet] and I'd love to hear the advice of others.

Wednesday, April 12

has_many :headaches

Continuing my rant from earlier, I decided to delve into the source and see if I could figure out exactly what's going on, and maybe by some miracle add the functionality I need and expected.

First stop was to find out where the "<<" operator was being overloaded. Yes, I know that in Ruby it's not an operator, it's a message, and it's not overloaded. But I'm an old fart, and those are the terms I was weaned on, so deal with it. But I digress.

I found the magic in this file:

rails/activerecord/lib/active_record/associations/association_collection.rb

And the code looks like this:

module ActiveRecord
module Associations
class AssociationCollection < AssociationProxy

...

def <<(*records)
result = true
load_target

@owner.transaction do
flatten_deeper(records).each do |record|
raise_on_type_mismatch(record)
callback(:before_add, record)
result &&= insert_record(record) unless @owner.new_record?
@target << record
callback(:after_add, record)
end
end

result && self
end

Pretty straight forward. It looks like it's defering to a method named "insert_record" to handle the databaseness, so that was my next stop, and I found it in this file:

rails/activerecord/lib/active_record/associations/has_many_association.rb

And here's the code:

module ActiveRecord
module Associations
class HasManyAssociation < AssociationCollection

...

def insert_record(record)
set_belongs_to_association_for(record)
record.save

end

Now some of the pieces are starting to fall into place. What it's doing here is only going to (and only does) work for a one-to-many relationship. What is does is takes the record passed in to the collection, sets its foreign key value, then saves the record.

What I need it to do is recognize that in some cases it's dealing with a many-to-many relationship and rather than saving the record that is passed into the collection it needs to create the record for the cross-reference.

Now the code above should have all of the information it needs to do just that. After all, it built the collection by querying the right tables, so it knows all of the tables names and their important columns, so it should be able to write that relationship back into the database. I need to dig a little more to figure out how I can leverage that.

I'm a bit surprised it hasn't already been done, which leads me to fear that it's rather ugly or nigh-impossible to accomplish, but I'm holding out hope. I'm hoping that somebody in the know stumbles across these rants and offers a helping hand in either making it work or explaining why it wasn't built to do so.

has_many: apparently doesn't have enough

These last couple weeks I've been making blazing progress on my pet Project Basement. I've fallen even more in love with Ruby on Rails, as if that was possible. You know the kind of sick infatuation when you start thinking to yourself, "I wonder how long it would take me to rewrite my company's six-year-old one-hudred-seventy-five-thousand-line Java application in Rails?" That's how sick it is. But I'm already off track before I've even begun. So on to my latest rant.

As the title implies, I've been a little disappointed with "has_many" as of late, specifically with the management of the "through" cross-reference records. For example, I've got the following models:

class Collectible < ActiveRecord::Base
end

class Collector < ActiveRecord::Base
has_many :possessions
has_many :collectibles, :through => :possessions
end

class Possession < ActiveRecord::Base
belongs_to :collector
belongs_to :collectible
end

Simple enough, right? If I want a list of all the Collectibles possessed by a Collector, I just call "collector.collectibles" and the back-end does all the magic for me. It's a thing of beauty.

But what if I want to add a new Collectible to a Collector? This is where things get ugly. I'm hoping that this is simply a misunderstanding on my part and somebody will set me straight, but I've been banging my head against this for the last couple night and it's not adding up.

According to the documentation for has_many:

collection<<(object, …) - adds one or more objects to the collection by setting their foreign keys to the collection’s primary key.

That seems to imply that I should be able to call "collector.collectibles << some_collectible" and the new collectible will automatically be added to the list and the appropriate database record created, but that's not what's happening. Here's one of my test cases from "test/unit/possession_test.rb":

def test_collector_collectibles
ted = Collector.find(1)
nickel = Collectible.find(2)

assert ! ted.collectibles.include?( nickel )
# add a collectible to a collector
ted.collectibles << nickel

# sanity check
assert ted.collectibles.include?( nickel )

ted = nil
ted = Collector.find(1)
assert ted.collectibles.include?( nickel )
end

That last assertion fails! When I reinitialize the Collector, forcing it to reload all it's guts from the database, the Collectible I just added to his Possessions isn't there. It apparently wasn't committed to the database.

I considered perhaps I needed to explicitly save the change, so I tried "ted.save" but that did nothing and "ted.collectibles.save" threw an error as the method isn't implemented.

Now, if I deal directly with the "possessions" list I can make it work, but I don't like the syntax at all:

def test_collector_possessions
nickel = Collectible.find(1)
ted = Collector.find(3)
assert ted.possessions.find_by_collectible_id( nickel.id ).nil?

ted.possessions.create( { :collectible_id => nickel.id } )

ted = nil
ted = Collector.find(3)
assert ! ted.possessions.find_by_collectible_id( nickel.id ).nil?
end

That, in my humble opinion, is butt-ugly by Ruby and Rails standards. I contend that my first test, the one with the simple and elegant syntax, should work!

I've been in this situation before, where I've found myself frustrated with something in Ruby on Rails, but I've almost always eventually hit the "A ha!" moment where it all made sense. But, this time, it ain't happening. Can somebody out there explain and/or justify this disconnect?

Monday, April 10

In-place select-and-submit for Ruby on Rails

If you'll recall from my last post (on my now deprecated blog), the built-in in-place-editting tools that come with Rails didn't provide exactly what I needed, so I had to roll my own. Well, it happened again; this time with a drop-down menu. I wanted to provide the user with a drop-down list that automatically updated the back-end, without refreshing the entire page, when an item was chosen from it.

Just like with my text in-place-editting solution, this one hides the drop-down element once an option is chosen so that the user can't quickly re-select another option causing a possible race condition. In its place, it shows the user a progress indicator, which for me is a little animated GIF of a spinning circular arrow and the phrase, "Saving..." Once the server has confirmed the value was saved, the element is restored back to a drop-down menu with the saved value pre-selected.

I ran into an odd little behavior while implementing this utility. I had originally tried to call "form.submit();" when the value of the selection was changed, but this resulted in the browser posting the form and completely ignoring the "onsubmit" directive declared in the form tag. This was frustrating, and Googling the issue didn't turn up anything useful. But, I discovered through trial and error that I could call "form.onsubmit();" directly and all was good.

So without furher ado, here's the method from my application_helper.rb file:

def select_and_submit( controller, action, id, element_for_results, options = {}, selected = nil, extra_hidden_values = {} )
label = "#{controller}_#{action}_#{id}"
extra_hidden_tags = ""
extra_hidden_values.each_pair do |key, value|
extra_hidden_tags << "<input id=\"#{key}\" name=\"#{key}\" type=\"hidden\" value=\"#{value}\" />"
end
options_rendered = ""
for value in options
if value.eql? selected then
selected_rendered = ' selected="selected"'
else
selected_rendered = ''
end
options_rendered << <<EOF
<option value="#{value.id}"#{selected_rendered}>#{value.name}</option>
EOF
end
<<EOF
<div id="#{label}_select">
<form id="#{label}_form" action="/#{controller}/#{action}" method="post" onsubmit="Element.hide( '#{label}_select' ); Element.show( '#{label}_saving' ); new Ajax.Updater('#{element_for_results}', '/#{controller}/#{action}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
<input id="select_id" name="select_id" type="hidden" value="#{id}" />
#{extra_hidden_tags}
<select id="value_id" name="value_id" onchange="this.form.onsubmit(); return false;">
<option value=""></option>
#{options_rendered}
</select>
</form>
</div>
<div id="#{label}_saving">
<img src="/images/indicator_arrows_circle.gif" alt="*">
Saving...
</div>
<script>
Element.hide( '#{label}_saving' );
</script>
EOF
end

And here's how I call it:

<%= select_and_submit 'collectibles', 'set_value_for_attribute', attribute.id, "attribute_row_#{attribute.id}", attribute.values, collectible.get_value_for_attribute( attribute ), { :collectible_id => collectible.id } %>

Sunday, April 9