【发布时间】:2013-04-09 22:36:28
【问题描述】:
如果一个人首先使用belong_to 和has_many 关联构建他们的模型,然后意识到他们需要迁移到embedded_in 和embeds_many 关联,那么如何在不使数千条记录失效的情况下做到这一点?需要以某种方式迁移它们。
【问题讨论】:
标签: ruby-on-rails mongoid
如果一个人首先使用belong_to 和has_many 关联构建他们的模型,然后意识到他们需要迁移到embedded_in 和embeds_many 关联,那么如何在不使数千条记录失效的情况下做到这一点?需要以某种方式迁移它们。
【问题讨论】:
标签: ruby-on-rails mongoid
我不太确定我的解决方案是否正确。这是您可能会尝试完成的事情。
假设你有模型 - 像这样
#User Model
class User
include Mongoid::Document
has_many :books
end
#Book Model
class Book
include Mongoid::Document
field :title
belongs_to :user
end
第一步,我将创建另一个模型,它类似于上面的 Book 模型,但它是嵌入的而不是引用的。
#EmbedBook Model
class EmbedBook
include Mongoid::Document
field :title
embedded_in :user
end
#User Model (Update with EmbedBook Model)
class User
include Mongoid::Document
embeds_many :embed_books
has_many :books
end
然后为上面的示例创建一个类似这样的 Mongoid 迁移
class ReferenceToEmbed < Mongoid::Migration
def up
User.all.each do |user|
user.books.each do |book|
embed_book = user.embed_books.new
embed_book.title = book.title
embed_book.save
book.destroy
end
end
end
def down
# I am not so sure How to reverse this migration so I am skipping it here
end
end
运行迁移后。从这里可以看到嵌入了参考书,但是嵌入模型的名称是 EmbedBook,模型 Book 仍然存在
所以下一步是把模型书做成嵌入的。
class Book
include Mongoid::Document
embedded_in :user
field :title
end
class User
include Mongoid::Document
embeds_many :books
embeds_many :embed_books
end
接下来就是将 embedbook 类型迁移到 book 类型
class EmbedBookToBook < Mongoid::Migration
def up
User.all.each do |user|
user.embed_books.each do |embed_book|
book = user.books.new
book.title = embed_book.title
book.save
embed_book.destroy
end
end
def down
# I am skipping this portion. Since I am not so sure how to migrate back.
end
end
现在,如果您看到 Book 从引用更改为嵌入。 您可以移除 EmbedBook 模型以完成更改。
【讨论】:
10gen 有几篇关于数据建模的文章可能很有用:
请记住,MongoDB 在嵌入方面有两个限制:
【讨论】:
试试这些步骤:
在User 模型中,保留has_many :books 关系,并添加
具有不同名称的嵌入关系不会覆盖books
方法。
class User
include Mongoid::Document
has_many :books
embeds_many :embedded_books, :class_name => "Book"
end
现在,如果您从 User 实例调用 embedded_books 方法
mongoid 应该返回一个空数组。
不向Book模型添加任何嵌入关系,编写自己的
迁移脚本:
class Book
include Mongoid::Document
field :title, type: String
field :price, type: Integer
belongs_to :user
def self.migrate
attributes_to_migrate = ["title","price"] # Use strings not symbols,
# we keep only what we need.
# We skip :user_id field because
# is a field related to belongs_to association.
Book.all.each do |book|
attrs = book.attributes.slice(*attributes_to_migrate)
user = book.user // through belong_to association
user.embedded_book.create!(attrs)
end
end
end
致电Book.migrate,您应该将所有书籍复制到每个用户中
与belongs_to 关系相关联。
现在您可以删除 has_many 和 belongs_to 关系,并且
最后切换到干净的嵌入式解决方案。
class User
include Mongoid::Document
embeds_many :books
end
class Book
include Mongoid::Document
field :title, type: String
field :price, type: Integer
embedded_in :user
end
我没有测试过这个解决方案,但理论上应该可以,请告诉我。
【讨论】:
我有一个更简洁的答案:
假设您有相同的模型:
#User Model
class User
include Mongoid::Document
has_many :books
end
#Book Model
class Book
include Mongoid::Document
field :title
belongs_to :user
end
所以改成嵌入:
#User Model
class User
include Mongoid::Document
embeds_many :books
end
#Book Model
class Book
include Mongoid::Document
field :title
embedded_in :user
end
并像这样生成一个 mongoid 迁移:
class EmbedBooks < Mongoid::Migration
@@attributes_to_migrate = [:title]
def self.up
Book.unscoped.where(:user_id.ne => nil).all.each do |book|
user = User.find book[:user_id]
if user
attrs = book.attributes.slice(*@@attributes_to_migrate)
user.books.create! attrs
end
end
end
def self.down
User.unscoped.all.each do |user|
user.books.each do |book|
attrs = @@attributes_to_migrate.reduce({}) do |sym,attr|
sym[attr] = book[attr]
sym
end
attrs[:user] = user
Book.find_or_create_by(**attrs)
end
end
end
end
这很有效,因为当您从类级别查询时,它正在寻找顶级集合(即使您更改了关系,它仍然存在),并且book[:user_id] 是访问文档属性而不是自动生成的方法的技巧这也存在,因为你没有做任何事情来删除它们。
你有它,从关系到嵌入式的简单迁移
【讨论】: