我如何保持has_many:在序列化为JSON并返回Rails 4.0.3时通过关系?

如何转换为JSON并返回并保持关系? 它认为当我取消包裹对象时它们不存在!

irb(main):106:0* p = Post.last => # p.tags => #<ActiveRecord::Associations::CollectionProxy [# 2 #### !!!!!!!!!!!! irb(main):110:0> json = p.to_json => "{\"id\":113,\"title\":... }" irb(main):111:0> p2 = Post.new( JSON.parse(json) ) => # p2.tags => # irb(main):113:0> p2.tags.count => 0 #### !!!!!!!!!!!! 

这是模型

 class Post  :destroy has_many :tags, :through => :taggings 

有人建议,但不起作用

 irb(main):206:0* Post.new.from_json p.to_json(include: :tags) ActiveRecord::AssociationTypeMismatch: Tag(#60747984) expected, got Hash(#15487524) 

我模拟了与你完全相同的场景,并发现:

每当一个模型(Post)有一个has_many through关联然后在创建该模型的实例时,即Post传递一个Hash ,例如: Post.new( JSON.parse(json) )Post.new(id: 113)似乎Rails虽然指向相同的记录,但对待它们的方式却不同。

我按顺序运行以下命令:

 p = Post.last p.tags p.tags.count json = p.to_json p2 = Post.new( JSON.parse(json) ) p2.tags p2.tags.count ## Gives incorrect count p3 = Post.find(JSON.parse(json)["id"]) ### See notes below p3.tags p3.tags.count ## Gives the correct count 

我没有直接使用Hash创建Post的新实例,而是使用从反序列化json获得的id从数据库中获取记录。 在这种情况下,实例p3和实例p2引用相同的Post,但Rails正在以不同的方式解释它们。

免责声明:这绝不是一个理想的解决方案(我称之为右下角俗气),但它是我能够为你的场景提出的唯一一件事。

Kirti Thorat所说的是正确的; 当你有一个依赖对象时,Rails希望哈希中的关联属于那个特定的类(在你的例子中是一个Tag对象)。 因此你得到的错误: Tag expected...got Hash

这是一个俗气的部分:正确反序列化复杂对象的一种方法是利用accepts_nested_attributes_for方法。 通过使用此方法,您将允许Post类正确地将依赖的Tag键值对反序列化为正确的Tag对象。 从这开始:

 class Post < ActiveRecord::Base accepts_nested_attributes_for :tags # rest of class end 

由于accepts_nested_attributes_for搜索具有给定关联的单词_attributes的键,因此通过覆盖Post类中的as_json方法,您as_json在呈现JSON时更改JSON,如下所示:

 def as_json(options={}) json_hash = super.as_json(options) unless json_hash["tags"].nil? json_hash["tags_attributes"] = json_hash["tags"] # Renaming the key json_hash.delete("tags") # remove the now unnecessary "tags" key end json_hash # don't forget to return this at the end end 

旁注:有许多json构建gem,例如acts_as_api ,可以让你删除这个as_json覆盖业务

因此,现在您呈现的JSON具有所有Post属性,以及键tags_attributes下的tag属性键值对tags_attributes

从技术上讲,如果你以Kirti建议的方式反序化反序列化这个渲染的JSON,它会工作,你会得到一个正确填充的活动记录对象。 但是 ,遗憾的是,父Post对象和依赖tag对象中都存在id属性意味着活动记录将触发至少一个SQL查询。 它将根据has_many关系的规范(具体地, collection=objects部分)快速查找标记以确定是否需要添加或删除任何内容。

既然你说你想避免命中数据库,我能找到的唯一解决方案是以leesungchul建议的方式渲染JSON,但具体排除id字段:

 p_json = p.to_json(except: [:id], include: {tags: {except: :id}}) 

如果你这样做:

 p2 = Post.new(JSON.parse(p_json)) 

您应该在没有任何数据库调用的情况下返回完全呈现的Post对象。

当然,这假设您不需要那些id字段。 如果你这样做......坦白说,除了重命名as_json方法中的id字段之外,我不确定一个更好的解决方案。

另请注意:使用此方法时,由于缺少id字段,您将无法使用p2.tags.count ; 它将返回零。 你必须使用.length代替。

你可以试试

 p2.as_json( :include => :tags ) 

你打电话的时候

 p2.tags 

你得到正确的标签但是p2还没有保存在数据库中。 这似乎是原因所在

 p2.tags.count 

一直给0。 如果你真的做了类似的事情:

 p2.id = Post.maximum(:id) + 1 p2.tags #Edit: This needs to be done to fetch the tags mapped to p from the database. p2.save p2.tags.count 

你得到正确的数量