IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Ruby on Rails Discussion :

NoMethodError sur nil:NilClass pour un objet qui n'est pas nil


Sujet :

Ruby on Rails

  1. #1
    Membre Expert
    Avatar de Bestiol
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    1 515
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2002
    Messages : 1 515
    Par défaut NoMethodError sur nil:NilClass pour un objet qui n'est pas nil
    Bonjour à tous

    Bon j'ai conscience que le titre de mon message puisse sembler bizarre, et il est d'ailleurs sans doute faux dans l'absolu... mais je n'y comprends plus rien, donc je viens chercher un peu de secours.

    Situation : j'ai une appli Rails qui utilise acts_as_commentable et acts_as_bookmarkable sur un même modèle Diagram. Pas de souci pour utiliser ces deux plugins d'une manière générale.
    Maintenant, dans la vue show de mon Diagram, j'aimerais afficher à la fois les commentaires et une ligne informative par bookmark, du genre "Machin a ajouté ce diagramme à ses favoris".

    Dans un controller, j'ai une action qui fait ce job:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        # Get bookmarked class from params and fetch the wanted instance
        model_class = Kernel.const_get(params[:commentable][:type].capitalize)
        instance = model_class.find params[:commentable][:id]
        report_error("Cannot find " << model_class.to_s << " with id " << params[:commentable][:id].to_s) if instance.nil?
        @comments = instance.comments
     
        # Include bookmarks if model is bookmarkable, and sort bookmarks and comments all together
        if BookmarksController::BOOKMARKABLE_MODELS.include?(model_class.to_s)
          @comments += instance.bookmarks
          @comments = @comments.sort_by {|obj| obj.created_at}
        end
    Donc en gros: je récupère les commentaires, j'ajoute les bookmarks à la collection si le modèle peut être "bookmarké", puis je trie le tout par date de création.

    J'ai ensuite une vue et quelques partials qui se chargent de l'affichage :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    ## Ficher index.html.haml
    = render :partial => "comment", :collection => @comments
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ## Fichier _comment.html.haml (appelé pour chaque élément de @comments donc)
    = debug comment
    = debug comment.user
     
    -# The 'comment' might be a bookmark. Handle it first.
    - if comment.instance_of?(Bookmark)
      .bookmark
        = render :partial => "/comments/comment_bookmark", :locals => { :bookmark => comment }
    - else
      .comment{:class => cycle("odd", "even")}
        = render :partial => "/comments/comment_content", :locals => { :comment => comment}
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    ## Fichier _comment_bookmark.html.haml
    = image_tag "/images/iLight/icon.png"
    == #{bookmark.user.display_name} lit this diagram <small>(#{time_ago_in_words bookmark.created_at} ago)</small>
    Le problème se trouve dans ce dernier fichier, le partial qui sert à l'affichage de la ligne d'info d'un bookmark. J'obtiens l'erreur :
    ActionView::TemplateError (You have a nil object when you didn't expect it!
    You might have expected an instance of Array.
    The error occurred while evaluating nil.include?) on line #2 of app/views/comments/_comment_bookmark.html.haml:
    1: = image_tag "/images/iLight/icon.png"
    2: == #{bookmark.user.display_name} lit this diagram <small>(#{time_ago_in_words bookmark.created_at} ago)</small>
    Déjà, je ne vois pas d'où il sort son appel à include?
    Un rapide test me montre que c'est l'appel bookmark.user.display_name qui cause l'exception. Un appel à n'importe quel attribut de bookmark.user, ou n'importe quelle méthode, causera la même erreur.
    Toutefois, bookmark.user n'est pas nil !

    Comme vous pouvez voir j'ai mis deux appels à debug dans _comment.html.haml.
    Dans le cas d'un commentaire, ça me donne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    --- !ruby/object:Comment 
    attributes: 
      comment: pouet
      created_at: 2010-10-11 00:44:02
      title: ""
      commentable_type: Diagram
      commentable_id: "29"
      updated_at: 2010-10-11 00:44:02
      id: "41"
      user_id: "2"
    attributes_cache: 
      created_at: 2010-10-11 00:44:02 Z
    --- !ruby/object:User 
    attributes: 
      created_at: 2010-05-23 18:31:02
      activated_at: 2010-06-08 22:23:19
      send_news: "1"
      remember_token_expires_at: 
      updated_at: 2010-09-19 22:36:42
      activation_code: 
      old_email: 
      id: "2"
      saved_once: "1"
      email_hash: 672585615_3008d2bf2e8e7f4455d48212f99a9343
      remember_token: 
      short_name: olance
      diagrams_count: "16"
      display_name: Olivier Lance
    attributes_cache: {}
    Dans le cas d'un bookmark, ça me donne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    --- !ruby/object:Bookmark 
    attributes: 
      bookmarkable_type: Diagram
      bookmarkable_id: "29"
      created_at: 2010-10-11 01:18:41
      title: new items
      updated_at: 2010-10-11 01:18:41
      id: "253"
      user_id: "2"
    attributes_cache: 
      created_at: 2010-10-11 01:18:41 Z
    #<User id: 2, created_at: "2010-05-23 18:31:02", updated_at: "2010-09-19 22:36:42", remember_token: nil, remember_token_expires_at: nil, activation_code: nil, activated_at: "2010-06-08 22:23:19", email_hash: "672585615_3008d2bf2e8e7f4455d48212f99a9343", display_name: "Olivier Lance", short_name: "olance", send_news: true, saved_once: true, diagrams_count: 16, old_email: nil, changed_email: nil>
    La différence de formatage de l'objet User m'interpelle... d'autant que si je coupe le serveur, le relance (j'utilise Mongrel en développement) et refresh la page, le debug comment.user pour un bookmark aura la même tête que pour un comment. Et l'appel à display_name fonctionne alors. Mais seulement la première fois juste après le relancement du serveur. Si je refresh une nouvelle fois, ça prend la tête d'au-dessus, et l'appel plante. (je sais pas si je suis clair ? )

    Voilà voilà... j'ai essayé de debugger avec ruby-debug mais ça ne m'a rien donné de concluant, je n'arrive pas à breaker sur l'exception TemplateError ou NoMethodError...
    Je viens de passer des heures là-dessus, je n'y comprends rien du tout et je n'ai pas beaucoup d'expérience dans la résolution de ce genre de souci. Donc si quelqu'un a une idée de solution ou de méthode pour dénicher le problème, je suis preneur !!

    edit: j'ai oublié de préciser que j'utilise Ruby 1.8.7 et Rails 2.3.8


    Olivier
    Mea est trop forte, elle flotte : mea coule pas !

    Basically this boot sector (Win95) code is 32 bit extension for a 16 bit patch to an 8 bit boot sector originally coded for a 4 bit microprocessor, written by a 2 bit company, that can't stand 1 bit of competition.

    olance.developpez.com
    Servez-vous, profitez, abusez de la FAQ Delphi !!

  2. #2
    Membre Expert
    Avatar de Bestiol
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    1 515
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2002
    Messages : 1 515
    Par défaut
    De l'aide reçue sur une mailing list semble avoir cerné le problème (il faut encore que je confirme).
    Il s'agirait d'un problème de rechargement de classe effectué par Rails, du fait de la config cache_classes=false en environnement de développement.

    La bonne nouvelle c'est que ce genre de problème ne se retrouve donc pas en production.
    La moins bonne, c'est que le ticket concernant ce problème a été ouvert fin 2008 lors du développements de Rails 2.2, et qu'aucun fix total n'a encore été produit.
    Il y a un peu de littérature pour ceux qui le souhaitent : https://rails.lighthouseapp.com/proj...4/tickets/1339

    Apparemment, quand les classes ne sont pas mises en cache (cache_classes=false, par défaut dans config/development.rb), il peut arriver que ActiveRecord::Base supprime toutes les méthodes des instances d'objets descendant d'AR::Base quand il pense qu'ils ne sont plus utilisés. Un genre de garbage collecting (?) destiné à corriger un memory leak, à la base.
    Mais si, comme dans mon cas, un objet est "détruit" de la sorte alors qu'il est toujours référencé quelque part, un accès à l'un de ses attributs/méthodes provoquera le plantage.

    Il y a quelques patchs proposés dans le ticket, mais bien qu'ils règlent le souci ils produisent apparemment des fuites mémoire en développement.
    Une autre solution serait de passer cache_classes à true dans development.rb, mais il faut alors redémarrer le serveur de dev à chaque modification de classe.

    Autre solution, que je tenterai ce soir, c'est de rajouter un middleware "ClassLoader" en développement, qui se charge de recharger certaines classes à chaque nouvelle requête si besoin : http://gist.github.com/439980
    Il faudra bien sûr adapter la méthode #call en fonction de l'application.

    Voilà voilà... en espérant que ça pourra éviter à d'autres les heures d'arrachages de cheveux que ça m'a donné !!
    Je confirmerai ce soir que le souci est bien "réglé" ainsi !

    Olivier
    Mea est trop forte, elle flotte : mea coule pas !

    Basically this boot sector (Win95) code is 32 bit extension for a 16 bit patch to an 8 bit boot sector originally coded for a 4 bit microprocessor, written by a 2 bit company, that can't stand 1 bit of competition.

    olance.developpez.com
    Servez-vous, profitez, abusez de la FAQ Delphi !!

  3. #3
    Membre expérimenté Avatar de rivsc
    Homme Profil pro
    Développeur Web
    Inscrit en
    Décembre 2008
    Messages
    213
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Décembre 2008
    Messages : 213
    Par défaut
    Ah ouai encore un problème à se tirer une balle ! Le debugger de netbeans (qui utilise le gem ruby-debug-ide) m'affiche souvent des objets nil alors qu'ils ne le sont pas, mais mon code s'execute comme il faut.

  4. #4
    Membre Expert
    Avatar de Bestiol
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    1 515
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2002
    Messages : 1 515
    Par défaut
    Hmm je ne suis pas sûr que ça soit lié, mais c'est possible ^^
    Car là dans mon cas l'objet n'est pas nil justement, mais toutes ses méthodes d'instance qui ne sont pas déclarées par AR::Base sont supprimées si j'ai bien compris.
    Ce qui fait que dans une méthode en particulier, un appel "include?" va être effectué sur un objet nil, en interne de l'objet.

    Après un peu plus de lecture tout à l'heure, il s'avère que ça vient en fait du plugin acts_as_bookmarkable dans mon cas précis : le plugin définit le modèle Bookmark, plutôt que de laisser le développeur le définir et y inclure les méthodes du plugin, comme c'est le cas avec acts_as_commentable.
    C'est plus pratique ainsi, mais du coup Rails ne considère pas utile de recharger ce modèle Bookmark à chaque requête, d'où le plantage dès le deuxième refresh, mais pas au premier !

    Vala vala
    (j'attends quand même de faire des vérifications non théoriques ce soir avant de passer en résolu)
    Mea est trop forte, elle flotte : mea coule pas !

    Basically this boot sector (Win95) code is 32 bit extension for a 16 bit patch to an 8 bit boot sector originally coded for a 4 bit microprocessor, written by a 2 bit company, that can't stand 1 bit of competition.

    olance.developpez.com
    Servez-vous, profitez, abusez de la FAQ Delphi !!

  5. #5
    Membre Expert
    Avatar de Bestiol
    Profil pro
    Inscrit en
    Mai 2002
    Messages
    1 515
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2002
    Messages : 1 515
    Par défaut
    Bonsoir !

    Je confirme que le problème est bien celui identifié.
    Côté solution, passer cache_classes à true en environnement de développement fonctionne, par contre je n'ai pas réussi à utiliser le code donné dans le gist...

    Au final, ma solution est la suivante : j'ai sorti la classe Bookmark du plugin pour la mettre à côté de Comment, avec mes autres modèles.
    Aucune autre modification à faire, Rails rechargera ce modèle comme les autres depuis ce répertoire.

    C'est simple sur ce coup-ci parce qu'il n'y a pas grand chose dans ce plugin... dans d'autres cas, il y a encore une autre solution que j'ai testée et qui fonctionne : rendre le plugin rechargeable à chaque requête.
    Pour cela, il y a deux choses à faire :
    1. Autoriser le rechargement de plugins. Pour cela ajouter dans config/environment.rb (attention, ça ne marchera pas dans development.rb) :
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      config.reload_plugins = true if RAILS_ENV == "development"
    2. Spécifier dans le plugin fautif, les symboles à recharger. Pour cela, ajouter dans le init.rb du plugin :
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      ActiveSupport::Dependencies.explicitly_unloadable_constants << "Bookmark" << "Et/ou nom de module du plugin"


    Redémarrer le serveur, et ça devrait rouler. De préférence, ne pas laisser ce méchanisme en place en production !

    Voilà, je crois qu'on a plus ou moins fait le tour de la question
    Encore une fois j'espère que ça pourra en aider d'autres !

    A bientôt
    Olivier
    Mea est trop forte, elle flotte : mea coule pas !

    Basically this boot sector (Win95) code is 32 bit extension for a 16 bit patch to an 8 bit boot sector originally coded for a 4 bit microprocessor, written by a 2 bit company, that can't stand 1 bit of competition.

    olance.developpez.com
    Servez-vous, profitez, abusez de la FAQ Delphi !!

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Construire un objet qui n est pas une chaine de caractères
    Par adel25 dans le forum Général JavaScript
    Réponses: 2
    Dernier message: 06/11/2013, 11h57
  2. Réponses: 3
    Dernier message: 22/10/2009, 18h30
  3. Réponses: 8
    Dernier message: 18/01/2007, 22h01
  4. Utlisation d'image pour les <li> qui ne marche pas
    Par Death83 dans le forum Balisage (X)HTML et validation W3C
    Réponses: 2
    Dernier message: 05/11/2005, 18h37

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo