¿Cómo anular to_json en Rails?

94

Actualizar:

Este problema no se exploró correctamente. El problema real está dentro render :json.

El primer pegado de código en la pregunta original producirá el resultado esperado. Sin embargo, todavía hay una salvedad. Vea este ejemplo:

render :json => current_user

NO es lo mismo que

render :json => current_user.to_json

Es decir, render :jsonno llamará automáticamente al to_jsonmétodo asociado con el objeto Usuario. De hecho , si to_jsonse anula en el Usermodelo, render :json => @usergenerará lo que se ArgumentErrordescribe a continuación.

resumen

# 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

Todo esto me parece una tontería. Esto parece decirme que en renderrealidad no está llamando Model#to_jsoncuando :jsonse especifica el tipo . ¿Alguien puede explicar lo que realmente está pasando aquí?

Cualquier genio que pueda ayudarme con esto probablemente pueda responder a mi otra pregunta: Cómo crear una respuesta JSON combinando @ foo.to_json (opciones) y @ bars.to_json (opciones) en Rails


Pregunta original:

He visto algunos otros ejemplos en SO, pero ninguno hace lo que estoy buscando.

Lo estoy intentando:

class User < ActiveRecord::Base

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

end

Me estoy poniendo ArgumentError: wrong number of arguments (1 for 0)en

/usr/lib/ruby/gems/1.9.1/gems/activesupport-2.3.5/lib/active_support/json/encoders/object.rb:4:in `to_json

¿Algunas ideas?

maček
fuente
Tu ejemplo funciona en uno de mis modelos. ¿Alguno de los métodos username, fooo barespera argumentos?
Jonathan Julian
No, usernameno es un método y un fooy barno requieren métodos. Actualicé mi pregunta para mostrar dónde está ocurriendo el error.
maček
Estoy ejecutando 1.8.7. Tendrá que abrir ese archivo y ver por qué está pasando un argumento a un método que espera cero argumentos.
Jonathan Julian

Respuestas:

214

Obtiene ArgumentError: wrong number of arguments (1 for 0)porque to_jsondebe anularse con un parámetro, el optionshash.

def to_json(options)
  ...
end

Explicación más larga de to_json, as_jsony renderizado:

En ActiveSupport 2.3.3, as_jsonse agregó para abordar problemas como el que ha encontrado. La creación del json debe estar separada de la representación del json.

Ahora, en cualquier momento to_jsonse llama a un objeto, as_jsonse invoca para crear la estructura de datos y luego ese hash se codifica como una cadena JSON usando ActiveSupport::json.encode. Esto sucede para todos los tipos: objeto, numérico, fecha, cadena, etc. (consulte el código de ActiveSupport).

Los objetos ActiveRecord se comportan de la misma manera. Existe una as_jsonimplementación predeterminada que crea un hash que incluye todos los atributos del modelo. Debe anular as_jsonen su modelo para crear la estructura JSON que desee . as_json, al igual que el anterior to_json, toma una opción hash donde puede especificar atributos y métodos para incluirlos declarativamente.

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

En su controlador, render :json => opuede aceptar una cadena o un objeto. Si es una cadena, se pasa a través del cuerpo de la respuesta, si es un objeto, to_jsonse llama, que se activa as_jsoncomo se explicó anteriormente.

Por lo tanto, siempre que sus modelos estén representados correctamente con as_jsonanulaciones (o no), el código de su controlador para mostrar un modelo debería verse así:

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

La moraleja de la historia es: Evite llamar to_jsondirectamente, permita renderque lo haga por usted. Si necesita modificar la salida JSON, llame as_json.

format.json { render :json => 
    @user.as_json(:only => [:username], :methods => [:avatar]) }
Jonathan Julian
fuente
@Jonathan Julian, esta es una explicación muy útil de as_json. Como puede ver en los documentos de ActiveRecord :: Serialization ( api.rubyonrails.org/classes/ActiveRecord/… ), hay muy poca (ninguna) documentación para esto. Lo intentaré :)
maček
1
@Jonathan Julian, si pudiera votar esto 10 veces, lo haría. ¡Dónde diablos están los as_jsonmédicos! Gracias de nuevo :)
maček
71

Si tiene problemas con esto en Rails 3, anule en serializable_hashlugar de as_json. Esto también obtendrá su formato XML gratis :)

Esto me tomó una eternidad averiguarlo. Espero que ayude a alguien.

Sam Soffes
fuente
1
¿Alguien sabe de alguna buena reseña sobre el método serializable_hash? Cuando lo uso, cambia mi salida xml posterior de envolver el objeto con su nombre (por ejemplo, "cita" para un objeto de cotización ") a, en cambio, siempre envolverlo con" <hash> ".
Tyler Collier
@TylerCollier deberían ser las mismas opciones queto_xml
Sam Soffes
¡Gracias por esta solución! Estoy usando ruby2 / rails4 y as_json no funcionaba con objetos anidados, el método anulado no se llamó en 'incluir', ¡con serializable_hash funciona!
santuxus
Consulte robots.thoughtbot.com/better-serialization-less-as-json para obtener una explicación de por qué serializable_hash debería anularse en su lugar.
Topher Hunt
36

Para las personas que no quieren ignorar las opciones de los usuarios pero que también agregan las suyas:

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

Espero que esto ayude a alguien :)

Danpe
fuente
1
Esta es la forma en que lo hago, excepto que utilizo el optionshash de = {}forma predeterminada, por lo que no es necesario al llamar
mroach
4

No anule to_json, sino as_json. Y desde as_json llama lo que quieras:

Prueba esto:

def as_json 
 { :username => username, :foo => foo, :bar => bar }
end
glebm
fuente
¿No es as_jsonsolo para ActiveResource?
Jonathan Julian
Aparentemente, ActiveRecord :: Serialization tiene as_json api.rubyonrails.org/classes/ActiveRecord/Serialization.html
glebm
@glebm, probé esto y obtengo el mismo resultado. Actualicé mi pregunta para mostrarte.
maček
@glebm, sigo recibiendo exactamente el mismo error. Incluso cuando lo hago render :json => current_user, obtengo el resultado predeterminado esperado (todos los atributos del Usermodelo en formato JSON). Cuando agrego el as_jsonmétodo a mi Usermodelo e intento lo mismo,
aparece
@glebm, gracias. Sé que estaba haciendo algo mal. Podría valer la pena consultar la pregunta actualizada.
maček