When is a Ruby class not that Ruby class? - ruby

I have this code in my controller for a Rails app:
def delete
object = model.datamapper_class.first(:sourced_id => params[:sourced_id])
if object.blank?
render :xml => "No #{resource} with sourced_id #{params[:sourced_id]}", :status => :not_found and return
end
object.destroy
render :xml => "", :status => :no_content
rescue MysqlError => e
puts "raised MysqlError #{e.message}"
render :xml => e.message, :status => :unprocessable_entity and return
rescue Mysql::Error => e
puts "raised Mysql::Error #{e.message}"
render :xml => e.message, :status => :unprocessable_entity and return
rescue Exception => e
puts "not a MysqlError, instead it was a #{e.class.name}"
render :xml => e.message, :status => :unprocessable_entity and return
end
When I run my spec to make sure my foreign key constraints work, I get this:
not a MysqlError, instead it was a MysqlError
What could be going on here?
Some ancestor information: When I change the rescue to give me this:
puts MysqlError.ancestors
puts "****"
puts Mysql::Error.ancestors
puts "****"
puts e.class.ancestors
This is what I get:
Mysql::Error
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
****
Mysql::Error
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
****
MysqlError
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
Could there be an alias in the global namespace that makes the MysqlError class unreachable?

Ruby classes are just objects, so comparison is based on object identity (ie, the same pointer under the hood).
Not sure what's happening in your case, but I'd try debugging in a few locations and seeing what object ids and ancestors you get for MysqlError. I suspect that there's two such objects in different modules and your catch clause is referencing the wrong one.
Edit:
That is quite strange. My guess now is that MysqlError or one of it's ancestors has been included at two different points along your controllers own class chain, and that's somehow tripping up the exception catching.
Theory #2 would be that since rails redefines const_missing to do auto-requires, where you'd expect to get an UndefinedConstant exception in the exception handling clauses is instead finding something by that name god knows where in the source tree. You should be able to see if that's the case by testing with the auto requiring off (ie do some debugs in both dev and prod mode).
There is a syntax for forcing your reference to start from the root which may be of some help if you can figure out the right one to be referencing:
::Foo::Bar
Rant:
This sort of thing is where I think some of the flaws of ruby show. Under the hood Ruby's object model and scoping is all object structures pointing to each other, in a way that's quite similar to javascript or other prototype based languages. But this is surfaced inconsistently in the class/module syntax you use in the language. It seems like with some careful refactoring you could make this stuff clearer as well as simplify the language, though this would of course be highly incompatible with existing code.
Tip:
When using puts for debugging, try doing puts foo.inspect as this will display it in the way you're used to from irb.

