nesquena / rabl

General ruby templating with json, bson, xml, plist and msgpack support
http://blog.codepath.com/2011/06/27/building-a-platform-api-on-rails/
MIT License
3.65k stars 335 forks source link

Generating JSOS suitable for use by jQuery DataTables #405

Closed davesag closed 11 years ago

davesag commented 11 years ago

I am making extensive use of jQuery Datatables and they require JSON feeds in a specific format which Rabl can't provide. The issue is I need to return un-named arrays which I can't work out how to get Rable to do.

I have resorted to coding up a helper in my Sinatra app to generate the appropriate JSON but would prefer to have this logic in a Rabl file if possible. Maybe I'm just missing something.

My working method is:

def recipe_datatable_json(recipes, echo, table_name)
  result = {
    'sEcho' => echo,
    'iTotalRecords' => recipes.count,
    'iTotalDisplayRecords' => recipes.count
  }
  aaData = []
  recipes.each do |r|
    aaData << {
      'DT_RowId' => "#{table_name}-id-#{r.id}",
      '0' => UnicodeUtils::titlecase(r.name),
      '1' => UnicodeUtils::titlecase(t.people(r.serves)),
      '2' => summarise(r.description),
      '3' => human_readable_time(r.preparation_time),
      '4' => human_readable_time(r.cooking_time),
      '5' => r.serves,
      '6' => r.preparation_time,
      '7' => r.cooking_time
    }
  end
  result['aaData'] = aaData
  return result.to_json
end

How would I get the same result in Rabl?

I tried this:

node do
  {
    'sEcho' => @echo,
    'iTotalRecords' => @recipes.count,
    'iTotalDisplayRecords' => @recipes.count
  }
end
node :aaData do
  @recipes.each do |r|
    {
      'DT_RowId' => "#{@table_name}-id-#{r.id}",
      '0' => UnicodeUtils::titlecase(r.name),
      '1' => UnicodeUtils::titlecase(t.people(r.serves)),
      '2' => summarise(r.description),
      '3' => human_readable_time(r.preparation_time),
      '4' => human_readable_time(r.cooking_time),
      '5' => r.serves,
      '6' => r.preparation_time,
      '7' => r.cooking_time
    }
  end
end

but that wraps the array of recipes in a 'recipes' object and also seems to include all the other recipe attributes (not shown), even though I don't require them.

I'd love to move all my JSON rendering into Rabl but this example has me stumped.

nesquena commented 11 years ago

try adding:

object false

# ...
node do
  {
    'sEcho' => @echo,
    'iTotalRecords' => @recipes.count,
    'iTotalDisplayRecords' => @recipes.count
  }
end

to the top of the template

davesag commented 11 years ago

Adding object false didn't seem to make any difference.

I get this from my Rabl

{"sEcho":1,"iTotalRecords":10,"iTotalDisplayRecords":10,"aaData":[{"recipe":{"cooking_time":15000,"description":"test","id":1,"meal_id":null,"method":"test","name":"test","owner_id":1,"preparation_time":8400,"requirements":"test","serves":6}},{"recipe":{"cooking_time":4260,"description":"Delicious fish soup. Yumo!","id":2,"meal_id":null,"method":"Mix the stuff up and cook it.","name":"Fish Soup","owner_id":1,"preparation_time":2700,"requirements":"Pot","serves":6}},{"recipe":{"cooking_time":900,"description":"Scrape and eat it all up. Yummy!","id":3,"meal_id":null,"method":"Cook and eat. Yum!","name":"Roadkill Salad","owner_id":1,"preparation_time":176760,"requirements":"A roa, and the willingness to use it.","serves":8}},{"recipe":{"cooking_time":900,"description":"A delicious salad made of fish.","id":4,"meal_id":null,"method":"Fry the fish and sprinkle with lettuce.  Serve hot.","name":"Fish Salad","owner_id":1,"preparation_time":1800,"requirements":"","serves":2}},{"recipe":{"cooking_time":900,"description":"A fish in the shape of a biscuit","id":5,"meal_id":null,"method":"Cook fish in oil, then cover in flour and cut into biscuits.","name":"Fish Biscuit","owner_id":1,"preparation_time":0,"requirements":"None","serves":1}},{"recipe":{"cooking_time":5100,"description":"A very yummy stew.","id":6,"meal_id":null,"method":"Add veggies to the water and boil it dry.","name":"Yummalishous Stew","owner_id":1,"preparation_time":2700,"requirements":"A big pot.","serves":11}},{"recipe":{"cooking_time":7200,"description":"meal for one","id":7,"meal_id":null,"method":"just cook it.","name":"testing again","owner_id":1,"preparation_time":900,"requirements":"time and space","serves":1}},{"recipe":{"cooking_time":129600,"description":"prep = 10m","id":8,"meal_id":null,"method":"Yum!","name":"test 3","owner_id":1,"preparation_time":600,"requirements":"testing","serves":2}},{"recipe":{"cooking_time":5400,"description":"test 4 d","id":9,"meal_id":null,"method":"test m","name":"test 4","owner_id":1,"preparation_time":1500,"requirements":"blah","serves":4}},{"recipe":{"cooking_time":900,"description":"A classic.","id":10,"meal_id":null,"method":"First up, lightly toast the bread and pan fry the bacon until it is cooked but not crisp.  Remove half of the bacon and continue to fry the other half until it is crisp.\nIn the meantime, slice the lettuce and tomatoes without bruising it then layer the lettuce and tomatoes onto the bread.  Salt the tomatoes lightly.\nAdd the soft bacon.  You might also want to apply a thin smear of mayo here too. Grind on some pepper and layer on the crispy bacon. \nClose the sandwich with the other piece of bread and slice into triangles.\n","name":"Bacon lettuce and tomato sandwich","owner_id":1,"preparation_time":300,"requirements":"It's best to use lettuce and tomatoes from your own garden if you can, and use a good heavy bread.\nSlice the tomato as thinly as you can.\n","serves":6}}]}

but this is the JSON I need to get (and that I do get from my Ruby method.)

{"sEcho":1,"iTotalRecords":10,"iTotalDisplayRecords":10,"aaData":[{"DT_RowId":"test-id-1","0":"Test","1":"6 People","2":"test","3":"2 hours 20 minutes","4":"4 hours 10 minutes","5":6,"6":8400,"7":15000},{"DT_RowId":"test-id-2","0":"Fish Soup","1":"6 People","2":"Delicious fish soup.","3":"45 minutes","4":"1 hour 11 minutes","5":6,"6":2700,"7":4260},{"DT_RowId":"test-id-3","0":"Roadkill Salad","1":"8 People","2":"Scrape and eat it all up.","3":"2 days 1 hour 6 minutes","4":"15 minutes","5":8,"6":176760,"7":900},{"DT_RowId":"test-id-4","0":"Fish Salad","1":"2 People","2":"A delicious salad made of fish.","3":"30 minutes","4":"15 minutes","5":2,"6":1800,"7":900},{"DT_RowId":"test-id-5","0":"Fish Biscuit","1":"One Person","2":"A fish in the shape of a biscuit","3":"none","4":"15 minutes","5":1,"6":0,"7":900},{"DT_RowId":"test-id-6","0":"Yummalishous Stew","1":"11 People","2":"A very yummy stew.","3":"45 minutes","4":"1 hour 25 minutes","5":11,"6":2700,"7":5100},{"DT_RowId":"test-id-7","0":"Testing Again","1":"One Person","2":"meal for one","3":"15 minutes","4":"2 hours ","5":1,"6":900,"7":7200},{"DT_RowId":"test-id-8","0":"Test 3","1":"2 People","2":"prep = 10m","3":"10 minutes","4":"1 day 12 hours ","5":2,"6":600,"7":129600},{"DT_RowId":"test-id-9","0":"Test 4","1":"4 People","2":"test 4 d","3":"25 minutes","4":"1 hour 30 minutes","5":4,"6":1500,"7":5400},{"DT_RowId":"test-id-10","0":"Bacon Lettuce And Tomato Sandwich","1":"6 People","2":"A classic.","3":"5 minutes","4":"15 minutes","5":6,"6":300,"7":900}]}

As you can see, the Rabl version includes all sorts of extra info from the Recipe object, and the 'aaData' array is an array of named 'recipe' objects, not an array of anonymous objects.

nesquena commented 11 years ago

Ahh i see it now:

@recipes.each do |r|

needs to be

@recipes.map do |r|

in node :aaData do. .each returns the entire recipe object.

davesag commented 11 years ago

Kick ass, that worked! Thanks. So in summary if I use @recipes.map do |r| instead of @recipes.each do |r| the node will only take the info I give it rather than trying to be clever.

davesag commented 11 years ago

FYI the presence of object false makes no difference to the output in this instance.

nesquena commented 11 years ago

Yeah exactly, use map along with node.