在串行化为JSON并返回Rails 4.0.3时,如何保持has_many:通过关系?

时间:2014-02-28 21:26:15

标签: ruby-on-rails ruby json ruby-on-rails-4 redis

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

irb(main):106:0* p = Post.last
=> #<Post ...
irb(main):107:0> p.tags
=> #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 41, ...
irb(main):109:0* p.tags.count
=> 2                               #### !!!!!!!!!!!!

irb(main):110:0> json = p.to_json
=> "{\"id\":113,\"title\":... }"
irb(main):111:0> p2 = Post.new( JSON.parse(json) )
=> #<Post id: 113, title: ... 

irb(main):112:0> p2.tags
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):113:0> p2.tags.count
=> 0                               #### !!!!!!!!!!!!

这是模型

class Post < ActiveRecord::Base
  has_many :taggings, :dependent => :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)

4 个答案:

答案 0 :(得分:3)

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

每当模型(Post)具有has_many through关联时,则在创建该模型的实例时,Post传递Hash例如:Post.new( JSON.parse(json) )或{{ 1}}似乎Rails对它们的处理方式不同,尽管它们指向相同的记录。

我按顺序运行以下命令:

Post.new(id: 113)

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

答案 1 :(得分:2)

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

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的密钥,因此在通过覆盖as_json方法呈现JSON时,您必须更改JSON以适应此目的在你的Post课程中,如下:

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构建宝石,例如acts_as_api,可以删除此as_json覆盖业务

现在,您呈现的JSON具有所有Post属性,以及键tag下的tags_attributes个属性键值对数组。

从技术上讲,如果你以Kirti建议的方式反序列化这个呈现的JSON,那么它会工作,你会得到一个正确填充的活动记录对象。 然而,遗憾的是,父{Benis}对象和依赖id对象中Post属性的存在意味着活动记录将至少触发一个SQL查询。根据{{​​3}}关系(特别是tag部分)的规范,它会快速查找标签以确定是否需要添加或删除任何内容。

既然你说你想避免访问数据库,我能找到的唯一解决方案是以leesungchul建议的方式渲染JSON,但明确排除collection=objects字段:< / p>

id

如果你这样做:

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

您应该在没有任何数据库调用的情况下返回完全呈现的p2 = Post.new(JSON.parse(p_json)) 对象。

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

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

答案 2 :(得分:1)

你可以尝试

p2.as_json( :include => :tags )

答案 3 :(得分:0)

致电时

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

您获得正确的计数