问题
Could Some one please explain to me what is going on here?
This is an example that I put together to show y'all whats up:
class Person
include DataMapper::Resource
property :id, Serial
property :type, Discriminator
property :name, String
property :age, Integer
end
class Male < Person
end
class Father < Male
property :job, String
end
class Son < Male
end
class Female < Person
end
class Mother < Female
property :favorite_song, String
end
class Daughter < Female
end
DataMapper.auto_upgrade!
If I call Person.all
I get:
Person.all
=> [#<Son @id=1 @type=Son @name="Mike" @age=23 @status=nil>,
#<Son @id=2 @type=Son @name="Carlos" @age=12 @status=nil>,
#<Father @id=3 @type=Father @name="Robert" @age=55 @job=<not loaded>>,
#<Mother @id=4 @type=Mother @name="Wanda" @age=47 @status=nil @favorite_song=<not loaded>>,
#<Daughter @id=5 @type=Daughter @name="Meg" @age=16 @status=nil>]
And if I call Person.get(3).type
I get:
Person.get(3).type
=> Father
And Male.all
gives me this:
Male.all
=> [#<Son @id=1 @type=Son @name="Mike" @age=23 @status=nil>,
#<Son @id=2 @type=Son @name="Carlos" @age=12 @status=nil>,
#<Father @id=3 @type=Father @name="Robert" @age=55 @job=<not loaded>>]
And Male.get(3).type
gives this:
Male.get(3).type
=> Father
But Person.all(:type => Male)
returns an empty array: (?)
Person.all(:type => Male)
=> []
However, Person.all(:type => Son)
returns all of the Son
type entries (=/)
Person.all(:type => Son)
=> [#<Son @id=1 @type=Son @name="Mike" @age=23 @status=nil>,
#<Son @id=2 @type=Son @name="Carlos" @age=12 @status=nil>]
If I try do do something like @person = People.all
I get all entries in @person exactly as you would expect. but I cannot do something like @men = @person.all(:type => Male)
I get an empty array.
@men = @people.all( :type => Male)
=> []
I would be using this with Sinatra. What I would like is to be able to for example grab all of my people in one gram from the DB and hold them in @people
but still be able to sort/filter them for various uses in my views. I have done something similar with associations before and it worked rather well. But I would like to try STI because it seems to be a more concise way to deal with my data.
I have noticed things as well that if I do something like @women = Female.all
I get all of the woman, but if I do something like @woman.each do |woman|
I am unable to access contain properties in my views (i.e. woman.favorite_song
returns nothing).
Am I missing something about how this works? I do not understand what is going on here at all and any help would be appreciated.
回答1:
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.
来源:https://stackoverflow.com/questions/8858451/datamapper-single-table-inheritance