How to return a value without `return` - ruby-on-rails

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'.

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.

StandardError redirect to page

I was handed a project that another developer worked on, without leaving any documentation behind. The code fetches some purchases from a shopping website, looks for a price and notifies the user.
The app may encounter errors like "no results found" and then I raise a standarderror.
I want to redirect the user to the error page and notify them about it but I can't do that because it isn't a controller, so the redirect_to option doesn't work.
services/purchase_checker.rb is called once an hour:
def call
user.transaction do
store_purchase
if better_purchase?
update_purchase
end
end
rescue MyError=> e
store_error(e)
end
def store_error(error)
user.check_errors.create!(error_type: error.class.name, message: error.message)
end
services/my_error.rb:
class MyError< StandardError
def initialize(error_type, error_message)
super(error_message)
#error_type = error_type
end
attr_reader :error_type
end
services/purchase_fetcher.rb:
def parse_result_page
raise purchase_form_page.error if purchase_form_page.error.present?
offer = purchase_page.map{|proposal_section|
propose(proposal_section, purchase) }
.min_by(&:price)
offer or raise MyError.new("No results", "No results could be found")
end
you should create another err class, eg NotFoundError:
offer or raise NotFoundError.new("No results", "No results could be found")
then in your controller:
begin
parse_result_page
rescue NotFoundError => e
redirect_to err_page, :notice => e.message
end
Since this is running in a job, the best way to notify the user would be by email, or some other async notification method. When an error is detected, an email is sent.
If that's not an option for some reason, you can check if a user has check_errors in any relevant controllers. Looking at the store_error(error) method that is called when an error is found, it seems it's creating a new record in the Database to log the error. You should be able to check if a user has any error logged via the user.check_errors relationship.
You could do it like this, for example:
class SomeController < ActionController::Base
# ...
before_action :redirect_if_check_errors
# ...
def redirect_if_check_errors
# Assuming you're using Devise or something similar
if current_user && current_user.check_errors.exists?
redirect_to some_error_page_you_create_for_this_path
end
end
end
This will check for these errors in every action of SomeController and redirect the user to an error page you should create, where you render the errors in the user.check_errors relationship.
There are multiple ways to do this, but I still think sending an email from the Job is a better option if you want to actively notify the user. Or perhaps add an interface element that warns the user whenever user.check_errors has stuff there, for example.
I propose that you do this synchronously so that the response can happen directly in the request/response cycle. Perhaps something like this:
# controller
def search
# do your searching
# ...
if search_results.blank?
# call model method, but do it synchrously
purchase_check = PurchaseChecker.call
end
if purchase_check.is_a?(MyError) # Check if it's your error
redirect_to(some_path, flash: { warning: "Warn them"})
end
end
# model, say PurchaseChecker
def call
# do your code
rescue MyError => e
store_error(e)
e # return the error so that the controller can do something with it
end

Ruby, Create a Custom Error Message

Here's the code, but a lot of it is irrelevant:
class BankAccount
def initialize(first_name, last_name)
#first_name = first_name
#last_name = last_name
#balance = 0
end
def public_deposit(amount)
#balance += amount
end
def protected_deposit(amount)
#balance += amount
end
protected :protected_deposit
def private_deposit(amount)
#balance += amount
end
private :private_deposit
def call_private_deposit(amount)
private_deposit(amount)
end
def call_protected_deposit(amount)
protected_deposit(amount)
end
#To show that you can't call a private method with a different instance of the same class.
def private_add_to_different_account(account, amount)
account.private_deposit(amount)
end
#To show that you can call a protected method with a different instance of the same class.
def protected_add_to_different_account(account, amount)
account.protected_deposit(amount)
end
end
I load this code into irb using "load './visibility.rb'" and then create an instance:
an_instance = BankAccount.new("Joe", "Bloggs")
Then, I generate a NoMethodError by typing:
an_instance.protected_deposit(1000)
This returns a NoMethodError. This is intentional. However, what I want to happen is for a custom message to be returned instead of the standard NoMethodError - something like "This is a custom error message."
I've been hacking away at this for hours and I'm at my wits end. I'm a relative beginner, so please bear this in mind.
Thanks.
You can rescue the error:
def call_protected_method
instance = BankAccount.new("Joe", "Bloggs")
instance.protected_deposit(1000)
rescue NoMethodError
puts "You called a protected method"
end
If you want to return a custom message, you can rescue the error and raise your own custom exception. Ruby lets you define your own exception classes. But I can't imagine why you would want to do this. You already have a built in exception to handle this.

return from method used by before_filter

I've inherited some rails code that checks to see if a user is defined in a method that is called by a before filter:
before_filter :get_user
def get_user
#user = User.find(params[:id])
if !#user
return false
end
end
Now, the problem is that this doesn't work :) If the user isn't found, we don't return from the controller, we just return from the get_user() method and then continue execution in show() or update() method with #user set to nil.
My simple solution is to add a redirect to get_user() if #user is nil:
def get_user
#user = User.find(params[:id])
if !#user
redirect_back
return false
end
end
Now, my test are passing and everything seems right with the world. But ... I don't understand what's going on. Can someone please explain why the return in get_user() didn't stop execution in the controller completely but only breaks us out of get_user() and causes us to fall into the controller method that was originally called?
Thanks!
http://guides.rubyonrails.org/action_controller_overview.html#filters
"The method simply stores an error message in the flash and redirects to the login form if the user is not logged in. If a "before" filter renders or redirects, the action will not run. If there are additional filters scheduled to run after that filter, they are also cancelled."
Pretty self explanatory, but the nature is that you can't return to halt execution in a filter.
Return inside an method just returns and break that method and nothing else. Se code below.
def foo
return "foo"
return "bar"
end
puts my_method # this will puts "foo" and the second return will never be called.
However, in ruby you still can execute code after an return with ensure.
def bar
return "bar"
ensure
#bar = 'Hello world'
end
puts bar # returns and prints "bar"
puts #bar # prints "Hello world" because the ensure part was still executed
And keep in mind that the last executed code in your method will be returned, so you don't always need to write return before your value. And if you have an ensure part in your method the last executed code before that will be returned if you haven't returned something already.
And theres no need to return false in your before filters. If i remember right rails before version 3.1 did stop the controller when an before filter was returning an falsy value. Nil is still falsy in ruby and to strip some rows you cud write your filter like below because if no user is found #user will be nil in this example.
def get_user
#user = User.find_by id: params[:id] # I use find_by to prevent exception, else we may return an 500 error which you may or may not want.
redirect_back unless #user
end
I would rewrite this code this way
def get_user
redirect_back if User.where(id: params[:id]).empty?
end
for two reasons. First, why if and all that if you can check it in a more simple way. Second, find raises an exception if object is not found so this check makes no sense at all!

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