Как переопределить to_json в Rails?

94

Обновить:

Эта проблема не была изучена должным образом. Настоящая проблема кроется внутри render :json.

Первая вставка кода в исходный вопрос даст ожидаемый результат. Однако все же есть нюанс. См. Этот пример:

render :json => current_user

это НЕ то же самое,

render :json => current_user.to_json

То есть render :jsonне будет автоматически вызывать to_jsonметод, связанный с объектом User. Фактически , если to_jsonэто переопределено в Userмодели, render :json => @userбудет сгенерировано ArgumentErrorописанное ниже.

резюме

# 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

Мне все это кажется глупым. Кажется, это говорит мне, что на renderсамом деле не вызывается, Model#to_jsonкогда :jsonуказан тип . Может кто-нибудь объяснить, что здесь происходит на самом деле?

Любой гений, который может мне в этом помочь, скорее всего, ответит на мой другой вопрос: как создать ответ JSON, объединив @ foo.to_json (параметры) и @ bars.to_json (параметры) в Rails.


Исходный вопрос:

Я видел несколько других примеров на SO, но я не делаю то, что ищу.

Я стараюсь:

class User < ActiveRecord::Base

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

end

Я получаю ArgumentError: wrong number of arguments (1 for 0)в

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

Любые идеи?

Мачек
источник
Ваш пример работает в одной из моих моделей. Есть ли какие - либо из username, fooили barметоды ожидают аргументы?
Джонатан Джулиан
Нет, usernameэто не метод , а fooи barне требуют методов. Я обновил свой вопрос, чтобы показать, где происходит ошибка.
maček
Я использую 1.8.7. Вам нужно будет открыть этот файл и понять, почему он передает аргумент методу, ожидающему нулевого аргумента.
Джонатан Джулиан,

Ответы:

216

Вы получаете, ArgumentError: wrong number of arguments (1 for 0)потому что to_jsonнеобходимо переопределить одним параметром, optionsхешем.

def to_json(options)
  ...
end

Более длинные объяснения to_json, as_jsonи рендеринга:

В ActiveSupport 2.3.3 as_jsonбыл добавлен для решения проблем, подобных той, с которой вы столкнулись. Создание в формате JSON должно быть отделено от оказания в формате JSON.

Теперь в любое время to_jsonвызывается для объекта, as_jsonвызывается для создания структуры данных, а затем этот хеш кодируется как строка JSON с использованием ActiveSupport::json.encode. Это происходит для всех типов: объектных, числовых, дат, строк и т. Д. (См. Код ActiveSupport).

Объекты ActiveRecord ведут себя точно так же. Существует as_jsonреализация по умолчанию, которая создает хэш, включающий все атрибуты модели. Вы должны переопределить as_jsonв своей модели, чтобы создать нужную структуру JSON . as_json, как и в предыдущем случае to_json, принимает хэш параметров, в котором вы можете указать атрибуты и методы для декларативного включения.

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

В вашем контроллере render :json => oможет принимать строку или объект. Если это строка, она передается как тело ответа, если это объект, to_jsonвызывается, as_jsonкак описано выше.

Итак, пока ваши модели правильно представлены с as_jsonпереопределениями (или нет), код вашего контроллера для отображения одной модели должен выглядеть так:

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

Мораль этой истории такова: не звоните to_jsonнапрямую, позвольте renderсделать это за вас. Если вам нужно настроить вывод JSON, позвоните as_json.

format.json { render :json => 
    @user.as_json(:only => [:username], :methods => [:avatar]) }
Джонатан Джулиан
источник
@Jonathan Julian, это очень полезное объяснение as_json. Как вы можете видеть в документации по ActiveRecord :: Serialization ( api.rubyonrails.org/classes/ActiveRecord/… ), документации по этому очень мало (нет). Я попробую :)
maček
1
@ Джонатан Джулиан, если бы я мог проголосовать за это 10 раз, я бы сделал это. Где, черт возьми, as_jsonдокументы!
Еще
71

Если у вас возникли проблемы с этим в Rails 3, переопределите serializable_hashвместо as_json. Это также бесплатно получит ваше XML-форматирование :)

Мне потребовалась целая вечность, чтобы понять. Надеюсь, это кому-то поможет.

Сэм Соффс
источник
1
Кто-нибудь знает какие-нибудь хорошие отзывы об этом методе serializable_hash? Когда я его использую, он меняет мой последующий вывод xml, заключающийся в заключении объекта в оболочку с его именем (например, «цитата» для объекта цитаты »), чтобы вместо этого всегда заключать его в« <hash> ».
Тайлер Коллиер
@TylerCollier, это должны быть те же параметры, что иto_xml
Сэм Соффес,
Спасибо за это решение! Я использую ruby2 / rails4, а as_json не работал с вложенными объектами, переопределенный метод не был вызван в 'include', с serializable_hash он работает!
santuxus
См. Robots.oughttbot.com/better-serialization-less-as-json для объяснения того, почему вместо этого следует переопределить serializable_hash.
Тофер Хант
36

Для людей, которые не хотят игнорировать параметры пользователей, но также добавляют свои:

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

Надеюсь, это кому-нибудь поможет :)

Данпе
источник
1
Я так и делаю, за исключением того, что по умолчанию я использую optionsхэш, = {}поэтому он не требуется при звонке
mroach
4

Переопределить не to_json, а as_json. И из as_json вызывайте то, что хотите:

Попробуй это:

def as_json 
 { :username => username, :foo => foo, :bar => bar }
end
глебм
источник
Не as_jsonтолько для ActiveResource?
Джонатан Джулиан
Судя по всему ActiveRecord :: Serialization имеет as_json api.rubyonrails.org/classes/ActiveRecord/Serialization.html
glebm 03
@glebm, пробовал и получаю тот же результат. Я обновил свой вопрос, чтобы показать вам.
maček
@glebm, я все еще получаю ту же ошибку. Даже когда я render :json => current_userполучаю ожидаемый результат по умолчанию (все атрибуты для Userмодели в формате JSON). Когда я добавляю as_jsonметод к своей Userмодели и пробую то же самое, я получаю сообщение об ошибке :(
maček
@glebm, спасибо. Я знаю, что делал что-то не так. Возможно, стоит проверить обновленный вопрос.
maček 03