Undefined Method for 'NilClass' - ruby

I know this is an oft' asked question, but I've been struggling to get past it despite research here for a number of hourds. I'm attempting to upload .xlsx documents to my AWS S3 Bucket, however I am receiving this error:
Error while uploading the file '' to the bucket '': undefined method `bucket' for nil:NilClass
However, I've reviewed my code and the error isn't obvious to me. I'll attach the code in question below.
attr_reader :year, :month, :data, :bucket_name, :file_name, :path
def initialize(year:, month:, data:)
#year = year
#month = month
#data = data
end
def call
REPORTS.each do |report|
data = generate_report_data(report)
tmp_path = report_tmp_path(report)
save_report_to_file(data: data, path: tmp_path)
upload_file_to_bucket?(bucket_name, file_name, path )
end
end
def generate_report_data(report)
report_class = "Accounting::Datev::Reports::#{report.to_s.camelize}".constantize
report_class.new(year: year, month: month).call
end
def save_report_to_file(data:, path:)
prepare_dir(path)
Accounting::Datev::Utils::ExcelGenerator.new(data: data, path: path).call
end
def s3_resource
#s3_resource ||= ::Aws::S3::Resource.new(access_key_id: Settings.aws_pair&.app_key_id,
secret_access_key: Settings.aws_pair&.app_secret_key,
region: Settings.aws_pair&.s3_region)
end
def BUCKET_NAME
#bucket_name = s3.bucket('textract-console-eu-central-1-3469d743-084a-020378550')
end
def upload_file_to_bucket?( file_name, path, bucket_name)
s3 = #s3_resource
obj = s3.bucket(bucket_name).object('/tmp/test.xls')
obj.upload_file(path)
true
rescue StandardError => e
puts "Error while uploading the file '#{file_name}' to the bucket '#{bucket_name}': #{e.message}"
end
def report_tmp_path(report)
"/tmp/reports/#{year}/#{month}/#{report}.xls"
end
def prepare_dir(path)
dir = File.dirname(path)
FileUtils.mkdir_p(dir)
end
end
end
end

Unfortunately, you didn't tell in which line the exception was raised and you didn't provide the full stack trace.
But at a first glance, there is this bucket call:
def BUCKET_NAME
#bucket_name = s3.bucket(...)
end
Here you are calling bucket on s3 but an s3 variable doesn't exist in your code nor an s3 method. Therefore all calls of that BUCKET_NAME method must fail.

Related

Boolean method not returning in different situations [RUBY]

I'm building a simple web-scraper (scraping jobs from indeed.com) for practice and I'm trying to implement the following method (low_salary?(salary)). The aim is for the method to compare a minimum (i.e. desired) salary, compare it with the offered salary contained in the job object (#salary):
class Job
attr_reader :title, :company, :location, :salary, :url
def initialize(title, company, location, salary, url)
#title = title
#company = company
#location = location
#salary = salary
#url = url
end
def low_salary?(minimum_salary)
return if !#salary
minimum_salary < #salary.split(/[^\d]/)[1..2].join.to_i
end
end
The method works fine when comapring #salary and the min_salary variable given to it, the delete_if appropriately deletes the elements that return true for low_salary? and returns correctly when #salary is nil (indeed listings don't always include the salary so my assumption is that there will be some nil values) in the following test program (Also: I am unsure as to why minimum_salary < #salary works but #salary < minimum_salary doesn't, but this does exactly what I want it to do):
require_relative('job_class.rb')
job = Job.new("designer", "company", "location", "£23,000 a year", "url")
job_results = []
job_results.push(job)
min_salary = 50000
print job.low_salary?(min_salary)
job_results.delete_if { |job| job.low_salary?(min_salary) }
print job_results
However in my scraper program, I get a no method error when calling the method: job_class.rb:16:in "low_salary?": undefined method `join' for nil:NilClass (NoMethodError)
require 'nokogiri'
require 'open-uri'
require_relative 'job_class.rb'
class JobSearchTool
def initialize(job_title, location, salary)
#document = Nokogiri::HTML(open("https://uk.indeed.com/jobs?q=#{job_title.gsub('-', '+')}&l=#{location}"))
#job_listings = #document.css('div.mosaic-provider-jobcards > a')
#salary = salary.to_i
#job_results = []
end
def scrape_jobs
#job_listings.each do |job_card|
#job_results.push(Job.new(
job_card.css('h2 > span').text, #title
job_card.css('span.companyName').text, #company
job_card.css('div.companyLocation').text, #location
job_card.css('span.salary-snippet').text, #salary
job_card['href']) #url
)
end
end
def format_jobs
#job_results.each do |job|
puts <<~JOB
#{job.title} - #{job.company} in #{job.location} :#{job.salary}
Apply at: #{job.url}
---------------------------------------------------------------------------------
JOB
end
end
def check_salary
#job_results.delete_if { |job| job.low_salary?(#salary) }
end
def run
scrape_jobs
check_salary
format_jobs
end
if __FILE__ == $0
job_search_tool = JobSearchTool.new(ARGV[0], ARGV[1], ARGV[2])
job_search_tool.run
end
Obviously something from the scraper programme is influencing the method somehow, but I can't understand what it could be. I'm using the method in the exact same way as the test program, so what difference is causing the method not to return when #salary is nil?
A quick search on the URL you're scraping shows there are job posts that don't have a salary, so, when you get the data from that HTML element and initialize a new Job object, the salary is an empty string, and knowing that "".split(/[^\d]/)[1..2] returns nil, that's the error you get.
You must add a way to handle job posts without a salary:
class Job
attr_reader :title, :company, :location, :salary, :url
def initialize(title, company, location, salary, url)
#title = title
#company = company
#location = location
#salary = salary.to_s # Explicit conversion of nil to string
#url = url
end
def low_salary?(minimum_salary)
return if parsed_salary.zero? # parsed_salary returns always an integer,
# so you can check when is zero,
# and not just when is falsy
minimum_salary < parsed_salary
end
private
def parsed_salary
salary[/(?<=£)(\d|,)*(?=\s)/]
.to_s # converts nil to "" if the regex doesn't capture anything
.tr(",", "") # removes the commas to parse the string as an integer
.to_i # parses the string to its corresponding integer representation
end
end
Notice the regex isn't meant to capture everything, but it works with the salary as rendered in the website.

