2010-02-03 9 views
16

Attenzione: Noob qui.Rails: refactoring, views, helpers: come vanno tutti insieme?

So che questo è un argomento banale ma sto avendo molte difficoltà a capire in che modo esattamente posso semplificare le mie opinioni spostandone parti in aiutanti. Ad esempio, ho sempre letto che i condizionali nei tuoi punti di vista sono i primi candidati per l'estrazione in aiutanti, ma non sono riuscito a trovare esempi di questo, e i miei tentativi di raggiungere questo risultato sono falliti.

Per esempio, supponiamo di avere:

#index.html.erb 

<% for beast in @beasts do -%> 
    <% if beast.dead? -%> 
    <%= beast.body %> 
    <%= link_to "bury", bury_beast_path(:id => beast.id) %> 
    <% else -%> 
    <%= beast.body %> 
    <%= link_to "kill!", kill_beast_path(:id => beast.id) %> 
    <% end -%> 
<% end -%> 

E mi scoccia un po 'per avere questo a mio avviso, ma esattamente come ho potuto passare ad un aiutante, invece? E ulteriormente semplificarlo, se possibile. (Ho letto da qualche parte che i condizionali sono male, ma è solo al di là di me come si potrebbe programmare nulla senza di loro.)

Un altro esempio: ho bisogno di id miei body tag con il formato controller_action. Il migliore che ho avuto finora è questo:

#index.html.erb 

<body id="<%= controller_action %>"> 

... e ...

#application_helper.rb 

def controller_action 
    @id = @controller.controller_name + "_" + @controller.action_name 
end 

Non sono un esperto, ma che è ancora brutto anche a me.

Per rendere le cose più complicate, Ryan Singer said something I liked: per trattare ERB come un tag immagine, utilizzando gli helper per "rivelare l'intenzione". Poi, nel prossimo respiro, dicendo che non dovresti avere HTML in helpers per quella è la via per l'inferno. WTF? In che modo entrambe le cose sono compatibili? Se si arriva al punto in cui si possono semplicemente dichiarare i comportamenti nella vista, sicuramente ci dovrebbe essere un sacco di HTML da rendere dietro le quinte? Non riesco a coglierlo.

Quindi, questo è fondamentalmente. Sarei grato se qualcuno potesse condividere alcune considerazioni su questo argomento, o indicarmi una buona lettura approfondita sull'argomento - che ho trovato per avere una copertura davvero debole sul web. Ho già cercato su Google fino allo sfinimento, ma chi lo sa.

+0

Nota secondaria. L'uso di 'for' va bene, ma l'uso di un iteratore è più una convenzione Ruby, ad es.' <% @ Beasts.each do | beast | %> '. –

+0

@Sarah: Grazie. È che mi piace molto l'aspetto del linguaggio naturale dell'uso di 'for'. –

risposta

26

Il refactoring facilita la manutenzione delle viste. Il problema è scegliere dove va il codice refactored.

tue due scelte sono parziali e aiutanti. Non ci sono regole dettate dalla pietra che devono essere usate dove. Ci sono un paio di linee guida che vagano come quelle che affermano che gli helper non dovrebbero contenere HTML.

Generalmente partial sono più adatti per il refactoring di sezioni che sono più HTML/ERB/​​HAML di ruby. Gli helper d'altra parte sono usati per pezzi di codice rubino con HTML minimo o per generare semplici HTML dai parametri.

Tuttavia, non sono d'accordo sul fatto che gli helper non debbano contenere alcun HTML. Un po 'va bene, basta non farlo. Il modo in cui gli helper vengono elaborati ne ostacola l'utilizzo per la produzione di grandi quantità di HTML. Ecco perché è suggerito che i tuoi aiutanti contengano quantità minime di HTML. Se guardi alla fonte gli helper che vengono forniti con le rotaie noterai che la maggior parte di loro genera HTML. I pochi che non lo fanno, sono usati principalmente per generare parametri e valutare condizioni comuni.

Ad esempio, uno qualsiasi degli helper di modulo o delle varianti di link_to è adatto alla prima forma di helper. Mentre cose come url_for e logged_in? come fornito da vari modelli di autenticazione sono del secondo tipo.

Questa è la catena decisionale che utilizzo per determinare se applicare il codice di un fattore da una vista a un parziale o un helper.

  1. Ripetere o istruzioni quasi identiche che producono un singolo tag html superficiale? => helper.
  2. Espressione comune utilizzata come argomento per un altro helper? => helper.
  3. Espressione lunga (più di 4 termini) utilizzata come argomento per un altro helper? => helper.
  4. 4 o più righe di ruby ​​(che non vengono valutate in HTML)? => helper.
  5. Praticamente tutto il resto => parziale.

ho intenzione di utilizzare il codice che stai cercando di refactoring come esempio:

avrei refactoring la vista nella domanda in questo modo:

app/aiutanti/beast_helper.rb :

def beast_action(beast) 
    if beast.dead? 
    link_to "bury", bury_beast_path(beast) 
    else 
    link_to "kill!", kill_beast_path(beast) 
    end 
end 

app/views/bestie/_beast.html.erb:

<%= beast.body %> 
<%= beast_action(beast) %> 

app/views/bestie/index.html.erb:

<%= render :partial => "beast", :collection => @beasts %> 

