【问题标题】:How do I keep has_many :through relationships when serializing to JSON and back in Rails 4.0.3?如何在序列化为 JSON 并返回 Rails 4.0.3 时保持 has_many :through 关系?
【发布时间】:2014-04-02 02:28:57
【问题描述】:

如何转换为 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)

【问题讨论】:

  • 你调用 p2 = Post.new( JSON.parse(json) ) 这不保存到数据库。并在您的数据库上调用 p2.tags.count 查询,但您不保存数据。
  • @Monk_Code 不,这仍然不起作用。另请参阅下面对答案的评论。 &gt; p2 = Post.new( JSON.parse(json) )ActiveRecord::AssociationTypeMismatch: Tag(#62190408) expected, got Hash(#15487524)

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


【解决方案1】:

我模拟了和你一模一样的场景,结果发现:

只要一个模型(帖子)有一个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

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

【讨论】:

  • 嗯,很有趣。我实际上是在将它们序列化和反序列化到 Redis 缓存中以加快速度。我希望避免使用数据库。也许不可能?
【解决方案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 的键,因此您必须在呈现时更改JSON 以通过覆盖as_json 类中的as_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 属性键值对数组。

从技术上讲,如果您按照 Kirti 建议的方式反序列化这个呈现的 JSON,它会起作用,并且您会得到一个正确填充的活动记录对象。 然而,不幸的是,id 属性在父 Post 对象和从属 tag 对象中的存在意味着活动记录将触发至少一个 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 对象,而不需要任何 DB 调用。

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

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

【讨论】:

  • 我想你的意思是说p_json = p.to_json(except: [:id], includes: {tags: {except: [:id]}}),因为p_json 是由你定义的syntax error
  • @KirtiThorat 哎呀,对。我把: 放在了错误的一边。谢谢!会更新。
  • 糟糕..我的意思是p_json = p.to_json(except: [:id], include: {tags: {except: [:id]}}) include 不是includes
  • @KirtiThorat 确认!再次感谢您。
  • 您的意思是在这里给as_json打电话吗?因为,调用to_json 将给出相同的错误标签预期...得到哈希。
【解决方案3】:

你可以试试

p2.as_json( :include => :tags )

【讨论】:

  • 您的意思是p.to_json?那没用:p2 = Post.new( JSON.parse(json) ) ActiveRecord::AssociationTypeMismatch: Tag(#60747984) expected, got Hash(#15487524)
  • as_json 甚至不输出有效的 JSON! {"id"=&gt;113, 无效,因为 =&gt; 不是 JSON。
  • @Chloe 您应该能够以相同的方式使用 to_json 代替。
  • @PaulRichter 我已经提到过反序列化时不起作用。
  • @Chloe 抱歉,我在用手机,没看到。不管怎样,as_json 返回一个哈希,它也有to_json 方法,所以如果你在返回的哈希上调用那个方法,它不会起作用吗?
【解决方案4】:

当你打电话时

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

你得到正确的计数

【讨论】:

  • 您的方法会在数据库中创建重复记录。 OP 正在尝试访问现有记录,而不是创建另一个记录。
  • 哦,对不起!现在我明白了他想做什么......在这种情况下,我认为像 p2.tags.to_a.count 这样的东西应该可以工作,不是吗?
  • @AhmedFathy 问题其实集中在 JSON/hash 中的has_many 关系上。 OP 最初的问题是他们没有在to_json 中包含关联,因此为什么.tags 返回为空。现在,他们的问题是,当他们尝试使用 JSON 作为.new 的参数时,他们会在问题的底部收到错误消息。这就是他们目前所处的位置。
  • @PaulRichter。是的,好的......我实际上已经为此提交了一个补丁,并等待看到 Rails 维护人员对此有何评论。
猜你喜欢
  • 2012-07-21
  • 1970-01-01
  • 2011-04-27
  • 1970-01-01
  • 2010-09-20
  • 1970-01-01
  • 2021-07-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多