"someotherstringid", :amount => 19.99, :metadata => [ { :typ" /> "someotherstringid", :amount => 19.99, :metadata => [ { :typ" /> "someotherstringid", :amount => 19.99, :metadata => [ { :typ"/>

Render array of hashes using rabl

472 Views Asked by At

I have the following hash in my controller.

@order = {
  :id => "somestringid",
  :user_id => "someotherstringid",
  :amount => 19.99,
  :metadata => [
    {
      :type => :shipping_data,
      :address => "line 1 of address, line 2 of address, city, state, pincode"
    },
    {
      :type => :payment,
      :stripe_customer_id => "somestringid",
      :stripe_card_id => "someotherstringid"
    },
    {
      :type => :contact,
      :email => "[email protected]",
      :phone => "1231231231"
    }
  ]
}

Notes:

  1. The "metadata" is a list of objects.
  2. There can be 0, 1 or more metadata objects.
  3. Each metadata object has a different structure.
  4. The only common key for all metadata objects is the "type" key.

I want to use rabl to generate the following json, but cannot figure out what I should put into my template.

The JSON output that I want should look like the following.

{
  "id": "somestringid",
  "user_id": "someotherstringid",
  "amount": 19.99,
  "metadata": [
    {
      "type": "shipping_data",
      "address": "line 1 of address, line 2 of address, city, state, pincode"
    },
    {
      "type": "payment",
      "stripe_customer_id": "somestringid",
      "stripe_card_id": "someotherstringid"
    },
    {
      "type": "contact",
      "email": "[email protected]",
      "phone": "1231231231"
    }
  ]
}

What should I put into my template, so that I get the desired output?

1

There are 1 best solutions below

3
Jignesh Gohel On

Note

I am not sure whether by rabl you mean you are using standard rabl gem OR rabl-rails because you haven't provided any details about the nature of your application and it is built using what all libraries.

But my solution below is in context of a Rails application using rabl-rails gem. And the rabl-rails gem's README says following:

rabl-rails is faster and uses less memory than the standard rabl gem while letting you access the same features. There are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes.

So I guess it should not be a problem to adapt this solution in context of standard rabl gem whether the application is built Rails or Non-Rails based. My aim is to provide a guidance on the approach which can be used to achieve your desired output.

Now coming to the solution approach:

Using some abstractions you can design a flexible and maintainable solution. Let me elaborate:

Assuming you have a plain ruby-class Order like following or if you don't have any such class you can define it easily using virtus gem which provides some handy out-of-the-box features for a class:

app/models/order.rb

  class Order
    attr_accessor :id, :user_id, :amount, :order_metadata_obj_arr

    ....
    ..
  end

app/models/order_metadata.rb

  class OrderMetadata
    attr_accessor :metadata_type, :metadata_data_obj

    ...
    ...
  end

app/models/shipping_data.rb

  class ShippingData
    attr_accessor :address
  end

app/models/payment_data.rb

  class PaymentData
    attr_accessor :stripe_customer_id, :stripe_card_id
  end

app/models/contact_data.rb

  class ContactData
    attr_accessor :email, :phone
  end

/app/json_views/orders/metadata/shipping.rabl

attribute :address

/app/json_views/orders/metadata/payment.rabl

attribute :stripe_customer_id, :stripe_card_id

/app/json_views/orders/metadata/contact.rabl

attribute :email, :phone

/app/json_views/orders/order_metadata.rabl

attribute :type

# Anonymous node.
node do |order_metadata_obj|
  template_path = "./orders/metadata/#{order_metadata_obj.type}"
  metadata_data_obj = order_metadata_obj.metadata_data_obj
  partial(template_path, object: metadata_data_obj)
end

/app/json_views/order.rabl

  attributes :id, :user_id, :amount

  node(:metadata) do |order_obj|
    partial('./orders/order_metadata', object: order_obj.order_metadata_obj_arr)
  end

Then in your controller do this

/app/controllers/my_controller.rb

 class MyController < MyBaseController

    def my_action
      order_obj = create_order_obj_from_hash(order_hash)

      order_json = order_json_representation(order_obj)
      status = 200

      render json: order_json, status: status
    end

    private

    def create_order_obj_from_hash(order_hash)
      order_metadata_arr = order_hash[:metadata]

      order_metadata_obj_arr = []

      order_metadata_arr.each do |item_hash|
        md_type = item_hash[:type]

        md_obj = case md_type
                        when :shipping_data
                          ShippingData.new(address: item_hash[:address])
                        when :payment
                          PaymentData.new(stripe_customer_id: item_hash[:stripe_customer_id], stripe_card_id: item_hash[:stripe_card_id])
                        when :contact
                          ContactData.new(email: item_hash[:email], phone: item_hash[:phone])
                        else
                          // unsupported md_type
                      end

        unless md_obj.nil?
          omd_obj = OrderMetadata.new(metadata_type: md_type, metadata_data_obj: md_obj)
          order_metadata_obj_arr << omd_obj
        end
      end

      order_attrs = order_hash.slice(:id, :user_id, :amount)

      order = Order.new(order_attrs)
      order.order_metadata_obj_arr = order_metadata_obj_arr
      order
    end

    def order_json_representation(order_obj)
      template_name = "order.rabl"
      template_path = Rails.root.join("app", "json_views")
      RablRails.render(order_obj, template_name, view_path: template_path, format: :json)
    end
  end

Hope this helps.

Thanks.