How to specify function for stub when using minitest mocha? - ruby-on-rails

Is it possible to specify which find_by! exception is raised in the following example (I want the second one to be raised, not the first):
def self.test
Instance.stubs(:find_by!).raises(ActiveRecord::RecordNotFound)
begin
function_one
rescue ActiveRecord::RecordNotFound
puts 'Failure'
end
begin
function_two
rescue ActiveRecord::RecordNotFound
puts 'Success'
end
end
def self.function_one
Model.find_by!(id: 1)
end
def self.function_two
Model.find_by!(id: 1)
end
*Assume id: 1 does not exist. As in the example, also assume these will be static class functions, but please mention any differences in the case that add any_instance will not be enough for instance methods.

How about this?
Instance.stubs(:find_by!).returns('result').then.raises(ActiveRecord::RecordNotFound)

Related

Best way to break and return a value

I have a method that's nested within a couple other methods, and I want this method to break from all recursive methods with a returned error. For instance, if I have pay_order being called here:
class API::StripeController < ApiController
def my_api_action
# ...
order = create_order
pay_order(order, token)
# do things if payment is successful
end
end
And pay_order is defined as:
def pay_order(order, token)
begin
order.pay(source: token)
rescue Stripe::CardError => e
break e
end
end
How do I break out of all parent methods and return the card error coming from the failed payment? I need something like break and return e or return e and break. But I know both the break and return statements immediately return, so I can't chain them (I don't think).
I could just add return statements to each function being called, but I plan on using this method in lots of places, and don't plan on ever needing it to behave differently, so I'm looking for the most reusable way to write it.
Why do you rescue inside the pay_order method? I'd rescue on the outer loop.
Given the following:
def method_a
10.times do |loop_a|
method_b
end
end
def method_b
5.times do |loop_b|
pay_order
end
end
def pay_order
...
end
I'd rescue inside method_a, for example:
def method_a
10.times do |loop_a|
method_b
end
rescue Stripe::CardError => e
# do whatever. no need to break
end
All the loops are "broken" automatically by the raising of the exception.
If you want to do something with the exception inside the pay_order method, then I would suggest to rescue and raise again:
def pay_order
order.pay
rescue Stripe::CardError => e
# do your stuff
raise e
end
Basically you can use throw, or raise (respectively fail, see here for a discussion whether to use raise or fail).
The difference is that with raise/fail, you are producing an exception (which you catch with rescue), and the Exception object can contain as payload the data you want to return.
With throw, you are simply doing kind of a goto upward the call chain. The landing point is defined using catch (see for instance here). This does not have any payload, which means that you need to "transport" any data back using an object which is accessible on both sides (for instance an instance variable).

How to access perform parameters in ActiveJob rescue

I am wondering on how do you access ActiveJob perform parameters in the resue block, such as
def perform object
end
rescue_from Exception do |e|
if e.class != ActiveRecord::RecordNotFound
**job.arguments.first**
# do something
end
end
Thank You !!
It is possible with arguments inside the rescue_from block:
rescue_from(StandardError) do |exception|
user = arguments[0]
post = arguments[1]
# ...
end
def perform(user, post)
# ...
end
This also works for callbacks as well (e.g. inside after_perform).
I had no idea on that as well, then simply decided to try and use self inside the rescue_from block, and it worked.
rescue_from(StandardError) do |ex|
puts ex.inspect
puts self.job_id
# etc.
end
As a side note -- NEVER ever rescue Exception:
Why is it a bad style to `rescue Exception => e` in Ruby?
You can access all Bindings by ex.bindings. To make sure you get the correct binding for your job you should check the receiver like this1:
method_binding = ex.bindings.find { |b| b.receiver.is_a?(self.class) }
Then you can get all local variables with .local_variable_get. Since method arguments are also local variables, you can at least explicitly fetch them:
user = method_binding.local_variable_get(:user)
post = method_binding.local_variable_get(:post)
So for you example:
def perform object
end
rescue_from Exception do |e|
if e.class != ActiveRecord::RecordNotFound
method_binding = ex.bindings.find { |b| b.receiver.is_a?(self.class) }
object = method_binding.local_variable_get(:object)
# do something
end
end
1. It is still possible that this binding is not the one of perform if you call other instance methods in your job's perform method and the error happens there. This can also be taken in account but is left out for brevity.

How to create a ActiveRecord::RecordInvalid for testing?

