Monday, March 16, 2009

Recipe.to_json

‹prev | My Chain | next›

As described the other day, I am not immune to the siren's song of adding the really cool feature that will certainly be used someday. As always is the case when I do these things, it costs me later. In particular, I added the following to my cooking blog's Recipe class:
  def to_hash
{
:title => title,
:date => date.to_s,
:label => label,
:image => image,
:serves => serves,
:cook_time => cook_time,
:prep_time => prep_time,
:inactive_time => inactive_time,
:serves => serves,
:tags => tags.map(&:name),
:ingredients => ingredients.map(&:name)
}
end

def to_json
to_hash.to_json
end
OK, so maybe it wasn't that cool. This must have pre-dated to_json being part of Rails core. The best explanation that I can is that it was done for a presentation, but that's a lousy reason for it to still be in production code. At any rate, I remove it from my local copy and enter into a script/console session. The default Recipe#to_json is not going to cut it:
>> r = Recipe.first
=> #<Recipe id: 24, title: "Caper and Red Wine Vinaigrette", label: "salad",
summary: " A vinaigrette is so simple and quick to prepare ...",
serves: 2, prep_time: 5, cook_time: 0, inactive_time: 0, image_old: "vinaigrette_7195.jpg",
date: "2006-04-04",
instructions: " \n We combine the vinegar and a small pinch of salt...",
recipe_group_id: nil, howto: false, author_id: nil, published: true>
>> r.to_json
=> {
"label": "salad",
"prep_time": 5,
"title": "Caper and Red Wine Vinaigrette",
"inactive_time": 0,
"published": true,
"howto": false,
"date": "2006\/04\/04",
"id": 24,
"image_old": "vinaigrette_7195.jpg",
"recipe_group_id": null,
"serves": 2,
"author_id": null,
"cook_time": 0,
"summary": " A vinaigrette is so simple and quick to prepare ...",
"instructions": " \n We combine the vinegar and a small pinch of salt..."
}
Fortunately, it is easy to include relationships as well as to exclude certain unnecessary attributes. What I eventually end up with is:
>> r.to_json(:methods => [:_id, :tag_names], 
:include => { :preparations => { :include => {:ingredient => {:except => :id }},
:except => [:ingredient_id, :recipe_id, :id] },
:tools => {:except => [:id, :label]} },
:except => [:id, :label])
The resulting JSON should include a key, tag_names, pointing to an array built from the results of a call to the tag_names method (def tag_names; tags.map(&:name) end).

Additionally, the resulting JSON should include two other arrays built from Recipe associations: preparations (of ingredients) and tools used to make the recipe. The resulting JSON data structure is pretty darn close to what I would eventually like to use:
=> {  
"prep_time": 5,
"title": "Caper and Red Wine Vinaigrette",
"inactive_time": 0,
"published": true,
"howto": false,
"tools": [
{
"title": "Bowl",
"amazon_title": "Pyrex Mixing Bowls",
"asin": "B0000644FE"
}
],
"date": "2006\/04\/04",
"_id": "2006-04-04-salad",
"image_old": "vinaigrette_7195.jpg",
"recipe_group_id": null,
"serves": 2,
"tag_names": [
"dinner",
"first-course",
"side",
"salad",
"vegetarian"
],
"author_id": null,
"cook_time": 0,
"summary": " A vinaigrette is so simple and quick to prepare ...",
"preparations": [
{
"brand": null,
"quantity": 1.0,
"order_number": null,
"unit": "teaspoon",
"description": null,
"ingredient": {
"kind": "red wine",
"name": "vinegar"
}
},
{
"brand": null,
"quantity": null,
"order_number": null,
"unit": null,
"description": null,
"ingredient": {
"kind": null,
"name": "salt and pepper"
}
},
{
"brand": null,
"quantity": 1.0,
"order_number": null,
"unit": "tablespoon",
"description": null,
"ingredient": {
"kind": "extra-virgin olive",
"name": "oil"
}
},
{
"brand": null,
"quantity": 0.25,
"order_number": null,
"unit": "teaspoon",
"description": "rinsed",
"ingredient": {
"kind": "packed in salt",
"name": "capers"
}
},
{
"brand": null,
"quantity": null,
"order_number": null,
"unit": null,
"description": null,
"ingredient": {
"kind": null,
"name": "salad greens"
}
},
{
"brand": null,
"quantity": null,
"order_number": null,
"unit": null,
"description": null,
"ingredient": {
"kind": "green, Picholine",
"name": "olives"
}
}
],
"instructions": " \n We combine the vinegar and a small pinch of salt..."
}
The tools data structure is simple enough (though I do not recall why we stored the Amazon.com title):
    {
"title": "Bowl",
"amazon_title": "Pyrex Mixing Bowls",
"asin": "B0000644FE"
}
The preparation data structure is a little more interesting and somewhat reminiscent of Cooking for Engineers. For instance, given some capers, we measure out 1/4 teaspoon of them and rinse them:
    { 
"brand": null,
"quantity": 0.25,
"order_number": null,
"unit": "teaspoon",
"description": "rinsed",
"ingredient": {
"kind": "packed in salt",
"name": "capers"
}
}
I can envision building richer data structures in the the future to describe higher level steps in a recipe, like mixing:
{ 
"description": "tossed",
"children": [
{
"quantity": 0.25,
"unit": "teaspoon",
"description": "rinsed",
"ingredient": {
"kind": "packed in salt",
"name": "capers"
},
},
{
"quantity": 1,
"unit": "bunch",
"ingredient": {
"name": "salad greens"
}
}
]
}
That's pie in the sky at this point, but I think I'll keep the data structure as-is for now. All that is left is to dump the data into CouchDB. Next time...

No comments:

Post a Comment