2010-01-24 10 views
4

Je rencontre un problème de portée qui empêche les variables d'instance d'être initialisées correctement par les helpers appelés depuis la vue.Ruby on Rails: Initialisation des variables d'instance avec les assistants en vue

#sample_controller.rb 
class SampleController < ApplicationController 
    def test 
    end 
end 

#application_controller.rb 
helper_method :display 
def display 
    if not defined? @display 
    return @display = "a" 
    else 
    return @display += "a" 
    end 
end 

#test.html.erb 
<%= display %> 
<%= display %> 
<%= @display %> 
<%= @display.reverse %> 

Lorsque l'échantillon/test est affiché, il meurt avec une erreur "tout en évaluant nil.reverse". C'est surprenant car les deux premiers appels à afficher auraient dû initialiser @display j'aurais pensé. Si <% = @ display.reverse%> est supprimé, la sortie est "aa", indiquant que la variable d'instance @display est définie par la méthode d'assistance, mais qu'il n'y a pas d'accès dans la vue.

Si le contrôleur est modifié de façon à devenir (avec le code de la vue originale):

class SampleController < ApplicationController 
    def test 
    display 
    end 
end 

La sortie devient "aaa a un". Si je fais 2 appels à afficher dans le contrôleur, je reçois, "aaa aaaa aa aa". Il semble donc que seuls les appels effectués dans le contrôleur modifieront la variable d'instance SampleController, tandis que les appels dans la vue modifieront une variable d'instance pour ApplicationController à laquelle la vue n'a pas accès.

Est-ce un bug dans Rails ou est-ce que je suis mal compris et c'est pour une raison ou pour une autre?

Le contexte dans lequel j'ai rencontré ce bogue est d'essayer de créer un login_in? Méthode ApplicationController qui configure une variable @user la première fois qu'elle est appelée et renvoie true ou false si un utilisateur est connecté. Cela ne fonctionnerait que si j'ajoutais un appel inutile dans le contrôleur avant de tenter de l'utiliser dans la vue.

Répondre

6

Il y a deux Ivars de @display au moment du rendu, l'un dans le contrôleur et un dans la vue. La vue ne peut pas accéder à celle du contrôleur et vice versa. Au début du rendu, Rails copie tous les fichiers ivars du contrôleur dans la vue. Mais à ce moment, @display n'existe pas et n'est pas copié. Dans tout programme ruby, un appel à @my_undefined_ivar renverra nil - ce qui est exactement ce qui vous arrive.

Pourrait être déroutant, ici il est dit d'une autre manière. Rails copie les fichiers ivars du contrôleur dans la vue au début du rendu. Ainsi, les changements dans l'ivar de la vue ne sont pas reflétés dans le contrôleur ivar et vice versa. L'assistant que vous avez défini permet à la vue d'invoquer une méthode sur le contrôleur afin que l'ivar du contrôleur puisse être renvoyé à la vue en tant que valeur de retour mais la modification du contrôleur lui-même n'est pas reflétée dans la vue. Utilisez simplement la méthode d'assistance dans ce cas et oubliez @display dans la vue.

+0

Donc dans ma vraie application, @display est en fait @user, et il est utilisé pour afficher des informations sur l'utilisateur. À titre d'exemple de ce que je veux dire, le code typique est quelque chose comme, <% if logged_in?%><% = @ User.name%><%end%>. Alors, est-ce la meilleure façon de faire cela pour avoir un appel before_filter dans le ApplicationController qui configure la variable @user? Parce que souvent, pour une vue donnée, la seule utilisation de @user est pour quelque chose comme je l'ai décrit ci-dessus, et il n'y a pas d'autre besoin pour qu'il soit utilisé dans le contrôleur lui-même. –

+0

Oui, le mécanisme de copie d'ivar correspond à ce que vous décrivez. Configurez @user dans un filtre avant. – ffoeg

+0

Merci @ffoeg, cette réponse est très utile. Pouvez-vous pointer vers une documentation ou des guides de rails où cela est expliqué? On ne sait pas quelles variables sont partagées entre View, Controller et Helpers. – CodeKid

0

Je pense que cela pourrait être le deuxième reserved words question que j'ai vu cette semaine ...

Astuce: vous avez besoin presque jamais utiliser le mot-clé return dans Ruby. En outre, votre utilisation de defined? me paniquer ... peut-être la peine d'essayer ceci:

if @display.nil? 
    @display = "a" 
    else 
    @display += "a" 
    end 
+0

Quelle est la question de mots réservés que vous voyez ici? Quoi qu'il en soit, je suppose que ce n'est pas le problème fondamental, puisque j'ai rencontré le bug en utilisant tous les mots différents (login comme je l'ai mentionné) et le code que j'ai généré pour comprendre ce qui ne va pas. Merci pour le conseil sur la déclaration de retour. Le défini? que j'ai utilisé semble faire ce que j'ai prévu, n'est-ce pas? Je l'ai obtenu à partir du code d'implémentation exemple pour authlogic où ils l'ont utilisé dans un but similaire. –

+0

selon la liste de mots réservés, "affichage" a été connu pour causer des problèmes. Je n'ai jamais entendu parler d'un problème de définition de variables d'instance dans les vues, car la plupart des modèles de connexion font exactement ce que vous essayez de faire ici, en définissant @current_user lorsque current_user est appelé pour la première fois. défini? fonctionnera techniquement ici, mais il est généralement utilisé pour les choses qui génèrent une erreur plutôt que de retourner nul si elles ne sont pas définies, comme les variables locales et les définitions de méthodes. néant? fonctionne très bien pour le cas très courant des vérifications de variables d'instance et est légèrement plus facile à analyser. – tfwright