Search code examples
ruby-on-railsrubyruby-on-rails-4mechanizemechanize-ruby

Mechanize in Module, Nameerror ' agent'


Looking for advice on how to fix this error and refactor this code to improve it.

require 'mechanize'
require 'pry'
require 'pp'




module Mymodule
  class WebBot 

    agent = Mechanize.new { |agent| 
        agent.user_agent_alias = 'Windows Chrome'
    }

    def form(response)
      require "addressable/uri"
      require "addressable/template"
      template = Addressable::Template.new("http://www.domain.com/{?query*}")
      url = template.expand({"query" => response}).to_s
      page = agent.get(url)  
    end 

    def get_products
      products = []
      page.search("datatable").search('tr').each do |row|
        begin
          product =  row.search('td')[1].text
        rescue => e
            p e.message
        end
        products << product
      end  
      products
    end  
  end 
end

Calling the module:

response = {size: "SM", color: "BLUE"}

t = Mymodule::WebBot.new
t.form(response)
t.get_products

Error:

NameError: undefined local variable or method `agent'

Solution

  • Ruby has a naming convention. agent is a local variable in the class scope. To make it visible to other methods you should make it a class variable by naming it @@agent, and it'll be shared among all the objects of WebBot. The preferred way though is to make it an instance variable by naming it @agent. Every object of WebBot will have its own @agent. But you should put it in initialize, initialize will be invoked when you create a new object with new

    class WebBot
      def initialize
        @agent = Mechanize.new do |a|
          a.user_agent_alias = 'Windows Chrome'
        end
      end
    .....
    

    And the same error will occur to page. You defined it in form as a local variable. When form finishes execution, it'll be deleted. You should make it an instance variable. Fortunately, you don't have to put it in initialize. You can define it here in form. And the object will have its own @page after invoking form. Do this in form:

    def form(response)
      require "addressable/uri"
      require "addressable/template"
      template = Addressable::Template.new("http://www.domain.com/{?query*}")
      url = template.expand({"query" => response}).to_s
      @page = agent.get(url)  
    end 
    

    And remember to change every occurrence of page and agent to @page and @agent. In your get_products for example:

    def get_products
      products = []
      @page.search("datatable").search('tr').each do |row|
      .....
    

    These changes will resolve the name errors. Refactoring is another story btw.