【问题标题】:Invalid single-table inheritance type with rake task and custom STI names具有 rake 任务和自定义 STI 名称的无效单表继承类型
【发布时间】:2018-02-08 12:25:21
【问题描述】:

我做了大量工作来重构大量代码,并对 db 架构进行了重大更改。现在我正在尝试编写一个 rake 任务来将记录从旧表迁移到新表。

我正在上这样的课:

#app/models/restream/service.rb
class Restream::Service < ActiveRecord::Base
def self.types
    %w(custom multiple_destinations_service one_destination_service) +
    Restream::Custom.types + Restream::MultipleDestinationsService.types
  end

  def self.find_sti_class(type_name) #allows to find classes by short names
    type_name = "Restream::#{type_name.camelcase}".constantize
    super
  end
end

#app/models/restream/custom.rb
class Restream::Custom < Restream::Service
  def self.sti_name
    "custom"
  end

  def self.types
    %w(periscope odnoklassniki vkontakte)
  end
end

#app/models/restream/periscope.rb
class Restream::Periscope < Restream::Custom
  def self.sti_name
    "periscope"
  end
end

一切正常。直到我尝试手动添加记录。 在我以前的版本中,我有这样的结构:

class Restream::Custom < ActiveRecord::Base
  def self.types; %w(custom periscope vkontakte); end
end

class Restream::Periscope < Restream::Custom
  def self.sti_name; 'periscope'; end
end

现在我尝试从旧的restream_custom 表中获取所有记录,然后复制类型。大致:

Restream::Custom.create(type: old_restream_custom.type)

那失败了:

ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: periscope is not a subclass of Restream::Custom

显然不是!但无论如何,我已经有一堆type: 'periscope' 的记录,所以我知道这是一个有效值。 这是什么原因,我该如何解决这个问题?

======

我可以看到两种方式:

1) 将type 设置为Restream::Periscope,而不仅仅是periscope。但这会创建记录,Restream::Periscope.find_eachRestream::Custom.find_each 或类似的东西找不到,因为它会在其 type 列中搜索带有 periscope 的记录,而不是 Restream::Periscope

2) 从restream_custom 表中仅选择customperiscope 等每种类型的记录,并为潜望镜创建Restream::Periscope,而不是Restream::Custom,并尝试在此处提供正确的类型。但我发现它有点不漂亮、不干燥且没有必要,我想知道我是否可以用它做得更漂亮。

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 activerecord rake rake-task


    【解决方案1】:

    如果重构不是太大,我会选择您的第一个选项 1) 将 type 设置为 Restream::Periscope 而不是 periscope,主要是因为它是 Rails convention

    如果实施了选项 1),您表示您对此的另一个担忧是 Restream::Periscope.find_each 将不再返回其他“类型”的记录,并将根据子类进行相应的自动过滤...确实有道理因为您的.find_each 正在查询Restream::Periscope,因此很直观,我期望所有返回的记录都是type "Restream::Periscope"

    现在如果您想查询“所有类型”,那么您可以只查询“父”类,即Restream::Service,您现在可以在其中执行以下操作:

    Restream::Service.find_each(type: old_restream_custom.type)
    # or
    Restream::Service.create(type: old_restream_custom.type)
    

    这是我的建议,除非重构所有代码确实是一项艰巨的任务。希望这会有所帮助。

    【讨论】:

    • 我想我会得到这个。但我真正想知道的是一些……你知道的……“聪明”的东西。关于如何从类中加载的一些信息,以便 ActiveRecord 可以知道这些是有效的并且不会引发错误。也许那不是我应该使用它的地方,但我真的对它很感兴趣。
    • 我的猜测是 Rails 使用字符串化常量,即Restream::Periscope 作为type 的值,因为它也正确处理了命名空间,Restream::PeriscopeAnotherNamespace::Periscope 不同。这也许就是为什么 Rails 没有一个“神奇”的解决方案来识别periscope,就 Rails 而言,它还不知道这实际上是什么子类。虽然,我只是知识有限;也许我也错过了一些东西。
    • 是的,我以某种方式理解它为什么这样做。但是使用self.sti_name 在运行rails 应用程序时会有所帮助,但是在运行rake 时由于某种原因它会忽略它..
    • 哦,我明白了,很有趣......在你这么说之后,我刚刚找到了this,这似乎也是你在做的事情。如果我能重现你的场景,我稍后会检查这个。
    • 您发送的问题正是我正在做的。正如您在我的问题中看到的那样,我拥有所有这些sti_name。现在我意识到没有什么真正依赖于此,我可以为 STI 使用传统类型名,因此删除了 self.typesself.sti_names,所有测试都成功通过了,但是,天哪,当我运行 Restream::Custom.find_by_sql("select * from restream_customs where type is not null").each ... 时,我得到了 The single-table inheritance mechanism failed to locate the subclass: 'periscope'. 因为记录已经在type col 中有这些值。所以现在我想如何管理这个......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多