如何在Rails中覆盖to_json方法?

96

更新:

这个问题并没有得到很好的研究。真正的问题在于render :json

原始问题中的第一个代码段将产生预期的结果。但是仍然有一个警告。看这个例子:

render :json => current_user

不能

render :json => current_user.to_json

render :json不会自动调用与User对象相关联的to_json方法。实际上,如果User模型上被覆盖了to_json,那么render :json => @user将生成下面描述的ArgumentError

摘要

# works if User#to_json is not overridden
render :json => current_user

# If User#to_json is overridden, User requires explicit call
render :json => current_user.to_json

对我来说,这一切似乎都很傻。它好像在告诉我,在指定类型为:json时,render实际上没有调用Model#to_json。有人能解释一下这里到底发生了什么吗?

任何聪明的人如果可以帮助我解决这个问题,可能也能回答我的另一个问题:如何在Rails中通过组合@foo.to_json(options)@bars.to_json(options) 来构建 JSON 响应


原始问题:

我在 SO 上看到了一些其他的例子,但它们都不是我想要的。

我正在尝试:

class User < ActiveRecord::Base

  # this actually works! (see update summary above)
  def to_json
    super(:only => :username, :methods => [:foo, :bar])
  end

end
我在代码中遇到了 ArgumentError: wrong number of arguments (1 for 0) 的错误提示。
/usr/lib/ruby/gems/1.9.1/gems/activesupport-2.3.5/lib/active_support/json/encoders/object.rb:4:in `to_json

有什么想法吗?


你的示例在我的一个模型中可以工作。usernamefoobar方法中是否有需要参数的? - Jonathan Julian
不,username不是一个方法,而foobar也不需要方法。我更新了我的问题以显示错误发生的位置。 - maček
我正在运行1.8.7版本。你需要打开那个文件,看看为什么它向一个期望零个参数的方法传递了一个参数。 - Jonathan Julian
4个回答

224

你遇到了 ArgumentError: wrong number of arguments (1 for 0) 的错误,是因为需要重写 to_json 方法并传入一个参数,即 options 哈希表。

def to_json(options)
  ...
end

关于to_jsonas_json和渲染的详细解释:

在ActiveSupport 2.3.3中,添加了as_json来解决您遇到的问题。创建json应该与渲染json分开。

现在,每当对象调用to_json时,都会调用as_json来创建数据结构,然后使用ActiveSupport::json.encode将该哈希编码为JSON字符串。这适用于所有类型:对象、数字、日期、字符串等(请参见ActiveSupport代码)。

ActiveRecord对象的行为方式相同。有一个默认的as_json实现,它创建包括模型所有属性的哈希。 您应该在您的模型中覆盖as_json以创建所需的JSON结构。就像旧的to_json一样,as_json接受选项哈希表,您可以在其中声明性地指定要包含的属性和方法。

def as_json(options)
  # this example ignores the user's options
  super(:only => [:email, :handle])
end

在您的控制器中,render :json => o 可以接受字符串或对象作为参数。 如果是字符串,则原样传递作为响应正文;如果是对象,则调用to_json方法,并触发上述解释中讲解的as_json方法。

因此,只要您的模型使用了正确的as_json重写(或未重写),则用于显示一个模型的控制器代码应如下所示:

format.json { render :json => @user }

故事的寓意是:避免直接调用to_json,让render为您完成。如果您需要调整JSON输出,请调用as_json

format.json { render :json => 
    @user.as_json(:only => [:username], :methods => [:avatar]) }

@Jonathan Julian,这是as_json非常有用的解释。正如您可以在ActiveRecord :: Serialization文档(http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html#M001874)中看到的那样,几乎没有关于此的文档。我会尝试一下 :) - maček
1
@Jonathan Julian,如果我可以点赞10次,我会这么做的。到底在哪里可以找到as_json文档!再次感谢 :) - maček

71

如果在Rails 3中遇到此问题,请覆盖 serializable_hash 而不是 as_json。这样还可以免费获取XML格式 :)


1
有没有人知道关于serializable_hash方法的好文章?当我使用它时,它会改变我的后续XML输出,从用对象名称(例如引用对象的“quote”)包装对象到始终用“<hash>”包装它。 - Tyler Collier
@TylerCollier 应该和 to_xml 有相同的选项。 - Sam Soffes
谢谢这个解决方案! 我使用的是ruby2 / rails4,而as_json在嵌套对象中无法工作,重写的方法在'include'中没有被调用,在serializable_hash中它可以工作! - santuxus
请参阅 https://robots.thoughtbot.com/better-serialization-less-as-json 了解为什么应该覆盖serializable_hash。 - Topher Hunt

37

对于那些不想忽略用户选项,但也想添加自己选项的人:

def as_json(options)
  # this example DOES NOT ignore the user's options
  super({:only => [:email, :handle]}.merge(options))
end

希望这能帮助到任何人 :)


1
这是我处理它的方式,除了我使用= {}默认options哈希,所以在调用时不需要它。 - mroach

5

不要覆盖 to_json 方法,而是重写 as_json 方法。从 as_json 方法中调用您需要的内容:

尝试这样做:

def as_json 
 { :username => username, :foo => foo, :bar => bar }
end

“as_json” 不仅适用于 ActiveResource 吗? - Jonathan Julian
显然,ActiveRecord::Serialization具有as_json方法。http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html - glebm
@glebm,我试过了,结果还是一样的。我更新了我的问题来展示给你看。 - maček
@glebm,我仍然收到完全相同的错误。 即使我执行 render:json => current_user,我也会获得预期的默认结果(以JSON格式显示User模型的所有属性)。 当我在我的User模型中添加了as_json方法并尝试相同的操作时,我却收到了错误:( - maček
@glebm,谢谢。我知道我做错了什么。也许值得查看更新后的问题。 - maček
基本上,从2.3.2开始,您应该调用as_json而不是to_json(即render:json => <...> .as_json - glebm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接