Como substituir to_json no Rails?

94

Atualizar:

Este problema não foi devidamente explorado. O verdadeiro problema está dentro render :json.

A primeira colagem de código na questão original produzirá o resultado esperado. No entanto, ainda há uma ressalva. Veja este exemplo:

render :json => current_user

NÃO é o mesmo que

render :json => current_user.to_json

Ou seja, render :jsonnão chamará automaticamente o to_jsonmétodo associado ao objeto Usuário. Na verdade , se to_jsonestiver sendo sobrescrito no Usermodelo, render :json => @userirá gerar o ArgumentErrordescrito abaixo.

resumo

# 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

Tudo isso me parece bobo. Isso parece me dizer que rendernão está realmente chamando Model#to_jsonquando o tipo :jsoné especificado. Alguém pode explicar o que realmente está acontecendo aqui?

Qualquer gênio que possa me ajudar com isso provavelmente pode responder à minha outra pergunta: Como construir uma resposta JSON combinando @ foo.to_json (opções) e @ bars.to_json (opções) no Rails


Questão original:

Eu vi alguns outros exemplos no SO, mas nenhum faço o que estou procurando.

Estou tentando:

class User < ActiveRecord::Base

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

end

Estou ficando ArgumentError: wrong number of arguments (1 for 0)em

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

Alguma ideia?

Macek
fonte
Seu exemplo funciona em um dos meus modelos. Siga qualquer um destes username, fooou barmétodos esperar argumentos?
Jonathan Julian,
Não, username não é um método e um fooe barnão requerem métodos. Eu atualizei minha pergunta para mostrar onde o erro está acontecendo.
maček,
Estou executando 1.8.7. Você terá que abrir esse arquivo e ver por que está passando um arg para um método que espera zero args.
Jonathan Julian,

Respostas:

214

Você está recebendo ArgumentError: wrong number of arguments (1 for 0)porque to_jsonprecisa ser substituído por um parâmetro, o optionshash.

def to_json(options)
  ...
end

Mais explicação sobre to_json, as_jsone renderização:

No ActiveSupport 2.3.3, as_jsonfoi adicionado para resolver problemas como o que você encontrou. A criação do json deve ser separada da renderização do json.

Agora, a qualquer momento to_jsoné chamado em um objeto, as_jsoné chamado para criar a estrutura de dados e, em seguida, esse hash é codificado como uma string JSON usando ActiveSupport::json.encode. Isso acontece para todos os tipos: objeto, numérico, data, string, etc (ver o código ActiveSupport).

Os objetos ActiveRecord se comportam da mesma maneira. Existe um padrãoas_json implementação que cria um hash que inclui todos os atributos do modelo. Você deve substituir as_jsonem seu modelo para criar a estrutura JSON desejada . as_json, assim como o antigo to_json, tem um hash de opção onde você pode especificar atributos e métodos para incluir declarativamente.

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

Em seu controlador, render :json => opode aceitar uma string ou um objeto. Se for uma string, ela será passada como o corpo da resposta, se for um objeto, to_jsoné chamado, o que dispara as_jsonconforme explicado acima.

Portanto, contanto que seus modelos sejam representados corretamente com as_jsonsubstituições (ou não), o código do controlador para exibir um modelo deve ser assim:

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

A moral da história é: evite ligar to_jsondiretamente, permita renderfazer isso por você. Se você precisar ajustar a saída JSON, chame as_json.

format.json { render :json => 
    @user.as_json(:only => [:username], :methods => [:avatar]) }
Jonathan Julian
fonte
@Jonathan Julian, esta é uma explicação muito útil sobre as_json. Como você pode ver nos documentos ActiveRecord :: Serialization ( api.rubyonrails.org/classes/ActiveRecord/… ), há muito pouca (nenhuma) documentação para isso. Vou tentar :)
maček
1
@Jonathan Julian, se eu pudesse votar 10 vezes, eu votaria. Onde diabos estão os as_jsondocumentos! Obrigado novamente :)
maček
71

Se você está tendo problemas com isso no Rails 3, substitua ao serializable_hashinvés deas_json . Isso também obterá sua formatação XML gratuitamente :)

Isso me levou uma eternidade para descobrir. Espero que ajude alguém.

Sam Soffes
fonte
1
Alguém sabe de algum bom artigo sobre o método serializable_hash? Quando eu o uso, ele altera minha saída xml subsequente de envolver o objeto com seu nome (por exemplo, "quote" para um objeto de cotação ") para sempre envolvê-lo com" <hash> ".
Tyler Collier
@TylerCollier deve ter as mesmas opções queto_xml
Sam Soffes
Obrigado por esta solução! Estou usando ruby2 / rails4 e as_json não estava funcionando com objetos aninhados, o método substituído não foi chamado em 'include', com serializable_hash ele funciona!
santuxus de
Consulte robots.thoughtbot.com/better-serialization-less-as-json para obter uma explicação de por que serializable_hash deve ser substituído.
Topher Hunt
36

Para pessoas que não desejam ignorar as opções dos usuários, mas também adicionar as deles:

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

Espero que isso ajude alguém :)

Danpe
fonte
1
É assim que eu faço, exceto que padrão o optionshash com, = {}portanto, não é necessário ao chamar
mroach
4

Substitua não to_json, mas as_json. E de as_json chame o que quiser:

Experimente isto:

def as_json 
 { :username => username, :foo => foo, :bar => bar }
end
glebm
fonte
Não é as_jsonapenas para ActiveResource?
Jonathan Julian,
Aparentemente ActiveRecord :: Serialization tem as_json api.rubyonrails.org/classes/ActiveRecord/Serialization.html
glebm
@glebm, tentei fazer isso e estou obtendo o mesmo resultado. Eu atualizei minha pergunta para mostrar a você.
maček,
@glebm, ainda estou recebendo exatamente o mesmo erro. Mesmo quando faço render :json => current_userisso, obtenho o resultado padrão esperado (todos os atributos do Usermodelo no formato JSON). Quando adiciono o as_jsonmétodo ao meu Usermodelo e tento a mesma coisa, recebo o erro :(
maček
@glebm, obrigado. Eu sei que estava fazendo algo errado. Pode valer a pena verificar a pergunta atualizada.
maček,