Wie überschreibe ich to_json in Rails?

94

Aktualisieren:

Dieses Problem wurde nicht richtig untersucht. Das eigentliche Problem liegt in render :json.

Das erste Einfügen von Code in die ursprüngliche Frage liefert das erwartete Ergebnis. Es gibt jedoch immer noch eine Einschränkung. Siehe dieses Beispiel:

render :json => current_user

ist NICHT das gleiche wie

render :json => current_user.to_json

Das heißt, render :jsondie to_jsondem Benutzerobjekt zugeordnete Methode wird nicht automatisch aufgerufen . In der Tat , wenn to_jsonauf der überschrieben wird UserModell render :json => @usergeneriert die ArgumentErrorunten beschrieben.

Zusammenfassung

# 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

Das alles kommt mir albern vor. Dies scheint mir zu sagen, dass rendernicht aufgerufen wird, Model#to_jsonwenn der Typ :jsonangegeben wird. Kann jemand erklären, was hier wirklich los ist?

Alle Genien, die mir dabei helfen können, können wahrscheinlich meine andere Frage beantworten: Wie erstelle ich eine JSON-Antwort, indem ich @ foo.to_json (Optionen) und @ bars.to_json (Optionen) in Rails kombiniere?


Ursprüngliche Frage:

Ich habe einige andere Beispiele auf SO gesehen, aber ich mache nicht das, wonach ich suche.

Ich versuche:

class User < ActiveRecord::Base

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

end

Ich steige ArgumentError: wrong number of arguments (1 for 0)ein

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

Irgendwelche Ideen?

maček
quelle
Ihr Beispiel funktioniert in einem meiner Modelle. Wählen Sie eine der username, foooder barMethoden Argumente erwarten?
Jonathan Julian
Nein, usernameist kein Verfahren und foound barkeine Methoden erfordern. Ich habe meine Frage aktualisiert, um zu zeigen, wo der Fehler auftritt.
Maček
Ich laufe 1.8.7. Sie müssen diese Datei öffnen und sehen, warum ein Argument an eine Methode übergeben wird, die keine Argumente erwartet.
Jonathan Julian

Antworten:

214

Sie erhalten, ArgumentError: wrong number of arguments (1 for 0)weil to_jsonmit einem Parameter, dem optionsHash , überschrieben werden muss .

def to_json(options)
  ...
end

Längere Erklärung to_json, as_jsonund Rendering:

In ActiveSupport 2.3.3 as_jsonwurde hinzugefügt, um Probleme wie das aufgetretene zu beheben . Die Erstellung des JSON sollte vom Rendering getrennt sein des JSON getrennt sein.

Jetzt wird jedes Mal, wenn to_jsonein Objekt as_jsonaufgerufen wird, aufgerufen, um die Datenstruktur zu erstellen, und dann wird dieser Hash mithilfe von als JSON-Zeichenfolge codiertActiveSupport::json.encode . Dies geschieht für alle Typen: Objekt, Zahl, Datum, Zeichenfolge usw. (siehe ActiveSupport-Code).

ActiveRecord-Objekte verhalten sich genauso. Es gibt eine Standardimplementierung as_json, die einen Hash erstellt, der alle Attribute des Modells enthält. Sie sollten as_jsonin Ihrem Modell überschreiben , um die gewünschte JSON-Struktur zu erstellen . as_jsonverwendet genau wie der alte to_jsoneinen Options-Hash, in dem Sie Attribute und Methoden angeben können, die deklarativ eingeschlossen werden sollen.

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

render :json => oKann in Ihrem Controller eine Zeichenfolge oder ein Objekt akzeptieren. Wenn es sich um eine Zeichenfolge handelt, wird sie als Antworttext durchlaufen, wenn es sich um ein Objekt to_jsonhandelt, das as_jsonwie oben erläutert ausgelöst wird.

Solange Ihre Modelle ordnungsgemäß mit as_jsonÜberschreibungen dargestellt werden (oder nicht), sollte Ihr Controller-Code zur Anzeige eines Modells folgendermaßen aussehen:

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

Die Moral der Geschichte lautet: Vermeiden Sie es, to_jsondirekt anzurufen , lassen renderSie das für Sie tun. Wenn Sie die JSON-Ausgabe optimieren müssen, rufen Sie auf as_json.

format.json { render :json => 
    @user.as_json(:only => [:username], :methods => [:avatar]) }
Jonathan Julian
quelle
@ Jonathan Julian, das ist eine sehr hilfreiche Erklärung von as_json. Wie Sie in den ActiveRecord :: Serialization-Dokumenten ( api.rubyonrails.org/classes/ActiveRecord/… ) sehen können, gibt es hierfür nur sehr wenig (keine) Dokumentation. Ich werde es versuchen :)
maček
1
@ Jonathan Julian, wenn ich dies 10 Mal abstimmen könnte, würde ich. Wo zum Teufel sind die as_jsonDokumente!
Nochmals vielen
71

Wenn Sie in Rails 3 Probleme damit haben, überschreiben Sie serializable_hashstatt as_json. Dadurch erhalten Sie auch Ihre XML-Formatierung kostenlos :)

Ich habe ewig gebraucht, um das herauszufinden. Hoffe das hilft jemandem.

Sam Soffes
quelle
1
Kennt jemand gute Aufzeichnungen über die Methode serializable_hash? Wenn ich es verwende, ändert es meine nachfolgende XML-Ausgabe vom Umschließen des Objekts mit seinem Namen (z. B. "Zitat" für ein Anführungszeichenobjekt "), um es stattdessen immer mit" <Hash> "zu umschließen.
Tyler Collier
@ TylerCollier sollte es die gleichen Optionen sein wieto_xml
Sam Soffes
Danke für diese Lösung! Ich verwende ruby2 / rails4 und as_json hat nicht mit verschachtelten Objekten gearbeitet, die überschriebene Methode wurde in 'include' nicht aufgerufen, mit serializable_hash funktioniert es!
Santuxus
Unter robots.thoughtbot.com/better-serialization-less-as-json finden Sie eine Erklärung, warum serializable_hash stattdessen überschrieben werden sollte.
Topher Hunt
36

Für Leute, die Benutzeroptionen nicht ignorieren, sondern auch ihre hinzufügen möchten:

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

Hoffe das hilft jedem :)

Danpe
quelle
1
Dies ist die Art und Weise, wie ich es mache, außer ich verwende standardmäßig den optionsHash mit, = {}so dass es nicht erforderlich ist, wenn
ich anrufe
4

Überschreiben Sie nicht to_json, sondern as_json. Und von as_json rufen Sie an, was Sie wollen:

Versuche dies:

def as_json 
 { :username => username, :foo => foo, :bar => bar }
end
glebm
quelle
Ist nicht as_jsonnur für ActiveResource?
Jonathan Julian
Anscheinend hat ActiveRecord :: Serialization as_json api.rubyonrails.org/classes/ActiveRecord/Serialization.html
glebm
@glebm, ich habe es versucht und ich bekomme das gleiche Ergebnis. Ich habe meine Frage aktualisiert, um sie Ihnen zu zeigen.
Maček
@glebm, ich bekomme immer noch genau den gleichen Fehler. Selbst wenn ich dies tue, render :json => current_usererhalte ich das erwartete Standardergebnis (alle Attribute für das UserModell im JSON-Format). Wenn ich die as_jsonMethode zu meinem UserModell hinzufüge und dasselbe versuche, erhalte ich den Fehler :(
maček
@glebm, danke. Ich weiß, dass ich etwas falsch gemacht habe. Es könnte sich lohnen, die aktualisierte Frage zu lesen.
Maček