Can anyone explain how caller method is matching correctly with the regex in the following code snippet - ruby

Here I am trying to check which method is calling the "trxn_message" method by using a case statement with caller[0]. How is this working?. can someone help me on this ?
I am using ruby 2.5.1
def trxn_message(attributes, records = nil)
#binding.pry
case caller[0]
when /insert/
binding.pry
puts "#{post_trxn_count} new record created: #{attributes}"
when /find/
binding.pry
puts "#{records.count} records found with criteria #{attributes}"
records.each {|r| puts r}
when /delete/
binding.pry
puts "#{post_trxn_count} records with criteria #{attributes} deleted"
else
end
end

caller[0] contains info about method, which call method, called caller[0].
Format of caller[0] output is:
<filename>:<string number>:in `<method name>'
So, if your caller is 'find', then caller[0] will be:
filename.rb:10:in `find'
And regular expression, which you check as /find/ return 'find' - string, becomes as not falsey.

Related

rspec fails even when variables should match

Methods in my class:
def get_class_info
#class_name = get_class_name
end
def get_class_name
puts "Enter the name for your class. Enter 'done' to return to the main prompt."
input = gets.chomp
if input.valid_class_name?
class_name = input.titleize
elsif input.downcase == "done"
run
else
puts "Invalid class name."
get_class_name
end
class_name
end
My test:
it "stores the name in the #class_name property" do
expect(cli.class_name).to eq('Song')
allow(cli).to receive(:gets) {'song'}
cli.get_class_info
end
It fails, saying it expected "Song" but got nil.
If I comment out the expect line and add a pry to the end of the test, querying cli.class_name returns "Song"!!!
Why is this test failing??
Update: Okay, moving the expect line to the bottom of the test apparently solved it. But I'm confused because usually the expectation goes before the actual method call. Am I wrong? What am I missing here?

Ruby with RSpec NoMethodError: undefined method length for nil:NilClass

