Jak nadpisać to_json w Railsach?

94

Aktualizacja:

Ten problem nie został właściwie zbadany. Prawdziwy problem leży wewnątrz render :json.

Pierwsze wklejenie kodu w pierwotnym pytaniu da oczekiwany wynik. Jednak nadal istnieje zastrzeżenie. Zobacz ten przykład:

render :json => current_user

NIE jest tym samym, co

render :json => current_user.to_json

Oznacza to, render :jsonże nie wywoła automatycznie to_jsonmetody skojarzonej z obiektem User. W rzeczywistości , jeśli to_jsonjest nadpisywany w Usermodelu, render :json => @userwygeneruje ArgumentErroropisane poniżej.

Podsumowanie

# 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

To wszystko wydaje mi się głupie. Wydaje się, że mówi mi, że w renderrzeczywistości nie wywołuje, Model#to_jsongdy :jsonokreślono typ . Czy ktoś może wyjaśnić, co się tu naprawdę dzieje?

Każdy genii, który może mi w tym pomóc, prawdopodobnie odpowie na moje inne pytanie: Jak zbudować odpowiedź JSON, łącząc @ foo.to_json (opcje) i @ bars.to_json (opcje) w Railsach


Oryginalne pytanie:

Widziałem kilka innych przykładów na SO, ale żaden nie robi tego, czego szukam.

Próbuję:

class User < ActiveRecord::Base

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

end

Dostaję ArgumentError: wrong number of arguments (1 for 0)w

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

Jakieś pomysły?

maček
źródło
Twój przykład działa w jednym z moich modeli. Wykonaj jedną z następujących username, fooalbo barmetody oczekiwać argumenty?
Jonathan Julian
Nie, usernamenie jest to metoda, a fooi barnie wymagają metod. Zaktualizowałem moje pytanie, aby pokazać, gdzie występuje błąd.
maček
Używam 1.8.7. Będziesz musiał otworzyć ten plik i zobaczyć, dlaczego przekazuje argument do metody, która oczekuje zerowych argumentów.
Jonathan Julian

Odpowiedzi:

216

Otrzymujesz, ArgumentError: wrong number of arguments (1 for 0)ponieważ to_jsonmusi zostać zastąpiony jednym parametrem, optionshashem.

def to_json(options)
  ...
end

Dłuższe wyjaśnienie to_json, as_jsoni renderowania:

W ActiveSupport 2.3.3 as_jsonzostał dodany w celu rozwiązania problemów, takich jak ten, który napotkałeś. Stworzenie z json powinny być oddzielone od renderowania z JSON.

Teraz to_jsonobiekt as_jsonjest wywoływany w dowolnym momencie , jest wywoływany w celu utworzenia struktury danych, a następnie ten skrót jest kodowany jako ciąg JSON przy użyciu ActiveSupport::json.encode. Dzieje się tak dla wszystkich typów: obiektowych, liczbowych, datowych, łańcuchowych itp. (Patrz kod ActiveSupport).

Obiekty ActiveRecord zachowują się w ten sam sposób. Istnieje domyślna as_jsonimplementacja, która tworzy skrót zawierający wszystkie atrybuty modelu. Należy nadpisać as_jsonw modelu, aby utworzyć żądaną strukturę JSON . as_json, podobnie jak stary to_json, pobiera skrót opcji, w którym można określić atrybuty i metody do uwzględnienia deklaratywnie.

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

W twoim kontrolerze render :json => omoże akceptować ciąg lub obiekt. Jeśli jest to łańcuch, jest on przekazywany jako treść odpowiedzi, jeśli jest to obiekt to_json, co wyzwala, as_jsonjak wyjaśniono powyżej.

Tak więc, o ile modele są poprawnie reprezentowane za pomocą as_jsonnadpisań (lub nie), kod kontrolera do wyświetlania jednego modelu powinien wyglądać następująco:

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

Morał tej historii jest następujący: unikaj dzwonienia to_jsonbezpośrednio, pozwól renderzrobić to za siebie. Jeśli chcesz dostosować dane wyjściowe JSON, wywołaj as_json.

format.json { render :json => 
    @user.as_json(:only => [:username], :methods => [:avatar]) }
Jonathan Julian
źródło
@Jonathan Julian, to jest bardzo pomocne wyjaśnienie as_json. Jak widać w dokumentacji ActiveRecord :: Serialization ( api.rubyonrails.org/classes/ActiveRecord/… ), jest bardzo mało (nie) dokumentacji na ten temat. Spróbuję :)
maček
1
@Jonathan Julian, gdybym mógł zagłosować za tym 10 razy, zrobiłbym to. Gdzie do cholery są as_jsondoktorzy!
Jeszcze
71

Jeśli masz z tym problemy w Railsach 3, zastąp serializable_hashzamiast as_json. Dzięki temu Twoje formatowanie XML również będzie darmowe :)

To zajęło mi całą wieczność, zanim to rozgryzłem. Mam nadzieję, że to komuś pomoże.

Sam Soffes
źródło
1
Czy ktoś zna jakieś dobre napisy na temat metody serializable_hash? Kiedy go używam, zmienia moje kolejne wyjście XML z zawijania obiektu jego nazwą (np. „Cytat” dla obiektu cytatu ”), aby zamiast tego zawsze zawijać go„ <hash> ”.
Tyler Collier
@TylerCollier, powinny być takie same opcje jakto_xml
Sam Soffes,
Dzięki za to rozwiązanie! Używam ruby2 / rails4, a as_json nie działał z zagnieżdżonymi obiektami, nadpisana metoda nie została wywołana w 'include', z serializable_hash to działa!
santuxus
Zobacz robots.thoughtbot.com/better-serialization-less-as-json, aby uzyskać wyjaśnienie, dlaczego zamiast tego należy nadpisać serializable_hash.
Topher Hunt
36

Dla osób, które nie chcą ignorować opcji użytkowników, ale także dodają swoje:

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

Mam nadzieję, że to pomoże każdemu :)

Danpe
źródło
1
To jest sposób, w jaki to robię, z wyjątkiem tego, że domyślnie options= {}
ustawię
4

Zastąp nie to_json, ale as_json. A z as_json zadzwoń, co chcesz:

Spróbuj tego:

def as_json 
 { :username => username, :foo => foo, :bar => bar }
end
glebm
źródło
Nie jest as_json tylko dla ActiveResource?
Jonathan Julian
Najwyraźniej ActiveRecord :: Serialization ma as_json api.rubyonrails.org/classes/ActiveRecord/Serialization.html
glebm
@glebm, próbowałem tego i otrzymuję ten sam wynik. Zaktualizowałem moje pytanie, aby pokazać.
maček
@glebm, nadal otrzymuję dokładnie ten sam błąd. Nawet jeśli to zrobię render :json => current_user, otrzymuję oczekiwany wynik domyślny (wszystkie atrybuty Usermodelu w formacie JSON). Kiedy dodam as_jsonmetodę do mojegoUser modelu i spróbuję tego samego,
pojawia się
@glebm, dziękuję. Wiem, że robiłem coś złego. Warto sprawdzić zaktualizowane pytanie.
maček