6

I'm writing a simple script, that fetches the details of some packages from the debian website. I encounter a problem when dealing with virtual packages that ave no version no associated with them.

I get the following error message

undefined method `first' for nil:NilClass (NoMethodError)

The culprit line is

version = doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last.first

I tried to put it into an if conditional like this but that doesn't work.

if doc.css('#content h1').text 
         version = doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last.first
      end

So I'd like to know how I can check if the object is not nil and then try to extract the sub-string from it.

Here is the entire script with the unless block added

require 'rubygems'
require 'nokogiri'
require 'open-uri'


# additional code to make sure that we can resume after a break seamlessly
last_package = 0
File.open('lastbreak','r') { |fptr| last_package = fptr.gets.to_i }
puts "Resuming from package:#{last_package}" if last_package != 0

# to read each package from packageslist.txt and fetch the required info
# also to store this into a file that can easily be read by the c++ program
BASE_URL = "http://packages.debian.org/stable/"

File.open('packages_list.txt','r') do | fptr |
  while line = fptr.gets
    package_id = line.split[0].to_i
    package = line.split[1]
    dependencies = ""
    url = BASE_URL + package
    if package_id >= last_package
      doc = doc = Nokogiri::HTML(open(url))
      doc.css(".uldep a").each do |dependency|
        dependencies << dependency.text + ","
      end
      dependencies = dependencies.split(',').uniq.join(',')
      description = doc.css('#pdesc').text.strip
      version = ""
      unless doc.css('#content h1').nil?
          version = doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last.first
      end

      File.open("packages/#{package}","w") do |wfptr|
      wfptr.puts "PackageId:#{package_id}"
      wfptr.puts "Name:#{package}"
      wfptr.puts "Version:#{version}"
      wfptr.puts "Deps:#{dependencies}"
      end
      File.open("packages/#{package}.description",'w') {|wf| wf.write(description.capitalize)}

      package_id += 1
      puts "Now Processing #{package_id}"
      File.open('lastbreak','w') { |fptr| fptr.puts "#{package_id}" }
    end
  end
end

now the error message is

/Users/ccuser008/Documents/oops_project/repo/repobuilder.rb:30:in `block': undefined method `first' for nil:NilClass (NoMethodError)
    from /Users/ccuser008/Documents/oops_project/repo/repobuilder.rb:15:in `<main>'
nikhil
  • 8,925
  • 21
  • 62
  • 102

4 Answers4

19

Ruby 2.3.0 added a safe navigation operator (&.) that checks for nil before calling a method.

foo&.bar

It will return nil if foo is nil, rather than raising NoMethodError.

user513951
  • 12,445
  • 7
  • 65
  • 82
3

Depends what is being returned nil doc.css('#content h1') or doc.css('#content h1').text or doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last

Example -

unless doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last.nil?
    version = doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last.first
end

you can include the proper condition in the unless clause.

From your exception doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last seems to be nil. So you can check on this.

Jayendra
  • 52,349
  • 4
  • 80
  • 90
  • what did you check for nil ? check for doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last.nil? as this is the point it seems to be breaking. – Jayendra Oct 17 '11 at 08:18
  • 1
    a suggestion! if you're going to spend the CPU cycles doing the whole doc.css.text.strip.scan.last dance .. why not store the result in a variable so you can check for it's null and just apply first to that variable instead of doing the whole chained method call again? :) – Aditya Sanghi Oct 17 '11 at 08:36
2

Have you used try?

Something like

doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last.try(:first)

This will work if you're including active_support in your project or if you are working in a Rails app.

Lookup the syntax and documentation of the "try" method.

Updated:

x = doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last
version = x.first if !x.nil? # why bother.

Just FYI: I know this doesnt really apply to your question, but perhaps chaining methods this long without checks for null in between is probably coupling very very hard to the input's document structure. What if .css("#content h1").text is null?

Aditya Sanghi
  • 13,370
  • 2
  • 44
  • 50
-1

Use

try(method, *args, &block)

in your case:

doc.css('#content h1').text.strip.scan( /\(([^>]*)\)/).last.try(:first)

For more info see:

http://www.intridea.com/blog/2010/11/2/calling-methods-on-potential-nil-objects-in-rails

Denis Krivitski
  • 654
  • 8
  • 11