STI and nested object on subclass

Hi there!

I'm trying to create an inheritance between two classes where the child
class has its own relations... but I it looks like the concept in rails
is a bit different than in other languages/framworks (or I'm doing
something wrong).

Let's say I have this two model classes, parent and child class, and a
relation to the child class:

class Parentclass < ActiveRecord::Base
end

## this child class has a anotherclass_id
class Childclass < Parentclass
end

class Anotherclass < ActiveRecord::Base
  has_one :childclass
end

My problem is that, when I try to access the relation to the child
class, rails tries to find the relation id in the parent class! I mean I
get this...

a = Anotherclass.find(1)
a.childclass

ActiveRecord::StatementInvalid: Mysql::Error: Unknown column
'parentclasses.anotherclass_id' in 'where clause': SELECT * FROM
`parentclasses` WHERE (`parentclasses`.anotherclass_id = 1) LIMIT 1

I even tried to force the class name like:

class Anotherclass < ActiveRecord::Base
  has_one :childclass, :class_name => "Childclass"
end

But same thing, the has_one relation goes to parent class to try to find
the relational id.

Anyone can help with this please??
Thanks in advance!

I even tried to force the class name like:

class Anotherclass < ActiveRecord::Base
has_one :childclass, :class_name => "Childclass"
end

But same thing, the has_one relation goes to parent class to try to find
the relational id.

If you want to change the name of the foreign key, use
the :foreign_key option.

Fred

Thanks for your reply Fred, but the fk name is ok, the problem is that
the has_one relation goes to the wrong table, it should go to the child
class, not to the parent class...

You're using sti - there is only one table.

Fred

Ok, then maybe I didn't ask properly... you are right it's not STI what
I'm trying to do, but I'll try to explain it better.

What I need is just to extend a base class (wich is a model with its db
table and all activerecord logic) with another class (a subclass with
its own table and all activerecord logic, plus the parent methods,
properties and so on). I tried to do it like I mentioned in my first
email, but when the child class (the extended class) has a fk to another
table, rails tries to find the fk in the parent class, not in the
extended class, like the example I posted before:

class Parentclass < ActiveRecord::Base
end

## this child class has a anotherclass_id
class Childclass < Parentclass
end

class Anotherclass < ActiveRecord::Base
  has_one :childclass
end

then...

a = Anotherclass.find(1)
a.childclass

ActiveRecord::StatementInvalid: Mysql::Error: Unknown column
'parentclasses.anotherclass_id' in 'where clause': SELECT * FROM
`parentclasses` WHERE (`parentclasses`.anotherclass_id = 1) LIMIT 1

is there any way to do what I'm trying to do in rails using
activerecord??

thanks

Rails doesn't do that - either the parent class is abstract (ie has no
table) or you get STI. You might have better look putting the common
functionality in a module, or having an abstract class which is the
parent of your 2 concrete classes.

Fred

Well I really wanted to have inheritance on activerecord based classes
(with db tables saving common data in base class and concrete data in
subclasses). Sad to know rails can't do it. I'll try to do what I need
with a different logic.

Thanks a lot for your help Fred :slight_smile:

jruiz wrote:

Well I really wanted to have inheritance on activerecord based classes
(with db tables saving common data in base class and concrete data in
subclasses). Sad to know rails can't do it. I'll try to do what I need
with a different logic.

You can do that with a has_one relationship.

Thanks a lot for your help Fred :slight_smile:

Best,

Yep that's finally how I will have to do it... but this is not what I
wanted. A has_one relation means that I have to access things like:

Anotherclass.property
Anotherclass.childclass.specific_property
Anotherclass.childclass.parentclass.common_property

And more important... I need to manually manage related objects (or
create hooks os similar). I mean I need for example to do something
like:

a = Anotherclass.new
b = Childclass.new
a.childclass = b

... and so on...

I was thinking "rails' magic" was really magic :wink:

[Please quote when replying. Otherwise the discussion is impossible to
follow.]

Javier Ruiz wrote:

Yep that's finally how I will have to do it... but this is not what I
wanted. A has_one relation means that I have to access things like:

Anotherclass.property
Anotherclass.childclass.specific_property
Anotherclass.childclass.parentclass.common_property

That makes sense from a relational point of view. If you don't like it,
then use STI.

And more important... I need to manually manage related objects (or
create hooks os similar). I mean I need for example to do something
like:

a = Anotherclass.new
b = Childclass.new
a.childclass = b

... and so on...

You'd need to do that regardless of whether your original idea worked.

I was thinking "rails' magic" was really magic :wink:

I suppose it is, but it doesn't extend to spreading one class across
multiple tables.

If you were ambitious, you probably *could* extend ActiveRecord to do
that, but I'm not sure it's a good idea -- it's trying to impose too
much of an OO approach on a relational DB. Alternatively, you could try
an OODB like GemStone/MagLev.

