【问题标题】:DataMapper - Single Table InheritanceDataMapper - 单表继承
【发布时间】:2012-02-10 02:55:08
【问题描述】:

有人可以向我解释一下这里发生了什么吗?

这是我汇总的一个示例,以向大家展示最新情况:

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 =&gt; Male) 返回一个空数组:(?)

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

但是,Person.all(:type =&gt; 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 =&gt; Male) 的事情,我得到一个空数组。

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

我会将它与 Sinatra 一起使用。例如,我想要的是能够从数据库中以一克的形式抓取我所有的人,并将它们保存在@people 中,但在我看来,仍然能够对它们进行排序/过滤以用于各种用途。我之前在关联方面做过类似的事情,而且效果很好。但我想尝试 STI,因为它似乎是一种更简洁的方式来处理我的数据。

我也注意到,如果我做类似@women = Female.all 的事情,我会得到所有的女人,但如果我做类似@woman.each do |woman| 的事情,我将无法在我的视图中访问包含属性(即woman.favorite_song 返回什么都没有)。

我是否缺少有关其工作原理的信息?我根本不明白这里发生了什么,任何帮助将不胜感激。

【问题讨论】:

    标签: ruby sinatra single-table-inheritance ruby-datamapper


    【解决方案1】:

    如果您查看正在生成的 SQL,它会为您提供有关正在发生的事情的线索(如果您不知道,您可以通过 DataMapper::Logger.new(STDOUT, :debug) 来做到这一点您对 @ 的调用之前987654327@).

    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 =&gt; Male) 生成:

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

    因此,当您使用 Male.all 时,Datamapper 知道创建一个包含所有适当类名称的 SQL IN 子句,但是当您使用 Person.all(:type =&gt; Male) 时,只是您指定的类型和没有子类。

    当您使用@people.all(:type =&gt; Male) 查询集合而不是数据库时,必须进行类似的直接比较。

    为了在查询中正确获取某个类型的所有子类,您可以使用descendants method

    People.all(:type =&gt; Male.descendants) 生成这条 SQL:

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

    在这种情况下,这将返回您想要的,但请注意 IN 子句不包含 Male,它只是模型的后代,不包括相关子树的父节点。

    要获得“顶级”课程,您可以使用:

    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 &lt;&lt; Male 中的 &lt;&lt; method 被标记为私有,因此警告在这里更适用。我从source to Discriminator 得到这个用法。


    缺少属性

    您关于无法获取某些属性的其他问题看起来与延迟加载有关,但我无法弄清楚到底发生了什么。这可能是一个错误。

    当您使用 @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>
    

    我不知道我是否在这里遗漏了什么,或者这确实是一个错误。

    【讨论】:

    • 哇!谢谢多么棒的答案!谢谢!我认为它与底层 SQL / 父 / 子 / 后代关系有关。和/或我试图在数据中请求的方式。非常感谢!
    • 关于缺少属性的问题,你认为我应该提交错误报告吗?
    • @DonGraziano 抱歉回复晚了。我发现了这个:github.com/datamapper/dm-core/issues/46 这似乎是同一个问题。这是一个已关闭的旧错误,但看起来并没有修复。打开一个新的错误可能是值得的。至少您可以对正在发生的事情进行解释。
    • 哇!优秀的答案马特。 +1
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-22
    • 2011-08-29
    • 2010-09-17
    • 2010-11-13
    • 2010-10-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多