E 'tecnicamente più complicato, perché è 3 file, e 10 linee totali in contrapposizione a 1 di file e 10 linee. Le viste ora sono solo 3 righe combinate distribuite su 2 file. Il risultato finale è che il tuo codice è molto più ASCIUTTO. Permettendovi di riutilizzare parti o tutto in altri controllori/azioni/viste con una minima complessità aggiuntiva.

Come per l'ID del tag del corpo. Dovresti davvero usare content_for/rendimento. Per quel genere di cose.

app/views/layout/application.html.erb

... 
<body id="<%= yield(:body_id) %>"> 
... 

app/views/bestie/index.html.erb

<% content_for :body_id, controller_action %> 
... 

Questo vi permetterà di ignorare l'id del corpo in qualsiasi vista che lo richiede. Ad esempio:

app/views/users/preferences.html.erb

<% content_for :body_id, "my_preferences" %> 
+0

Finché il partial è su 'beasts/_beast.html.erb' puoi semplicemente eseguire' render @ beasts'. – Garrett

+0

@Garrett: funziona solo in Rails 2.3 o versioni successive. Non ho idea del perché un nuovo utente utilizzi una versione precedente. Ma sentivo che quella versione era più sicura. – EmFi

+0

Buon punto, con Rails 3 in arrivo penso che sia sicuro iniziare a spingere 'render @ beasts' – Garrett

8

La prima cosa che farei sarebbe questo:

#index.html.erb 
<%= render @beasts %> 

#_beast.html.erb 
<%= beast.body %> 
<%= link_to_next_beast_action(beast) %>  

#beast_helper.rb 
def link_to_next_beast_action(beast) 
    if beast.dead? 
    link_to "bury", bury_beast_path(:id => beast.id) 
    else 
    link_to "kill!", kill_beast_path(:id => beast.id) 
    end 
end 

Quello che ho fatto è separata fuori il rendering della bestia in un parziale che utilizza la semantica di raccolta.

Quindi ho spostato la logica per mostrare i collegamenti kill/bury in un mostro bestia. In questo modo se decidi di aggiungere un'altra azione (ad esempio, "riporta da morto"), dovrai solo cambiare l'helper.

Questo aiuto?

+0

Si può anche fare 'rendering @ bestie' perché si renderà conto che è una raccolta. – Garrett

+0

Sembra che tu sia arrivato allo stesso refactoring che ho fatto. Non ho notato la nuova risposta mentre stavo componendo la mia. – EmFi

+2

@EmFi Ho votato la tua risposta perché hai inserito maggiori dettagli su quando utilizzare partial/helper. – jonnii

0

Un altro startegy sarebbe quella di non utilizzare i modelli e aiutanti a tutti. Per il rendering è possibile:

  1. rendere le visualizzazioni direttamente dai controller utilizzando render (: inline =>). Se si desidera mantenere formalmente separate le viste e i controllori, è possibile creare moduli/mixin da includere nei controller.
  2. oppure crea le tue classi di vista e usale per rendere la tua risposta.

L'idea alla base di questo è che aiutanti e rotaie sistema di template erb non approfittare della programmazione orientata agli oggetti, in modo che alla fine della giornata non si può definire comportamenti generali che si specializzano in base a ciascun controller di/esigenze della richiesta; il più delle volte si finisce per riscrivere blocchi di codice molto simili, il che non è molto bello dal punto di vista della manutenzione.

Quindi, se sono ancora necessari alcuni metodi di supporto (ad esempio form_tag, h, raw, ...), è necessario includerli solo nella classe di controllo/dedicata alla vista.

Vedi questo: rails-misapprehensions-helpers-are-shit per un articolo divertente ma utile.

EDIT: per non sembrare una doccia completa, direi che l'implementazione dipende da quanto dovrebbe essere grande la tua applicazione e da quanto spesso dovrai aggiornare il tuo codice. Inoltre, se si sta delegando il progetto a un non programmatore, è probabile che si trovi in ​​alcuni corsi di programmazione prima di scavare nel codice, il che, ammettiamolo, sarebbe meno direttamente comprensibile rispetto alla sintassi dei modelli.

1

Una terza scelta consiste nell'utilizzare un modello di visualizzazione da Cells gem. Questo è un framework molto popolare che porta l'orientamento agli oggetti al livello vista in Rails.

# app/cells/beast/cell.rb 

class Beast::Cell < Cell::Concept 
    def show 
    return dead if model.dead? 
    kill 
    end 

private 
    def dead 
    link_to "bury", bury_beast_path(:id => model.id) 
    # you could render a view here, too! 
    end 

    def kill 
    link_to "kill!", kill_beast_path(:id => model.id) 
    end 
end 

Quindi si esegue il rendering di un modello di vista utilizzando un helper (nella vista o nel controller).

# app/views/beasts/index.erb 

<%= concept(:beast, @beast).call %> 
<%-# this returns the link content %> 

Questo è tutto! È possibile testare questa cella isolata in un test separato. Le celle offrono anche il rendering della vista, l'ereditarietà della vista e molte altre cose.

Ad esempio, è possibile utilizzare una vista per il collegamento kill.

# app/cells/beast/cell.rb 

class Beast::Cell < Cell::Concept 

    # .. 

    def kill 
    render :kill 
    end 
end 

Questo rende la vista dell'assassino della cella.

# app/cells/beast/views/index.erb 

<%= link_to "kill!", kill_beast_path(:id => model.id) %> 

Nota l'ubicazione della vista, è ben impacchettata nella directory della cella.

E, sì, le celle possono eseguire HAML e qualsiasi altro motore di template supportato da AbstractController.