Best,

Hello Javier,

I was just checking the set_table_name class method of ActiveRecord and I think that could work for you:

class AnotherClass < ActiveRecord::Base
has_one :worker
end

class Person < ActiveRecord::Base
def something
self.name
end
end

class Worker < Person
set_table_name “workers”
end

w = Worker.create(:name => “WorkerName”)
p = Person.create(:name => “PersonName”)
ac = AnotherClass.create

ac.worker = w
ac.save

ac.worker.something # => “WorkerName”

I hope that would be helpful

Daniel Gaytán

Hi Daniel

Thanks for helping :slight_smile:

This is exactly how I was thinking it should work. But I just tried
using set_table_name and I still got the same result, for example, using
your code (which is exactly what I'd love to do), I get this:

w = Worker.create(:name => "WorkerName") # ok
p = Person.create(:name => "PersonName") # ok
ac = AnotherClass.create # ok

ac.worker = w # here i got the error! like:
# ActiveRecord::StatementInvalid: Mysql::Error: Unknown column
'person.another_class_id' in 'where clause': SELECT * FROM `person`
WHERE (`person`.another_class_id = 1) LIMIT 1

It still goes to the parent class to search for the relational id.
"set_table_name" works for you in this particular case? could it be the
activerecord version I use? My gems are:

actionmailer (2.3.9)
actionpack (2.3.9)
activerecord (2.3.9)
activeresource (2.3.9)
activesupport (2.3.9)
builder (2.1.2)
capistrano (2.5.19)
highline (1.6.1)
i18n (0.4.1, 0.3.7)
json (1.4.6)
json_pure (1.4.6)
multi_json (0.0.4)
mysql (2.8.1)
net-scp (1.0.3)
net-sftp (2.0.5)
net-ssh (2.0.23)
net-ssh-gateway (1.0.1)
pg (0.9.0)
racc (1.4.6)
rack (1.1.0)
rails (2.3.9)
rake (0.8.7)
rspec (1.3.0)
text-format (1.0.0)
text-hyphen (1.0.0)
tmail (1.2.7.1)
tzinfo (0.3.23)
will_paginate (2.3.14)

And the trace when I get the error:

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/connection_adapters/abstract_adapter.rb:227:in `log'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/connection_adapters/mysql_adapter.rb:324:in `execute'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/connection_adapters/mysql_adapter.rb:639:in `select'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/connection_adapters/abstract/database_statements.rb:7:in `select_all_without_query_cache'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/connection_adapters/abstract/query_cache.rb:62:in `select_all'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/base.rb:665:in `find_by_sql'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/base.rb:1582:in `find_every'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/base.rb:1539:in `find_initial'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/base.rb:617:in `find'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/associations/has_one_association.rb:81:in `find_target'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/associations/association_proxy.rb:236:in `load_target'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/associations/association_proxy.rb:113:in `reload'

from /usr/lib64/ruby/gems/1.8/gems/activerecord-2.3.9/lib/active_record/associations.rb:1256:in `worker'
  from (irb):2

Regards,

Javi Ruiz

Hello Javier

I don’t know what would happen, I installed rails 2.3.9 to try the same code I wrote in rails 3 and worked like a charm.

There is something weird in your code since the table name for Person model should be People… I don’t know if there is a problem with your ActiveRecord or maybe a code you are missing to write =S.

Here is my code, now in rails 2.3.9 :

class Person < ActiveRecord::Base
def something
self.name
end
end

class Worker < Person
set_table_name “workers”
end

class Task < ActiveRecord::Base
has_one :worker
end

Worker(id: integer, name: string, task_id: integer, created_at: datetime, updated_at: datetime)
Person(id: integer, name: string, created_at: datetime, updated_at: datetime)
Task(id: integer, description: string, created_at: datetime, updated_at: datetime)

Worker.table_name # => “workers”

w = Worker.create(:name => “WorkerName”) # => #<Worker id: 1, name: “WorkerName”, task_id: nil, created_at: “2010-09-14 15:22:55”, updated_at: “2010-09-14 15:22:55”>
p = Person.create(:name => “PersonName”) # => #<Person id: 1, name: “PersonName”, created_at: “2010-09-14 15:23:01”, updated_at: “2010-09-14 15:23:01”>
t = Task.create # => #<Task id: 1, description: nil, created_at: “2010-09-14 15:23:16”, updated_at: “2010-09-14 15:23:16”>

t.worker = w # => #<Worker id: 1, name: “WorkerName”, task_id: 1, created_at: “2010-09-14 15:22:55”, updated_at: “2010-09-14 15:23:26”>
t.worker.something # => “WorkerName”

Let me know if this worked,please.

Daniel Gaytán