This was a simple class redefinition bug. Ruby lets you redefine a top-level constant, but it doesn't destroy the original constant when you do it. Objects that still hold references to that constant can still use it, so it could still be used to generate exceptions, like in the issue I was having.
Since my redefinition was happening in dependencies, I solved this by searching for the original class in the Object space, and hanging on to a reference to it to use when catching exceptions. I added this line to my controller:
ObjectSpace.each_object(Class){|k| ##mysql_error = k if k.name == 'MysqlError'}
That gets a reference to the original version of MysqlError. Then I was able to do this:
rescue ##mysql_error => e
render :xml => e.message, :status => :unprocessable_entity and return
This happens because the mysql gem is getting loaded after MysqlError has already been defined. Here is some test console joy:
Loading test environment (Rails 2.3.2)
>> MysqlError.object_id
=> 58446850
>> require 'mysql'
C:/Ruby/lib/ruby/gems/1.8/gems/mysql-2.7.3-x86-mswin32/ext/mysql.so: warning: already initialized constant MysqlError
=> true
>> MysqlError.object_id
=> 58886080
>> ObjectSpace._id2ref(MysqlError.object_id)
=> Mysql::Error
You can do this in IRB without a require pretty easily; here's a trick that works because irb doesn't look up Hash by name every time you declare a Hash literal:
irb(main):001:0> Hash = Class.new
(irb):1: warning: already initialized constant Hash
=> Hash
irb(main):002:0> hash = {:test => true}
=> {:test=>true}
irb(main):003:0> hash.class
=> Hash
irb(main):004:0> hash.is_a? Hash
=> false
I can see why you might want to do this, it could be used like alias_method_chain for the global namespace. You could add a mutex to a class that isn't threadsafe, for example, and not need to change old code to reference your threadsafe version. But I do wish RSpec hadn't silenced that warning.

Related

Accessing the Receiving Object that Throws an Exception

While debugging a really strange issue with ActionMailer, I came to realize I didn't know how to access an object that was creating the exception. Not the exception, but the object itself.
begin
AppMailer.send_invoice(hostel_resident).deliver_later
flash[:success] = "Your invoice was sent successfully!"
rescue => msg
# display the system generated error message
flash[:error] = "#{msg}"
end
NoMethodError: undefined method `disposition_type' for #<Mail::UnstructuredField:0x009g71c2a68258>
This code works great to catch any exceptions and print the message.
However, how do I get ahold of the <Mail::UnstructuredField:0x009g71c2a68258> object? I'd like to be able to play around with this guy, read messages inside it, and just generally have access to it.
This has to be possible, but inspect doesn't help, cause is no use and backtrace just shows you where it happened. I need that object though, the receiver of the nonexistent method.
Thanks!
actionmailer (4.2.4)
mail (2.6.3)
Seems like you're using mail gem. This is a known issue which is already reported in the GitHub. See #851.
Try using different version of the gem, something in 2.6 series.
This seems to work, using receiver on NameError (which NoMethodError is a child of)
obj = Object.new
puts obj.to_s
begin
obj.do_something
rescue NoMethodError => e
puts e.message
puts e.receiver
end
# #<Object:0x007fa5ac84da88>
# undefined method `do_something' for #<Object:0x007fa5ac84da88>
# #<Object:0x007fa5ac84da88>
This seems to require ruby >= 2.3, to do this for < 2.3, AFAIK you have to do something like this (not tested in older rubies, but should work):
class MyNoMethodError < NoMethodError
attr_accessor :my_receiver
end
obj = Object.new
puts obj.to_s
begin
begin
obj.do_something
rescue NoMethodError => e
# rescue the exception and wrap it in the method that caused it, using `self` instead of `obj`
error = MyNoMethodError.new(e)
error.my_receiver = obj
raise error
end
rescue MyNoMethodError => c
puts c.inspect # custom exception stuff
puts c.cause.inspect # original exception stuff
puts c.my_receiver
end
# #<Object:0x007f884e846d58>
# #<MyNoMethodError: undefined method `do_something' for #<Object:0x007f884e846d58>>
# #<NoMethodError: undefined method `do_something' for #<Object:0x007f884e846d58>>
# #<Object:0x007f884e846d58>

How to serialize Exception

According to ruby-doc and apidock, you can serialize and deserialize an exception using to_json and json_create.
But after having wasted some time trying to use them, I still haven't found a way.
Calling exc.to_json gives me an empty hash, and Exception.json_create(hash) gives me this error: undefined method 'json_create' for Exception:Class
I guess I could easily recreate those functions since the source is available, but I'd rather understand what I'm doing wrong... Any idea?
The JSON module doesn't extend Exception by default. You have to require "json/add/exception". I'm not sure if this is documented anywhere:
require "json/add/exception"
begin
nil.foo
rescue => exception
ex = exception
end
puts ex.to_json
# => {"json_class":"NoMethodError","m":"undefined method `foo' for nil:NilClass","b":["prog.rb:5:in `<main>'"]}
Check out ext/json/lib/json/add in the Ruby source to see which classes work this way. If you do require "json/add/core" it will load JSON extensions for Date, DateTime, Exception, OpenStruct, Range, Regexp, Struct, Symbol, Time, and others.
The answer from Jordan is correct. If you have a case eg. that you need to serialize an Exception and send to to ActiveJob where you want to reconstruct it, then you need to use .as_json method.
require "json/add/exception"
begin
nil.foo
rescue => exception
ex = exception
end
puts ex.to_json.class
# => String
string = ex.to_json
puts Exception.json_create(string).message
# => m
puts ex.as_json.class
# => Hash
hash = ex.as_json
puts Exception.json_create(hash).message
# => undefined method `foo' for nil:NilClass
You need to read the source code to understand why Exception.json_create(string).messagereturns m :)
It's also important to note that the Exception.json_create example doesn't keep the error class.
Exception.json_create(JSON.parse(ArgumentError.new('error').to_json))
# => #<Exception: error>
Try instead:
require "json/add/exception"
def deserialize_exception(json)
hash = JSON.parse(json)
hash['json_class'].constantize.json_create(hash)
end

Ruby catching NameError

