Home > Back-end >  Ruby // Nokogiri trying to store selectors for various objects in a hash
Ruby // Nokogiri trying to store selectors for various objects in a hash

Time:01-12

I am trying to store the selectors in hashes assigned to the appropriate topics which I will then scrape from a webpage. However, when I do so, I am met with an 'undefined method' error for the "css" method.

Example:

@@letters_hash = {
      "a" => {
         uppercase: "A",
         history: css('div.class_1').css('div.class_2').text,
         url: "www.alphabet.com"
      }
}

Is there a way to encapsulate this? Or, if I store it as a string, is there a way to remove the string and get it back to the methods?

Thank you for your time.

CodePudding user response:

css('div.class_1').css('div.class_2').text is self.css('div.class_1').css('div.class_2').text and self is your Database class. It doesn't have a css method. You need to call the method on something which has a css method like a Nokogiri node.

Callbacks

If you want to store a set of methods to call on some Nokogiri node you'll get later, you make a callback using a little anonymous function called a lambda.

@letters = {
      "a" => {
         uppercase: "A",
         history: ->(node) { node.css('div.class_1').css('div.class_2').text },
         url: "www.alphabet.com"
      }
}

That takes a node as an argument and calls the methods on the node.

Then later when you have a node you can call this function.

@letters_hash[letter][:history].call(node)

Objects

At this point it's getting compliated and should be encapsulated in an object.

class LetterTopic
  def initialize(letter)
    @letter = letter
  end

  def node_history(node)
    node.css('div.class_1').css('div.class_2').text
  end

  def uppercase
    @letter.upcase
  end

  def url
    "www.alphabet.com"
  end
end

letters = {
  "a" => LetterTopic.new("a")
}

node = ...get a Nokogiri node...

letters[letter].node_history(node)

A Note About Class Variables

@@letters_hash does not do what you think. Class variables in Ruby are shared by subclasses. If you subclass Database they will all share a single @@letters_hash variable.

class Database
  @@letters = {}

  def self.letters
    @@letters
  end
end

class Databasement < Database
end

Database.letters[:a] = 'database'
Databasement.letters[:a] = 'databasement'

p Database.letters     # {:a=>"databasement"}
p Databasement.letters # {:a=>"databasement"}

Instead, use Class Instance Variables. Like everything else in Ruby, the Database class is an object and can have its own instance variables.

class Database
  # Everything inside `class << self` works on the class object.
  class << self
    def letters
      @letters ||= {}
    end
  end
end

class Databasement < Database
end

Database.letters[:a] = 'database'
Databasement.letters[:a] = 'databasement'

p Database.letters       # {:a=>"database"}
p Databasement.letters   # {:a=>"databasement"}
  •  Tags:  
  • Related