DataMapper - Single Table Inheritance

≯℡__Kan透↙ 提交于 2019-12-06 05:21:11

If you look at the SQL that's being generated it gives you a clue as to what's going on (if you don't know, you can do this with DataMapper::Logger.new(STDOUT, :debug) before your call to DataMapper::setup).

Person.all simply generates:

SELECT "id", "type", "name", "age" FROM "people" ORDER BY "id"

as you would expect.

Male.all generates:

SELECT "id", "type", "name", "age" FROM "people"
  WHERE "type" IN ('Male', 'Father', 'Son') ORDER BY "id"

and Person.all(:type => Male) generates:

SELECT "id", "type", "name", "age" FROM "people"
  WHERE "type" = 'Male' ORDER BY "id"

So when you use Male.all Datamapper knows to create an SQL IN clause containing the names of all the appropriate classes, but when you use Person.all(:type => Male) uses just the type you specify and none of the subclasses.

A similar direct comparison must be happening when you query a collection rather than the database with @people.all(:type => Male).

In order to correctly get all subclasses of a type in a query you can use the descendants method.

People.all(:type => Male.descendants) generates this SQL:

SELECT "id", "type", "name", "age" FROM "people"
  WHERE "type" IN ('Father', 'Son') ORDER BY "id"

In this case this will return what you want, but note that the IN clause doesn't contain Male, it's only the descendants of the model, not including the parent of the subtree in question.

To get the 'top' class as well, you could use:

Person.all(:type => Male.descendants.dup << Male)

to add the Male class to the IN clause. Note the dup is needed, otherwise you'll get stack level too deep (SystemStackError).

This will also work on collections as expected:

@people.all(:type => Male.descendants.dup << Male)

will return all males without hitting the database (assuming @people already contains all the people).

If you do decide to use the descendants method, note that although it isn't marked as private in the docs, it's marked @api semipublic in the source, so look out when upgrading Datamapper. The << method in Male.descendants.dup << Male is marked as private, so the warning applies even more here. I got this usage from the source to Discriminator.


Missing properties

Your other issue about not being able to get certain properties looks like it's something to do with lazy loading, but I can't work out exactly what's going on. It might be a bug.

When you load all females with @women = Female.all the SQL generated is:

SELECT "id", "type", "name", "age" FROM "people"
  WHERE "type" IN ('Female', 'Mother', 'Daughter') ORDER BY "id"

so only attributes possessed by all members are fetched. The favorite_song of mothers is then fetched the first time you reference it. The lines:

puts women.first.inspect
puts women.first.favorite_song
puts women.first.inspect

give (including the SQL log showing when the missing value is fetched):

#<Mother @id=4 @type=Mother @name="Wanda" @age=47 @favorite_song=<not loaded>>
 ~ (0.000069) SELECT "id", "type", "favorite_song" FROM "people" WHERE "id" = 4 ORDER BY "id"
Suspicious minds
#<Mother @id=4 @type=Mother @name="Wanda" @age=47 @favorite_song="Suspicious minds">

However, when I do something similar in an each block, the query to fetch the missing value doesn't incude the favorite_song, and the value is set to nil in the model:

women.each do |w|
  next unless w.respond_to? :favorite_song
  puts w.inspect
  puts w.favorite_song
  puts w.inspect
end

gives the output (again including the SQL):

#<Mother @id=4 @type=Mother @name="Wanda" @age=47 @favorite_song=<not loaded>>
 ~ (0.000052) SELECT "id", "type" FROM "people" WHERE "type" IN ('Female', 'Mother', 'Daughter') ORDER BY "id"

#<Mother @id=4 @type=Mother @name="Wanda" @age=47 @favorite_song=nil>

I don't know if I'm missing something here, or if this is indeed a bug.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!