nesquena / rabl

General ruby templating with json, bson, xml, plist and msgpack support

Home Page:http://blog.codepath.com/2011/06/27/building-a-platform-api-on-rails/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How would you render a tree ?

simonc opened this issue · comments

Hi,

I have a folders tree in my application and I would like to know how I could render it in json with rabl.
I guess I'll have to use a partial but I'm not sure how.

Any idea ?

Thanks !

First decide how you'd like the final JSON to look in the API. Perhaps something like:

{ 'folder' => { 'subfolder' =>  { ... } }

Once you decide the JSON response you want, how you craft it in RABL will be im sorry to say probably very custom (and could use partials or not). It could be something like:

object false

node :tree do |m|
   @tree.folders.map { |folder| # ... build a hash here ... }
   # Really this can be anything, array, hash, etc 
end

Presumably that method might be elaborate but essentially my guess is a tree is going to be pretty custom so it amounts to just constructing the hash yourself either in the model or in the rabl output. I am not sure of a better way to do it unless you describe the breakdown of your models and/or the expected json output.

Ok, some more infos:

I use Mongoid::Tree to get the tree structure on the model side.
The simple JSON output I want for now should look like this:

[{ folder:
   {
     name: "some-name",
     children: [{ folder:
       {
          name: "some-sub-folder",
          children: []
       }
    }]
  }
},
{
  folder:
  {
    ...
  }
}]

For HTML it's just a simple partial calling itself. I was thinking about the same thing with rabl but it's maybe not the right way to do it with rabl.

What do you think ?

I have a similar situation, with arbitrarily deep nesting. The model is like:

class Node < ActiveRecord::Base
  has_many :children, :class_name => "Node", :foreign_key => "parent_id"
end

I'd like to render something like:

{"node":{
  "id":1,
  "name":"RootNode",
  "children":[
    {
      "id":2,
      "name":"Child 1 Level 1",
      "children":[
        {
          "id":3
          "name":"Child 1 Level 2",
          "children":[
            {
              "id":6
              "name":"Child 1 Level 3"
             }
          ]
         }
      ]
    },
    {
      "id":4,
      "name":"Child 2 Level 1",
      "children":[
        {
          "id":5
          "name":"Child 2 Level 2"
         }
      ]
    }
  ]
}

Is something like that possible? The solution doesn't seem immediately obvious to me.

Couldn't this work with a recursive extends?

# node/show.json.rabl
attributes :id, :name
child :children => :children do
  extends "nodes/show"
end

Just an experiment to try, let me know if this works at all

This is very close! The only issue is that the nested children are all named "child", e.g. (pseudo):

node: {
    children: [
        child: {
            children: [ ]
        }
    ]
}

The desired output would be:

node: {
    children: [
        node: {
            children: [ ]
        }
    ]
}

The template looks like:

object @root_node => :node
attributes :id, :name, :parent_id
child :children => :children do
  extends "molecule/index"
end

Thoughts? (Thanks for the quick follow-up!)

Alright how about:

object @root_node => :node
attributes :id, :name, :parent_id
child :children => :nodes do
  extends "molecule/index"
end

I think we are getting close :)

If not maybe a bit more manual with "node":

object @root_node => :node
attributes :id, :name, :parent_id
node :children do |n|
   n.children.map { |c| { :node => partial("molecule/index", :object => c) }  }
end

Ah hah! The 2nd form is perfect. I had tried several permutations of something similar, but had not hit upon the correct thing. :)

(The 1st form results in what was desired to be 'children' being named 'nodes')

Thanks again!

Maybe update the docs? It's completely non-obvious how to do this, and I spent more than 2 hrs hacking around before I read this, and even this explanation is less than complete (ie, where did the template "molecule/index" come from? Is it the same as this template, or is it a different template only used for rendering the child and lower levels)

Added the docs to link to this ticket in case others need to render a tree. If anyone can help with a wiki page for rendering a tree, I will link there instead.

Alright, so I'm trying to do something similar, but I don't have a root node, just a group of nodes, each of which is the root of a tree. Here's what I have so far:

# the root page, index.rabl
object false    

@elements.map do |element|    
  { :element => partial("elements/tree", :object => element) }    
end
# the partial, _tree.rabl
attributes :id, :title

node :children do |element|
  element.children.map do |child_element|
    { :element => partial("elements/tree", :object => child_element) }
  end 
end

The interesting thing is, if I print out the result in the log, it looks exactly like what I want, but then by the time it gets rendered to the client, I get an empty json object, {}. Any ideas what's going wrong?

Can you try:

# index.rabl
collection @elements, :root => false, :object_root => "element"
extends "elements/tree"

and see how close that gets you?

That's perfect, thanks!

Can someone please suggest changes in my code so that I can get the desired layout:

#desired output:
{"length":2,
 "agents":[
            {"object":"agent","firstName":"Sallie","lastName":"Bla","email":"agent@dantler.us","agentId":1},
            {"object":"agent","firstName":null,"lastName":null,"email":"new@dantler.xa","agentId":2}
             ]}

#what I'm getting is this:
{"length":2,
 "agents":[
            {"agent":{"object":"agent","firstName":"Sallie","lastName":"Bla","email":"agent@dantler.us","agentId":1}},
            {"agent":{"object":"agent","firstName":null,"lastName":null,"email":"new@dantler.xa","agentId":2}}
             ]}

# agents.rabl
object false
node (:length) {|m| @agents.length}

child @agents => "agents" do |x|
    extends 'agent'
end

# agent.rabl
object @agent => ""
node do |x|
{
    :object => "agent",
    :firstName => x.first_name,
    :lastName => x.last_name,
    :email => x.email,
    :agentId => x.id
}
end