Activerecord rescue - ruby

I have a ruby app that uses ActiveRecord but not Rails.
I want to rescue from all database errors which in my case can include a SQLite3::BusyException.
Is there a better way than to wrap every Model.find, Model.where, obj.save, etc in a rescue?
I thought adding a module to every model that monkey patches/hijacks DB actions such as, but where appears complex and something not to be trifled with:
def where
super
rescue ActiveRecord::RecordNotFound
rescue SQLite3::BusyException => e
p [:warning, e.message]
end

When using Celluloid I ran into a similar issue where I needed to trap errors due to the connection pooling not working correctly with how Celluloid was using fibers. I used something like the following wrapper method to help make sure errors were trapped and resolved the connection reaping in my code. For your scenario, it could look like this:
module TrackActiveRecordErrors
def db(&block)
begin
yield block
rescue StandardError => e
p [:warning, e.message]
# ... and other sort of logging, alerting you'd like
raise e
ensure
# Put your clean-up code here
end
end
end
In classes you want to use this wrapper:
class DoSomething
include TrackActiveRecordErrors
def find_something(id)
db do
a_something = Model.find(id)
end
a_something
end
end
It's not pretty, but it's a lot easier than trying to tune AR's magic in the model classes.

Related

How do I override a method in ruby for a one time rake task?

I want to run a service object in a rake task but need to change a method in a different service that my main service object calls. For example if I have:
main_service.rb
class MainService
def perform
SecondaryService.new.perform
end
end
secondary_service.rb
class SecondaryService
def perform
some_method
end
def some_method
puts 'something'
end
end
And I want to change some_method to be puts 'anything' for a one time data fix in a rake task, could I override it by simply redefining the method and is there a way to scope it to just the rake task? I don't want this service to accidentally be called while I run the rake task in case. I was thinking something like this:
one_time.rake
class SecondaryService
def some_method
puts 'anything'
end
end
def one_time_change
MainService.new.perform
end
The code you've suggested should work fine. You could go with that.
However, depending on the context, there is a risk that this strategy may have unintended consequences - i.e. What happens if you accidentally execute the class monkey-patching but expected the original behaviour to be preserved?
A more robust approach is to use dependency injection. By doing this, you can override behaviour with classical inheritance (or even by passing an entirely new object!). For example, something like:
class MainService
def perform(secondary_service: SecondaryService.new)
secondary_service.perform
end
end
class ModifiedSecondaryService < SecondaryService
def some_method
puts 'anything'
end
end
Now, for your one-off rake task, you can run:
PrimaryService.new.perform(secondary_service: ModifiedSecondaryService.new)

Is there a way to use a matcher like 'Verify' in rspec?

So far I'm using 'expect' in my test framework which will stop the execution when it meets a fail condition. I want something like, the execution should happen even if it meets the fail condition. I could see that there is a matcher called 'Verify' in rspec where I need to inherit 'Test::Unit::TestCase' class, but my issue is that, I need the matcher in my spec file which is not written under a ruby class.
There isn't a way to do this with RSpec, out of the box.
Because Rspec is designed to test small, isolated logics.
On failure, Rspec matchers raise Error, So what you can do is to wrap the matchers in a rescue block.
To satisfy your need, you could write a wrapper like this:
def report_last(&block)
begin
yield
rescue Exception => e
puts "Failure: #{e}"
end
end
In your test case:
describe Calculator do
it “should add 2 numbers” do
report_last do
expect(described_class.new(2, 3).sum)to eq(5)
end
end
end

Mocking a patch module

