2017-04-06 6 views
1

J'ai une méthode dans mon application CLI qui me donne une erreur.méthode non définie `attr 'pour nil: NilClass (NoMethodError)

La méthode est:

def self.deal_page(input, product_url) 
    self.open_deal_page(input) 
    deal = {} 
    html = open(@product_url) 
    doc = Nokogiri::HTML(html) 
    data = doc.text.strip 
    deal[:name] = doc.css("h1").text.strip 
    deal[:discription] = doc.css(".textDescription").text.strip 
    @purchase_link = nil 
    @purchase_link= doc.at_css("div.detailLeftColumn a.success").attr("href") 
     if @purchase_link.nil? 
     deal[:purchase] = @product_url 
     else 
     deal[:purchase] = @purchase_link 
     end 
    deal 
    end 

et l'erreur est:

/home/himachhag-45739/code/popular-deals-from-slickdeals.net-cli/lib/popular_deals/newdeals.rb:54:in `deal_page': undefined method `attr' for nil:NilClass (NoMethodError) 
     from /home/himachhag-45739/code/popular-deals-from-slickdeals.net-cli/lib/popular_deals/cli.rb:70:in `disply_deal'             
     from /home/himachhag-45739/code/popular-deals-from-slickdeals.net-cli/lib/popular_deals/cli.rb:49:in `menu'               
     from /home/himachhag-45739/code/popular-deals-from-slickdeals.net-cli/lib/popular_deals/cli.rb:9:in `call'               
     from /home/himachhag-45739/code/popular-deals-from-slickdeals.net-cli/bin/popular-deals:10:in `<top (required)>'             
     from /usr/local/rvm/gems/ruby-2.3.1/bin/popular-deals:22:in `load'                         
     from /usr/local/rvm/gems/ruby-2.3.1/bin/popular-deals:22:in `<main>'                        
     from /usr/local/rvm/gems/ruby-2.3.1/bin/ruby_executable_hooks:15:in `eval'                       
     from /usr/local/rvm/gems/ruby-2.3.1/bin/ruby_executable_hooks:15:in `<main>' 

J'ai essayé xpath, at_css, unless, if .. else, mais ne permet pas. De plus, je n'obtiens pas cette erreur à chaque fois, mais j'aimerais m'en débarrasser.

+1

Lorsque vous faites face à de tels problèmes, il est important de revenir en arrière et de voir ce qui a échoué. Il semble que 'doc.at_css (...)' n'ait rien trouvé. Une autre chose à noter est d'essayer de garder votre indentation cohérente. Cette clause «si» est poussée là-dedans comme si elle avait été déplacée. – tadman

+0

@tadman Merci beaucoup pour votre suggestion. Je suis d'accord avec vous qu'il semble que doc.at_css (...) n'a rien trouvé. Mais si j'essaye encore de chercher le même deal, ça me donne la sortie! que je ne comprends pas. Je ferai plus attention à l'indentation aussi. –

+0

Bienvenue sur SO. Veuillez lire "[mcve]" et la page liée. Vous devez fournir un code qui nous permet de confirmer le problème. Actuellement, nous ne pouvons pas faire cela parce que vous ne nous avez pas dit comment appeler votre méthode. –

Répondre

4

Une façon de résoudre ce problème est d'être un peu paranoïaque:

@purchase_link = doc.at_css("div.detailLeftColumn a.success").try(:attr, "href") 

deal[:purchase] = @purchase_link || @product_url 

Un couple de choses à garder à l'esprit ici. En Ruby seulementnil et false sont logiquement faux, il est donc très rare que vous avez besoin de tester spécifiquement nil? sur les choses. Le seul cas qui est nécessaire est quand vous voulez faire la distinction entre nil et false, qui, comme vous pouvez l'imaginer n'est pas très souvent.

Donc, dans ce cas, soit vous frappez quelque chose avec at_css ou vous ne l'avez pas fait, dans ce cas l'appel try ne fait rien. S'il trouve quelque chose, l'appel try continue pour un autre appel. Ensuite, vous pouvez faire une affectation avec un opérateur || (ou) simple pour les sélectionner en priorité.

Une autre chose est que puisque ce code est à l'intérieur d'une méthode de classe, occasionnellement l'utilisation de variables d'instance peut être un problème. Si des choses comme purchase_link ne sont utilisées que dans cette méthode, supprimez le @ qui les rend persistantes.

Une autre chose à faire attention à comment votre méthode est définie comme:

def self.deal_page(input, product_url) 

qui déclare un argument product_url, mais à l'intérieur:

html = open(@product_url) 

Cette référence à l'instance de classe variable de @product_url qui est pas le même. Vous appelez peut-être open avec la mauvaise variable ici.

+2

Notez que '.try()' est une méthode [Rails] (https://apidock.com/rails/v3.2.1/Object/try), pas une méthode Ruby de base. L '[opérateur de navigation sûr] (http://stackoverflow.com/questions/36812647/what-does-ampersand-dot-mean-in-ruby) serait l'équivalent Ruby avec 'doc.at_css() &. Attr() '. – anothermh

+0

2.3 a présenté l'opérateur de navigation sûr qui est en réalité beaucoup plus cohérent que 'try'. J'ai oublié que c'était Rails spécifique. – tadman

+1

@tadman Cette solution fonctionne! J'apprécie vraiment votre temps et vos suggestions. –

0

Comme votre trace de la pile montre cette ligne est à l'origine de l'erreur:

@purchase_link= doc.at_css("div.detailLeftColumn a.success").attr("href") 

méthode attr ne peut pas être appelé nulle. Voir si l'élément existe dans le HTML.

Vous pouvez déboguer en imprimant la valeur de

doc.at_css("div.detailLeftColumn a.success") 

Pour plus d'informations vous pouvez consulter http://www.nokogiri.org/tutorials/.

0

est ici où je pense que le problème est:

def self.deal_page(input, product_url) 
    ... 
    html = open(@product_url) 

Vous utilisez product_url comme paramètre, mais en essayant d'ouvrir @product_url.

open déclenche une erreur si @product_url est vide ou nul, donc @product_url doit être prédéfini quelque part, mais évidemment pas dans cette méthode. Je suppose que ce n'est pas la page que vous pensez, et, par conséquent, le sélecteur échoue.

Vous avez d'autres problèmes dans votre code:

deal[:name] = doc.css("h1").text.strip 
deal[:discription] = doc.css(".textDescription").text.strip 

Vous utilisez css, qui retourne une NodeSet

doc.css('h1').class # => Nokogiri::XML::NodeSet 

puis text qui concatène tout le texte du NodeSet, qui est presque toujours pas ce que vous voulez faire. Considérez ceci:

require 'nokogiri' 

doc = Nokogiri::HTML(DATA.read) 
doc.css('h1').text # => "foobar" 
doc.css('h1').map(&:text) # => ["foo", "bar"] 

__END__ 
<html> 
    <body> 
    <h1>foo</h1> 
    <h2>blah</h2> 
    <h1>bar</h1> 
    <h2>blah</h2> 
    </body> 
</html> 

doc.css('h1').text concaténés "foo" et "bar" entraînant "foobar". Une fois que vous avez cela, il est extrêmement difficile de démêler le chaos causé par la concaténation. Au lieu de cela, vous devez utiliser doc.css('h1').map(&:text), sauf dans rare situation où vous savez que le texte doit effectivement être concaténé. J'ai seulement rencontré cette situation ... oh ... jamais des fois.