ActiveResource: How to avoid “undefined class/module” exception

I use webmock to stub external endpoints in test and development mode. It’s a fantastic gem and I cannot recommend it highly enough.

I ran into some problems this week using it together with ActiveResource (AR). We were allowing our test-driven approach to define the response of a yet-to-be-built API method. We were defining the API response in YAML, and then when setting the webmock stub, converting the YAML to json. Nothing too fancy there.

The problems started happening when we added a nested level to our YAML file. When AR loads a response, any nested levels are created as nested AR classes, and our app was bombing out when we introduced this nested level. We kept getting “undefined class/module …” exceptions.

Here’s what our YAML looked like:

:quote_id: 102
:detail:
  :premium: 100.00
  :total: 145.00

See the nested detail level? When AR receives the json version of this response, the AR object will have quote_id as an instance method. But, it will have premium and total as instance methods on a nested Detail object.

Why was this an issue for us?

We receive the API response and need to persist it in our application, but we want to do this persistence using the session (and ActiveRecord session mind). It’s not a good idea to put complex objects into the session store, so before doing that we use Marshal to serialize the object. Similarly, when retrieving the object from the session, we then need to deserialize the object before we can use it. It’s at this point that things go wrong because AR will not know about the nested class within the object. It will fail.

The solution is quite simple, although not entirely flexible in that you do need to know about which nested classes you will be handling.

I created two class methods to deal with the serialization. Serializing the object is easy. Deserializing requires us to use the find_or_create_resource_for method, which can be found in the ActiveResource::Base class. It’s used internally by AR to overcome this exact issue.

Here’s my AR class (simplified for brevity). Note that the single-item list of nested classes contains the Detail class, as defined by the nested level in my YAML file above.

class PaymentDetail < ActiveResource::Base
  self.site   = Rails.application.config.special_api
  self.format = :json

  NESTED_CLASSES = %w(Detail)

  def self.stringify_quote obj
    Marshal.dump obj
  end

  def self.load_quote str
    # preload nested classes to avoid an ActiveResource undefined class exception
    NESTED_CLASSES.each { |klass| PaymentDetail.new.send(:find_or_create_resource_for, klass) }
    Marshal.load str
  end

end

How does this help me?

This will allow you to easily persist any AR object in a session (or cache) for later use, which is especially useful for API’s that are read-only. It means you can persist the state of an AR object even when the source of that object – the external API – does not allow the changing of that state.

Hope this saves someone some time!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s