使用ActiveRecord :: Persistence#转换单表继承模型

时间:2014-02-17 14:46:29

标签: ruby-on-rails activerecord single-table-inheritance

我在Rails项目中使用单表继承并尝试将一个模型的类型更改为另一个模型的类型。以下是相关的架构和模型:

create_table "images", force: true do |t|                                                                                                                                          
  t.string   "url"                                                                                                                                                                  
  t.string   "type"                                                                                                                                                                  
  t.datetime "created_at"                                                                                                                                                          
  t.datetime "updated_at"                                                                                                                                                            
  t.integer  "user_id",    limit: 255                                                                                                                                                 
end


class Image < ActiveRecord::Base
  validates :url, :user_id, presence: true
end

class UnconfirmedImage < Image
end

class ConfirmedImage < Image
end

我需要将UnconfirmedImage转换为ConfirmedImage,反之亦然。我应该可以使用ActiveRecord::Persistance#becomes!执行此操作。

但是,当我尝试保存更改时,它似乎无声地失败:

foo = UnconfirmedImage.new(url: "foo", user_id:1)
=> #<UnconfirmedImage id: nil, url: "foo", type: "UnconfirmedImage", created_at: nil,    updated_at: nil, user_id: 1>
foo.save
#sql omitted                                                                                                                  
=> true


bar = foo.becomes!(ConfirmedImage)                                                                                                                                      
=> #<ConfirmedImage id: nil, url: "foo", type: "ConfirmedImage", created_at: nil,  updated_at: nil, user_id: 1>

bar.save

注意这里生成错误的sql。类型的WHERE子句检查新类型,而不是旧类型。这不应该返回。

[13891][12:32:05.583 +0000][DEBUG]:    (0.1ms)  begin transaction 
[13891][12:32:05.598 +0000][DEBUG]:   SQL (0.3ms)  UPDATE "images" SET "type" = ?,"updated_at" = ? WHERE "images"."type" IN ('ConfirmedImage') AND "images"."id" = 2 [["type", "ConfirmedImage"], ["updated_at", Mon, 17 Feb 2014 12:32:05 UTC +00:00]]                                                                                                                         
[13891][12:32:05.599 +0000][DEBUG]:    (0.1ms)  commit transaction                                                                                                                      
=> true

当我尝试查询对象时,会确认这一点。

UnconfirmedImage.all
[13891][12:33:59.525 +0000][DEBUG]:   UnconfirmedImage Load (0.3ms)  SELECT "images".*   FROM "images" WHERE "images"."type" IN ('UnconfirmedImage')
=> #<UnconfirmedImage id: 2, url: "foo", type: "UnconfirmedImage", created_at: "2014-02- 17 12:31:15", updated_at: "2014-02-17 12:31:15", user_id: 1>]>

ConfirmedImage.all
[13891][12:33:39.646 +0000][DEBUG]:   ConfirmedImage Load (0.2ms)  SELECT "images".* FROM "images" WHERE "images"."type" IN ('ConfirmedImage')
 => #<ActiveRecord::Relation []>

有人能建议最好的解决方案吗?我不确定这是预期的行为,还是Rails中的错误。

由于

1 个答案:

答案 0 :(得分:1)

这次曾多次咬过我。某处ActiveRecord或ARel挂起旧类型。我过去的方式就是简单地做这样的事情:

image = image.becomes(ConfirmedImage)
Image.where(id: image.id).update_all(type: 'ConfirmedImage')

所以稍后在构建查询时,type列具有ARel预期的正确值并且更新通过。重要的是从STI链中的父类调用范围#update_all,否则ActiveRecord将再次对该类型进行范围调整。

我会提醒一下,有时这实际上是状态机的工作,并且查看模型的命名,我猜想state_machine gem可能比使用STI和弯曲ActiveRecord更好。