Ruby logger is nil - ruby

When I try to use Ruby's logger, it comes up as nil. Is an instance variable the correct way to do this?
require 'logger'
class LogStuff
# Log to STDOUT and `tee` it to a file
#logger = Logger.new("| tee output.log")
#logger.level = :debug
def run
(1..10).each do |n|
#logger.debug('l1') { #logger.debug "proceessing #{n}" }
end
['a', 'b', 'c', 'd', 'e'].each do |letter|
#logger.debug('letters') { #logger.debug "proceessing #{letter}" }
end
end
end
if __FILE__ == $0
lggr = LogStuff.new
lggr.run
end
Error message:
$ ruby script.rb
script.rb:11:in `block in run': undefined method `debug' for nil:NilClass (NoMethodError)
from script.rb:10:in `each'
from script.rb:10:in `run'
from script.rb:23:in `<main>'

Try this one
def initialize
# Log to STDOUT and `tee` it to a file
#logger = Logger.new("| tee output.log")
#logger.level = :debug
end
Obviously into the LogStuff class. Basically, otherwise your #logger variable is defined in another scope.

Related

Ruby Zip: Cannot open entry for reading while its open for writing

I'm trying to write some mail merge code where I open a docx file (as a zip) replace tags with data and then create a new docx file (as a zip) and iterate over the old zip file either adding my new replaced data or pulling the existing file from the old docx file and adding that instead.
The problem I'm getting is anytime I try to access the out.get_output_stream method, I'm getting the following error:
cannot open entry for reading while its open for writing - [Content_Types].xml (StandardError)
[Content_Types].xml happens to be first file in the docx so that's why its bombing on that particular file. What am I doing wrong?
require 'rubygems'
require 'zip' # rubyzip gem
class WordMailMerge
def self.open(path, &block)
self.new(path, &block)
end
def initialize(path, &block)
#replace = {}
if block_given?
#zip = Zip::File.open(path)
yield(self)
#zip.close
else
#zip = Zip::File.open(path)
end
end
def force_settings
#replace["word/settings.xml"] = %{<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:settings xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main"><w:zoom w:percent="100"/></w:settings>}
end
def merge(rec)
xml = #zip.read("word/document.xml")
# replace tags with correct content
#replace["word/document.xml"] = xml
end
def save(path)
Zip::File.open(path, Zip::File::CREATE) do |out|
#zip.each do |entry|
if #replace[entry.name]
# this line creates the error
out.get_output_stream(entry.name).write(#replace[entry.name])
else
# this line also will do it.
out.get_output_stream(entry.name).write(#zip.read(entry.name))
end
end
end
end
def close
#zip.close
end
end
w = WordMailMerge.open("Option_2.docx")
w.force_settings
w.merge({})
w.save("Option_2_new.docx")
The following is the stack trace:
/home/aaron/.rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/delegate.rb:85:in `call': cannot open entry for reading while its open for writing - [Content_Types].xml (StandardError)
from /home/aaron/.rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/delegate.rb:85:in `method_missing'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/streamable_stream.rb:28:in `get_input_stream'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/streamable_stream.rb:45:in `write_to_zip_output_stream'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/file.rb:313:in `block (3 levels) in commit'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/entry_set.rb:38:in `block in each'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/entry_set.rb:37:in `each'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/entry_set.rb:37:in `each'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/file.rb:312:in `block (2 levels) in commit'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/output_stream.rb:53:in `open'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/file.rb:311:in `block in commit'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/file.rb:409:in `block in on_success_replace'
from /home/aaron/.rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/tmpdir.rb:130:in `create'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/file.rb:407:in `on_success_replace'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/file.rb:310:in `commit'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/file.rb:334:in `close'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/file.rb:103:in `ensure in open'
from /home/aaron/.rvm/gems/ruby-2.4.1#appt/gems/rubyzip-1.2.1/lib/zip/file.rb:103:in `open'
from zip.rb:34:in `save'
from zip.rb:56:in `<main>'
You need to change your update code to below
def save(path)
Zip::File.open(path, Zip::File::CREATE) do |out|
#zip.each do |entry|
if #replace[entry.name]
# this line creates the error
out.get_output_stream(entry.name){ |f| f.puts #replace[entry.name] }
else
# this line also will do it.
# out.get_output_stream(entry.name).write(#zip.read(entry.name))
out.get_output_stream(entry.name){ |f| f.puts #zip.read(entry.name) }
end
end
end
end
And then the file will get created
Edit-1
Below is the final code that I had used for testing
require 'rubygems'
require 'zip' # rubyzip gem
class WordMailMerge
def self.open(path, &block)
self.new(path, &block)
end
def initialize(path, &block)
#replace = {}
if block_given?
#zip = Zip::File.open(path)
yield(self)
#zip.close
else
#zip = Zip::File.open(path)
end
end
def force_settings
#replace["word/settings.xml"] = %{<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:settings xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main"><w:zoom w:percent="100"/></w:settings>}
end
def merge(rec)
xml = #zip.read("word/document.xml")
# replace tags with correct content
#replace["word/document.xml"] = xml.gsub("{name}", "Tarun lalwani")
end
def save(path)
Zip::File.open(path, Zip::File::CREATE) do |out|
#zip.each do |entry|
if #replace[entry.name]
# this line creates the error
out.get_output_stream(entry.name){ |f| f.puts #replace[entry.name] }
else
# this line also will do it.
# out.get_output_stream(entry.name).write(#zip.read(entry.name))
out.get_output_stream(entry.name){ |f| f.puts #zip.read(entry.name) }
end
end
end
end
def close
#zip.close
end
end
w = WordMailMerge.open("Option_2.docx")
w.force_settings
w.merge({})
w.save("Option_2_new.docx")
Option_2.docx
Option_2_new.doc

active_record configuration from yaml file: debuglevel

I'm trying to configure the debuglevel for active-record logger from a YAML configuration file but get the following error, how could i do this other than using a number in the YAML ?
sample.rb:30 warning: toplevel constant LEVEL referenced by Logger::LEVEL
"DEBUG"
ArgumentError: comparison of Fixnum with String failed
here is the sample.rb
require 'java'
require 'active_record'
require 'activerecord-jdbc-adapter'
require 'yaml'
require 'logger'
def get_jar_path
if __FILE__[/.+\.jar!/] #in case run from JAR
scriptpath = __FILE__[/(.*)\/.+\.jar!/]
$1[6..-1]
else #in case run with jRuby
'..'
end
end
def load_config
path = "#{get_jar_path}/#{File.basename(__FILE__, ".*")}.configuration.yml"
p path
$conf = YAML::load_file(path)
end
load_config
LEVEL = $conf['debug_level'] #string 'DEBUG' from configuration file
$log = Logger.new( "#{get_jar_path}/log_#{Time.now.strftime("%Y%m%d")}.txt", 'monthly' )
ActiveRecord::Base.logger = $log
ActiveRecord::Base.logger.level = Logger::DEBUG #works
ActiveRecord::Base.logger.level = Logger::LEVEL #doesn't work
p ActiveRecord::Base.logger.level
$log.info "start #{__FILE__}"
The available log levels are: :debug, :info, :warn, :error, :fatal,
and :unknown, corresponding to the log level numbers from 0 up to 5
respectively.
http://guides.rubyonrails.org/debugging_rails_applications.html
require 'logger'
puts Logger::DEBUG
--output:--
0
str = "DEBUG"
puts Logger.const_get(str)
--output:--
0
So you should do something like:
level = $conf['debug_level'] #string 'DEBUG' from configuration file
$log = Logger.new( "#{get_jar_path}/log_#{Time.now.strftime("%Y%m%d")}.txt", 'monthly' )
ActiveRecord::Base.logger = $log
ActiveRecord::Base.logger.level = Logger.const_get(level)
I'm not sure why you thought defining a constant, LEVEL, in the current scope would make that constant appear in the Logger scope, so that you could write Logger::LEVEL. You essentially did this:
MYCONST = "hello"
module SomeModule
SOMECONST = "goodbye"
end
You can write:
puts MYCONST #=>hello
..and you can write:
puts SomeModule::SOMECONST #goodbye
..but you cannot write:
puts SomeModule::MYCONST
--output:--
1.rb:10:in `<main>': uninitialized constant SomeModule::MYCONST (NameError)

How to scrape data from list of URLs and save data to CSV with nokogiri

I have a file called bontyurls.csv that looks like this:
http://bontrager.com/model/11383
http://bontrager.com/model/01740
http://bontrager.com/model/09595
I want my script to read that file and then spit out a file like this: bonty_test_urls_results.csv
url,model_names
http://bontrager.com/model/11383,"Road TLR Conversion Kit"
http://bontrager.com/model/01740,"404 File Not Found"
http://bontrager.com/model/09595,"RXL Road"
Here's what I've got so far:
# based on code from here: http://www.andrewsturges.com/2011/09/how-to-harvest-web-data-using-ruby-and.html
require 'nokogiri'
require 'open-uri'
require 'csv'
#urls = Array.new
#model_names = Array.new
urls = CSV.read("bontyurls.csv")
(0..urls.length - 1).each do |index|
puts urls[index][0]
doc = Nokogiri::HTML(open(urls[index][0]))
doc.xpath('//h1').each do |model_name|
#model_name << model_name.content
end
end
# write results to file
CSV.open("bonty_test_urls_results.csv", "wb") do |row|
row << ["url", "model_names"]
(0..#urls.length - 1).each do |index|
row << [
#urls[index],
#model_names[index]]
end
end
That code isn't working. I'm getting this error:
$ ruby bonty_test_urls.rb
http://bontrager.com/model/00310
bonty_test_urls.rb:15:in `block (2 levels) in <main>': undefined method `<<' for nil:NilClass (NoMethodError)
from /home/simon/.rvm/gems/ruby-1.9.3-p194/gems/nokogiri-1.5.5/lib/nokogiri/xml/node_set.rb:239:in `block in each'
from /home/simon/.rvm/gems/ruby-1.9.3-p194/gems/nokogiri-1.5.5/lib/nokogiri/xml/node_set.rb:238:in `upto'
from /home/simon/.rvm/gems/ruby-1.9.3-p194/gems/nokogiri-1.5.5/lib/nokogiri/xml/node_set.rb:238:in `each'
from bonty_test_urls.rb:14:in `block in <main>'
from bonty_test_urls.rb:11:in `each'
from bonty_test_urls.rb:11:in `<main>'
Here is some code that returns the model_name at least. I'm just having trouble getting it to work in the larger script:
require 'open-uri'
require 'nokogiri'
doc = Nokogiri::HTML(open("http://bontrager.com/model/09124"))
doc.xpath('//h1').each do |node|
puts node.text
end
Also, I haven't figured out how to handle the URLs that return a 404.
This is how I'd do it:
require 'csv'
require 'nokogiri'
require 'open-uri'
CSV_OPTIONS = {
:write_headers => true,
:headers => %w[url model_names]
}
CSV.open('bonty_test_urls_results.csv', 'wb', CSV_OPTIONS) do |csv|
csv_doc = File.foreach('bontyurls.csv') do |url|
url.chomp!
begin
doc = Nokogiri.HTML(open(url))
h1 = doc.at('h1').text.strip
h1 = doc.at('title').text.strip.sub(/^Bontrager: /i, '') if (h1.empty?)
csv << [url, h1]
rescue OpenURI::HTTPError => e
csv << [url, e.message]
end
end
end
Which generates a CSV file like:
url,model_names
http://bontrager.com/model/11383,Road TLR Conversion Kit (Model #11383)
http://bontrager.com/model/01740,404 File Not Found
http://bontrager.com/model/09595,RXL Road (Model #09595)
You declare #model_names, but try to push in to #model_name, which is why it's nil.

How to run a file in pry that takes arguments

I can start a pry session of a command line app like this
pry -r ./todo.rb
However, if I want to call the list function
pry -r ./todo.rb list
I'm getting an error message.
Without pry, I call the list function
ruby todo.rb list
This is the error message
/Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/gems/pry-0.9.10/lib/pry/repl_file_loader.rb:16:in `initialize': No such file: /Users/michaeljohnmitchell/Sites/todo/bin/list (RuntimeError)
from /Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/gems/pry-0.9.10/lib/pry/pry_class.rb:161:in `new'
from /Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/gems/pry-0.9.10/lib/pry/pry_class.rb:161:in `load_file_through_repl'
from /Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/gems/pry-0.9.10/lib/pry/cli.rb:162:in `block in <top (required)>'
from /Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/gems/pry-0.9.10/lib/pry/cli.rb:65:in `call'
from /Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/gems/pry-0.9.10/lib/pry/cli.rb:65:in `block in parse_options'
from /Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/gems/pry-0.9.10/lib/pry/cli.rb:65:in `each'
from /Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/gems/pry-0.9.10/lib/pry/cli.rb:65:in `parse_options'
from /Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/gems/pry-0.9.10/bin/pry:16:in `<top (required)>'
from /Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/bin/pry:19:in `load'
from /Users/michaeljohnmitchell/.rvm/gems/ruby-1.9.2-p290#global/bin/pry:19:in `<main>'
Source Code
TODO_FILE = 'todo.txt'
def read_todo(line)
line.chomp.split(/,/)
end
def write_todo(file,name,created=Time.now,completed='')
file.puts("#{name},#{created},#{completed}")
end
command = ARGV.shift
case command
when 'new'
new_task = ARGV.shift
File.open(TODO_FILE,'a') do |file|
write_todo(file,new_task)
puts "Task added."
end
when 'list'
File.open(TODO_FILE,'r') do |file|
counter = 1
file.readlines.each do |line|
name,created,completed = read_todo(line)
printf("%3d - %s\n",counter,name)
printf(" Created : %s\n",created)
unless completed.nil?
printf(" Completed : %s\n",completed)
end
counter += 1
end
end
when 'done'
task_number = ARGV.shift.to_i
binding.pry
File.open(TODO_FILE,'r') do |file|
File.open("#{TODO_FILE}.new",'w') do |new_file|
counter = 1
file.readlines.each do |line|
name,created,completed = read_todo(line)
if task_number == counter
write_todo(new_file,name,created,Time.now)
puts "Task #{counter} completed"
else
write_todo(new_file,name,created,completed)
end
counter += 1
end
end
end
`mv #{TODO_FILE}.new #{TODO_FILE}`
end
Update
when I try
pry -r ./todo.rb -e list
I'm getting the following error
NameError: undefined local variable or method `list' for main:Object
From pry --help:
-e, --exec A line of code to execute in context before the session starts
So, if your list method is defined on main (if you don't know, it probably is), then you can do this:
pry -r ./todo.rb -e list
Update
Pry doesn't let you pass in arguments for scripts it loads (or at least it isn't documented). But all is not lost, you can call pry from your script. Just drop this at wherever you want to inspect:
require 'pry'; binding.pry
This will spawn a pry session that has access to all the local variables and methods.
I think you can use:
ruby -rpry ./todo.rb -e list

Ruby error with CSV and <<

Well, I think the code speaks out for itself :)
# book_in_stock.rb
class BookinStock
attr_reader :isbn, :price
def initialize(isbn, price)
#isbn = isbn
#price = Float(price)
end
end
# csv_reader.rb
require 'csv'
class CsvReader
def initialize
#book_in_stock = []
end
def read_in_csv_data(csv_file_name)
CSV.foreach(csv_file_name, headers: true) do |row|
#books_in_stock << BookinStock.new(row["ISBN"], row["Amount"])
end
end
# later we'll see how to use inject to sum a collection
def total_value_in_stock
sum = 0.0
#books_in_stock.each { |book| sum += book.price }
sum
end
def number_of_each_isbn
# ...
end
end
# stock_stats.rb
reader = CsvReader.new()
ARGV.each do |csv_file_name|
STDERR.puts "[+] Processing #{csv_file_name}"
reader.read_in_csv_data(csv_file_name)
end
puts "[+] Total value = #{reader.total_value_in_stock}"
When running I get:
# +search/pickaxe/csv $ ruby1.9.1 test.rb data.csv
# [+] Processing data.csv
# test.rb:23:in `block in read_in_csv_data': undefined method `<<' for nil:NilCla
# ss (NoMethodError)
# from /usr/lib/ruby/1.9.1/csv.rb:1760:in `each'
# from /usr/lib/ruby/1.9.1/csv.rb:1196:in `block in foreach'
# from /usr/lib/ruby/1.9.1/csv.rb:1334:in `open'
# from /usr/lib/ruby/1.9.1/csv.rb:1195:in `foreach'
# from test.rb:22:in `read_in_csv_data'
# from test.rb:46:in `block in <main>'
# from test.rb:44:in `each'
# from test.rb:44:in `<main>'
What've I done wrong?
'#books_in_stock' != '#book_in_stock' # !
(typo)
You have a typo in the initialize function (#book_in_store insteadn of #books_in_store). Is that it?
By the way, the sum of the prices can be done with inject in one line
#books_in_stock.inject(0) { |sum, book| sum + book.price }
If you run the command with ruby -w, it'll turn on warnings, and the interpreter will explain what went wrong:
$ ruby -w book_in_stock.rb
book_in_stock.rb:28: warning: instance variable #books_in_stock not initialized
book_in_stock.rb:28:in `total_value_in_stock': undefined method `each' for nil:NilClass (NoMethodError)
from book_in_stock.rb:47:in `<main>'

Resources