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