Search code examples
ruby-on-railsjsonwebhookseasypost

How to write a case statement with nested JSON in rails


I have an app that is integrated with the Easypost API (for shipping stuff, really cool). I'm using their tracking information webhooks to fire events in my app. I'm using ruby case statements to accomplish this. Here's a link to Easypost's API doc: https://www.easypost.com/docs/api#webhooks

I would like to match my case statement to the result.status key value in the JSON. Initially I was using just the description, but I need it to be more specific. Take a look:

{
"id": "evt_qatAiJDM",
"object": "Event",
"created_at": "2014-11-19T10:51:54Z",
"updated_at": "2014-11-19T10:51:54Z",
"description": "tracker.updated",
"mode": "test",
"previous_attributes": {
  "status": "unknown"
},
"pending_urls": [],
"completed_urls": [],
"result": {
  "id": "trk_Txyy1vaM",
  "object": "Tracker",
  "mode": "test",
  "tracking_code": "EZ4000000004",
  "status": "delivered",
  "created_at": "2014-11-18T10:51:54Z",
  "updated_at": "2014-11-18T10:51:54Z",
  "signed_by": "John Tester",
  "weight": 17.6,
  "est_delivery_date": "2014-08-27T00:00:00Z",
  "shipment_id": null,
  "carrier": "UPS",

Here's the code I was using

class HooksController < ApplicationController
# to bypass CSRF token authenticity error
skip_before_filter  :verify_authenticity_token

  def stripe
    #using easypost now as the webhook event
    case params[:description]
      # From EasyPost API https://www.easypost.com/docs/api#webhooks: 
      # tracker.updated: Fired when the status for the scan form changes
      when 'tracker.updated'
        @shipments = Spree::Shipment.where(transferred: false, state: "shipped")
        @shipments.each do |shipment|
          item_total = 0
          shipment.line_items.each do |item|
            item_total += item.product.price * item.quantity
          end  
          transfer = Stripe::Transfer.create(
            # Take 10% for ourselves from the total cost
            # of items per supplier(shipment)
            :amount => (item_total * (100 - shipment.supplier.commission_percentage)).floor,
            :currency => "usd",
            :recipient => shipment.supplier.token
          )
          shipment.transferred = true
          shipment.save!
        end
      end
  end
end

Since the status key is nested one level in the JSON do I need to account for that in my webhook code? Might it be case params[:status] or case params[:result :status]?

Thanks

UPDATE

I now realize I want to use some more granular details of the EasyPost API. I need to access the status within the array of Tracking Details:

//Easypost API
{
"id": "evt_qatAiJDM",
"object": "Event",
"created_at": "2014-11-19T10:51:54Z",
"updated_at": "2014-11-19T10:51:54Z",
"description": "tracker.updated",
"mode": "test",
"previous_attributes": {
  "status": "unknown"
},
"pending_urls": [],
"completed_urls": [],
"result": {
  "id": "trk_Txyy1vaM",
  "object": "Tracker",
  "mode": "test",
  "tracking_code": "EZ4000000004",
  "status": "delivered",
  "created_at": "2014-11-18T10:51:54Z",
  "updated_at": "2014-11-18T10:51:54Z",
  "signed_by": "John Tester",
  "weight": 17.6,
  "est_delivery_date": "2014-08-27T00:00:00Z",
  "shipment_id": null,
  "carrier": "UPS",
  "tracking_details": [
    {
      "object": "TrackingDetail",
      "message": "BILLING INFORMATION RECEIVED",
      "status": "pre_transit",
      "datetime": "2014-08-21T14:24:00Z",
      "tracking_location": {
        "object": "TrackingLocation",
        "city": null,
        "state": null,
        "country": null,
        "zip": null
      }

So I need to access result and then tracking_details and then status. this case statement: case params[:result][:tracking_details][:status] gave me this error:

    May 14 10:31:30 leemaeats app/web.1:  TypeError (no implicit conversion of Symbol into Integer): 
May 14 10:31:30 leemaeats app/web.1:    app/controllers/hooks_controller.rb:28:in `[]' 
May 14 10:31:30 leemaeats app/web.1:    app/controllers/hooks_controller.rb:28:in `stripe'

Seems like it's because I need to specify the element in this array tracking_details. Anyone know how to reference that? Thanks


Solution

  • Probably case params[:result][:status]

    params[:result] returns a hash which contains, among other things, the "status" member that you can access with [:status]

    EDIT to answer UPDATE

    params[:result][:tracking_details] returns an array, so you could try any of the following:

    To get the first element:

    • params[:result][:tracking_details][0][:status]
    • params[:result][:tracking_details].first[:status]

    To get all the elements:

    • params[:result][:tracking_details].map{|x| x[:status]}