How do I handle the wrong number of method arguments? - ruby

I'm working on a URL shortener and attemtping to convert the URL ID, which is a number, into a string, using base 36.
I'm receiving the error listed below the code:
def self.create_link(original)
url = Url.create(:original => original)
if Link.first(:indentifier => url.id.to_s(36)).nil? or !DIRTY_WORDS.include? url.id.to_s(36)
link = Link.new(:identifier => url.id.to_s(36))
link.url = url
link.save
return link
else
create_link(original)
end
end
I'm receiving the following error:
wrong number of arguments(1 for 0) file: tinyclone.rb location: to_s line: 91
When I researched the error, I found someone who mentioned that this error is common when you attempt to pass in parameter values when a method doesn't accept them. The error is specifically referring the following line.
if Link.first(:indentifier => url.id.to_s(36)).nil? or !DIRTY_WORDS.include? url.id.to_s(36)

What's the type of url.id?
I think your expecting it to be a FixNum whose to_s method accepts a radix, but you're getting something else instead... maybe a string containing a number? (e.g. "1234")
Anyway, the method seems to require no arguments and you are passing 36 nevertheless
EDIT:
Can't find the reference to the class you pointed out (Serial), but this might be worth a try:
url.id.to_i.to_s(36)

One thing I see right away is:
if Link.first(:indentifier => url.id.to_s(36)).nil? or !DIRTY_WORDS.include? url.id.to_s(36)
link = Link.new(:identifier => url.id.to_s(36))
Notice that in the first line you have :indentifier and in the second it's :identifier.
Otherwise, I agree with #Pablo Fernandez's answer that it's probably tied to the type of id.

you have 2 models, but take full responsibility on the one of them only. please take a look at code separated logic:
# Link model
def self.create_link(original)
url = Url.create(:original => original)
url_id = url.encoded_id
find_or_create_by_identifier!(:identifier => url_id)
end
# Url model
def before_validate_on_create
if url.id.to_s.include? DIRTY_WORDS
self.errors.add(:base, 'the url is invalid')
end
end
def encoded_id
url.id.to_s(36)
end

Related

How do I tack a string onto a variable and evaluated the entire thing as a variable in Ruby?

I have the following Ruby code:
module BigTime
FOO1_MONEY_PIT = 500
FOO2_MONEY_PIT = 501
class LoseMoney
##SiteName = 'FOO1'
#site_num = ##SiteName_MONEY_PIT
def other_unimportant_stuff
whatever
end
end
end
So, what I'm trying to do here is set the SiteName and then use SiteName and combine it with the string _MONEY_PIT so I can access FOO1_MONEY_PIT and store its contents (500 in this case) in #site_num. Of course, the above code doesn't work, but there must be a way I can do this?
Thanks!!
If you want to dynamically get the value of a constant, you can use Module#const_get:
module BigTime
FOO1_MONEY_PIT = 500
FOO2_MONEY_PIT = 501
class LoseMoney
##SiteName = 'FOO1'
#site_num = BigTime.const_get(:"#{##SiteName}_MONEY_PIT")
end
end
Do not, under any circumstance, use Kernel#eval for this. Kernel#eval is extremely dangerous in any context where there is even the slightest possibility that an attacker may be able to control parts of the argument.
For example, if a user can choose the name of the site, and they name their site require 'fileutils'; FileUtils.rm_rf('/'), then Ruby will happily evaluate that code, just like you told it to!
Kernel#eval is very dangerous and you should not get into the habit of just throwing an eval at a problem. It is a very specialized tool that should only be employed when there is no other option (spoiler alert: there almost always is another option), and only after a thorough security review.
Please note that dynamically constructing variable names is already a code smell by itself, regardless of whether you use eval or not. It pretty much always points to a design flaw somewhere. In general, you can almost guaranteed replace the multiple variables with a data structure. E.g. in this case something like this:
module BigTime
MONEY_PITS = {
'FOO1' => 500,
'FOO2' => 501,
}.freeze
class LoseMoney
##SiteName = 'FOO1'
#site_num = MONEY_PITS[##SiteName]
end
end
You can refactor this as to use a Hash for your name lookups, and a getter method to retrieve it for easy testing/validation. For example:
module BigTime
MONEY_PITS = { FOO1: 500, FOO2: 501 }
MONEY_PIT_SUFFIX = '_MONEY_PIT'
class LoseMoney
##site = :FOO1
def initialize
site_name
end
def site_name
#site_name ||= '%d%s' % [MONEY_PITS[##site], MONEY_PIT_SUFFIX]
end
end
end
BigTime::LoseMoney.new.site_name
#=> "500_MONEY_PIT"

