Multiple Foreign Keys In Rails…

November 12th, 2008

So yesterday at work, I ran into a seriously interesting problem and its the first time in the three years I’ve been working with Rails that I’ve really butted heads with ActiveRecord. I’m not really sure if ActiveRecord won or I did, but I eventually got it to work. Luckily, I can blame most of the problem on will_paginate and my own laziness.

Here is the scenario: I’ve got a model, lets call it Items. Now Item belongs to Users by the way of user_id. However it is also possible for different User to temporarily rent an Item, this is represented using renter_id.

Now in Rails this is pretty straight forward:
class Item < ActiveRecord::Base
belongs_to :user
belongs_to :renter, :class_name => “User”
end
class User < ActiveRecord::Base
has_many :my_items, :class_name => “Item”, :foreign_key => “user_id”
has_many :rented_items, :class_name => “Item”, :foreign_key => “renter_id”
end

No problem, right? Well, what if i wanted a list of all items that a User has access to. I basically want a mySQL select of this:
SELECT * FROM items WHERE (items.user_id = user.id or items.renter_id = user.id)

What would I like to do is this:
has_many :items, :foreign_key => ["user_id", "renter_id"]

Unfortunately that was not to be. So my next idea was just to use a method that gathers both and passes it back. For example:
def items
my_items + rented_items
end

However, this is gloriously inefficient, requiring the entire collection to be gathered from the database then chopped up and paginated in Ruby. Bad idea… Besides, will_paginate doesn’t like working that way (which is a good thing)

After beating my head against rails and mySQL and trying to remember which comes first AND or OR. I finally broke down and decided to implement it old school using :finder_sql, which ended up looking something like this:
has_many :items, :finder_sql => 'SELECT * FROM items WHERE (items.user_id = #{id} or items.renter_id = #{id})'

(And yes the single quotes are quite important in this context because you want the literal string ‘#{id}’ to be evaluated later, not when the string is generated.)

And this will work fine for things like User.find(:first).items, and I was happy. Until I tried to do User.find(:first).items.paginate and it freaked the hell out (being unable to build a proper request from the finder_sql which is totally understandable). And again I fell into despair and hit up the rails api. And lo and behold I found the :extend key on has_many. What it allows you to do is define custom methods, usually finders or creators for the assocation, using the proxy reflection data that it provides it was “easy-ish” to accomplish what I wanted to do, by overwriting the paginate method from will_paginate and build the Pagination collection by hand, and the controller programmer is none the wiser that I have totally hijacked the ball.

It ended up looking something like this:
has_many :items, :finder_sql => 'SELECT * FROM items WHERE (items.user_id = #{id} or items.renter_id = #{id})' do
def paginate(page, per_page)
total_entries = proxy_reflection.class_name.constantize.count_by_sql proxy_reflection.options[:counter_sql].gsub(’#{id}’, proxy_owner.id.to_s)
WillPaginate::Collection.create(page || 1, per_page || 10, total_entries) do |pager|
pager.replace proxy_reflection.class_name.constantize.find_by_sql(proxy_reflection.options[:finder_sql].gsub(’#{id}’, proxy_owner.id.to_s) + ” LIMIT #{pager.per_page} OFFSET #{pager.offset}”)
end
end
end

Finally!

New Project, WiiMocap.

April 25th, 2008

While I’m trying to finish off a website for a friend of mine, John Hill, and that still is taking top priority in my spare time, those of you who know me, know I teach part time over at ITT. Far from the most glamorous teaching position in the world, but I enjoy it. Free time seems to be more and more of a luxury these days.

Either way, I got into a discussion with my class the other night, and they were talking about how it was suggested before they started school that the college either had on site, or had access to, a mocap facility. After a long discussion about how a mocap studio wont make you better animator, which I firmly believe, I remembered an idea I had several months earlier.

The idea was to try to construct a primitive yet functional motion capture system using mostly off the shelf products. The Wii Remote is little more than a simplified IR 1024×768 camera. The best part is that the Wii Remote does most of the heavy lifting and can keep track of 4 distinct IR light sources. Using multiple Wii Remotes, and a custom build IR suit rig, you should be able to toggle just 4 points on the suit each frame and still get a decent sampling rate across the entire suit.

Last night in class, I started working on a prototype. I’ll post some photos as work progresses.