【问题标题】:Workaround for AssociationTypeMismatch?AssociationTypeMismatch 的解决方法?
【发布时间】:2019-05-07 19:08:13
【问题描述】:

我正在进行重构,我为新传入数据编写了新模型,这些新模型将位于功能标志后面,同时为尚未拥有该功能的人的传入数据保持旧模型处于活动状态旗帜。新旧模型都与数据库中的同一张表进行交互。


class Poem < ApplicationRecord
   belongs_to: :author
   belongs_to: :anthology, optional: true
end

class Anthology < ApplicationRecord
   belongs_to: :author
   has_many: :poems
end

class Author < ApplicationRecord
   has_many: :anthologies
   has_many: :poems
end


class NewPoem < ApplicationRecord
   self.table_name = 'poems'

   belongs_to: :author, class_name: 'NewAuthor'
   belongs_to: :anthology, class_name: 'NewAnthology', optional: true
end

class NewAnthology < ApplicationRecord
   self.table_name = 'anthologies'

   belongs_to: :author, class_name: 'NewAuthor'
   has_many: :poems, class_name: 'NewPoem'
end

class NewAuthor < ApplicationRecord
   self.table_name = 'authors'
   has_many: :anthologies, class_name: 'NewAnthology'
   has_many: :poems, class_name: 'NewPoem'
end

当我创建新书时,我想将新书分配给作者

anthology = Anthology.find(1)
@poem = NewPoem.new
@poem.author = anthology.author

这给了我一个错误

ActiveRecord::AssociationTypeMismatch (NewAuthor(#70008442274840) expected, got Author(#47031421032620)):

有没有办法解决这个问题?或者如果没有数据迁移,我将无法将模型与旧模型相关联?

【问题讨论】:

    标签: ruby-on-rails activerecord refactoring associations


    【解决方案1】:

    您需要更改获取Anthology 记录的第一行。目前,它获取Anthology 模型的记录为anthology = Anthology.find(1)。但是,此模型指的是旧的Author 模型。
    在下一行中,@poem = NewPoem.new 实例化了NewPoem 模型的一个对象,并且下一行尝试为其分配作者。但是,@poem.author 将是 NewAuthor 类的对象,但它被赋予了 @poem.author = anthology.author 的值,其中 anthology.author 仍然指的是旧的 Author 表。

    将第一行更改为 anthology = NewAnthology.find(1) 将解决此问题。它将获取与Anthology.find(1) 相同的记录,因为它们查询的是同一张表。
    整体代码则变为:

    anthology = NewAnthology.find(1)
    @poem = NewPoem.new
    @poem.author = anthology.author
    

    【讨论】:

    • 哦,好的,我明白了。因此,如果以这种方式定义新模型,调用 NewAnthology.find(1) 仍会为您提供相同的表条目。既然你这么说,那是有道理的!谢谢!
    【解决方案2】:

    看起来新模型只是旧模型的超集。您可以将NewPoem(以及其他新模型)定义为:

    class NewPoem < Poem
      validates :new_colums
    
      def new_cool_stuff
      end
    end
    

    这样您就可以避免重新定义关联。

    【讨论】:

      【解决方案3】:

      ActiveRecord 实际上有一个内置的单表继承机制,您可以在这里使用。

      首先在表中添加一个名为 type 的列:

      class AddTypeToPoems < ActiveRecord::Migration[5.2]
        def change
          add_column :poems, :type, :string
          add_index :poems, :type
        end
      end
      

      然后设置类继承:

      class Poem < ApplicationRecord
      end
      
      class NewPoem < Poem
        # gets its table name from the parent class
      end
      

      ActiveRecord 自动设置type 列,甚至在检索记录时使用它:

      irb(main):001:0> NewPoem.create
         (0.3ms)  BEGIN
        NewPoem Create (1.3ms)  INSERT INTO "poems" ("created_at", "updated_at", "type") VALUES ($1, $2, $3) RETURNING "id"  [["created_at", "2019-05-08 19:17:45.086947"], ["updated_at", "2019-05-08 19:17:45.086947"], ["type", "NewPoem"]]
         (0.7ms)  COMMIT
      => #<NewPoem id: 1, title: nil, created_at: "2019-05-08 19:17:45", updated_at: "2019-05-08 19:17:45", type: "NewPoem">
      irb(main):002:0> Poem.first
        Poem Load (0.7ms)  SELECT  "poems".* FROM "poems" ORDER BY "poems"."id" ASC LIMIT $1  [["LIMIT", 1]]
      => #<NewPoem id: 1, title: nil, created_at: "2019-05-08 19:17:45", updated_at: "2019-05-08 19:17:45", type: "NewPoem">
      irb(main):003:0> 
      

      所以即使我们使用Poem.first,它也会从结果中实例化一个NewPoem 实例。如果您在子类上使用查找器,rails 将自动确定范围:

      irb(main):003:0> NewPoem.all
        NewPoem Load (1.8ms)  SELECT  "poems".* FROM "poems" WHERE "poems"."type" IN ('NewPoem') LIMIT $1  [["LIMIT", 11]]
      

      如果你只想要你需要使用的“旧”类型:

      Poem.where(type: [nil, 'Poem'])
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-06-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多