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.
Related
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).
Mongoid::Persistable::Creatable::ClassMethods.module_eval do
def create(attributes = nil, &block)
begin
super
rescue Mongo::Error::OperationFailure => e
Rails.logger.error "failed to create notifications #{e.message}, #{e.backtrace}"
raise
end
end
end
Hello all, I'm trying to override a method from mongoid gem. So I've implemented the above method in config/initializers/mongo.rb,
expecting my create method to run as defined in the gem, while leaving error log in case it there is a Mongo::Error::OperationFailure. But instead it gives me this error.
[1] pry(main)> Notification.create(id: 'ididididididid')
NoMethodError: super: no superclass method `create' for Notification:Class
I would like to know why this error occurs and how I can fix it. Thank you.
Monkey patching it directly is hacky and overwrites the method altogether. You expected super to call the original implementation, but it is no longer there. Instead create a new module and include it there:
module CreateWithErrorLogging
def create(attributes = nil, &block)
begin
super
rescue Mongo::Error::OperationFailure => e
Rails.logger.error "failed to create notifications #{e.message}, #{e.backtrace}"
raise
end
end
end
Mongoid::Persistable::Creatable::ClassMethods.include CreateWithErrorLogging
I'm building an around_action for my customer_mailer class so that I don't have to wrap begin and rescue around every time I call deliver_now
class CustomerMailer < ApplicationMailer
around_action :rescue_error
def send_email(customer)
...
end
def invite_friend(customer, invitee_email)
...
end
private
def rescue_error
yield
rescue => e
msg = "Caught exception! #{e} | #{action_name}"
puts msg
raise
end
end
So in the rescue, I want to log the message with information such as which action was called, I managed to find the method action_name to show which action was called, but I couldn't find a way to retrieve the parameters that were passed into the action, any ideas?
Thanks!
Before I answer your question: would using Bugsnag or something similar work in your case? Alternatively would rescue_from Exception, with: :exception_handler work for you? (it won't allow you to reraise the exception though)
I dug into Rails source code and it seems that parameters are not stored anywhere. They are just passed as a splat to an instance method defined in your mailer class. However, there is a way to store them (without monkey-patching).
Mailers inherit from AbstractController::Base. Looking at the snippet below:
# Call the action. Override this in a subclass to modify the
# behavior around processing an action. This, and not #process,
# is the intended way to override action dispatching.
#
# Notice that the first argument is the method to be dispatched
# which is *not* necessarily the same as the action name.
def process_action(method_name, *args)
send_action(method_name, *args)
end
# Actually call the method associated with the action. Override
# this method if you wish to change how action methods are called,
# not to add additional behavior around it. For example, you would
# override #send_action if you want to inject arguments into the
# method.
alias send_action send
we can see that we can override #send_action and make it store the arguments. Add the following to your ApplicationMailer:
class ApplicationMailer < ActionMailer::Base
def send_action(method_name, *args)
#action_args = args
super
end
end
The arguments will be available as #action_args in all your mailers.
Just store the parameters with which the action has been called to an instance variable, say #params. Then these parameters will be accessible in rescue_error via #params. As per your example:
class CustomerMailer < ApplicationMailer
around_action :rescue_error
def send_email(customer)
#params = { customer: customer }
...
end
def invite_friend(customer, invitee_email)
#params = { customer: customer, invitee_email: invitee_email }
...
end
private
def rescue_error
begin
yield
rescue => e
msg = "Caught exception! #{e} | #{action_name} | #{#params.inspect}"
puts msg
raise
end
end
end
You can make the assignment to #params a bit cleaner by using hash parameters in your actions, e.g.
def invite_friend(options = {})
#params = params
...
end
Of course, this requires accessing the parameters via options, such as options[:customer] to access customer, and options[:invitee_email] to access invitee_email.
The action name have to be yielded , it depends on the way you use your rescue_error .
Define a variable in the block that will be yielded
or raise specifics errors (maybe your custom exception class )
this way you'll retrieve invormation via "e"
post an exemple use case of rescue_error.
I am receiving NoMethodErrors when my DeltaSynWorker runs. This happens in an application that was built as a revision of a currently working application. I am not the original programmer, and I am coming at it from a Java background (I mention this because it is possible I am missing something very obvious to others). I cannot figure out why NoMethodeError is being thrown when the code is very similar to code that is currently working fine in a different web application.
The Error:
NoMethodError: undefined method `client' for #<ApiRequestBuilder:0x0000000705a8f0>
delta_sync_worker.rb
class DeltaSyncWorker < SyncWorker
include Sidekiq::Worker
sidekiq_options queue: "delta_syncs"
def perform(subscriber_id, client_id)
sleep(10)
current_subscriber = ApiSubscriberDecorator.decorate(Subscriber.find(subscriber_id))
Time.zone = current_subscriber.time_zone
client = ApiClientDecorator.decorate(Client.find(client_id))
arb = ApiRequestBuilder.new(URI.parse(SR_HOST + '/servlet/sync/process'))
if arb.client(:subscriber => current_subscriber, :client => client)
arb.transmit
if arb.success?
current_subscriber.touch(:sync_updated_at)
decorated_client = ClientDecorator.decorate(client.model)
ConfirmationsSyncWorker.perform_in(1.hours, current_subscriber.id)
else
error_params = {:subscriber => current_subscriber.id, :response_body => arb.response.body, :request_body => arb.request.body, :sync_type => "DeltaSyncWorker"}
Airbrake.notify(:error_class => "sync_error", :error_message => "Sync Error: #{arb.response.message}", :params => error_params)
end
end
end
end
api_request_builder.rb
require 'nokogiri'
class ApiRequestBuilder < AbstractController::Base
include AbstractController::Rendering
include AbstractController::Layouts
include AbstractController::Helpers
include AbstractController::Translation
include AbstractController::AssetPaths
self.view_paths = "app/api"
attr_accessor :request_body, :request, :response, :append_request_headers, :request_method, :url
def initialize(url, *args)
#url = url
if args
args.each do |arg|
arg.each_pair{ |k,v| instance_variable_set("##{k.to_s}", v) }
end
end
end
# this will search for an api request template in api/requests, render that template and set any instance variables
def method_missing(meth, *args, &block)
if lookup_context.template_exists?("requests/#{meth.to_s}")
if args
args.each do |arg|
arg.each_pair{|k,v| instance_variable_set("##{k.to_s}", v) }
end
end
#request_body = (render :template => "requests/#{meth.to_s}")
else
super
end
end
def transmit
#request_method ||= "Post"
#request = "Net::HTTP::#{#request_method}".constantize.new(#url.path)
#request['x-ss-user'] = #subscriber.sr_user if #subscriber && #subscriber.sr_user.present?
#request['x-ss-pwd'] = #subscriber.sr_password if #subscriber && #subscriber.sr_password.present?
unless #append_request_headers.nil?
#append_request_headers.each_pair{ |k,v| #request[k] = v }
end
#request.body = #request_body if request_body? && #request.request_body_permitted?
#http = Net::HTTP.new(#url.host, #url.port)
#http.use_ssl = true
#http.verify_mode = OpenSSL::SSL::VERIFY_NONE
#response = #http.request(#request)
end
def success?
if #response.code == 200.to_s
return true
else
return false
end
end
def request_body?
unless #request_body.nil?
return true
else
return false
end
end
end
I have been looking at other NoMethodError questions here, but I cannot find an answer I feel applies to my situation. Any help is greatly appreciated. Thanks!
method_missing will catch sent messages for which there is no method defined, and the call to super at the end will pass it up to Ruby's standard method_missing behavior, which is what you are seeing (NoMethodError). However, this only happens if the if condition is not met, which is what I suspect is happening here, and would explain why it works in some situations but not in others. The call to :client, having found no matching methods along the inheritance chain, will look for a template called "requests/client" - try adding this template and see if that fixes the issue.
I know Ive seen this before and I feel like it wasn't what it appeared to be, but ya basically method missing is just intercepting the method call and when you call arb.client, it is caught by method missing and therefore tries to render api/client.xml.arb or api whatever the file type is. -- Note that there should be a file in the initializers directory named somethig like api_template_handler.rb or arb_temmplate_handler.rb, which is what allows rails to see that template type in the view directory -- make sure that is there first. Also sidenote, _client.xml.api is a partial used by the other api request in that directory (full sync),
To debug Id start by, above the following line
if lookup_context.template_exists?("requests/#{meth.to_s}")
Add a log statement
Rails.logger.debug "Can I see View?" + lookup_context.template_exists?("requests/#{meth.to_s}")
If true, then the problem is the error isnt getting caught properly because of method missing. If false, then the sidekiq worker isnt loading rails properly, or the view path isn't being added onto the rails view paths.
If true, Im guessing it might have something to do with the client model not being loaded, or an attribute on the client model not existing, that the builder is trying to call, and the error is somehow bubbling up to the api request builder class.
Oh also, just general stuff, but make sure redis and sidekiq are running, and restart passenger if its non local environment.
Let me know how it goes.
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