I have the following code with two classes
class DeliveryService
attr_reader :cities
def initialize *cities
#cities = cities
end
end
class Product
attr_accessor :name
def initialize name
#name = name
end
def deliver_to delivery_service, city
if delivery_service.cities.include?(city)
puts "Product has been delivered"
else
puts "Сontact another delivery service"
end
end
end
I want to deliver_to method throw something like "Choose valid delivery service" when invalid delivery_service provided (which doesn't exist), but instead I get NameError
I tried to put this in different places in my code
rescue NameError
puts "Choose valid delivery service"
but it doesn't work
irb(main):001:0> require './DeliveryService.rb'
=> true
irb(main):002:0> fedex = DeliveryService.new "Moscow", "Saint-Petersburg"
=> #<DeliveryService:0x007f66ded31520 #cities=["Moscow", "Saint-Petersburg"]>
irb(main):003:0> product = Product.new "mug"
=> #<Product:0x007f66ded1e498 #name="mug">
irb(main):004:0> product.deliver_to fedex, "Moscow"
Product has been delivered
=> nil
irb(main):005:0> product.deliver_to dhl, "Moscow"
NameError: undefined local variable or method `dhl' for main:Object
from (irb):6
from /home/budkin/.rbenv/versions/2.1.2/bin/irb:11:in `<main>'
irb(main):006:0>
Use Dependency Injection on the Correct Object
This is a design problem, in that you aren't getting NameError from within your class; it's being raised when you call product.deliver_to dhl, "Moscow" from outside the class. Even if you have a rescue clause as part of your class definition, the NameError is going to be raised by the caller.
The right way to fix this is to pass a valid Product object to a valid DeliveryService object instead. A product shouldn't know anything about deliveries anyway; it's a violation of the single responsibility principle. So:
class DeliveryService
def deliver product
end
end
This will give you an instance method that takes a Product, and delivers it through the current delivery service. For example:
product = Product.new :widget
dhl = DeliveryService.new 'Moscow'
dhl.deliver product
Even if you perform this inversion, you will have problems if you pass invalid objects around, so don't do that. :)
dh1 does not exist, so you need to wrap your part of the code that attempts to dereference dh1.
begin
product.deliver_to dhl, "Moscow"
rescue NameError => e
puts e.message
end
Although this is quite a messy approach. A better way would be to create an array or set containing all your companies, and then check if the company is defined in that set/array.

How can I serialize DataMapper::Validations::ValidationErrors to_json in Sinatra?

I'm developing a RESTful API using Sinatra and DataMapper. When my models fail validation, I want to return JSON to indicate what fields were in error. DataMapper adds an 'errors' attribute to my model of type DataMapper::Validations::ValidationErrors. I want to return a JSON representation of this attribute.
Here's a single file example (gotta love Ruby/Sinatra/DataMapper!):
require 'sinatra'
require 'data_mapper'
require 'json'
class Person
include DataMapper::Resource
property :id, Serial
property :first_name, String, :required => true
property :middle_name, String
property :last_name, String, :required => true
end
DataMapper.setup :default, 'sqlite::memory:'
DataMapper.auto_migrate!
get '/person' do
person = Person.new :first_name => 'Dave'
if person.save
person.to_json
else
# person.errors - what to do with this?
{ :errors => [:last_name => ['Last name must not be blank']] }.to_json
end
end
Sinatra::Application.run!
In my actual app, I'm handling a POST or PUT, but to make the problem easy to reproduce, I'm using GET so you can use curl http://example.com:4567/person or your browser.
So, what I have is person.errors and the JSON output I'm looking for is like what's produced by the hash:
{"errors":{"last_name":["Last name must not be blank"]}}
What do I have to do to get the DataMapper::Validations::ValidationErrors into the JSON format I want?
So, as I was typing this up, the answer came to me (of course!). I've burned several hours trying to figure this out, and I hope this will save others the pain and frustration I've experienced.
To get the JSON I'm looking for, I just had to create a hash like this:
{ :errors => person.errors.to_h }.to_json
So, now my Sinatra route looks like this:
get '/person' do
person = Person.new :first_name => 'Dave'
if person.save
person.to_json
else
{ :errors => person.errors.to_h }.to_json
end
end
Hope this helps others looking to solve this problem.
I know, I am answering this late, but, in case you are just looking for just validation error messages, you can use object.errors.full_messages.to_json. For example
person.errors.full_messages.to_json
will result in something like
"[\"Name must not be blank\",\"Code must not be blank\",
\"Code must be a number\",\"Jobtype must not be blank\"]"
This will rescue on client side from iterating over key value pair.

Why is the stubbed method not called?

It seems I understood something wrong. I have a class
module Spree
class OmnikassaPaymentResponse
#...
# Finds a payment with provided parameters trough ActiveRecord.
def payment(state = :processing)
Spree::Payment.find(:first, :conditions => { :amount => #amount, :order_id => #order_id, :state => state } ) || raise(ActiveRecord::RecordNotFound)
end
end
end
Which is specced in Rspec:
describe "#payment" do
it 'should try to find a Spree::Payment' do
Spree::Payment.any_instance.stub(:find).and_return(Spree::Payment.new)
Spree::Payment.any_instance.should_receive(:find)
Spree::OmnikassaPaymentResponse.new(#seal, #data).payment
end
end
This, however, always throws ActiveRecord::RecordNotFound. I expected any_instance.stub(:find).and_return() to make sure that whenever, wherever I call a #find on whatever instance I happen to have of Spree::Payment, it returns something.
In other words: I would expect the stub.and_return would avoid getting to || raise(ActiveRecord::RecordNotFound). But it does not.
Is my assumption wrong, my code? Something else?
In your case find is not an instance method, but a class method of Spree::Payment. That means you should stub it directly without any_instance like that:
Spree::Payment.stub(:find).and_return(Spree::Payment.new)

Resources