Timur Minulin
Timur Minulin

Reputation: 81

ActiveRecord Subclassing dilemma

So here we go. I've got an Activerecord::Base model, let it be called a human.

class human < ActiveRecord::Base
   has_one :Animal
end

Animal is an abstract class -

class animal < ActiveRecord::Base
   self.abstract_class = true;
 end

And I have a subclass of animal, let it be dog

class dog < Animal

in case I don't use abstract class, I can't add instance variables to 'Dog' (because it stores in 'Animal' table). In case I use abstract class, I can't add an 'Animal' to 'Human' - because rails doesn't know, how to store, for example, 'Dog'(ActiveRecord error: couldn't find table ''). This situation drives me crazy, and I just can't get over it. Am I missing something or just doin' it completely wrong?

Upvotes: 2

Views: 2445

Answers (3)

Jason Newell
Jason Newell

Reputation: 601

By convention in Ruby, Animal would refer to a class (actually, it's a bit more involved - this link has some more detail). In your original post, "class dog" should be "class Dog" b/c the class name is a constant, and if you had a has_one association between human and animal, you could say human.animal = (some instance of animal), but human.Animal is likely to have strange effects if it doesn't just immediately crash. The STI approach that others are recommending will do exactly what you want, though you would set the 'type' value, not 'Animal' (please don't actually do this directly).

You should read up on the meaning of capitalization in Ruby and RoR, STI, active record associations, and polymorphic associations. Something like this should work (not tested, and it's bad normalization - you can use has_one associations and a pattern called delegation to set up a situation where generic animal traits are in one table, and 'human specific' traits are in another to avoid a bunch of NULL columns in your database):

# remember to set up your migrations to add a 'type' column to your Animal table
# if animals can own other animals who own other animals, you may want to look at
# acts_as_tree, which does trees in relational databases efficiently 

class Animal < ActiveRecord::Base 
  self.abstract_class = true
end

class Dog < Animal
  # this is bad normalization - but you can keep this simple by adding 
  # a human_id field in your animal table (don't forget to index)
  # look into the 'belongs_to' / 'references' type available for DB migrations   
  belongs_to :human
end

class Human < Animal
  has_one :dog, :autosave => true # or you could use 'has_many :dogs' 
end

human = Human.new # => adds record to Animal table, with type = 'human'
dog = Dog.new
human.dog = dog
human.save

Upvotes: 2

Rich Drummond
Rich Drummond

Reputation: 3519

ActiveRecord by default gets the table name from the name of a model. You can override that, however. If you want Dogs in one table, Cats in other, etc. then you can do (in Rails 3.2):

class Dog < Animal
  self.table_name = 'dogs'
end

class Cat < Animal
  self.table_name = 'cats'
end

(You'll have to add migrations to create those tables.)

However, if you want all animals to exist in one table, you should look at Single-Table-Inheritance. See the ActiveRecord docs for more on that.

Upvotes: 0

David Grayson
David Grayson

Reputation: 87516

ActiveRecord has built-in support for polymorphic associations, so you could do that:

http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Upvotes: 1

Related Questions