Ruby, Create a Custom Error Message - ruby-on-rails

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.

Related

Rspec: Getting error while testing private method

Getting error while testing a private method. Please suggest how to test private method is called from a public method.
Public
def public_method
private_method
end
Private
def private_method
tries = 0
begin
raise Product::StaleObjectError.new("Product is changed while you were editing") if stale_object?
// Do some work
raise Exception.new("Total amount used is greater than approved") if total_approved < 0
// Save Product
rescue Product::StaleObjectError => e
if tries < MAX_RETRIES
tries += 1
sleep(1 + tries)
reload
retry
else
raise Product::StaleObjectError("Product is changed while you were editing")
end
end
attributes
end
Test Case:
before(:each) do
#prod_v1 = Product.new
end
it 'test private method called' do
expect_any_instance_of {Product}.to receive(:private_method)
#prod_v1.public_method
end
I am getting following error for test case
Failure/Error: expect_any_instance_of {Product}.to receive(:)
ArgumentError:
wrong number of arguments (0 for 1)
According to the documentation for expect_any_instance_of, this will receive the class as a method argument, so you should use parenthesis instead of curly braces:
it 'test private method called' do
expect_any_instance_of(Product).to receive(:private_method)
...
end

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

Getting undefined method error in RSpec

I'm using RSpec and FactoryGirl for testing my models and I'm stuck at "highest_priority" method which can't be seen by RSpec for some reason.
Here's the method itself:
models/task.rb
class Task < ActiveRecord::Base
#some stuff
def self.highest_priority
p = Task.order(:priority).last.try(:priority)
p ? p + 1 : 1
end
end
And when I run task_spec.rb
require 'spec_helper'
describe Task do
it "returns highest priority" do
last_task = FactoryGirl.build(:task, priority: "5")
last_task.highest_priority
expect(last_task(:priority)).to eq("6")
end
end
I get the following error:
When I'm calling this method in my controller like this
def create
#task = current_user.tasks.build(task_params)
#task.highest_priority
#task.complete = false
respond_to do |format|
if #task.save
format.js
else
format.js
end
end
end
And the method looks like
def highest_priority
self.maximum(:priority).to_i + 1
end
I'm getting
First of all, you better use ActiveRecord's maximum instead of ordering and then picking one, you'll avoid the instance initialization and get a number directly from the query
Task.maximum(:priority)
this could be put in a class method like this
def self.maximum_priority
Task.maximum(:priority) || 0 # fall back to zero if no maximum exists
end
Then for the second half which is updating the method, i would create an instance method for that, and using the class method
def set_maximum_priority
self.priority = self.class.maximum_priority + 1
self
end
Note that I returned self at the end for chainability
Then your action would become something like this
def create
#task = current_user.tasks.build(task_params).set_maximum_priority
#task.complete = false
...
end
You need to create the method as an instance method of Task model. Like below :
class Task < ActiveRecord::Base
#some stuff
def highest_priority
p = Task.order(:priority).last.try(:priority)
p ? p + 1 : 1
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'.

How to test such class behavior in Rspec

I have a class which is responsible for dealing with some response from payments gateway.
Let's say:
class PaymentReceiver
def initialize(gateway_response)
#gateway_response = gateway_response
end
def handle_response
if #gateway_response['NC_STATUS'] != '0'
if order
order.fail_payment
else
raise 'LackOfProperOrder'
# Log lack of proper order
end
end
end
private
def order
#order ||= Order.where(id: #gateway_response['orderID']).unpaid.first
end
end
In payload from payment I've NC_STATUS
which is responsible for information if payment succeed and orderID which refers to Order ActiveRecord class byid`.
I would like to test behavior(in rspec):
If PaymentReceiver receives response where NC_STATUS != 0 sends fail_payment to specific Order object referred by orderID.
How you would approach to testing this ? I assume that also design could be bad ...
You have to make refactorization to remove SRP and DIR principles violations.
Something below I'd say:
class PaymentReceiver
def initialize(response)
#response = response
end
def handle_response
if #response.success?
#response.order.pay
else
#response.order.fail_payment
end
end
end
# it wraps output paramteres only !
class PaymentResponse
def initialize(response)
#response = response
end
def order
# maybe we can check if order exists
#order ||= Order.find(#response['orderID'].to_i)
end
def success?
#response['NCSTATUS'] == '0'
end
end
p = PaymentReceiver.new(PaymentResponse({'NCSTATUS' => '0' }))
p.handle_response
Then testing everything is easy.

Resources