如何在 Rails 中覆盖_json?


更新:

这个问题没有得到正确的探讨,真正的问题在于 render :json

原始问题中的第一个代码粘贴将产生预期的结果。然而,这里仍然有一个警告。看这个例子:

render :json => current_user

没有

render :json => current_user.to_json

也就是说,render :json不会自动调用与 User 对象关联的 to_json方法。如果 to_jsonUser模型上被覆盖,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。有人能解释一下这到底是怎么回事吗?

任何可以帮助我的 Genii 都可以回答我的另一个问题: 如何通过在 Rails 中组合@foo. to _ JSON (options)和@bar. 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

有什么想法吗?

51860 次浏览

Override not to_json, but as_json. And from as_json call what you want:

Try this:

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

You are getting ArgumentError: wrong number of arguments (1 for 0) because to_json needs to be overridden with one parameter, the options hash.

def to_json(options)
...
end

Longer explanation of to_json, as_json, and rendering:

In ActiveSupport 2.3.3, as_json was added to address issues like the one you have encountered. The creation of the json should be separate from the rendering of the json.

Now, anytime to_json is called on an object, as_json is invoked to create the data structure, and then that hash is encoded as a JSON string using ActiveSupport::json.encode. This happens for all types: object, numeric, date, string, etc (see the ActiveSupport code).

ActiveRecord objects behave the same way. There is a default as_json implementation that creates a hash that includes all the model's attributes. You should override as_json in your Model to create the JSON structure you want. as_json, just like the old to_json, takes an option hash where you can specify attributes and methods to include declaratively.

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

In your controller, render :json => o can accept a string or an object. If it's a string, it's passed through as the response body, if it's an object, ABC1 is called, which triggers as_json as explained above.

So, as long as your models are properly represented with as_json overrides (or not), your controller code to display one model should look like this:

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

The moral of the story is: Avoid calling ABC0 directly, allow ABC1 to do that for you. If you need to tweak the JSON output, call as_json.

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

If you're having issues with this in Rails 3, override serializable_hash instead of as_json. This will get your XML formatting for free too :)

This took me forever to figure out. Hope that helps someone.

For people who don't want to ignore users options but also add their's:

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

Hope this helps anyone :)