Can you pass a block of code that returns an error to a method?

I often find myself dealing with these kind of scenarios:
require 'nokogiri'
require "open-uri"
url = "https://www.random_website.com/contains_info_I_want_to_parse"
nokodoc = Nokogiri::HTML(open(url))
# Let's say one of the following line breaks the ruby script
# because the element I'm searching doesn't contain an attribute.
a = nokodoc.search('#element-1').attribute('href').text
b = nokodoc.search('#element-2').attribute('href').text.gsub("a", "A")
c = nokodoc.search('#element-3 h1').attribute('style').text.strip
What happens is that I'll be creating about 30 variables all searching for different elements in a page, and I'll be looping that code over multiple pages. However, a few of these pages may have an ever-so-slightly different layout and won't have one of those div. This will break my code (because you can't call .attribute or .gsub on nil for example). But I can never guess which line before-hand.
My go-to solution is usually surround each line with:
begin
line #n
rescue
puts "line #n caused an error"
end
I'd like to be able to do something like:
url = "https://www.random_website.com/contains_info_I_want_to_parse"
nokodoc = Nokogiri::HTML(open(url))
catch_error(a, nokodoc.search('#element-1').attribute('href').text)
catch_error(b, nokodoc.search('#element-2').attribute('href').text.gsub("a", "A"))
catch_error(c, nokodoc.search('#element-3 h1').attribute('style').text.strip)
def catch_error(variable_name, code)
begin
variable_name = code
rescue
puts "Code in #{variable_name} caused an error"
end
variable_name
end
I know that putting & before each new method works:
nokodoc.search('#element-1')&.attribute('href')&.text
But I want to be able to display the error with a 'puts' in my terminal to see when my code gives an error.
Is it possible?
You can't pass your code as a regular argument to a method because it'll be evaluated (and raise an exception) before it gets passed to your catch_error method. You could pass it as a block--something like
a = catch_error('element_1 href text') do
nokodoc.search('#element-1').attribute('href').text
end
def catch_error(error_description)
yield
rescue
puts "#{error_description} caused an error"
end
Note that you can't pass a to the method as variable_name: it hasn't been defined anywhere before calling that method, so you'll get an undefined local variable or method error. Even if you define a earlier, it won't work correctly. If your code works without raising an exception, the method will return the right value but the value won't get stored anywhere outside the method scope. If there is an exception, variable_name will have whatever value a had before the method (nil if you defined it without setting it), so your error message would output something like Code in caused an error. That's why I added an error_description parameter.
You could also try logging the message and backtrace if you didn't want to have to specify an error description every time.
a = catch_error(nokodoc) do |doc|
doc.search('#element-1').attribute('href').text
end
def catch_error(doc)
yield doc
rescue => ex
puts doc.title # Or something else that identifies the document
puts ex.message
puts ex.backtrace.join("\n")
end
I made one additional change here: passing the document in as a parameter so that rescue could easily log something that identifies the document, in case that's important.

How do I call a function in Ruby?