I've got a codebase which is tested in two scenarios: run via entry point A, and B. When it's run via A, the db connection is used as is. When it's run via B, ActiveRecord::Base.connection is monkey patched.
Since B is just a helper script, it's currently tested in rspec by running it as an external command and checking the output. I'd like to bring some sanity back and test the behaviour without spawning new processes though.
Is there a way in rspec mocks to "temporarily extend" a class? I'd like to get the behaviour of doing:
before do
ActiveRecord::Base.connection.extend(App::SomePatch)
end
after do
ActiveRecord::Base.connection.unextend(App::SomePatch)
end
Of course unextend doesn't exist. I have only 3 methods to patch, so I could potentially use the mocks for each method instead, but a method alias makes this complicated.
The patch module looks like this:
module SomePatch
def SomePatch.included(mod)
alias :old_execute :execute
end
def execute(*args) ... end
def some_storage
#some_storage ||= []
end
end
I would go with cloning, something along this lines:
before do
#original_connection = ActiveRecord::Base.connection
ActiveRecord::Base.connection = #original_commention.dup
ActiveRecord::Base.connection.extend(App::SomePatch)
end
after do
ActiveRecord::Base.connection = #original_connection
end
I did not test that, but as long there are not "quirks" with cloning the object, this should be fine.
Edit: Ok, this does not work, because there's no connection= method, so you can probably try with mocking:
before do
#original_connection = ActiveRecord::Base.connection
new_connection = #original_connection.dup
new_connection.extend(App::SomePatch)
allow(ActiveRecord::Base).to receive(:connection).and_return(new_connection)
end
And you probably don't need after because the mock will be "undone"

How to rescue ruby callbacks?

Is there a simple way to wrap ruby/rails with begin rescue blocks?
We're implementing the search functionality in our webapp, using ElasticSearch + Tire. Tire uses callbacks on models that we want to index on the ES server. Sometimes these callbacks fail for some reason.
I want to be able to rescue these errors - Is there a simple way of doing that?
Without you providing more detail, what about something like this
class Model < ActiveRecord::Base
after_save :my_callback
def my_callback
begin
# do the thing you want
rescue
# callback failed - raise error or whetever
end
end
end
I would check out http://mrchrisadams.tumblr.com/post/333036266/catching-errors-in-rails-with-rescue-from, which discusses the rescue_from capability and how to use it application wide.

Ruby exception inheritance with dynamically generated classes

