DataMapper - наследование одной таблицы - PullRequest
2 голосов
/ 14 января 2012

Может ли кто-нибудь объяснить мне, что здесь происходит?

Это пример, который я собрал, чтобы показать вам, что происходит:

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!

Если я позвонюPerson.all Я получаю:

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>]

И если я звоню Person.get(3).type, я получаю:

Person.get(3).type
=> Father

И Male.all дает мне это:

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>>]

И Male.get(3).type дает следующее:

Male.get(3).type
=> Father

Но Person.all(:type => Male) возвращает пустой массив: (?)

Person.all(:type => Male)
=> []

Однако Person.all(:type => Son) возвращает все Sonвведите записи (= /)

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>]

Если я попытаюсь сделать что-то вроде @person = People.all, я получу все записи в @person точно так, как вы ожидаете.но я не могу сделать что-то вроде @men = @person.all(:type => Male) Я получаю пустой массив.

@men = @people.all( :type => Male)
=> []

Я бы использовал это с Синатрой.Я хотел бы иметь возможность, например, взять всех моих людей в один грамм из БД и сохранить их в @people, но при этом иметь возможность сортировать / фильтровать их для различных применений в моих представлениях.Я делал нечто подобное с ассоциациями раньше, и это работало довольно хорошо.Но я хотел бы попробовать ИППП, потому что это, кажется, более краткий способ справиться с моими данными.

Я также заметил, что если я делаю что-то вроде @women = Female.all, я получаю всю женщину,но если я делаю что-то вроде @woman.each do |woman|, я не могу получить доступ к свойствам содержимого в моих представлениях (т. е. woman.favorite_song ничего не возвращает).

Я что-то упускаю из-за того, как это работает?Я вообще не понимаю, что здесь происходит, и любая помощь будет оценена.

1 Ответ

10 голосов
/ 15 января 2012

Если вы посмотрите на генерируемый 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>

Я не знаю, упускаю ли я что-то здесь или это действительно ошибка.

...