Mapping external objects locally to arbitrarily named column

Hi all,

I’m working on an app where I have a simplified set up like this:

create_table :tasks do |t|
t.column :subject, :string
t.column :description, :string
t.column :requester, :integer
t.column :assignee, :integer
end

create_table :users do |t|
t.column :name, :string
end

class User < ActiveRecord:Base
end

class Task < ActiveRecord::Base
composed_of :requester, :class_name => ‘User’, :mapping => [ [
:requester, :id ] ]
composed_of :assignee, :class_name => ‘User’, :mapping => [ [
:assignee, :id ] ]
end

This is a common pattern we use through many of our apps to track users
and the roles they fill. Each role is guaranteed to have only one user
fit it at any one time, but any user may fit a role in multiple Tasks.


I was hoping that composed_of was the magic bullet, but it doesn’t seem
to be. Given the following little script:

user1 = User.create( :name => “John D.” )
user2 = User.create( :name => “Jane Doe” )

task = Task.create( :subject => “Paint the house”, :description => “How
about a nice shade of yellow?”, :assignee => user1, :requester => user2
)

fromdb = Task.find task.id

puts fromdb.assignee


But the output looks like this:

=> #<User:0x26b9b88 @attributes={“name”=>“John D.”, “id”=>1},
@new_record=false, @errors=#<ActiveRecord::Errors:0x26b73d8 @errors={},
@base=#<User:0x26b9b88 …>>>

user2=User.create( :name => “Jane Doe” )
=> #<User:0x26a190c @attributes={“name”=>“Jane Doe”, “id”=>2},
@new_record=false, @errors=#<ActiveRecord::Errors:0x26a0df4 @errors={},
@base=#<User:0x26a190c …>>>

task=Task.create( :subject => “Paint the house”, :description => “How about a nice shade of yellow?”, :assignee => user1, :requester => user2 )
=> #<Task:0x2679240 @assignee=#<User:0x26b9b88
@attributes={“name”=>“John D.”, “id”=>1}, @new_record=false,
@errors=#<ActiveRecord::Errors:0x26b73d8 @errors={},
@base=#<User:0x26b9b88 …>>>, @attributes={“id”=>1, “subject”=>“Paint
the house”, “description”=>“How about a nice shade of yellow?”,
“assignee”=>1, “requester”=>2}, @requester=#<User:0x26a190c
@attributes={“name”=>“Jane Doe”, “id”=>2}, @new_record=false,
@errors=#<ActiveRecord::Errors:0x26a0df4 @errors={},
@base=#<User:0x26a190c …>>>, @new_record=false,
@errors=#<ActiveRecord::Errors:0x266c6e4 @errors={},
@base=#<Task:0x2679240 …>>>

fromdb=Task.find task.id
=> #<Task:0x26510ec @attributes={“subject”=>“Paint the house”,
“id”=>“1”, “description”=>“How about a nice shade of yellow?”,
“assignee”=>“1”, “requester”=>“2”}>

puts fromdb.assignee
TypeError: can’t dup Fixnum
from
/usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:1505:in
dup' from /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:1505:inattributes=’
from
/usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/base.rb:1354:in
initialize_without_callbacks' from /usr/local/lib/ruby/gems/1.8/gems/activerecord-1.14.4/lib/active_record/callbacks.rb:236:ininitialize’
from (eval):3:in new' from (eval):3:inassignee’
from (irb):7


At first glance, it seems clear I have done something completely wrong,
and that composed_of isn’t for me in this instance. I’m hoping I just
have a typo, but I don’t think I do…

So I was hoping someone could point me in the direction of the
“correct” way of doing this? I use this particular pattern in many
places, and I’m wondering if I really have to implement this style of
accessor/mutator everywhere I do (assuming I renamed the two db columns
to requester/assignee_user_id):

def requester
return User.find( @requester_user_id )
end

def requester=(requester)
user = User.find( requester.id )

if user
	@requester_user_id	=	user.id
end

end

def assignee
return User.find( @assignee_user_id )
end

def assignee=(assignee)
user = User.find( assignee.id )

if user
	@assignee_user_id	=	user.id
end

end

Any help would be much appreciated. Thanks!

– mark

mark j. hughes ïèøåò:

class User < ActiveRecord:Base
end

class Task < ActiveRecord::Base
composed_of :requester, :class_name => ‘User’, :mapping => [ [
:requester, :id ] ]
composed_of :assignee, :class_name => ‘User’, :mapping => [ [
:assignee, :id ] ]
end

class Task < ActiveRecord::Base
belongs_to :requester, :class_name => ‘User’, :foreign_key =>
‘requester’
belongs_to :assignee, :class_name => ‘User’, :foreign_key =>
‘assignee’
end

You can RDoc everything else.

I also suggest you to rename columns to requester_id and assignee_id.

Wow. How embarassing…

Thanks for your reply, Maxim. Ex post facto, it makes total sense, but
it just didn’t appear intuitive that the foreign_key is actually the
local key, since belongs_to is defining a backwards relationship from
another object/table.

Thanks so much!