2

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?

Timur Minulin
  • 81
  • 1
  • 9
  • Your capitalizations are wrong. Class names should be capitalized, association symbols should not. – jcm Nov 03 '13 at 11:52

3 Answers3

2

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
Community
  • 1
  • 1
Jason Newell
  • 591
  • 6
  • 11
1

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

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

David Grayson
  • 84,103
  • 24
  • 152
  • 189
  • That doesn't help, I don't have polymorphic associations here. The problem is that ActiveRecord can't store abstract class. – Timur Minulin Aug 22 '12 at 11:38
  • You can have polymorphic associations if you want. Make a table/model called Dog and a table/model called Frog. Then make *module* called Animal and include it in both of them. (I suspect you could also accomplish this with an Animal base class if you tried Rich Drummond's answer, but a module is probably easier.) You'll need to migrate the human table to have a animal_type string field. – David Grayson Aug 22 '12 at 15:56
  • Module isn't a solution for me. So now I came to that, I have fields for all Animal subclasses in Animal class (and don't make it abstract) and methods live in subclasses. It's not that cool that it should be, but since I don't know better solution... – Timur Minulin Aug 23 '12 at 06:21
0

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.

Rich Drummond
  • 3,439
  • 1
  • 15
  • 16
  • That doesn't help, actually. The thing is that Dog.new works, but dog = Dog.new human = Human.new human.Animal = dog - doesn't! – Timur Minulin Aug 22 '12 at 11:42