I have this piece of code that I am trying to test:
def error_from_exception(ex)
if ex.is_a?(ActiveRecord::RecordInvalid)
...
To get into the if block, I need to pass in the correct ex param.
How do I create an ActiveRecord::RecordInvalid?
With rspec, I'm trying to do something like this:
context 'exception is ActiveRecord::RecordInvalid' do
it 'returns the validation error' do
begin
raise ActiveRecord::RecordInvalid
rescue Exception => ex
binding.pry
###
# From my pry session:
# $ ex
# $ <ArgumentError: wrong number of arguments (0 for 1)>
# $ ex.class
# $ ArgumentError < StandardError
###
end
end
end
How do I find out what argument type the library is looking for?
RecordInvalid link
None of the above methods worked for me so I finally did the following while doing a spec:
class InvalidRecord
include ActiveModel::Model
end
raise ActiveRecord::RecordInvalid.new(InvalidRecord.new)
Hope it helps!
EDIT: This is now possible in Rails 5.1.1. The record argument is no longer required after this commit: https://github.com/rails/rails/commit/4ff626cac901b41f86646dab1939d2a95b2d26bd
If you are on a Rails version under 5.1.1, see the original answer below:
It doesn't seem like it is possible to raise an ActiveRecord::RecordInvalid by itself. If you look at the source code for ActiveRecord::RecordInvalid, it requires a record when initializing:
class RecordInvalid < ActiveRecordError
attr_reader :record # :nodoc:
def initialize(record) # :nodoc:
#record = record
...
end
end
(source: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/validations.rb)
Something you could do to work around this would be to simply create an actual record that is invalid and attempt to save it using save! (such as calling User.new.save! when User.name is required). However, keep in mind that this might potentially become a problem in the future if the model you use is changed and it becomes valid inside your test (User.name is no longer required).
I needed to do something similar to test my code's handling of ActiveRecord::RecordInvalid. I wanted to do
allow(mock_ar_object).to receive(:save!).and_raise(ActiveRecord::RecordInvalid)
but that gives ArgumentError: wrong number of arguments (0 for 1) when RSpec tries to instantiate RecordInvalid.
Instead I wrote a RecordInvalid subclass and overrode initialize like this:
class MockRecordInvalid < ActiveRecord::RecordInvalid
def initialize
end
end
allow(mock_ar_object).to receive(:save!).and_raise(MockRecordInvalid)
Then a rescue ActiveRecord::RecordInvalid will catch MockRecordInvalid and MockRecordInvalid.new.is_a?(ActiveRecord::RecordInvalid) is true.
ActiveRecord::RecordInvalid needs an object to be created.
If you want to test just the exception itself, try:
null_object = double.as_null_object
ActiveRecord::RecordInvalid.new(null_object)
Understand the double as null object here (https://www.relishapp.com/rspec/rspec-mocks/v/2-6/docs/method-stubs/as-null-object)
I am using update_attribute(attr, 'value') instead of save! and I can mock the update_attribute method as follows:
expect(mock_object).to receive(:update_attribute).with(attr, 'value').and_raise(ActiveRecord::RecordInvalid)
We can stub it to the Model like this,
ModelName.any_instance.stubs(<method_name>).raises(ActiveRecord::RecordInvalid.new(record))
Example:
Post.any_instance.stubs(:save!).raises(ActiveRecord::RecordInvalid.new(post))

Raise custom Exception with arguments

I'm defining a custom Exception on a model in rails as kind of a wrapper Exception: (begin[code]rescue[raise custom exception]end)
When I raise the Exception, I'd like to pass it some info about a) the instance of the model whose internal functions raise the error, and b) the error that was caught.
This is going on an automated import method of a model that gets populated by POST request to from foreign datasource.
tldr; How can one pass arguments to an Exception, given that you define the Exception yourself? I have an initialize method on that Exception but the raise syntax seems to only accept an Exception class and message, no optional parameters that get passed into the instantiation process.
create an instance of your exception with new:
class CustomException < StandardError
def initialize(data)
#data = data
end
end
# => nil
raise CustomException.new(bla: "blupp")
# CustomException: CustomException
Solution:
class FooError < StandardError
attr_reader :foo
def initialize(foo)
super
#foo = foo
end
end
This is the best way if you follow the Rubocop Style Guide and always pass your message as the second argument to raise:
raise FooError.new(foo), 'argh'
You can get foo like this:
rescue FooError => error
error.foo # => 1234
error.message # => 'argh'
If you want to customize the error message then write:
class FooError < StandardError
attr_reader :foo
def initialize(foo)
super
#foo = foo
end
def message
"The foo is: #{foo}"
end
end
This works great if foo is required. If you want foo to be an optional argument, then keep reading.
If you don't follow the Rubocop Style Guide
And want this to work:
raise FooError.new('argh', foo)
You need to pass the message to super as the only argument:
class FooError < StandardError
attr_reader :foo
def initialize(message, foo)
super(message)
#foo = foo
end
end
Explanation:
Pass your message as the second argument to raise
As the Rubocop Style Guide says, the message and the exception should be passed separately. If you write:
raise FooError.new('argh')
And want to pass a backtrace, there is no way to do it without passing the message twice:
raise FooError.new('argh'), 'argh', other_error.backtrace
You need to pass a backtrace if you want to re-raise an exception as a new instance with the same backtrace and a different message or data. Sometimes this is very useful.
Why is this so complicated?
The crux of the problem is a design flaw in Ruby: exception messages get set in two different ways.
raise StandardError, 'argh' # case 1
raise StandardError.new('argh') # case 2
In case 1, raise just calls StandardError.new('argh'), so these are the same. But what if you pass an exception instance and a message to raise?
raise FooError.new(foo), 'argh', backtrace
raise will set 'argh' as the message on the FooError instance, so it behaves as if you called super('argh') in FooError#initialize.
We want to be able to use this syntax, because otherwise, we'll have to pass the message twice anytime we want to pass a backtrace:
raise FooError.new(foo, 'argh'), 'argh', backtrace
raise FooError.new('argh', foo), 'argh', backtrace
But what if foo is optional? Then FooError#initialize is overloaded.
raise FooError, 'argh' # case A
raise FooError.new(foo), 'argh' # case B
In case A, raise will call FooError.new('argh'), but your code expects an optional foo, not a message. This is bad. What are your options?
accept that the value passed to FooError#initialize may be either foo or a message.
Don't use case A style. If you're not passing foo, write raise FooError.new(), 'argh'
Make foo a keyword argument
IMO, don't do 2. The code's not self-documenting, so you have to remember all of this. Too complicated.
If you don't want to use a keyword argument, my implementation of FooError way at the top of this answer actually works great with 1. This is why FooError#initialize has to call super and not super(). Because when you write raise FooError, 'argh', foo will be 'argh', and you have to pass it to the parent class to set the message. The code doesn't break if you call super with something that isn't a string; nothing happens.
3 is the simplest option, if you're ok with a keyword argument - h/t Lemon Cat. Here's the code for that:
class FooError < StandardError
attr_reader :foo
def initialize(message, foo: nil)
super(message)
#foo = foo
end
end
raise FooError, 'message', backtrace
raise FooError(foo: foo), 'message', backtrace
Here is a sample code adding a code to an error:
class MyCustomError < StandardError
attr_reader :code
def initialize(code)
#code = code
end
def to_s
"[#{code}] #{super}"
end
end
And to raise it:
raise MyCustomError.new(code), message
TL;DR 7 years after this question, I believe the correct answer is:
class CustomException < StandardError
attr_reader :extra
def initialize(message=nil, extra: nil)
super(message)
#extra = extra
end
end
# => nil
raise CustomException.new('some message', extra: "blupp")
WARNING: you will get identical results with:
raise CustomException.new(extra: 'blupp'), 'some message'
but that is because Exception#exception(string) does a #rb_obj_clone on self, and then calls exc_initialize (which does NOT call CustomException#initialize. From error.c:
static VALUE
exc_exception(int argc, VALUE *argv, VALUE self)
{
VALUE exc;
if (argc == 0) return self;
if (argc == 1 && self == argv[0]) return self;
exc = rb_obj_clone(self);
exc_initialize(argc, argv, exc);
return exc;
}
In the latter example of #raise up above, a CustomException will be raised with message set to "a message" and extra set to "blupp" (because it is a clone) but TWO CustomException objects are actually created: the first by CustomException.new, and the second by #raise calling #exception on the first instance of CustomException which creates a second cloned CustomException.
My extended dance remix version of why is at: https://stackoverflow.com/a/56371923/5299483
Simple pattern for custom errors with additional information
If the extra information you're looking to pass is simply a type with a message, this works well:
# define custom error class
class MyCustomError < StandardError; end
# raise error with extra information
raise MyCustomError, 'Extra Information'
The result (in IRB):
Traceback (most recent call last):
2: from (irb):22
1: from (irb):22:in `rescue in irb_binding'
MyCustomError (Extra Information)
Example in a class
The pattern below has become exceptionally useful for me (pun intended). It's clean, can be easily modularized, and the errors are expressive. Within my class I define new errors that inherit from StandardError, and I raise them with messages (for example, the object associated with the error).
Here's a simple example, similar to OP's original question, that raises a custom error within a class and captures the method name in the error message:
class MyUser
# class errors
class MyUserInitializationError < StandardError; end
# instance methods
def simulate_failure
raise MyUserInitializationError, "method failed: #{__method__}"
end
end
# example usage:
MyUser.new.simulate_failure
# => MyUser::MyUserInitializationError (method failed: simulate_failure)
It's counterintuitive for programmers coming from e.g. Java, but the most effective way to do this is not to write a custom initializer, but rather to write your own replacement for the Exception::exception class method.
Per the Kernel#raise docs:
the first parameter should be an Exception class (or another object that returns an Exception object when sent an exception message). [Emphasis added.]
class MyException < StandardError
class << self
def exception(arg)
# per `Exception::exception` docs
return self if arg.nil? || self.equal?(arg)
return MyException.new(arg.to_s) unless arg.is_a?(MyModel)
# $! is a magic global variable holding the last raised
# exception; Kernel#raise will also inject it as the
# cause attribute of the exception we construct here
error_caught = $!
msg = custom_message_for(arg, error_caught)
ex = MyException.new(msg)
# … any additional initialization goes here
ex
end
private
def custom_message_for(my_model_instance, error_caught)
# …
end
end
end
This way, you can raise your custom exception normally, with a model instance instead of a string message, without having to remember to call new explicitly and upset RuboCop, as well as confusing Ruby programmers that come to your code later expecting the standard syntax.
begin
my_model.frob
rescue => e
raise MyException, my_model # works
end
raise MyException, 'some other reason' # also works
The message & initialization logic from MyException#exception could also go in a custom initializer, letting you just write MyException.new(arg, $!), but in that case make sure the initializer is smart enough to also handle a plain string message, and make sure it at some point calls super with a string message.
You can create an new instance of your Exception subclass, then raise that. For instance:
begin
# do something
rescue => e
error = MyException.new(e, 'some info')
raise error
end

memcached as an Object store in Rails

I am using Memcached as an Object Store with my Rails application where I store search results which are User objects in memcached
Now when I fetch the data out I get the Memcached Undefined Class/Module Error. I found a solution for this problem in this blog
http://www.philsergi.com/2007/06/rails-memcached-undefinded-classmodule.html
before_filter :preload_models
def preload_models
Model1
Model2
end
which recommends pre-loading the models before hand. I would like to know if there is a more elegant solution to this problem and are there any drawbacks in using the preloading technique.
Thanks in advance
I had this problem as well and I think i came up with a nice solution.
You can overwrite the fetch method and rescue the error and load the right constants.
module ActiveSupport
module Cache
class MemCacheStore
# Fetching the entry from memcached
# For some reason sometimes the classes are undefined
# First rescue: trying to constantize the class and try again.
# Second rescue, reload all the models
# Else raise the exception
def fetch(key, options = {})
retries = 2
begin
super
rescue ArgumentError, NameError => exc
if retries == 2
if exc.message.match /undefined class\/module (.+)$/
$1.constantize
end
retries -= 1
retry
elsif retries == 1
retries -= 1
preload_models
retry
else
raise exc
end
end
end
private
# There are errors sometimes like: undefined class module ClassName.
# With this method we re-load every model
def preload_models
#we need to reference the classes here so if coming from cache Marshal.load will find them
ActiveRecord::Base.connection.tables.each do |model|
begin
"#{model.classify}".constantize
rescue Exception
end
end
end
end
end
end
Ran across this today, managed to come up with a more terse solution that should work for all classes.
Rails.cache.instance_eval do
def fetch(key, options = {}, rescue_and_require=true)
super(key, options)
rescue ArgumentError => ex
if rescue_and_require && /^undefined class\/module (.+?)$/ =~ ex.message
self.class.const_missing($1)
fetch(key, options, false)
else
raise ex
end
end
end
Not sure why [MemCacheStore] is not having is [MemCacheStore.const_missing] method called and everything getting called in the normal “Rails-y” way. But, this should emulate that!
Cheers,
Chris

Resources