web-dev-qa-db-fra.com

Comment remplacer to_json dans Rails?


Mise à jour:

Ce problème n'a pas été correctement étudié. Le vrai problème réside dans render :json.

La première pâte de code dans la question d'origine donnera le résultat attendu. Cependant, il y a encore une mise en garde. Voir cet exemple:

render :json => current_user

[~ # ~] n'est pas [~ # ~] identique à

render :json => current_user.to_json

Autrement dit, render :json N'appellera pas automatiquement la méthode to_json Associée à l'objet Utilisateur. En fait, si to_json Est surchargé sur le modèle User, render :json => @user Générera le ArgumentError décrit ci-dessous.

sommaire

# 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 render n'appelle pas réellement Model#to_json Lorsque le type :json Est spécifié. Quelqu'un peut-il expliquer ce qui se passe vraiment ici?

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


Question d'origine:

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

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?

92
maček

Vous obtenez ArgumentError: wrong number of arguments (1 for 0) parce que to_json doit être remplacé par un paramètre, le hachage options.

def to_json(options)
  ...
end

Explication plus longue de to_json, as_json, et rendu:

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

Maintenant, à tout moment to_json est appelé sur un objet, as_json est invoqué pour créer la structure de données, puis ce hachage est codé 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 y a une valeur par défaut as_json implémentation qui crée un hachage qui inclut tous les attributs du modèle. Vous devez remplacer as_json dans votre modèle pour créer la structure JSON souhaitée. as_json, tout comme l'ancien to_json, prend une option de hachage 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 => o peut accepter une chaîne ou un objet. S'il s'agit d'une chaîne, elle est transmise en tant que corps de réponse, s'il s'agit d'un objet, to_json est appelé, ce qui déclenche as_json comme expliqué ci-dessus.

Donc, tant que vos modèles sont correctement représentés avec as_json remplace (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: Évitez d'appeler to_json directement, autorisez render à le faire pour vous. Si vous devez modifier la sortie JSON, appelez as_json.

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

Si vous rencontrez des problèmes avec cela dans Rails 3, remplacez serializable_hash au lieu de as_json. Vous obtiendrez également votre mise en forme XML gratuitement :)

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

71
Sam Soffes

Pour les personnes qui ne veulent pas ignorer les options des utilisateurs mais également ajouter 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 :)

33
Danpe

Ne remplacez 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
3
glebm