Если вы посмотрите на генерируемый SQL, он даст вам подсказку о том, что происходит (если вы не знаете, вы можете сделать это с DataMapper::Logger.new(STDOUT, :debug)
до вашего звонка на DataMapper::setup
).
Person.all
просто генерирует:
SELECT "id", "type", "name", "age" FROM "people" ORDER BY "id"
как и следовало ожидать.
Male.all
генерирует:
SELECT "id", "type", "name", "age" FROM "people"
WHERE "type" IN ('Male', 'Father', 'Son') ORDER BY "id"
и Person.all(:type => Male)
генерируют:
SELECT "id", "type", "name", "age" FROM "people"
WHERE "type" = 'Male' ORDER BY "id"
Поэтому, когда вы используете Male.all
Datamapper знает, как создать предложение SQL IN
, содержащее имена всех соответствующих классов, но при использовании Person.all(:type => Male)
использует просто указанный вами тип и ничего подклассов.
Аналогичное прямое сравнение должно происходить, когда вы запрашиваете коллекцию, а не базу данных с @people.all(:type => Male)
.
Чтобы правильно получить все подклассы типа в запросе, вы можете использовать метод descendants
.
People.all(:type => Male.descendants)
генерирует этот SQL:
SELECT "id", "type", "name", "age" FROM "people"
WHERE "type" IN ('Father', 'Son') ORDER BY "id"
В этом случае это вернет то, что вы хотите, но учтите, что предложение IN
не содержит Male
, это только потомки модели, не включая родителя рассматриваемого поддерева.
Чтобы получить класс 'top', вы можете использовать:
Person.all(:type => Male.descendants.dup << Male)
чтобы добавить класс Male
в предложение IN
. Обратите внимание, что dup
необходим, иначе вы получите stack level too deep (SystemStackError)
.
Это также будет работать с коллекциями, как и ожидалось:
@people.all(:type => Male.descendants.dup << Male)
вернет всех мужчин без попадания в базу данных (при условии, что @people
уже содержит всех людей).
Если вы решите использовать метод descendants
, обратите внимание, что, хотя он не помечен как закрытый в документах, он помечен @api semipublic
в источнике, поэтому обратите внимание при обновлении Datamapper , <<
метод в Male.descendants.dup << Male
помечен как как частный, поэтому предупреждение применяется здесь еще больше. Я получил это использование от источника до Дискриминатора .
Недостающие свойства
Ваша другая проблема с невозможностью получить определенные свойства выглядит так, как будто это связано с отложенной загрузкой, но я не могу точно понять, что происходит. Это может быть ошибка.
Когда вы загружаете всех женщин с @women = Female.all
, генерируется SQL:
SELECT "id", "type", "name", "age" FROM "people"
WHERE "type" IN ('Female', 'Mother', 'Daughter') ORDER BY "id"
поэтому выбираются только атрибуты, которыми обладают все участники. Затем favorite_song
матерей получают при первом обращении к нему. Строки:
puts women.first.inspect
puts women.first.favorite_song
puts women.first.inspect
give (включая журнал SQL, показывающий, когда извлекается отсутствующее значение):
#<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">
Однако, когда я делаю что-то подобное в блоке each
, запрос на выбор пропущенного значения не включает favorite_song
, и в модели устанавливается значение nil:
women.each do |w|
next unless w.respond_to? :favorite_song
puts w.inspect
puts w.favorite_song
puts w.inspect
end
дает вывод (снова включая 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>
Я не знаю, упускаю ли я что-то здесь или это действительно ошибка.