I'm trying to call but I keep getting an error. This is my code:
require 'rubygems'
require 'net/http'
require 'uri'
require 'json'
class AlchemyAPI
#Setup the endpoints
##ENDPOINTS = {}
##ENDPOINTS['taxonomy'] = {}
##ENDPOINTS['taxonomy']['url'] = '/url/URLGetRankedTaxonomy'
##ENDPOINTS['taxonomy']['text'] = '/text/TextGetRankedTaxonomy'
##ENDPOINTS['taxonomy']['html'] = '/html/HTMLGetRankedTaxonomy'
##BASE_URL = 'http://access.alchemyapi.com/calls'
def initialize()
begin
key = File.read('C:\Users\KVadher\Desktop\api_key.txt')
key.strip!
if key.empty?
#The key file should't be blank
puts 'The api_key.txt file appears to be blank, please copy/paste your API key in the file: api_key.txt'
puts 'If you do not have an API Key from AlchemyAPI please register for one at: http://www.alchemyapi.com/api/register.html'
Process.exit(1)
end
if key.length != 40
#Keys should be exactly 40 characters long
puts 'It appears that the key in api_key.txt is invalid. Please make sure the file only includes the API key, and it is the correct one.'
Process.exit(1)
end
#apiKey = key
rescue => err
#The file doesn't exist, so show the message and create the file.
puts 'API Key not found! Please copy/paste your API key into the file: api_key.txt'
puts 'If you do not have an API Key from AlchemyAPI please register for one at: http://www.alchemyapi.com/api/register.html'
#create a blank file to hold the key
File.open("api_key.txt", "w") {}
Process.exit(1)
end
end
# Categorizes the text for a URL, text or HTML.
# For an overview, please refer to: http://www.alchemyapi.com/products/features/text-categorization/
# For the docs, please refer to: http://www.alchemyapi.com/api/taxonomy/
#
# INPUT:
# flavor -> which version of the call, i.e. url, text or html.
# data -> the data to analyze, either the the url, text or html code.
# options -> various parameters that can be used to adjust how the API works, see below for more info on the available options.
#
# Available Options:
# showSourceText -> 0: disabled (default), 1: enabled.
#
# OUTPUT:
# The response, already converted from JSON to a Ruby object.
#
def taxonomy(flavor, data, options = {})
unless ##ENDPOINTS['taxonomy'].key?(flavor)
return { 'status'=>'ERROR', 'statusInfo'=>'Taxonomy info for ' + flavor + ' not available' }
end
#Add the URL encoded data to the options and analyze
options[flavor] = data
return analyze(##ENDPOINTS['taxonomy'][flavor], options)
print
end
**taxonomy(text,"trees",1)**
end
In ** ** I have entered my call. Am I doing something incorrect. The error I receive is:
C:/Users/KVadher/Desktop/testrub:139:in `<class:AlchemyAPI>': undefined local variable or method `text' for AlchemyAPI:Class (NameError)
from C:/Users/KVadher/Desktop/testrub:6:in `<main>'
I feel as though I'm calling as normal and that there is something wrong with the api code itself? Although I may be wrong.
Yes, as jon snow says, the function (method) call must be outside of the class. The methods are defined along with the class.
Also, Options should be a Hash, not a number, as you call options[flavor] = data, which is going to cause you another problem.
I believe maybe you meant to put text in quotes, as that is one of your flavors.
Furthermore, because you declared a class, this is called an instance method, and you must make an instance of the class to use this:
my_instance = AlchemyAPI.new
my_taxonomy = my_instance.taxonomy("text", "trees")
That's enough to get it to work, it seems like you have a ways to go to get this all working though. Good luck!

Twitter API - Ruby Twitter Gem

How can I access Twitter::Cursor hash values returned by the Twitter API?
I am following the Jumpstartlab Microblogger tutorial for using the Twitter gem via the jumpstart_auth gem.
I am on iteration 4 step 1. I can return a friends object with the following code:
def friends_last_tweets
friends = client.friends
puts friends
end
=> Twitter::Cursor:0x00000104051928
However, the example account, 'client' in this case, has two 'friends' not just one so why does it only return one object? I thought maybe that object is the array or arrays with all of the friends accordingly in hash values within, thus use [] to access, but this returns "undefined method for Twitter::Cursor". I run each on the Twitter::Cursor object and it returns two fixnums:
def friends_last_tweets
friends = client.friends
friends.each { |f| puts f }
end
=> 18908095
108528349
So surely these numbers must represent each 'friend object' within the Twitter::Cursor object me thinks. I need to access the key/value pairs within that object, yet my attempted hash accessing results in undefined method or variable.
In case it's version issue related, I'm using Twitter5.11.0 and Jumpstart_auth 0.6.0.
those answers didn't helped me to get the last message (maybe the API changed in the meantime), that's how I finally did it:
def everyones_last_tweet
puts "\n\n here are the latest tweets of your friends:"
friends = #client.friends.collect { |f| #client.user(f) }
friends.each do |friend|
puts "\n\n#{friend.screen_name} wrote: \n\t #{friend.status.text}"
end
return ""
end
I'm not happy with that return string though
Access the 'friends' object in the same way you accessed the 'followers' object earlier in the tutorial in order to get a list of your followers' screen names.
To get an array of followers' screen names:
screen_names = #client.followers.collect {|f| #client.user(f).screen_name }
To get an array of friends' screen names:
screen_names = #client.friends.collect {|f| #client.user(f).screen_name }
To get the last tweet of a friend, you can use the object_id's you posted above, as:
last_tweet = #client.user(object_id).status.tweet
I hope this helps. I was caught on this issue for a while too.

Ruby parsing XML: no implicit conversion of String into Integer

I'm working on a Sinatra application that pulls in a list of dates through an XML file and then creates an hash of all the dates.
I'm running into a strange issue that's happening when I'm pulling the id and assigning it to a variable.
The error I'm getting is:
no implicit conversion of String into Integer and it's being thrown on the event_date_id = event_date["date_id"] line. I have almost identical method in my code and it's working just fine. When I puts event_date['date_id'] it gives me the correct numerical date_id.
Just in case it helps, the class of the event_date['date_id'] is REXMLUtiliyNodeString, same as the id field in the other method. If I try to event_date['date_id'].to_i it gives breaks at that point.
def get_dates(event_id)
url = "some_url_to_some_xml"
puts '==================='
puts "Pulling in #{url}"
puts '==================='
date_xml = Crack::XML.parse(open(url))
dates = {}
date_xml['document']['date'].each do | event_date |
event_date_id = event_date['date_id']
single_date = {
'date_id' => event_date_id,
'date_start' => event_date['datestart'],
'date_end' => event_date['dateend'],
'date_live' => event_date['live'],
'time_start' => event_date['timestart'],
'time_end' => event_date['timestart'],
'date_available' => event_date['date_available']
}
dates.merge!( event_date_id => single_date )
end
return dates
end
Here is the xml format:
<document>
<date>
<date_id>881908</date_id>
<live>y</live>
<datestart>2017-08-14</datestart>
<dateend>2017-08-15</dateend>
<timestart>13:00</timestart>
<timeend>0:00</timeend>
<date_available>10000</date_available>
</date>
<document>
I have a feeling this is something really simple but I'm wracking my brains trying to figure it out. If anyone could shed some light on this, I'd definitely appreciate it.
Edit 1: When I run the code in irb, it does indeed work without error. Somewhere, somehow it seems Sinatra, Rack or Shotgun are getting in the way.
I have found the cause of my issue. It only occurs when I'm parsing an xml file with one entry for date/event whatever.
This question explains the exact issue I was having and the answer includes a work around that worked for me.
if(!date_xml['document']['date'].is_a?(Array))
date_xml['document']['date'] = [ date_xml['document']['date'] ]
end
This message occurs when you try to use a string index to look up a value in an array.
2.0.0p353 :001 > results = [""]
=> [""]
2.0.0p353 :002 > results["x"]
TypeError: no implicit conversion of String into Integer
from (irb):2:in `[]'
from (irb):2
from /home/jeff/.rvm/rubies/ruby-2.0.0-p353/bin/irb:12:in `<main>'
Since arrays can only be accessed by integer indexes, Ruby attempts to transform your key name into an integer, and fails because it doesn't know what number should be used to represent arbitrary string data. Hence the message "no implicit conversion of String into Integer".
If you're experiencing this, the answer is to fix your code so that it doesn't try to access an array like a hash. If you're importing from XML or JSON data on the assumption that the key desired is always there and will always be imported, but you're still getting this, your assumption is wrong; the data is not formatted as expected. Either fix the data or fix the code to handle the differing format.
I randomly stumbled upon what may be a better answer to this question, although I am very inexperienced so I think it needs to be verified.
I had an identical issue and saw that much of the trouble seems to be because I was returning a somewhat confusing array of one object.
Once I added ".first" to my query, I was able to retrieve my intended attribute.
response = File.open('ncaa_bb_schedule.xml')
doc = Nokogiri::XML(response)
doc.remove_namespaces!
doc.xpath('//game').each do |game|
h = game.xpath('home').first
p h['id']
end
But my original query, shown here
response = File.open('ncaa_bb_schedule.xml')
doc = Nokogiri::XML(response)
doc.remove_namespaces!
doc.xpath('//game').each do |game|
h = game.xpath('home')
p h['id']
end
end
was giving me the same error: "TypeError: no implicit conversion of String into Integer." Hope that helps somebody as it is much shorter than the aforementioned workaround.

Resources