I'm new to Ruby and RSpec trying to write a unit case for string length. I have 3 rb files as follows
1. Calling file
require_relative 'ruby_final_operations'
require_relative 'ruby_helper'
require 'uri'
require 'open-uri'
require 'prime'
module RubyOperations
# Public: Various commands for the user to interact with RubyCommand.
class Command
res = RubyOperations::Operations.new
res.letter_count(res.inputstr)
2nd File - Method Implementation
require_relative 'ruby_helper'
require 'logger'
$FILE_LOG = RubyOperations.create_log(File.expand_path('~/RubyOperations_LOG.log'), Logger::DEBUG)
$STD_LOG = RubyOperations.create_log(nil, Logger::INFO)
module RubyOperations
class Operations
def inputstr
RubyOperations.log('Enter the String:[Length 20]',:BOTH)
#str = gets.chomp
raise StandardError if #str =~ /\d/ || #str.empty? || #str.length > 20
rescue StandardError,ArgumentError => e
RubyOperations.log(e,:ERROR)
end
def letter_count(str)
result = #str.length
RubyOperations.log("The number of letters in the string: #{result}",:BOTH)
end
3rd file - RSpec
require 'ruby_final_operations'
describe 'RubyOperations' do
describe 'Operations' do
subject = RubyOperations::Operations.new
describe '.letter_count' do
context 'when operation is provided' do
it 'returns letter count' do
allow(subject.letter_count("hello").to receive(:result).and_return(5)
end
end
end
The problem is that in the 2nd File he argument is 'str' but the typed string is stored is '#str'.
How can I pass the string 'hello' from the rspec file to test this.
There are a few issues:
Calling a instance_method with an argument that is not used
def letter_count #get rid of argument, the argument does nothing,
#basically it looks you added the argument,
# just, so you can call the other method there.
Make your main simple, with a clear sequence
res.inputstr
res.letter_count
But about your actual question, in your test you change the wrong thing the wrong method
allow(subject.letter_count("hello").to receive(:result).and_return(5)
# letter count should do the log entry, not return five, at least that what your method say
So you probably want to set the #str before you test the letter_count method.
subject.instance_variable_set("hello")
# then test for what you expect the method to return
expect(subject.letter_count).to eq(5)
# this specific test will fail, because you are doing a log entry, and not return the length on letter_count.

How do I get ruby to print a full backtrace that includes arguments passed to functions?

Sometimes backtrace is enough to diagnose problem. But sometimes reason of crash is not obvious without knowledge what was passed to function.
Getting information what was passed to function that caused crash would be quite useful, especially in cases where reproducing is not obvious because it was caused by for example exception in network connection, weird user input or because program is depends on randomisation or processes data from external sensor.
Lets say that there is following program
def handle_changed_input(changed_input)
raise 'ops' if changed_input =~ /magic/
end
def do_something_with_user_input(input)
input = "#{input.strip}c"
handle_changed_input(input)
end
input = gets
do_something_with_user_input(input)
where user typed "magic" as input. Normally one has
test.rb:2:in `handle_changed_input': ops (RuntimeError)
from test.rb:7:in `do_something_with_user_input'
from test.rb:11:in `<main>'
as output. What one may do to show also what was passed to function? Something like
test.rb:2:in `handle_changed_input("magic")': ops (RuntimeError)
from test.rb:7:in `do_something_with_user_input("magi\n")'
from test.rb:11:in `<main>'
It would be useful in many situations (and not truly useful where parameters are not representable as strings of reasonable legth, there is a good reason why it is not enabled by default).
How one may add this functionality? It is necessary that program works as usually during normal operation and preferably there is no additional output before crash.
I tried for example
def do_something_with_user_input(input)
method(__method__).parameters.map do |_, name|
puts "#{name}=#{binding.local_variable_get(name)}"
end
raise 'ops' if input =~ /magic/
end
input = gets
found in Is there a way to access method arguments in Ruby? but it would print on every single entrance to function what both would flood output and make program significantly slower.
I don't have a complete solution but... But you can get method arguments of all called methods in controlled environment with TracePoint class from Ruby core lib.
Look at the example:
trace = TracePoint.new(:call) do |tp|
puts "===================== #{tp.method_id}"
b_self = tp.binding.eval('self')
names = b_self.method(tp.method_id).parameters.map(&:last)
values = names.map { |name| tp.binding.eval(name.to_s) }
p names.zip(values)
end
trace.enable
def method_a(p1, p2, p3)
end
method_a(1, "foobar", false)
#=> ===================== method_a
#=> [[:p1, 1], [:p2, "foobar"], [:p3, false]]
To print exception backtraces, Ruby uses the C function exc_backtrace from error.c (exc_backtrace on github). Unless you patch Ruby with the functionality you need, I don't think there a way to change exception backtrace outputs.
Here is a snippet (trace.rb) you might find useful:
set_trace_func -> (event, file, line, id, binding, classname) do
if event == 'call' && meth = binding.eval('__method__')
params = binding.method(meth).parameters.select{|e| e[0] != :block}
values = params.map{|_, var| [var, binding.local_variable_get(var)]}
printf "%8s %s:%-2d %15s %8s %s\n", event, file, line, id, classname, values.inspect
else
printf "%8s %s:%-2d %15s %8s\n", event, file, line, id, classname
end
end
def foo(a,b = 0)
bar(a, foo: true)
end
def bar(c, d = {})
puts "!!!buz!!!\n"
end
foo('lol')
The output of that snippet is:
c-return /path/to/trace.rb:1 set_trace_func Kernel
line /path/to/trace.rb:12
c-call /path/to/trace.rb:12 method_added Module
c-return /path/to/trace.rb:12 method_added Module
line /path/to/trace.rb:16
c-call /path/to/trace.rb:16 method_added Module
c-return /path/to/trace.rb:16 method_added Module
line /path/to/trace.rb:20
call /path/to/trace.rb:12 foo Object [[:a, "lol"], [:b, 0]]
line /path/to/trace.rb:13 foo Object
call /path/to/trace.rb:16 bar Object [[:c, "lol"], [:d, {:foo=>true}]]
line /path/to/trace.rb:17 bar Object
c-call /path/to/trace.rb:17 puts Kernel
c-call /path/to/trace.rb:17 puts IO
c-call /path/to/trace.rb:17 write IO
!!!buz!!!
c-return /path/to/trace.rb:17 write IO
c-return /path/to/trace.rb:17 puts IO
c-return /path/to/trace.rb:17 puts Kernel
return /path/to/trace.rb:18 bar Object
return /path/to/trace.rb:14 foo Object
I hope that helps you as much as it helped me.
I think that it is possible. The code below is not perfect and would require some additional work, but it caputers the primary idea of a stacktrace with argument values. Please note, that in order to know the call site, I am zipping the original stacktrace with the entry sites catched by trace function. To distinguishe these entries I use '>' and '<' respectively.
class Reporting
def self.info(arg1)
puts "*** #{arg1} ***"
end
end
def read_byte(arg1)
Reporting.info(arg1)
raise Exception.new("File not found")
end
def read_input(arg1)
read_byte(arg1)
end
def main(arg1)
read_input(arg1)
end
class BetterStacktrace
def self.enable
set_trace_func -> (event, file, line, id, binding, classname) do
case event
when 'call'
receiver_type = binding.eval('self.class')
if receiver_type == Object
meth = binding.eval('__method__')
params = binding.method(meth).parameters.select{|e| e[0] != :block}
values = params.map{|_, var| [var, binding.local_variable_get(var)]}
self.push(event, file, line, id, classname, values)
else
self.push(event, file, line, id, classname)
end
when 'return'
self.pop
when 'raise'
self.push(event, file, line, id, classname)
Thread.current[:_keep_stacktrace] = true
end
end
end
def self.push(event, file, line, id, classname, values=nil)
Thread.current[:_saved_stacktrace] = [] unless Thread.current.key?(:_saved_stacktrace)
unless Thread.current[:_keep_stacktrace]
if values
values_msg = values.map(&:last).join(", ")
msg = "%s:%d:in `%s(%s)'" % [file, line, id, values_msg]
else
msg = "%s:%d:in `%s'" % [file, line, id]
end
Thread.current[:_saved_stacktrace] << msg
end
end
def self.pop()
Thread.current[:_saved_stacktrace] = [] unless Thread.current.key?(:_saved_stacktrace)
unless Thread.current[:_keep_stacktrace]
value = Thread.current[:_saved_stacktrace].pop
end
end
def self.disable
set_trace_func nil
end
def self.print_stacktrace(calls)
enters = Thread.current[:_saved_stacktrace].reverse
calls.zip(enters).each do |call, enter|
STDERR.puts "> #{enter}"
STDERR.puts "< #{call}"
end
Thread.current[:_saved_stacktrace] = []
end
end
BetterStacktrace.enable
begin
main(10)
rescue Exception => ex
puts "--- Catched ---"
puts ex
BetterStacktrace.print_stacktrace(ex.backtrace)
end
BetterStacktrace.disable
begin
main(10)
rescue Exception
puts "--- Catched ---"
puts ex
puts ex.backtrace
end
The output of the above code is as follows:
*** 10 ***
--- Catched ---
File not found
> work/tracing_with_params.rb:10:in `read_byte'
< work/tracing_with_params.rb:10:in `read_byte'
> work/tracing_with_params.rb:8:in `read_byte(10)'
< work/tracing_with_params.rb:14:in `read_input'
> work/tracing_with_params.rb:13:in `read_input(10)'
< work/tracing_with_params.rb:18:in `main'
> work/tracing_with_params.rb:17:in `main(10)'
< work/tracing_with_params.rb:82:in `<main>'
*** 10 ***
--- Catched ---
File not found
work/tracing_with_params.rb:10:in `read_byte'
work/tracing_with_params.rb:14:in `read_input'
work/tracing_with_params.rb:18:in `main'
work/tracing_with_params.rb:82:in `<main>'
EDIT:
The calls to class functions are not recorded. This has to be fixed in order for the stacktrace printing function not to get invalid output.
Moreover I used the STDERR as output to easily get one or the other output. You can change it if you wish.
MAX_STACK_SIZE = 200
tracer = proc do |event|
if event == 'call' && caller_locations.length > MAX_STACK_SIZE
fail "Probable Stack Overflow"
end
end
set_trace_func(tracer)

What is Camping::Server.start invoking in /bin/camping?

I'm studying how the Camping web framework works right now, and I don't understand what the Camping::Server.start at line 10 in /bin/camping is doing.
I expected this to call the start method in /lib/camping/server.rb at line 131, and so I put a simple puts 'hello' statement at the beginning of that method, expecting that statement to be invoked when I ran /bin/camping. However, I never saw my puts statement get called, so I can only assume that it's not that start method getting called.
I feel like I'm missing something obvious here. Here is the link to the camping github page and the relevant sections of code:
Github: https://github.com/camping/camping
From /bin/camping:
#!/usr/bin/env ruby
$:.unshift File.dirname(__FILE__) + "/../lib"
require 'camping'
require 'camping/server'
begin
Camping::Server.start
rescue OptionParser::ParseError => ex
puts "did it error"
STDERR.puts "!! #{ex.message}"
puts "** use `#{File.basename($0)} --help` for more details..."
exit 1
end
From /lib/server.rb:
def start
if options[:server] == "console"
puts "** Starting console"
#reloader.reload!
r = #reloader
eval("self", TOPLEVEL_BINDING).meta_def(:reload!) { r.reload!; nil }
ARGV.clear
IRB.start
exit
else
name = server.name[/\w+$/]
puts "** Starting #{name} on #{options[:Host]}:#{options[:Port]}"
super
end
end
My puts 'hello' on Camping::Server.start wasn't getting called because I didn't understand how static methods were defined in ruby.
start was being called statically, and I realize now that the start method I was looking at in the snippet wasn't a static method, which meant that another start method was getting called. I looked into Camping::Server and realized that it inherited from Rack::Server, which has the following method:
def self.start(options = nil)
new(options).start
end
That was the method getting called, not the one on /lib/camping/server.rb. I had been looking at the wrong method.

How do I search a YAML file?

I have a YAML file books.yaml:
- !ruby.object:Book
title: Ruby for Newbz
author: LeeRoy Jenkins
category: Educational
I already have a method that adds books to this file, but I need a method that can search the YAML file using a regular expression. If no book matches the title then it should raise an exception NoBookfound. If there are any matches, that list should be returned to the caller.
Here is my existing code:
require 'yaml'
require './book'
class Library
attr_accessor :books
def initialize file_name = false
#books = file_name ? YAML::load(File.read(file_name)) : []
end
def add_book(book)
#books.push(book)
end
def search_library(file_name , book)
if
YAML::load(File.read(file_name)).include?(book) == false
raise 'No Book Found'
end
end
end
This is something I tried for the exception portion, but I feel like I'm way off. I'm really new to Ruby and this seems to be a pretty hard task. Does anyone have any ideas?
What you need is to test the class from the loaded object:
book = YAML::load(File.read(file_name))
raise 'No Book Found' unless book.kind_of? Book
There are also some kind_of? alternatives, which you can read about the differences on this nice question
Based on your post, I guess that book param is a Regexp object.
I think you should remove the file_name param from search method, since the file is already loaded on initialize method.
My implemetation would be:
def search_library term
#books.find_all {|b| b.title =~ term}
end
Since you already have "#books" in an enumerable you can use the select method to find books whose title have the search term as a substring.
def search_library(term)
matches = #books.select { |x| x.title.index(term) }
raise "No books have title like '#{term}'" if matches.empty?
matches
end

Resources