Ruby why does rackup raise this undefined method error upon boot?

I have a class for writing recurring entries. The initializer verifies that the start date (#date_1) is after the end date (#date_2).
class Recurrent
include Format
attr_accessor :intitule, :categorie, :sens
attr_reader :mensualite, :date_1, :date_2
def initialize (user, date_1, date_2 = date_1.next_year)
#date_1 = date_1
#date_2 = date_2
#jour = Date.today
#user = user
#compte = #user.compte
# start date before end date ? :
inversees?
end
# cut for brievity
private
def inversees?
if #date_1 < #date_2
return true
else
raise StandardError.new("start date must be before end date.")
end
end
begin
inversees?(false)
rescue => error
puts error.message
end
end
when I start the server I get this error message :
[me#manjaro accountsapp]$ rackup
undefined method `inversees?' for Recurrent:Class
Although, when I provoke the error by entering wrong dates (and disabling the javascript test that I do before data is sent to the server) , I does work. Why does this error message appear?

"undefined method 'becomes' for nil:NilClass" where it shouldn't

I was testing a snippet today
unless resource.nil?
resource = resource.becomes(Accounts::Admin)
end
this raises an error
undefined method `becomes' for nil:NilClass
if I do this
unless resource.nil?
a = resource.becomes(Accounts::Admin)
resource = a
end
Everything goes right.
What is the difference if the right part of the =operator is executed first ?
EDIT :
Something nasty is happening, the last line under if false is being executed, but "ALOHA" is never printed.
<%
puts "AAAA #{resource.inspect}"
if false
puts "ALOHA"
# this line is being executed !
# if I comment it out the BBBB output is correct
resource = nil
end
puts "BBBB #{resource.inspect}"
%>
it prints
AAAA User id: nil, nome: nil, endereco_id: nil, created_at: nil,
updated_at: nil, email: ""
BBBB nil
but if I do this
<%
res = resource
puts "AAAA #{res.inspect}"
if false
puts "ALOHA"
res = nil
end
puts "BBBB #{res.inspect}"
%>
it print correctly
AAAA User id: nil, nome: nil, endereco_id: nil, created_at: nil,
updated_at: nil, email: ""
BBBB User id: nil, nome: nil,
endereco_id: nil, created_at: nil, updated_at: nil, email: ""
I already have tried to restart the server. This snippet is at devise/registrations/new.html.erb. The resourcevariable is supposed to be an instance of User, created by devise's RegistrationController
I have checked the text for hidden characters, the snippets I pasted here are the whole text for the file being tested.
This is the content of the controller at ~/.rvm/gems/ruby-2.3.3#mygem/gems/devise-4.2.0/app/controllers/devise/registrations_controller.rb
class Devise::RegistrationsController < DeviseController
prepend_before_action :require_no_authentication, only: [:new, :create, :cancel]
prepend_before_action :authenticate_scope!, only: [:edit, :update, :destroy]
prepend_before_action :set_minimum_password_length, only: [:new, :edit]
# GET /resource/sign_up
def new
build_resource({})
yield resource if block_given?
respond_with resource
end
# POST /resource
def create
build_resource(sign_up_params)
resource.save
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication?
set_flash_message! :notice, :signed_up
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}"
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
set_minimum_password_length
respond_with resource
end
end
# GET /resource/edit
def edit
render :edit
end
# PUT /resource
# We need to use a copy of the resource because we don't want to change
# the current user in place.
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
resource_updated = update_resource(resource, account_update_params)
yield resource if block_given?
if resource_updated
if is_flashing_format?
flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ?
:update_needs_confirmation : :updated
set_flash_message :notice, flash_key
end
bypass_sign_in resource, scope: resource_name
respond_with resource, location: after_update_path_for(resource)
else
clean_up_passwords resource
respond_with resource
end
end
# DELETE /resource
def destroy
resource.destroy
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
set_flash_message! :notice, :destroyed
yield resource if block_given?
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
def cancel
expire_data_after_sign_in!
redirect_to new_registration_path(resource_name)
end
protected
def update_needs_confirmation?(resource, previous)
resource.respond_to?(:pending_reconfirmation?) &&
resource.pending_reconfirmation? &&
previous != resource.unconfirmed_email
end
# By default we want to require a password checks on update.
# You can overwrite this method in your own RegistrationsController.
def update_resource(resource, params)
resource.update_with_password(params)
end
# Build a devise resource passing in the session. Useful to move
# temporary session data to the newly created user.
def build_resource(hash=nil)
self.resource = resource_class.new_with_session(hash || {}, session)
end
# Signs in a user on sign up. You can overwrite this method in your own
# RegistrationsController.
def sign_up(resource_name, resource)
sign_in(resource_name, resource)
end
# The path used after sign up. You need to overwrite this method
# in your own RegistrationsController.
def after_sign_up_path_for(resource)
after_sign_in_path_for(resource)
end
# The path used after sign up for inactive accounts. You need to overwrite
# this method in your own RegistrationsController.
def after_inactive_sign_up_path_for(resource)
scope = Devise::Mapping.find_scope!(resource)
router_name = Devise.mappings[scope].router_name
context = router_name ? send(router_name) : self
context.respond_to?(:root_path) ? context.root_path : "/"
end
# The default url to be used after updating a resource. You need to overwrite
# this method in your own RegistrationsController.
def after_update_path_for(resource)
signed_in_root_path(resource)
end
# Authenticates the current scope and gets the current resource from the session.
def authenticate_scope!
send(:"authenticate_#{resource_name}!", force: true)
self.resource = send(:"current_#{resource_name}")
end
def sign_up_params
devise_parameter_sanitizer.sanitize(:sign_up)
end
def account_update_params
devise_parameter_sanitizer.sanitize(:account_update)
end
def translation_scope
'devise.registrations'
end
end
ruby-2.3.3
rails (4.2.7.1)
devise (4.2.0)
Look at this ruby snippet:
if true
foo = "hello"
end
puts foo
#=> hello
And this one:
if false
foo = "hello"
end
puts foo
#=> nil
In many languages, if-statements have their own scope, but in ruby they share the scope of the surrounding function. This means variables declared inside if-statements are accessible outside of if-statements.
The issue here is that variables are declared at compile-time, before ruby knows whether the if-statement is true or false. So in ruby, all local variables are declared and initialized as nil, even if they're in a conditional statement.
This code:
unless resource.nil?
resource = resource.becomes(Accounts::Admin)
end
Causes problems because of another rule in ruby which gives local variables priority over methods. So when you say resource = resource you're actually calling the method resource and saving its value to the local variable resource, which then overshadows the method by the same name.
Ultimately you're getting the error:
undefined method `becomes' for nil:NilClass
because at compile-time, the local variable resource is being created, overshadowing the method. Then, at run-time, the condition is being executed because resource is not yet nil. But at the line where you create the local variable, it instantly comes into scope, making resource = nil and causing this the error.
The error can be reproduced in this generalized example:
def blah
"foo"
end
unless blah.nil?
blah = blah.size
end
puts blah
And the fix for it is to specify the method itself:
def blah
"foo"
end
def blah= value
#do nothing
end
unless blah.nil?
self.blah = blah.size
end
puts blah
That being said, I'm not sure whether devise actually implements resource=(). If it doesn't, then your best solution is the one you already came up with- use a local variable:
unless resource.nil?
res = resource.becomes(Accounts::Admin)
end
puts res
After doing some research, I found that local variables in ruby are defined from top to bottom and left to right, based on position in the file, not their position in program flow. For example:
if x="foo"
puts x
end
#=> "foo"
puts y if y="foo"
#NameError: undefined variable or method 'y'
This is part of the ruby spec, according to matz.
If using ruby Ruby 2.3.0 and above use Safe Navigation Operator (&.)
resource = resource&.becomes(Accounts::Admin)
Try this in IRb, it should be fine:
a = 'hello'
unless a.nil?
a = a.upcase
end
a
# => "HELLO"
The second example is equivalent to the first, the expression on the right side of the = is assigned to the var on the left.
Maybe some hidden characters or a mis-spelling?
Are you sure the stacktrace points to that line, are you using becomes elsewhere in the file?

API integration error HTTParty

I'm learning how to work with HTTParty and API and I'm having an issue with my code.
Users/admin/.rbenv/versions/2.0.0-p481/lib/ruby/2.0.0/uri/generic.rb:214:in `initialize': the scheme http does not accept registry part: :80 (or bad hostname?)
I've tried using debug_output STDOUT both as an argument to my method and after including HTTParty to have a clue but with no success. Nothing gets displayed:
require 'httparty'
class LolObserver
include HTTParty
default_timeout(1) #timeout after 1 second
attr_reader :api_key, :playerid
attr_accessor :region
def initialize(region,playerid,apikey)
#region = region_server(region)
#playerid = playerid
#api_key = apikey
end
def region_server(region)
case region
when "euw"
self.class.base_uri "https://euw.api.pvp.net"
self.region = "EUW1"
when "na"
self.class.base_uri "https://na.api.pvp.net"
self.region = "NA1"
end
end
def handle_timeouts
begin
yield
#Timeout::Error, is raised if a chunk of the response cannot be read within the read_timeout.
#Timeout::Error, is raised if a connection cannot be created within the open_timeout.
rescue Net::OpenTimeout, Net::ReadTimeout
#todo
end
end
def base_path
"/observer-mode/rest/consumer/getSpectatorGameInfo"
end
def current_game_info
handle_timeouts do
url = "#{ base_path }/#{region}/#{playerid}?api_key=#{api_key}"
puts '------------------------------'
puts url
HTTParty.get(url,:debug_output => $stdout)
end
end
end
I verified my URL which is fine so I'm lost as to where the problem is coming from.
I tested with a static base_uri and it doesn't change anything.
The odd thing is when I do:
HTTParty.get("https://euw.api.pvp.net/observer-mode/rest/consumer/getSpectatorGameInfo/EUW1/randomid?api_key=myapikey")
Everything is working fine and I'm getting a response.
HTTParty doesn't seem to like the way you set your base_uri.
Unless you need it to be like that just add another attr_reader called domain and it will work.
require 'httparty'
class LolObserver
include HTTParty
default_timeout(1) #timeout after 1 second
attr_reader :api_key, :playerid, :domain
attr_accessor :region
def initialize(region,playerid,apikey)
#region = region_server(region)
#playerid = playerid
#api_key = apikey
end
def region_server(region)
case region
when "euw"
#domain = "https://euw.api.pvp.net"
self.region = "EUW1"
when "na"
#domain = "https://na.api.pvp.net"
self.region = "NA1"
end
end
def handle_timeouts
begin
yield
#Timeout::Error, is raised if a chunk of the response cannot be read within the read_timeout.
#Timeout::Error, is raised if a connection cannot be created within the open_timeout.
rescue Net::OpenTimeout, Net::ReadTimeout
#todo
end
end
def base_path
"/observer-mode/rest/consumer/getSpectatorGameInfo"
end
def current_game_info
handle_timeouts do
url = "#{domain}/#{ base_path }/#{region}/#{playerid}?api_key=#{api_key}"
puts '------------------------------'
puts url
HTTParty.get(url,:debug_output => $stdout)
end
end
end

Ruby: Chatterbot can't load bot data

I'm picking up ruby language and get stuck at playing with the chatterbot i have developed. Similar issue has been asked here Click here , I did what they suggested to change the rescue in order to see the full message.But it doesn't seem right, I was running basic_client.rb at rubybot directory and fred.bot is also generated at that directory . Please see the error message below: Your help very be very much appreciated.
Snailwalkers-MacBook-Pro:~ snailwalker$ cd rubybot
Snailwalkers-MacBook-Pro:rubybot snailwalker$ ruby basic_client.rb
/Users/snailwalker/rubybot/bot.rb:12:in `rescue in initialize': Can't load bot data because: No such file or directory - bot_data (RuntimeError)
from /Users/snailwalker/rubybot/bot.rb:9:in `initialize'
from basic_client.rb:3:in `new'
from basic_client.rb:3:in `<main>'
basic_client.rb
require_relative 'bot.rb'
bot = Bot.new(:name => 'Fred', :data_file => 'fred.bot')
puts bot.greeting
while input = gets and input.chomp != 'end'
puts '>> ' + bot.response_to(input)
end
puts bot.farewell
bot.rb:
require 'yaml'
require './wordplay'
class Bot
attr_reader :name
def initialize(options)
#name = options[:name] || "Unnamed Bot"
begin
#data = YAML.load(File.read('bot_data'))
rescue => e
raise "Can't load bot data because: #{e}"
end
end
def greeting
random_response :greeting
end
def farewell
random_response :farewell
end
def response_to(input)
prepared_input = preprocess(input).downcase
sentence = best_sentence(prepared_input)
reversed_sentence = WordPlay.switch_pronouns(sentence)
responses = possible_responses(sentence)
responses[rand(responses.length)]
end
private
def possible_responses(sentence)
responses = []
#data[:responses].keys.each do |pattern|
next unless pattern.is_a?(String)
if sentence.match('\b' + pattern.gsub(/\*/, '') + '\b')
if pattern.include?('*')
responses << #data[:responses][pattern].collect do |phrase|
matching_section = sentence.sub(/^.*#{pattern}\s+/, '')
phrase.sub('*', WordPlay.switch_pronouns(matching_section))
end
else
responses << #data[:responses][pattern]
end
end
end
responses << #data[:responses][:default] if responses.empty?
responses.flatten
end
def preprocess(input)
perform_substitutions input
end
def perform_substitutions(input)
#data[:presubs].each {|s| input.gsub!(s[0], s[1])}
input
end
# select best_sentence by looking at longest sentence
def best_sentence(input)
hot_words = #data[:responses].keys.select do |k|
k.class == String && k =~ /^\w+$/
end
WordPlay.best_sentence(input.sentences, hot_words)
end
def random_response(key)
random_index = rand(#data[:responses][key].length)
#data[:responses][key][random_index].gsub(/\[name\]/, #name)
end
end
I'm assuming that you are trying to load the :data_file passed into Bot.new, but right now you are statically loading a bot_data file everytime. You never mentioned about bot_data in the question. So if I'm right it should be like this :
#data = YAML.load(File.read(options[:data_file]))
Instead of :
#data = YAML.load(File.read('bot_data'))

Resources