Mock scope method from ActiveRecord in Rails/RSpec? - ruby-on-rails

I have an ActiveRecord class like:
class DBGroup < ActiveRecord:Base
# ...
scope :example_method, -> { // does something }
# ...
end
And now I have another method in another class, something like
def method1
do #open block for rescue method
DBGroup
.example_method
.group(:some_column)
.having("COUNT(*) > 5")
rescue StandardError => e
e
end
end
And in RSpec I am testing kind of like:
it "my test, want to test rescue block"
expect_any_instance_of(DBGroup).to receive(:example_method).and_raise(StandardError)
expect{subject.method1}.to raise_error(StandardError)
end
But I am getting the error like:
RSpec::Mocks::MockExpectationError: DBGroup does not implement #example_method

Scopes are not instance methods, they are class methods, so you should not use expect_instance_of, but expect.
Also, you are rescuing the StandardError inside the method, so the method itself does not raise a StandardError. Actually, a StandardError is raised inside the method and it's rescued inside of it aswel and your test is all about what's happening at it's exterior.
I hope you can see the difference:
def method_that_raises_an_error
raise(StandardError)
end
def method_that_returns_an_error
raise(StandardError)
rescue StandardError => e
e
end
As you can see, your code is more like the second. So, to test it you should do:
expect(DBGroup).to(receive(:example_method).and_raise(StandardError)
method1_return = subject.method1
expect(method1_return).to(be_instance_of(StandardError))

Related

Where and how should I handle multiple exceptions?

I have problem with handling exceptions. I know how to do it, but I'm not sure what's the correct place to rescue them. For example:
class ExampleService
def call
...
raise ExampleServiceError, 'ExampleService message'
end
end
class SecondExampleService
def call
raise SecondExampleServiceError if something
ExampleService.call
rescue ExampleService::ExampleServiceError => e ---> should I rescue it here?
raise SecondExampleServiceError, e.message
end
end
Class ExampleController
def update
SecondExampleService.call
rescue ExampleService::ExampleServiceError, SecondExampleService::SecondExampleServiceError => e
render json: { error: e.message }
end
end
As you can see in example I have two Services. ExampleService raises his own exception. SecondExampleService call the ExampleService, can raise exception and is used in ExampleController. Where should I rescue ExampleServiceError? In ExampleController or SecondExampleService? If in ExampleController, what about when we use such a Service in multiple controllers (rescue code will be repeated many times)?
When the controller only calls SecondExampleService directly and doesn't know anything about ExampleService, then it should not rescue from ExampleServiceError.
Only SecondExampleService knows that ExampleService is used internally, and therefore SecondExampleService should rescue from ExampleServiceError and translate it into a SecondExampleServiceError.
That is my interpretation of the law of demeter.

How to forward a block to a method that generates methods

In my current project, I am having the following repetitive pattern in some classes:
class MyClass
def method1(pars1, ...)
preamble
# implementation method1
rescue StandardError => e
recovery
end
def method2(pars2, ...)
preamble
# implementation method2
rescue StandardError => e
recovery
end
# more methods with the same pattern
end
So, I have been thinking about how to dry that repetive pattern. My goal is to have something like this:
class MyClass
define_operation :method1, pars1, ... do
# implementation method1
end
define_operation :method2, pars2, ... do
# implementation method2
end
# more methods with the same pattern but generated with define_wrapper_method
end
I have tried to implement a kind of metagenerator but I have had problems for forwarding the block that would receive the generator. This is more or less what I have tried:
def define_operation(op_name, *pars, &block)
define_method(op_name.to_s) do |*pars|
preamble
yield # how can I do here for getting the block? <-----
rescue StandardError => e
recovery
end
end
Unfortunately, I cannot find a way for forwarding block to the define_method method. Also, it is very possible that the parameters, whose number is variable, are being passed to define_method in a wrong way.
I would appreciate any clue, help, suggestion.
You do not need metaprogramming to achieve this. Just add a new method that wraps the common logic like below:
class MyClass
def method1(param1)
run_with_recovery(param1) do |param1|
# implementation method1
end
end
def method2(param1, param2)
run_with_recovery(param1, param2) do |param1, param2|
# implementation method2
end
end
private
def run_with_recovery(*params)
preamble
yield(*params)
rescue StandardError => e
recovery
end
end
Test it here: http://rubyfiddle.com/riddles/4b6e2
If you really wanted to do metaprogramming, this will work:
class MyClass
def self.define_operation(op_name)
define_method(op_name.to_s) do |*args|
begin
puts "preamble"
yield(args)
rescue StandardError => e
puts "recovery"
end
end
end
define_operation :method1 do |param1|
puts param1
end
define_operation :method2 do |param1, param2|
puts param1
puts param2
end
end
MyClass.new.method1("hi")
MyClass.new.method2("hi", "there")
Test this here: http://rubyfiddle.com/riddles/81b9d/2
If I understand correctly you are looking for something like:
class Operation
def self.op(name,&block)
define_method(name) do |*args|
op_wrap(block.arity,*args,&block)
end
end
def op_wrap(arity=0,*args)
if arity == args.size || (arrity < 0 && args.size >= arity.abs - 1)
begin
preamble
yield *args
rescue StandardError => e
recovery
end
else
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected #{arity < 0 ? (arity.abs - 1).to_s.+('+') : arity })"
end
end
def preamble
puts __method__
end
def recovery
puts __method__
end
end
So your usage would be
class MyClass < Operation
op :thing1 do |a,b,c|
puts "I got #{a},#{b},#{c}"
end
op :thing2 do |a,b|
raise StandardError
end
def thing3
thing1(1,2,3)
end
end
Additionally this offers you both options presented as you could still do
def thing4(m1,m2,m3)
#m1 = m1
op_wrap(1,'inside_wrapper') do |str|
# no need to yield because the m1,m2,m3 are in scope
# but you could yield other arguments
puts "#{str} in #{__method__}"
end
end
Allowing you to pre-process arguments and decide what to yield to the block
Examples
m = MyClass.new
m.thing1(4,5,6)
# preamble
# I got 4,5,6
#=> nil
m.thing2('A','B')
# preamble
# recovery
#=> nil
m.thing3
# preamble
# I got 1,2,3
#=> nil
m.thing1(12)
#=> #<ArgumentError: wrong number of arguments (given 1, expected 3)>

Calling method_missing for undefined attributes in Rails after using "update" method

Is there a short way for obj.update(attr1: "something") method to call method_missing instead of raising the error ActiveModel::UnknownAttributeError (unknown attribute 'attr1' for Obj.)?
I am thinking about simply rescuing it and then mimicking/calling the method_missing, but this way feels too bulky.
From the source code (Rails 4.2) it seems that ActiveRecord::UnknownAttributeError is raised when the model instance does not respond_to? to the given attribute setter. So what you have to do is define a method_missing as well as respond_to_missing? in the model for all your dynamic attributes. This is actually what you always should do when using method_missing anyway:
class Model < ActiveRecord::Base
DYNAMIC_ATTRIBUTES = [:attr1, :attr2]
def method_missing(method_name, *arguments, &block)
if DYNAMIC_ATTRIBUTES.map { |attr| "#{attr}=" }.include?(method_name.to_s)
puts "custom code for #{method_name} #{arguments.first.inspect}"
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
DYNAMIC_ATTRIBUTES.map { |attr| "#{attr}=" }.include?(method_name.to_s) || super
end
end
Test in Rails console:
Model.first.update(attr1: 'aa', attr2: 'bb')
# => custom code for attr1= "aa"
# => custom code for attr2= "bb"
Model.first.update(attr1: 'aa', attr2: 'bb', attr3: 'cc')
# => ActiveRecord::UnknownAttributeError: unknown attribute 'attr3' for Model.
Something like this?
rescue_from ActiveModel::UnknownAttributeError do |exception|
raise NoMethodError
# or however you want to hanle
end
You can try this:
begin
obj.update(attr1: "something")
rescue Exception => ex
if ex.class == ActiveRecord::UnknownAttributeError
method_missing
else
raise ex
end
end

How to return a value without `return`

How can I return without return like devise's authenticate! method does?
class GroupsController < ApplicationController
def create
authenticate! # After here, the code below will not be excuted.
#group = Group.create(group_params)
redirect_to groups_path
end
end
I am wondering how to do this.
The devise authenticate! doesn't return on failure, it actually throw exceptions if the user isn't authenticated. The exception will get propagated all through the call chain, until it hits a matched rescue statement. The Rails framework is smart enough that it will rescue this kind of exception and convert specific exceptions to the corresponding HTTP status code, for example, ActiveRecord::RecordNotFound will be converted to 404.
This is common programming tricks to return from deep call hierarchy.
def a
raise "ha"
end
def b
puts "calling a"
a
not executed
end
def c
b rescue nil
end
c #=> calling a
And ruby provides catch/throw which serve for this kind of deeper jump.
catch(:ret) do
(1..5).each do |i|
(1..5).each do |j|
puts i * j
throw :ret if i * j > 3
end
end
end
Are you just asking how to return without using the keyword 'return'?
Ruby will automatically return the result from the last line in the method, if thats what you mean. but it would be the same as using the keyword 'return'.

RSpec having trouble stubbing method called with inline rescue

I'm trying test a method on a controller:
def a_method(list)
#users = []
list.each do |x|
if user=User.find(x) rescue nil
#users << user
end
end
end
In my Rspec example I have :
it "should do something" do
User.stub :find => 'user'
controller.a_method([1,2,3,4])
assigns[:users].should == ['user','user','user','user']
end
Problem:
it always rescues the find method user=User.find(x) rescue nil, even though I've stubbed it out.
If I remove the rescue nil it works fine.
Any ideas?
The if conditional doesn't accept a statement modifier such as rescue half way into the statement.
You can put the rescue at the end of the complete if statement as in:
if user=User.find(x)
#users << user
end rescue nil
why don't you use find_by_id instead of find?
find_by_id returns nil if the id does not exists instead of throwing the exception, it's almost the same you are doing but a little faster I guess and cleaner to read it

Resources