5

So che quando utilizzo i modelli di visualizzazione (html, rabl), non ho bisogno di una chiamata di rendering esplicita nell'azione del mio controller perché, per impostazione predefinita, Rails rende il modello con il nome corrispondente al nome dell'azione del controller. Mi piace questo concetto (non mi interessa il rendering nel mio codice controller) e quindi mi chiedo se questo è possibile anche quando si utilizza ActiveModel :: Serializer?ActiveModel :: Serializer richiede una chiamata di rendering esplicita?

esempio, questo è il codice da un controllore generato (Rotaie 4.1.0):

class ProductsController < ApplicationController 
    before_action :set_product, only: [:show, :edit, :update, :destroy] 

    #other actions 
    # GET /products/1 
    # GET /products/1.json 
    def show 
    end 
end 

e questo è il serializzatore:

class ProductSerializer < ActiveModel::Serializer 
    attributes :id, :name, :description, :url, :quantity, :price 
end 

/products/1.json Colpire, avrei si aspettano due cose accadano:

  1. campi non elencati nel serializzatore da ommited,
  2. Oggetto JSON intero da incapsulare in un campo di livello superiore 'prodotto'.

Tuttavia, ciò non accade, l'intero serializzatore viene ignorato. Ma allora se modifico il metodo Show al seguente:

# GET /products/1 
# GET /products/1.json 
def show 
    @product = Product.find(params[:id]) 
    respond_to do |format| 
    format.html 
    format.json { render json: @product } 
    end 
end 

e ora è tutto bene, ma ho perso il vantaggio del filtro before_action (e mi sembra che ho qualche codice ridondante).

Come dovrebbe essere fatto?

+0

@zmilojko Hai provato a utilizzare ['answer_with'] (http://api.rubyonrails.org/classes/ActionController/MimeResponds.html#method-i-respond_with)? Penso che 'respond_with (@product)' ti avvicini se non esattamente quello che vuoi. [Esempio dal README di 'ActiveModel :: Serializer'] (https://github.com/rails-api/active_model_serializers#render-json). –

+0

@PaulFioravanti Ma non è quello che sto cercando. Mi piacerebbe che il metodo 'show' rimanga vuoto mentre il generatore di Rails4 lo crea, ma per essere ancora in grado di usare Serializer come definito nella domanda (e non in jbuilder, come preferirebbe Rails). – zmilojko

+0

@zmilojko Questa è una app per Rails 4.1? O un'app per rails-api? Come stai creando lo stato iniziale della tua app? – noel

risposta

0

Il 'codice ridondante' che vediamo nel secondo, è solo questa linea:

@product = Product.find(params[:id])

e credo che questa è la stessa logica il tuo before_action. Non hai bisogno di questa linea, basta rimuoverla. Ora la duplicazione è stata rimossa.

Per la parte rimanente. Un'azione deve sapere cosa rendere. Per impostazione predefinita, se l'azione è vuota o assente, il corrispondente 'action_name'.html.erb (e altri formati specificati da respond_to) verranno esaminati e sottoposti a rendering.

Questo è il motivo per cui il generatore Rails 4 creato funziona: crea il show.html.erb e show.json.jbuilder che viene visualizzato.

Con ActiveModel::Serializer, non si dispone di un modello. Se lasci l'azione vuota, non ha idea di cosa renderizzare. Quindi è necessario dire che per rendere il @product come JSON, mediante:

render json: @product

o

respond_with @product

+0

Ma il mio obiettivo non era quello di rimuovere la linea duplicata, ma il resto. Mi piacerebbe che il metodo di 'come rimanere vuoto e invocare ancora il serializzatore. Sembra non funzionare, rendendo l'intero ActiveModel: Serializer inutile. – zmilojko

0

Senza un esplicito render o respond_with o respond_to Rails cercherà un template matching . Se quel modello non esiste Rails genera un errore.

Tuttavia, è possibile creare il proprio resolver per ignorarlo.Per esempio, supponiamo che si è creato app\models\serialize_resolver.rb e mettere questo in esso:

class SerializeResolver < ActionView::Resolver 
    protected 
    def find_templates(name, prefix, partial, details) 
    if details[:formats].to_a.include?(:json) && prefix !~ /layout/ 
     instance = prefix.to_s.singularize 
     source = "<%= @#{instance}.active_model_serializer.new(@#{instance}).to_json.html_safe %>" 
     identifier = "SerializeResolver - #{prefix} - #{name}" 
     handler = ActionView::Template.registered_template_handler(:erb) 
     details = { 
     format: Mime[:json], 
     updated_at: Date.today, 
     virtual_path: "/#{normalize_path(name, prefix)}" 
     } 
     [ActionView::Template.new(source, identifier, handler, details)] 
    else 
     [] 
    end 
    end 

    def normalize_path(name, prefix) 
    prefix.present? ? "#{prefix}/#{name}" : name 
    end 
end 

E poi, in entrambi il controller applicazione (o in un controller individuale) posto:

append_view_path ::SerializeResolver.new 

Con che si dovrebbe essere in grado fare ciò che vuoi Se si tratta di una richiesta JSON, creerà un modello ERB con il contenuto giusto e lo restituirà.

Limitazioni:

  • Questo è un po 'goffo perché si basa su Erb, che non è necessario. Se avrò tempo creerò un semplice gestore di template. Quindi possiamo invocarlo senza ERB.
  • Ciò cancella la risposta predefinita json.
  • Essa si basa sul nome del controller per trovare la variabile di istanza (/posts viene convertito in @post.)
  • Ho provato solo questo un po '. La logica potrebbe probabilmente essere più intelligente.

Note:

  • Se un modello è presente, verrà utilizzato per primo. Ciò ti consente di ignorare questo comportamento.
  • Non è possibile semplicemente creare un nuovo renderer e registrarlo, perché il processo predefinito non lo colpisce. Se il modello non viene trovato, si ottiene un errore. Se il file viene trovato, passa direttamente al richiamo del gestore di template.
+0

Ho riutilizzato del codice dal libro "Crafting Rails" di Jose Valim per creare questa risposta. – noel

+0

Hmmm, stavo cercando una soluzione per semplificare il mio codice, questo è praticamente l'opposto. Suppongo che l'approccio di jbuilder vince e ActiveModel: Serializer non è così utile ... – zmilojko

+0

Si potrebbe dire così. Ma deve essere fatto solo una volta. – noel