I'm new to Ruby, so I'm having some trouble understanding this weird exception problem I'm having. I'm using the ruby-aaws gem to access Amazon ECS: http://www.caliban.org/ruby/ruby-aws/. This defines a class Amazon::AWS:Error:
module Amazon
module AWS
# All dynamically generated exceptions occur within this namespace.
#
module Error
# An exception generator class.
#
class AWSError
attr_reader :exception
def initialize(xml)
err_class = xml.elements['Code'].text.sub( /^AWS.*\./, '' )
err_msg = xml.elements['Message'].text
unless Amazon::AWS::Error.const_defined?( err_class )
Amazon::AWS::Error.const_set( err_class,
Class.new( StandardError ) )
end
ex_class = Amazon::AWS::Error.const_get( err_class )
#exception = ex_class.new( err_msg )
end
end
end
end
end
This means that if you get an errorcode like AWS.InvalidParameterValue, this will produce (in its exception variable) a new class Amazon::AWS::Error::InvalidParameterValue which is a subclass of StandardError.
Now here's where it gets weird. I have some code that looks like this:
begin
do_aws_stuff
rescue Amazon::AWS::Error => error
puts "Got an AWS error"
end
Now, if do_aws_stuff throws a NameError, my rescue block gets triggered. It seems that Amazon::AWS::Error isn't the superclass of the generated error - I guess since it's a module everything is a subclass of it? Certainly if I do:
irb(main):007:0> NameError.new.kind_of?(Amazon::AWS::Error)
=> true
It says true, which I find confusing, especially given this:
irb(main):009:0> NameError.new.kind_of?(Amazon::AWS)
=> false
What's going on, and how am I supposed to separate out AWS errors from other type of errors? Should I do something like:
begin
do_aws_stuff
rescue => error
if error.class.to_s =~ /^Amazon::AWS::Error/
puts "Got an AWS error"
else
raise error
end
end
That seems exceptionally janky. The errors thrown aren't class AWSError either - they're raised like this:
error = Amazon::AWS::Error::AWSError.new( xml )
raise error.exception
So the exceptions I'm looking to rescue from are the generated exception types that only inherit from StandardError.
To clarify, I have two questions:
Why is NameError, a Ruby built in exception, a kind_of?(Amazon::AWS::Error), which is a module?
Answer: I had said include Amazon::AWS::Error at the top of my file, thinking it was kind of like a Java import or C++ include. What this actually did was add everything defined in Amazon::AWS::Error (present and future) to the implicit Kernel class, which is an ancestor of every class. This means anything would pass kind_of?(Amazon::AWS::Error).
How can I best distinguish the dynamically-created exceptions in Amazon::AWS::Error from random other exceptions from elsewhere?
Ok, I'll try to help here :
First a module is not a class, it allows you to mix behaviour in a class. second see the following example :
module A
module B
module Error
def foobar
puts "foo"
end
end
end
end
class StandardError
include A::B::Error
end
StandardError.new.kind_of?(A::B::Error)
StandardError.new.kind_of?(A::B)
StandardError.included_modules #=> [A::B::Error,Kernel]
kind_of? tells you that yes, Error does possess All of A::B::Error behaviour (which is normal since it includes A::B::Error) however it does not include all the behaviour from A::B and therefore is not of the A::B kind. (duck typing)
Now there is a very good chance that ruby-aws reopens one of the superclass of NameError and includes Amazon::AWS:Error in there. (monkey patching)
You can find out programatically where the module is included in the hierarchy with the following :
class Class
def has_module?(module_ref)
if self.included_modules.include?(module_ref) and not self.superclass.included_modules.include?(module_ref)
puts self.name+" has module "+ module_ref.name
else
self.superclass.nil? ? false : self.superclass.has_module?(module_ref)
end
end
end
StandardError.has_module?(A::B::Error)
NameError.has_module?(A::B::Error)
Regarding your second question I can't see anything better than
begin
#do AWS error prone stuff
rescue Exception => e
if Amazon::AWS::Error.constants.include?(e.class.name)
#awsError
else
whatever
end
end
(edit -- above code doesn't work as is : name includes module prefix which is not the case of the constants arrays. You should definitely contact the lib maintainer the AWSError class looks more like a factory class to me :/ )
I don't have ruby-aws here and the caliban site is blocked by the company's firewall so I can't test much further.
Regarding the include : that might be the thing doing the monkey patching on the StandardError hierarchy. I am not sure anymore but most likely doing it at the root of a file outside every context is including the module on Object or on the Object metaclass. (this is what would happen in IRB, where the default context is Object, not sure about in a file)
from the pickaxe on modules :
A couple of points about the include statement before we go on. First, it has nothing to do with files. C programmers use a preprocessor directive called #include to insert the contents of one file into another during compilation. The Ruby include statement simply makes a reference to a named module. If that module is in a separate file, you must use require to drag that file in before using include.
(edit -- I can't seem to be able to comment using this browser :/ yay for locked in platforms)
Well, from what I can tell:
Class.new( StandardError )
Is creating a new class with StandardError as the base class, so it is not going to be a Amazon::AWS::Error at all. It is just defined in that module, which is probably why it is a kind_of? Amazon::AWS::Error. It probably isn't a kind_of? Amazon::AWS because maybe modules don't nest for purposes of kind_of? ?
Sorry, I don't know modules very well in Ruby, but most definitely the base class is going to be StandardError.
UPDATE: By the way, from the ruby docs:
obj.kind_of?(class) => true or false
Returns true if class is the class of obj, or if class is one of the superclasses of obj or modules included in obj.
Just wanted to chime in: I would agree this is a bug in the lib code. It should probably read:
unless Amazon::AWS::Error.const_defined?( err_class )
kls = Class.new( StandardError )
Amazon::AWS::Error.const_set(err_class, kls)
kls.include Amazon::AWS::Error
end
One issue you're running into is that Amazon::AWS::Error::AWSError is not actually an exception. When raise is called, it looks to see if the first parameter responds to the exception method and will use the result of that instead. Anything that is a subclass of Exception will return itself when exception is called so you can do things like raise Exception.new("Something is wrong").
In this case, AWSError has exception set up as an attribute reader which it defines the value to on initialization to something like Amazon::AWS::Error::SOME_ERROR. This means that when you call raise Amazon::AWS::Error::AWSError.new(SOME_XML) Ruby ends up calling Amazon::AWS::Error::AWSError.new(SOME_XML).exception which will returns an instance of Amazon::AWS::Error::SOME_ERROR. As was pointed out by one of the other responders, this class is a direct subclass of StandardError instead of being a subclass of a common Amazon error. Until this is rectified, Jean's solution is probably your best bet.
I hope that helped explain more of what's actually going on behind the scenes.

Resources