Ruby // Nokogiri trying to store selectors for various objects in a hash

Solution 1:

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"}