NoMethodError when using string.scan under Ruby [closed] - ruby

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
I'm writing a little script with Ruby that takes twitter posts and breaks them up even further. When I send a normal string (non-twitter) against it, it works fine and split strings that are bigger than 32 characters. But for some reason I get an error like this when using the twitter data:
in chunk': private methodscan' called for ["twituser: foo. #yolo"]:Array (NoMethodError)
require 'rubygems'
require 'twitter'
Twitter.configure do |config|
config.consumer_key = ''
config.consumer_secret = ''
config.oauth_token = ''
config.oauth_token_secret = ''
#config.auth_method = :oauth
end
def chunk(string,size)
string.scan(/.{1,#{size}}/)
end
twitterfeed = Twitter.search("#yolo", :count => 1, :result_type => "recent").results.map do |status|
"#{status.from_user}: #{status.text}"
end
twitterfeed.join
puts "#{twitterfeed}\n"
sendchunks = chunk(twitterfeed,32)
sendchunks.each do |string|
puts "\x52\x31\x10\xAC\x4E\x31\x31\x32\x33\x34\x35\x36\x35\x41\x0E" + string + "\x0D\x0A"
puts "#{string}\n"
sleep(10)
end
puts "done\n"
So why the difference? I'm new to ruby so I'm trying to get my head around the difference in how variable types work. A normal string works but even after array to string conversion it horks.
Thanks!
[Edited with the full code of script as the answers are getting off track causing things to burst]
Solution was to do the following:
twitterfeed_string = twitterfeed.join
puts "#{twitterfeed_string}\n"
sendchunks = chunk(twitterfeed_string,32)

The error says private methodscan' called for #Array. This is because you are not storing the result of twitterfeed.join('').to_s anywhere. You are then passing twitterfeed as an array to your chunk method.
You are not overriding twitterfeed.
You probably want to do
twitterfeed_as_string = twitterfeed.join
sendchunks = chunk(twitterfeed_as_string , 32)

Related

Print editable to console in Ruby [duplicate]

This question already has answers here:
What will give me something like ruby readline with a default value?
(6 answers)
Closed 6 years ago.
Let's say I have the following code in Ruby:
print("Enter a filename:")
editableprint("untitled.txt")
filename = gets.chomp!
What would be the function "editableprint" so that "untitled.txt" is part of the input of the user for the gets function? (thus the user can edit the "untitled.txt" string or simply leave it as is")
There are similar questions here and here
However, the solutions there don't seem to work as expected, so it looks this is ruby version or platform dependent?
For example, this does not work for me, but also does not throw an error.
require "readline"
filename = Readline.insert_text("untitled.txt").readline("Enter a filename:")
print filename
But since it looks much better, and should work according to the documentation for ruby >= 2, I am leaving it there for now.
The following works on my system (ruby 2.3.1, OS X)
require "readline"
require 'rb-readline'
module RbReadline
def self.prefill_prompt(str)
#rl_prefill = str
#rl_startup_hook = :rl_prefill_hook
end
def self.rl_prefill_hook
rl_insert_text #rl_prefill if #rl_prefill
#rl_startup_hook = nil
end
end
RbReadline.prefill_prompt("untitled.txt")
str = Readline.readline("Enter a filename:", true)
puts "You entered: #{str}"
I would use vim to edit the file. Vim will save edited files in ~/.viminfo. The last edited file is marked with '0. The pattern of a file entry is 'N N N filename where N stands for a integer.
def editableprint(filename)
system "vi #{filename}"
regex = /(?<='0\s{2}\d\s{2}\d\s{2}).*/
viminfo = File.expand_path("~/.viminfo")
File.read(viminfo).scan(regex).first
end
In order to make this to work you would have to change your code
print("Enter a filename:")
filename = gets.chomp!
filename = "untitled.txt" if filename.emtpy?
edited_filename = editableprint("untitled.txt")

How to pass to CSV file only the first five tracks? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
require "openssl"
require "nokogiri"
require 'csv'
require "open-uri"
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
$n=0
#~ Open_Page
page = ('http://www.residentadvisor.net/dj/aguycalledgerald/tracks?sort=mostcharted')
html = Nokogiri::HTML(open(page))
#~ Array
names= []
html.css('a').each do |x|
names<< x.text.strip.gsub(/\t/,'')
names.delete('RA on YouTube')
names.delete('Login')
names.delete('Register')
names.delete('Resident Advisor')
names.delete('Submit')
names.delete('Listings')
names.delete('Clubs')
names.delete('News')
names.delete('Reviews')
names.delete('Features')
names.delete('Films')
names.delete('Submit event')
names.delete('Artists')
names.delete('Photos')
names.delete('DJ Charts')
names.delete('Labels')
names.delete('Podcasts')
names.delete('Search')
names.delete('Top 1000')
names.delete('Top 100')
names.delete('Local')
names.delete('Favourites')
names.delete('Create an artist profile')
names.delete('Reviews')
names.delete('Features')
names.delete('A')
names.delete('B')
names.delete('C')
names.delete('D')
names.delete('E')
names.delete('F')
names.delete('G')
names.delete('H')
names.delete('I')
names.delete('J')
names.delete('K')
names.delete('L')
names.delete('M')
names.delete('N')
names.delete('O')
names.delete('P')
names.delete('Q')
names.delete('R')
names.delete('S')
names.delete('T')
names.delete('U')
names.delete('V')
names.delete('W')
names.delete('X')
names.delete('Y')
names.delete('Z')
names.delete('0-9')
names.delete('RA')
names.delete('About')
names.delete('Advertise')
names.delete('Jobs')
names.delete('RA In Residence')
names.delete('Ticketing FAQ')
names.delete('Sell tickets on RA')
names.delete('Privacy')
names.delete('Terms')
names.delete('RA is also available in Japanese. 日本版')
names.delete('Download the RA Guide')
names.delete('RA on Twitter')
names.delete('RA on Facebook')
names.delete('RA on Google+')
names.delete('RA on Instagram')
names.delete('RA on Soundcloud')
names.delete('Biography')
names.delete('Events')
names.delete('Tracks')
names.delete('RA News')
names.delete('RA Editorial')
names.delete('Remixes')
names.delete('Solo productions')
names.delete('Collaborations')
names.delete('Laboratory Instinct')
names.delete('Highgrade Records')
names.delete('Bosconi')
names.delete('!K7')
names.delete('Perlon')
names.delete('Beatstreet')
names.delete('Title')
names.delete('Label')
names.delete('Release Date')
names.delete('51 chartings')
puts names
end
#~ To_CSV
for $n in 0..names.count do
CSV.open('Most_Charted.csv','a+') do |csv|
csv << [names[$n]]
end
end
That creates a CSV file with:
PositiveNoise (Carl Craig remix) System 7 & Guy Called Gerald A-Wave 22 chartings
Voodoo Ray (Shield Re-Edit) A Guy Called Gerald 18 chartings
Falling (D. Digglers Cleptomania remix) Tom Clark & Benno Blome feat.
A Guy Called Gerald 18 chartings
How Long Is Now A Guy Called Gerald 14 chartings
Groove Of The Ghetto A Guy Called Gerald 12 chartings
Voodoo Ray A Guy Called Gerald 10 chartings
Falling (D Diggler's Rescue remix) Tom Clark & Benno Blome feat. A
Guy Called Gerald 9 chartings
and so on.
How do I pass only the first 5 song names to the CSV file?
Be sure you know what you are doing when you disable SSL checks.
You can find a better selector for the track list, so you do not need all those "deletes". The tracks are all inside ul.tracks
Then i'd suggest you make the whole thing a class. So you can encapsulate the behavior. And then don't use $ globals. Not needed and usually a sign of bad code.
Here is a working sample:
require "openssl"
require "nokogiri"
require 'csv'
require "open-uri"
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
class Tracklist
def initialize(url)
#url = url
end
def parse(top = nil)
html = Nokogiri::HTML(open(url))
result = []
html.css('ul.tracks li').each do |node|
title = node.css('div.title a:nth-child(1)').first
result << title.text if !title.nil?
break if top && result.length == top
end
result
end
private
attr_reader :url
end
list = Tracklist.new("https://www.residentadvisor.net/dj/aguycalledgerald/tracks?sort=mostcharted")
p list.parse(5)
If you need more information about the tracks, then you can extract more details in the loop inside the parsemethod.
This code stops parsing after top has been reached. Afterwards you can build your CSV as you like.

Creating a Ruby API

I have been tasked with creating a Ruby API that retrieves youtube URL's. However, I am not sure of the proper way to create an 'API'... I did the following code below as a Sinatra server that serves up JSON, but what exactly would be the definition of an API and would this qualify as one? If this is not an API, how can I make in an API? Thanks in advance.
require 'open-uri'
require 'json'
require 'sinatra'
# get user input
puts "Please enter a search (seperate words by commas):"
search_input = gets.chomp
puts
puts "Performing search on YOUTUBE ... go to '/videos' API endpoint to see the results and use the output"
puts
# define query parameters
api_key = 'my_key_here'
search_url = 'https://www.googleapis.com/youtube/v3/search'
params = {
part: 'snippet',
q: search_input,
type: 'video',
videoCaption: 'closedCaption',
key: api_key
}
# use search_url and query parameters to construct a url, then open and parse the result
uri = URI.parse(search_url)
uri.query = URI.encode_www_form(params)
result = JSON.parse(open(uri).read)
# class to define attributes of each video and format into eventual json
class Video
attr_accessor :title, :description, :url
def initialize
#title = nil
#description = nil
#url = nil
end
def to_hash
{
'title' => #title,
'description' => #description,
'url' => #url
}
end
def to_json
self.to_hash.to_json
end
end
# create an array with top 3 search results
results_array = []
result["items"].take(3).each do |video|
#video = Video.new
#video.title = video["snippet"]["title"]
#video.description = video["snippet"]["description"]
#video.url = video["snippet"]["thumbnails"]["default"]["url"]
results_array << #video.to_json.gsub!(/\"/, '\'')
end
# define the API endpoint
get '/videos' do
results_array.to_json
end
An "API = Application Program Interface" is, simply, something that another program can reliably use to get a job done, without having to busy its little head about exactly how the job is done.
Perhaps the simplest thing to do now, if possible, is to go back to the person who "tasked" you with this task, and to ask him/her, "well, what do you have in mind?" The best API that you can design, in this case, will be the one that is most convenient for the people (who are writing the programs which ...) will actually have to use it. "Don't guess. Ask!"
A very common strategy for an API, in a language like Ruby, is to define a class which represents "this application's connection to this service." Anyone who wants to use the API does so by calling some function which will return a new instance of this class. Thereafter, the program uses this object to issue and handle requests.
The requests, also, are objects. To issue a request, you first ask the API-connection object to give you a new request-object. You then fill-out the request with whatever particulars, then tell the request object to "go!" At some point in the future, and by some appropriate means (such as a callback ...) the request-object informs you that it succeeded or that it failed.
"A whole lot of voodoo-magic might have taken place," between the request object and the connection object which spawned it, but the client does not have to care. And that, most of all, is the objective of any API. "It Just Works.™"
I think they want you to create a third-party library. Imagine you are schizophrenic for a while.
Joe wants to build a Sinatra application to list some YouTube videos, but he is lazy and he does not want to do the dirty work, he just wants to drop something in, give it some credentials, ask for urls and use them, finito.
Joe asks Bob to implement it for him and he gives him his requirements: "Bob, I need YouTube library. I need it to do:"
# Please note that I don't know how YouTube API works, just guessing.
client = YouTube.new(api_key: 'hola')
video_urls = client.videos # => ['https://...', 'https://...', ...]
And Bob says "OK." end spends a day in his interactive console.
So first, you should figure out how you are going to use your not-yet-existing lib, if you can – sometimes you just don't know yet.
Next, build that library based on the requirements, then drop it in your Sinatra app and you're done. Does that help?

Structuring Nokogiri output without HTML tags

I got Ruby to travel to a web site, iterate through a list of campaigns and scrape the pages for specific data. The problem I have now is getting it from the structure Nokogiri gives me, and outputting it into a readable form.
campaign_list = Array.new
campaign_list.push(1042360, 1042386, 1042365, 992307)
browser = Watir::Browser.new :chrome
browser.goto '<redacted>'
browser.text_field(:id => 'email').set '<redacted>'
browser.text_field(:id => 'password').set '<redacted>'
browser.send_keys :enter
file = File.new('hourlysales.csv', 'w')
data = {}
campaign_list.each do |campaign|
browser.goto "<redacted>"
if browser.text.include? "Application Error"
puts "Error loading page, I recommend restarting script"
# Possibly automatic restart of script
else
hourly_data = Nokogiri::HTML.parse(browser.html).text
# file.write data
puts hourly_data
end
This is the output I get:
{"views":[[17,145],[18,165],[19,99],[20,71],[21,31],[22,26],[23,10],[0,15],[1,1], [2,18],[3,19],[4,35],[5,47],[6,44],[7,67],[8,179],[9,141],[10,112],[11,95],[12,46],[13,82],[14,79],[15,70],[16,103]],"orders":[[17,10],[18,9],[19,5],[20,1],[21,1],[22,0],[23,0],[0,1],[1,0],[2,1],[3,0],[4,1],[5,2],[6,1],[7,5],[8,11],[9,6],[10,5],[11,3],[12,1],[13,2],[14,4],[15,6],[16,7]],"conversion_rates":[0.06870229007633588,0.05442176870748299,0.050505050505050504,0.014084507042253521,0.03225806451612903,0.0,0.0,0.06666666666666667,0.0,0.05555555555555555,0.0,0.02857142857142857,0.0425531914893617,0.022727272727272728,0.07462686567164178,0.06134969325153374,0.0425531914893617,0.044642857142857144,0.031578947368421054,0.021739130434782608,0.024390243902439025,0.05063291139240506,0.08571428571428572,0.06741573033707865]}
The arrays stand for { views [[hour, # of views], [hour, # of views], etc. }. Same with orders. I don't need conversion rates.
I also need to add the values up for each key, so after doing this for 5 pages, I have one key for each hour of the day, and the total number of views for that hour. I tried a couple each loops, but couldn't make any progress.
I appreciate any help you guys can give me.
It looks like the output (which from your code I assume is the content of hourly_data) is JSON. In that case, it's easy to parse and add up the numbers. Something like this:
require "json" # at the top of your script
# ...
def sum_hours_values(data, hours_values=nil)
# Start with an empty hash that automatically initializes missing keys to `0`
hours_values ||= Hash.new {|hsh,hour| hsh[hour] = 0 }
# Iterate through the [hour, value] arrays, adding `value` to the running
# count for that `hour`, and return `hours_values`
data.each_with_object(hours_values) do |(hour, value), hsh|
hsh[hour] += value
end
end
# ... Watir/Nokogiri stuff here...
# Initialize these so they persist outside the loop
hours_views, orders_views = nil
campaign_list.each do |campaign|
browser.goto "<redacted>"
if browser.text.include? "Application Error"
# ...
else
# ...
hourly_data_parsed = JSON.parse(hourly_data)
hours_views = sum_hours_values(hourly_data_parsed["views"], hours_views)
hours_orders = sum_hours_values(hourly_data_parsed["orders"], orders_views)
end
end
puts "Views by hour:"
puts hours_views.sort.map {|hour_views| "%2i\t%4i" % hour_views }
puts "Orders by hour:"
puts hours_orders.sort.map {|hour_orders| "%2i\t%4i" % hour_orders }
P.S. There's a really nice recursive version of sum_hours_values I didn't include since the iterative version is clearer to most Ruby programmers. If you're into recursion I leave it as an exercise for you. ;)

Is there a ruby-er way to do these assignments? [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
I am trying to read Sonar report in XML format using ruby. I have written the following code:
class Resource
attr_accessor :file_name, :lines, :generated_lines, :ncloc, :generated_ncloc, :statements, :comment_lines, :commented_out_code_lines, :lines_to_cover, :uncovered_lines, :conditions_to_cover, :uncovered_conditions, :line_coverage, :branch_coverage, :coverage, :test_success_density, :test_failures, :skipped_tests, :test_errors, :num_of_tests, :lang, :qualifier
def to_s
"#{#name} #{#lines} #{#generated_lines} #{#ncloc} #{#generated_ncloc} #{#statements} #{#comment_lines} #{#commented_out_code_lines} #{#lines_to_cover} #{#uncovered_lines} #{#conditions_to_cover} #{#uncovered_conditions} #{#line_coverage} #{#branch_coverage} #{#coverage} #{#test_success_density} #{#test_failures} #{#skipped_tests} #{#test_errors}#{#num_of_tests} #{#lang} #{#qualifier}"
end
end
sonar.root.each_element do |node1| # resources
next if node1.name != "resources"
node1.each_element do |node2| #resource
resource = Resource.new
resource.lang = node2.text if node2.name == "lang"
resource.qualifier = node2.text if node2.name == "qualifier"
resource.name = node.text if node2.name == "name"
end
end
As you can see this takes up too many statements whether I use if conditionals or case statements. Is there a more succinct way of doing this in ruby?
Filter the nodes first instead of switching while iterating. And since you’re doing the same thing for each possible value of the name attribute, DRY that up with a loop too.
sonar.root.each_element_with_attribute 'name', 'resources' do |node1|
%w[lang qualifier name].each do |name_val|
node1.each_element_with_attribute 'name', name_val do |node2|
resource = Resource.new
resource.public_send :"#{name_val}=", node2.text
end
end
end
Note: I’m assuming you’re using REXML from your code and your lack of answer to my comment as of this writing. I would, however, suggest you use the friendlier Nokogiri instead.

Resources