Comment remplacer to_json dans Rails?

94

Mettre à jour:

Ce problème n'a pas été correctement exploré. Le vrai problème réside à l'intérieur render :json.

Le premier collage de code dans la question d'origine donnera le résultat attendu. Cependant, il y a toujours une mise en garde. Voir cet exemple:

render :json => current_user

n'est PAS la même chose que

render :json => current_user.to_json

Autrement dit, render :jsonn'appellera pas automatiquement la to_jsonméthode associée à l'objet User. En fait , si to_jsonest remplacé sur le Usermodèle, render :json => @usergénérera le ArgumentErrordécrit ci-dessous.

résumé

# 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

Tout cela me semble idiot. Cela semble me dire que ce rendern'est pas réellement un appel Model#to_jsonlorsque le type :jsonest spécifié. Quelqu'un peut-il expliquer ce qui se passe vraiment ici?

Tous les génies qui peuvent m'aider dans ce domaine peuvent probablement répondre à mon autre question: Comment créer une réponse JSON en combinant @ foo.to_json (options) et @ bars.to_json (options) dans Rails


Question originale:

J'ai vu d'autres exemples sur SO, mais je ne fais pas ce que je recherche.

J'essaie:

class User < ActiveRecord::Base

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

end

Je reçois ArgumentError: wrong number of arguments (1 for 0)dans

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

Des idées?

maček
la source
Votre exemple fonctionne dans l'un de mes modèles. L'une des méthodes username, fooou barattend-elle des arguments?
Jonathan Julian
Non, usernamen'est pas une méthode et fooet barne nécessitent pas de méthodes. J'ai mis à jour ma question pour montrer où se produit l'erreur.
maček
J'utilise la version 1.8.7. Vous devrez ouvrir ce fichier et voir pourquoi il passe un argument à une méthode qui n'attend aucun argument.
Jonathan Julian

Réponses:

214

Vous obtenez ArgumentError: wrong number of arguments (1 for 0)parce qu'il to_jsondoit être remplacé par un paramètre, le optionshachage.

def to_json(options)
  ...
end

Explication plus longue to_json, as_jsonet le rendu:

Dans ActiveSupport 2.3.3, a as_jsonété ajouté pour résoudre des problèmes tels que celui que vous avez rencontré. La création du json doit être distincte du rendu du json.

Désormais, à tout moment to_jsonest appelé sur un objet, est appelé as_jsonpour créer la structure de données, puis ce hachage est encodé en tant que chaîne JSON à l'aide de ActiveSupport::json.encode. Cela se produit pour tous les types: objet, numérique, date, chaîne, etc. (voir le code ActiveSupport).

Les objets ActiveRecord se comportent de la même manière. Il existe une as_jsonimplémentation par défaut qui crée un hachage qui inclut tous les attributs du modèle. Vous devez remplacer as_jsondans votre modèle pour créer la structure JSON souhaitée . as_json, tout comme l'ancien to_json, prend un hachage d'option où vous pouvez spécifier des attributs et des méthodes à inclure de manière déclarative.

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

Dans votre contrôleur, render :json => opeut accepter une chaîne ou un objet. S'il s'agit d'une chaîne, elle est transmise en tant que corps de la réponse, si c'est un objet, to_jsonest appelée, ce qui se déclenche as_jsoncomme expliqué ci-dessus.

Ainsi, tant que vos modèles sont correctement représentés avec des as_jsonremplacements (ou non), le code de votre contrôleur pour afficher un modèle devrait ressembler à ceci:

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

La morale de l'histoire est la suivante: évitez d'appeler to_jsondirectement, laissez renderfaire cela pour vous. Si vous avez besoin de modifier la sortie JSON, appelez as_json.

format.json { render :json => 
    @user.as_json(:only => [:username], :methods => [:avatar]) }
Jonathan Julian
la source
@Jonathan Julian, c'est une explication très utile de as_json. Comme vous pouvez le voir dans la documentation ActiveRecord :: Serialization ( api.rubyonrails.org/classes/ActiveRecord/… ), il y a très peu (pas) de documentation pour cela. Je vais essayer :)
maček
1
@Jonathan Julian, si je pouvais voter 10 fois, je le ferais. Où diable sont les as_jsondocs! Merci encore :)
maček
71

Si vous rencontrez des problèmes avec cela dans Rails 3, passer outre au serializable_hashlieu de as_json. Cela obtiendra également votre formatage XML gratuitement :)

Cela m'a pris une éternité à comprendre. J'espère que cela aide quelqu'un.

Sam Soffes
la source
1
Est-ce que quelqu'un connaît de bons articles sur la méthode serializable_hash? Quand je l'utilise, cela change ma sortie XML suivante en enveloppant l'objet avec son nom (par exemple "quote" pour un objet de citation ") pour à la place toujours l'envelopper avec" <hash> ".
Tyler Collier
@TylerCollier ça devrait être les mêmes options queto_xml
Sam Soffes
Merci pour cette solution! J'utilise ruby2 / rails4 et as_json ne fonctionnait pas avec des objets imbriqués, la méthode surchargée n'a pas été appelée dans 'include', avec serializable_hash cela fonctionne!
santuxus
Voir robots.thoughtbot.com/better-serialization-less-as-json pour une explication des raisons pour lesquelles serializable_hash devrait être remplacé à la place.
Topher Hunt
36

Pour les personnes qui ne veulent pas ignorer les options des utilisateurs mais qui ajoutent également les leurs:

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

J'espère que cela aide n'importe qui :)

Danpe
la source
1
C'est comme ça que je le fais, sauf que j'ai par défaut le optionshachage avec = {}donc ce n'est pas nécessaire lors de l'appel
mroach
4

Remplacez non pas to_json, mais as_json. Et depuis as_json, appelez ce que vous voulez:

Essaye ça:

def as_json 
 { :username => username, :foo => foo, :bar => bar }
end
glebm
la source
N'est-ce pas as_jsonseulement pour ActiveResource?
Jonathan Julian
Apparemment ActiveRecord :: Serialization a as_json api.rubyonrails.org/classes/ActiveRecord/Serialization.html
glebm
@glebm, j'ai essayé ceci et j'obtiens le même résultat. J'ai mis à jour ma question pour vous montrer.
maček
@glebm, j'obtiens toujours exactement la même erreur. Même lorsque render :json => current_userj'obtiens le résultat par défaut attendu (tous les attributs du Usermodèle au format JSON). Quand j'ajoute la as_jsonméthode à mon Usermodèle et que j'essaye la même chose, j'obtiens l'erreur :(
maček
@glebm, merci. Je sais que je faisais quelque chose de mal. Cela vaut peut-être la peine de consulter la question mise à jour.
maček