In my app, I want to avoid records being deleted when they are used in associated records. For example if a business partner has documents (as sender OR receiver), it should not be destroyed and the user should get a flash message.
I use below code, yet get an exception.
before_destroy do |business_partner|
if Document.where(:sender_id => business_partner).exists? ||
Document.where(:receiver_id => business_partner).exists? ||
Annotation.where(:sender_id => business_partner).exists? ||
Annotation.where(:receiver_id => business_partner).exists?
raise "#{business_partner.name} has ongoing activity and cannot be deleted."
end
end
Tried several alternatives - like flash[:notice] and message:
How should it be done?
You get an exception, because you raise it.
You can change it to redirection somewhere with an alert:
redirect_to root_path, alert: "#{business_partner.name} has ongoing activity and cannot be deleted."
You might also want to take a look into associations, dependent: :restrict_with_error:
:restrict_with_error causes an error to be added to the owner if
there are any associated objects.
Related
I have a rails app where Stripe is set up similar to this documentation. When I hit the "Pay With Card" button and enter the test card information it says it's successful, then gives me an error message saying:
No route matches [POST] "/charges/new"
Even though my routes are like this:
resources :charges
get 'charges/shipping'
get 'charges/address'
post 'charges/update_order'
My charges_controller reads like this:
class ChargesController < ApplicationController
...
def new
#order = current_order
end
def create
# Amount in cents
#amount = current_order.total * 100
#user = current_user
#order = current_order
if #user.stripe_customer_id
customer = #user.stripe_customer_id
else
customer = Stripe::Customer.create(
:email => params[:stripeEmail],
:source => params[:stripeToken]
)
#user.update_attributes(stripe_customer_id: customer.id)
end
#order.update_attributes(order_status_id: 2)
#order.update_attributes(date_placed: DateTime.now)
# OrderMailer.order_placed(#order.user, #order).deliver_now
session[:order_id] = nil
charge = Stripe::Charge.create(
:customer => #user.stripe_customer_id,
:amount => #amount.to_i,
:description => 'Rails Stripe customer',
:currency => 'usd'
)
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to new_charge_path
end
private
def order_params
params.permit(:subtotal, :tax, :shipping, :total, :order_status_id, :user_id, :date_placed, :shipping_choice)
end
end
If I manually insert a post "charges/new" into the routes.rb it says it's successful, but it redirects to the same page (indicating a Stripe::CardError) and no charge registers in the stripe dashboard.
Can anyone see where I'm going wrong?
It looks like you're not permitting the 'stripeToken' parameter in your order_params. Because of camel-case that might be a little fiddly, but make sure you're allowing that param with something like:
params.permit( :"stripeToken" …
Without this, that parameter is null, and would give you a Stripe failure.
Without seeing your client side code, I can't be sure - but it sounds like your client side code is trying to POST to /charges/new - which doesn't exist - so it's failing. I'd suggest you update your client side code to POST to the route you want to have handle that, and then continue debugging from there.
The problem is this part here in your controller:
rescue Stripe::CardError => e
flash[:error] = e.message
redirect_to new_charge_path
You should remove redirect_to new_charge_path, and simply just let Stripe render the error message. No need to redirect upon error. Stripe takes care of the error-handling for you (which is one of the things making Stripe great).
Currently, your redirect_to is being treated as the path after each successful purchase. Not just on errors.
Update
Upon further looking at your code. You don´t seem to actually have a redirect_to after each successful purchase. So regardless, you should remove the redirect_to new_charge_path because it´s currently arbitrary to => e. It might look like it has something to do with the Error handiling, cause it's below that block, but in fact it doesn't. You should instead add redirect_to successful_path or render :success (for instance) right above the rescue Stripe::CardError =>. And then let Stripe handle (or rescue) failed attempts to make a purchase, as explained before.
Try to restart your server to reload the routes. Run rake routes | grep charges to verify that you see POST /charges/new.
Wait, I know why. You are supposed to GET /charges/new and POST to /charges. Show us your view code, specifically the form submission URL address.
See http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions
In the end I had to change the code on the form to charge_path instead of new_charge_path so it would hit the create action. Even though this was different from the Stripe documentation it ended up working.
I have an application which uses Devise as authentication. I don't want user to be able to changed there email address. I've done this by setting the email attribute to read only in the User model.
class User < ActiveRecord::Base
attr_readonly :email
end
This works fine: Rails rollback the transaction. Devise however thinks that the update was succesful and displays a message succesfull message.
"Your account has been updated successfully."
I've tried several things including creating my onw methode that would return a flash notice but it keeps saying that the account has been succesfully updated.
Is there a way to raise an error when the record is not saved succesfully?
Edit after Ashvin's anwser. This is what I have in my model:
def email=(address)
begin
if new_record?
write_attribute(:email, address)
end
rescue Exception => error
flash[:alert] = error.message
end
end
I dont know I got your question or not, But from what I get following is solution. You can use exception handling while record is rollback
begin
# do some stuff here
rescue Exception => e
flash[:notice] = e.message
end
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
So, I've been trying to set up a before_filter for checking permissions for whether or not someone can delete an object. But it hasn't been working... eventually I do the following:
before_filter :test_hack, :only => :destroy
def test_hack
return false
end
the destroy method here:
def destroy
#content = Content.find(params[:id])
#will get rid of this when the before filter works...
# but this doesn't stop it from getting deleted either
if not has_permission_to_change?(#content)
puts "This content is not gonig to get deleted"
flash[:error] = 'You do not have permission to delete this content.'
else
#content.destroy
end
the failing test:
should "not allow the deleting of #{plural_name} on different accounts" do
login_as(#user)
p = Factory(factory_name, :account => Factory(:account))
assert_difference("#{klass}.count", 0) do
begin
delete :destroy, :id => p.id
raise "program flow should not reach this message"
rescue ActiveRecord::RecordNotFound
assert true
end
end
Content belongs_to an account
console output:
Loaded suite test/functional/contents_controller_test
Started
This content is not gonig to get deleted
E
Finished in 0.649422 seconds.
1) Error:
test: destroy contents! should not allow the deleting of contents on different accounts. (ContentsControllerTest):
RuntimeError: program flow should not reach this message
Once again, the bahavior of your test is absolutely normal:
Your line raise "program flow should not reach this message"will ALWAYS be executed since there is an object with the id you pass: you just created it
You should just keep:
assert_difference("#{klass}.count", 0) do
delete :destroy, :id => p.id
end
And I an't see where your before_filter is useful here
In your test,
delete :destroy, :id => p.id
won't raise any exceptions, so the execution continues normally, reaching the next line
raise "program flow should not reach this message"
and the test fails, because this is not caught.
The before_filter has nothing to do with it, according to your test output, it's not even invoked.
In my edit action, I have
#item = current_user.shop.items.find(params[:id])
So that the user can only edit items that belong to their shop. If they try to edit an item that does not belong to their shop, then they get an ActiveRecord::RecordNotFound error.
What is the best way of handling this error in situations like this? should i raise an exception? should i redirect somewhere and set the flash (if so, how do i do that), should i just leave it as is? Any advice is appreciated.
Thanks
Add something like the following to your controller:
rescue_from ActiveRecord::RecordNotFound do
flash[:notice] = 'The object you tried to access does not exist'
render :not_found # or e.g. redirect_to :action => :index
end
+1 for the solution of #Koraktor. If you want to avoid ActiveRecord::RecordNotFound, you can use find_by method instead of find.
#item = current_user.shop.items.find_by_id(params[:id])
if #item.nil?
flash[:error] = "Item not found"
else
# Process the #item...
end
Additionally you should set the http status while rendering the template :
rescue_from ActiveRecord::RecordNotFound do
render :not_found, :status => :not_found
end
Check the Rails guide (section 2.2.13.4) for the status list.
As stated somewhere else (for example here ) you should never make a redirection when sending a 40x status, only 30x statuses should allow redirecting. So you may encounter a weird "You are being redirected" page whenever trying to make a redirect with a :not_found status.