I have a simple ruby script I would like to run in the rails console, using bundle exec rails c
ids = [1, 2]
if ids.length() > 5
puts "More than 5 ids, quitting"
exit
end
ids.each do |id|
puts id
end
The rails console quits the program as soon as it sees the exit command. What would be the best way around this?
One way to do this is to use throw/catch:
ids = [1, 2]
catch(:bail) do
if ids.length > 5
puts "More than 5 ids, quitting"
throw :bail
end
ids.each do |id|
puts id
end
end
catch :bail defines a block that is labeled with the symbol :bail (the name doesn't really matter). It will execute normally until you throw :bail at which point Ruby zips up the call stack until its finds a corresponding catch. If no matching catch is found an UncaughtThrowError will be raised.
This is somewhat like break but can be used in any context and to break out of deeply nested constructs.
Another alternative would be to simply define a method or raise (and rescue) an exception if the input is invalid.
Raise ArgumentError When Passed Arguments are Somehow Invalid
There are a lot of ways to fix your code, but your essential problem is that Kernel#exit will exit both a standard REPL session and an application, not simply return from a method or break out of an if-statement. Since your code doesn't show that you're inside a method, you can't use return because there's no appropriate method or closure to return from. You could possibly work around calling #exit with nested irb sessions or the like, but that's ultimately solving the wrong problem.
In most cases, you want to raise an exception with a useful message rather than simply exit when something goes unfixably wrong. The following would do that in your application, while in an irb or pry console it will raise the exception but remain within your REPL session:
ids = [1, 2]
raise ArgumentError, "More than 5 ids: #{ids}" if ids.length > 5
ids.each { |id| puts id }
If you were inside a method, you could use #warn and then return an appropriate return value instead. However, since you are presumably inside the top-level object of your REPL, there's nothing to return from, so this is likely your best general-purpose option.
Related
What I'll show is a fulish function, but it's only to explain my problem in a simple way. If I solve the problem in this function, I solved the problem in the original code too.
The problem is simple, and probably the solution too, but I'm new in Ruby and have this doubt. I want to print the values in a range:
def test
(0...5).each do |i|
puts i
end
end
When I call the function, the result that I want is
0
1
2
3
4
but the result that I have is
0
1
2
3
4
0...5
Why this 0...5 is printed together? How can I avoid that?
i don't think the 0..5 is being produced as part of the puts call. Rather, when you call this in your REPL (irb, pry, rails console, etc), you're seeing because it's the last returned value in your code.
Let me show you an example.
Say I save a file called test.rb with the following content:
1.upto(5).each { |i| puts i }
If I call ruby test.rb, I see the expected output,
0
1
2
3
4
If I open irb and run require("./test.rb"), I see the same output.
It's only when I paste the code into irb that I see the additional output (=> 0...5). So I would just ignore this.
In addition to #Max's answer
Whenever any expression is executed in IRB sessions, it will also print the value returned by every expression executed.
In case of method definition it returns the method_name just defined.
> def my_method
?> puts 'this is my method'
?> end
=> :my_method
You see, the :my_method is printed
When the method is invoked, it should print the value returned by the method execution i.e. response of the last expression in the method i.e. puts
> my_method
this is my method
=> nil
but it printed nil because puts always returns nil. I mentioned this because normally developers are astonished when they see their methods returning nil unexpectedly.
My question involves Ruby's find method and why it works in one situation but not in another. NOTE: I'm using this method in a Cucumber step definition written in Ruby.
This code block works the way I want it to:
Then(/^I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow$/) do
#boltworkflowservice ||= BoltWorkflowService.new
Watir::Wait.until(60) do
#job_family = #boltworkflowservice.get_workflows_job_family(#job_id).json
#job_family.find { |job| job['type'] == "IngestUpdateWorkflow" }
end
expect(#job_family.find{|job| job['type'] == "IngestUpdateWorkflow"}.nil?).to eq(false), "IngestUpdateWorkflow child not found"
end
But I wanted to rewrite this code block to get rid of the instance variable and reduce the block by one line. I tried the following:
Then(/^I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow$/) do
#boltworkflowservice ||= BoltWorkflowService.new
Watir::Wait.until(60) do
puts #boltworkflowservice.get_workflows_job_family(#job_id).json
#boltworkflowservice.get_workflows_job_family(#job_id).json.find {|job| job['type'] == "IngestUpdateWorkflow"}
end
expect(#job_family.find{|job| job['type'] == "IngestUpdateWorkflow"}.nil?).to eq(false), "IngestUpdateWorkflow child not found"
end
I just added the puts to see what the code would return. According to the puts, it returns an array that contains three hash objects but I still get an error. Here is what I get when I try to run the second block:
[{"id"=>914295, "type"=>"CrossrefDepositsDaf", "subType"=>"", "parentId"=>0, "state"=>"SUCCESS", "subState"=>"SUCCESS", "dataPointer"=>"s3://sequoia-install/app-resources/test.cirrostratus.org/workflows/CrossrefDepositsDaf/2015/7/29/148c7ebc-3da4-4650-a22e-2e8962d448fc", "created"=>"2015-07-29 01:58:14", "metadata"=>[]}, {"id"=>914296, "type"=>"CrossRefDeposits", "subType"=>"BoltCatDaf", "parentId"=>914295, "state"=>"SUCCESS", "subState"=>"SUCCESS", "dataPointer"=>"s3://sequoia-install/app-resources/test.cirrostratus.org/workflows/CrossRefDeposits/2015/7/29/3a703736-d4a2-456e-baa9-7bf2c0f4a0e2", "created"=>"2015-07-29 01:58:17", "metadata"=>[]}, {"id"=>914297, "type"=>"IngestUpdateWorkflow", "subType"=>"CrossRefDeposits", "parentId"=>914296, "state"=>"SUCCESS", "subState"=>"CREATED", "dataPointer"=>"s3://sequoia-install/app-resources/test.cirrostratus.org/workflows/IngestUpdateWorkflow/2015/7/29/9282591b-f93c-49d6-b656-4afbd14156ef", "created"=>"2015-07-29 01:58:23", "metadata"=>[]}]
NoMethodError: undefined method `find' for nil:NilClass
./features/step_definitions/catdaf_integration_crossref_deposits_steps.rb:42:in `/^I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow$/'
C:\Users\Dugan23\workspace\cloudy_clouds\cucumber\features\content_management\daf\integration\crossref_deposits.feature:11:in `And I expect the bolt service children for CrossrefDepositsDaf to include only: IngestUpdateWorkflow'
1 scenario (1 failed)
6 steps (1 failed, 5 passed)
9m30.221s
Process finished with exit code 1
Can somebody please explain to me why the second block of code doesn't work? The puts shows that I should be feeding the find method an array so why does it say that I"m trying to run the find method on a nil:NilClass. Thank you in advance for any and all of your help.
NOTE: When I added a puts statement to the first block of code to print out #job_family it returned the same exact array as the second block of code. Does this mean that in the second block of code Ruby is trying to run the find method before evaluating the code before it? I tried to remedy this by adding parentheses as follows, but to no avail:
(#boltworkflowservice.get_workflows_job_family(#job_id).json).find {|job| job['type'] == "IngestUpdateWorkflow"}
In the second version, you have avoided using #job_family inside the block, but still you are referring to it on the expect, which obviously would be nil unless you have defined it elsewhere
The code below defines a hook Kernel#at_exit to capture an exception and do things at exit, and then raises a Sass::SyntaxError by passing an invalid SASS string.
require "sass"
module Kernel
at_exit do
puts "--Before backtrace"
p $!
$!.backtrace
puts "--After backtrace"
p $!
end
end
Sass::Engine.new("Invalid {sass").render
The output it gives is as below:
...
--Before backtrace
#<Sass::SyntaxError: ...>
--After backtrace
nil
...
It indicates that $! was a Sass::SyntaxError, but it became nil right after backtrace has been called on it. Why did $! change just by calling backtrace on it?
This effect does not seem to happen when Sass::SyntaxError is raised manually as follows:
raise Sass::SyntaxError.new("foo")
or when a different type of error is raised (may be wrong).
Edit
I am not sure, but probably sass manipulates the backtrace using set_backtrace when a sass error is raised. This is to provide information about where the sass syntax error was caused in a sass file. And the different behaviour between manually raising an error and programatically raising an error is reminiscent of a bug in Ruby 2.1 that half-way implemented backtrace_locations, but returned nil in some cases. I have a broad guess that these factors are interfering, but am not sure.
The reason it is happening is because this method overrides backtrace method:
def backtrace
return nil if super.nil?
return super if sass_backtrace.all? {|h| h.empty?}
sass_backtrace.map do |h|
"#{h[:filename] || "(sass)"}:#{h[:line]}" +
(h[:mixin] ? ":in `#{h[:mixin]}'" : "")
end + super
end
Where sass_backtrace is an array of hashes populated in initializer. Line which causes $! to be nil is:
return super if sass_backtrace.all? {|h| h.empty?}
This happens only when all? returns nil. I did some fiddling with it, and I found out that the problem always occurs, when we call any iterator which doesn't finish the whole iteration (all? terminates iteration when encounter the first not satisfying element). The problem might be simply reproduced with:
at_exit do
p $! #=> #<RuntimeError: hello>
[<any_non_empty_array>].all? {false}
# Those would break $! as well
# [<ANA>].any? {true}
# [1,2,3].find {|n| n.even?}
# Those will not break $!
# [<ANA>].any? {false}
# [<ANA>].all? {true}
# [1,2,3].find {|n| n > 4}
p $! #=> nil
end
raise 'hello'
The only reason I can think of why it would work like that is that ruby loops are controlled internally with exceptions. When iteration is to be stopped, it is done with special type of exception being raised and rescued outside the loop.
My guess is that Ruby creators didn't want this control exception to be visible in $! variable, since this would suggest that something went wrong, and decided to set it back to nil.
Is there a way of doing the old "on error resume next" routine in ruby?
I've got array of value filled in dynamically from elsewhere (read from MQTT topics to be precise) then I want to do a bunch of numeric calculations on them and publish the results. The values SHOULD be numeric but are possibly missing or non-numeric.
At the moment my code looks something like
values=[]
//values get loaded here
begin
Publish('topic1',value[0]*10+value[1])
rescue TypeError,NoMethodError,ZeroDivisionError
end
begin
Publish('topic2',value[3]/value[4])
rescue TypeError,NoMethodError,ZeroDivisionError
end
//etc etc
If the calculation fails for any reason the program should just skip that step and go on.
It works but surely theres a better way than all those identical begin..rescue blocks? Ruby is about "DRY" after all..
Is there a way of re-writing the above so that a single begin..rescue construct is used while still allowing all calculations to be attempted?
UPDATED
How safe to do something like
def safe_Publish(topic,value)
return if value.nil?
Publish(topic,value)
end
and call with
safe_Publish('topic2',(value[3]/value[4] rescue nil))
The main problem is that the above catches ALL exceptions not just the ones I'm expecting which makes me a little nervous.
The on error resume next coding style is really dangerous - as it makes finding new bugs you accidentally introduce to your program very hard to find. Instead, I would just write a different version of publish that doesn't throw those exceptions:
def try_publish(topic_name)
begin
Publish('topic1',yield)
rescue TypeError,NoMethodError,ZeroDivisionError
# are you sure you don't want to do anything here? Even logging the errors
# somewhere could be useful.
end
end
You can then call this with:
try_publish('topic1') { value[0]*10+value[1] }
If TypeError,NoMethodError or ZeroDivisionError are thrown by the expression, they will be caught and ignored.
Now your original method won't require any rescues.
If you really wanted an on error resume next, you could possibly do it by monkey patching the raise method in Kernel, but that would be a horrible idea.
If you think a bit more carefully about what you are doing, and why you want on error resume next, I think you will see that you don't really need to suppress all exceptions. As the other posters pointed out, that would make it hard to find and fix bugs.
Your problem is that you have a bunch of numbers scraped from the Internet, and want to run some calculations on them, but some may be invalid or missing. For invalid/missing numbers, you want to skip over any calculations which would use those numbers.
A few possible solutions:
Pre-filter your data and remove anything which is not a valid number.
Put each calculation you want to do into a method of its own. Put a rescue Exception on the method definition.
Define "safe" wrappers for the numeric classes which don't raise exceptions on divide by zero, etc. Use these wrappers for your calculations.
The "wrappers" might look something like this (don't expect complete, tested code; this is just to give you the idea):
# This is not designed for "mixed" arithmetic between SafeNumerics and ordinary Numerics,
# but if you want to do mixed arithmetic, that can also be achieved
# more checks will be needed, and it will also need a "coerce" method
class SafeNumeric
attr_reader :__numeric__
def initialize(numeric)
#__numeric__ = numeric.is_a?(String) ? numeric.to_f : numeric
end
def zero?
#__numeric__.zero?
end
def /(other)
if other.zero? || #__numeric__.nil? || other.__numeric__.nil?
SafeNumeric.new(nil) # could use a constant for this to reduce allocations
else
SafeNumeric.new(#__numeric__ / other.__numeric__)
end
end
def to_s; #__numeric__.to_s; end
def inspect; #__numeric__.inspect; end
# methods are also needed for +, -, *
end
Then use it like:
numbers = scraped_from_net.map { |n| SafeNumeric.new(n) }
# now you can do arithmetic on "numbers" at will
This shows how to wrap a bunch of quick operations into a loop with each one being protected by a begin/rescue:
values = [1,2,3,0,4]
ops = [ ->{values[0]/values[1]}, ->{values[2]/values[3]} ]
ops.each do |op|
begin
puts "answer is #{op.call}"
rescue ZeroDivisionError
puts "cannot divide by zero"
end
end
I prefer the safe_publish method, however, as you can unit test that and it encapsulates the logic of making safe calls and handling errors in a single place:
def safe_publish(topic, &block)
begin
value = block.call
publish(topic, value)
rescue
# handle the error
end
end
and then you can call this with code like:
safe_publish 'topic0' do
value[0]*10+value[1]
end
In the Poignant Guide this example of the redo keyword is given:
class LotteryTicket
def self.new_random
new(rand(25) + 1, rand(25) + 1, rand(25) + 1)
rescue ArgumentError
redo
end
end
It's supposed to keep calling new until all three random numbers are unique. But after I typed this code in and ran it a few times, I got this error: LocalJumpError: unexpected redo. I looked up the redo keyword elsewhere and it looks like it is only supposed to work for loops and iterators. So why did why try to use it like this in his example? How should this method be rewritten to work correctly?
He must have meant to use retry, not redo.
redo restarts a block:
l = lambda {puts "hi"; redo}
l.call