Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize nested Model in Rails?

I'm having an extremely difficult time figuring out how to serialize the nested attributes of a model in rails. I have a RecipeTemplate which will store an already existing Recipe in it's template_data attribute. Recipe has nested attributes two levels deep.

This is on rails 3.1.0.rc4

class RecipeTemplate < ActiveRecord::Base
  serialize :template_data, Recipe
 ...
end

class Recipe < ActiveRecord::Base
  has_many :ingredients
  accepts_nested_attributes_for :ingredients
 ...
end

Ingredients in Recipe also has nested attributes (SubIngredients).

If I set the template_data with an object like so:

Recipe.includes(:ingredients => [:sub_ingredients]).find(1)

I'll get a TypeError "can't dump anonymous class Class" which makes sense, since it doesn't know how to serialize the Ingredients or SubIngredients.

How can you serialize the nested attributes in a model so that you can use:

 serialize :template_data, Recipe

Or do I have to serialize the data in some other manner and perform the type safety checks myself?

Thanks in advance for any help

like image 396
MunkiPhD Avatar asked Jul 16 '11 20:07

MunkiPhD


1 Answers

I can see why you would want the template itself to be stored inside of a serialized column, but you need a little more manipulation of the data being stored than is permitted by that type of column. Here's what I would do:

app/models/recipe_template.rb

class RecipeTemplate < ActiveRecord::Base
  serialize :template_data

  attr_accessible :name, :recipe

  def recipe=(r)
    self.template_data = r.serializable_hash_for_template
  end

  def recipe
    Recipe.new(template_data)
  end
end

app/models/recipe.rb

class Recipe < ActiveRecord::Base
  has_many :ingredients, as: :parent
  accepts_nested_attributes_for :ingredients

  attr_accessible :name, :ingredients_attributes

  def serializable_hash_for_template(options={})
    options[:except] ||= [:id, :created_at, :updated_at]
    serializable_hash(options).tap do |h|
      h[:ingredients_attributes] = ingredients.map(&:serializable_hash_for_template)
    end
  end
end

app/models/ingredient.rb

class Ingredient < ActiveRecord::Base
  belongs_to :parent, polymorphic: true
  has_many :sub_ingredients, class_name: 'Ingredient', as: :parent
  accepts_nested_attributes_for :sub_ingredients

  attr_accessible :name, :sub_ingredients_attributes

  def serializable_hash_for_template(options={})
    options[:except] ||= [:id, :parent_id, :parent_type, :created_at, :updated_at]
    serializable_hash(options).tap do |h|
      h[:sub_ingredients_attributes] = sub_ingredients.map(&:serializable_hash_for_template)
    end
  end
end

Then to create and use a template:

# create a recipe to use as a template
taco_meat = Ingredient.create(name: "Taco Meat")
taco_seasoning = taco_meat.sub_ingredients.create(name: "Taco Seasoning")
sams_tacos = Recipe.create(name: "Sam's Tacos")
sams_tacos.ingredients << taco_meat

# create a template from the recipe
taco_recipe = RecipeTemplate.create(name: "Taco Recipe", recipe: sams_tacos)

# build a new recipe from the template
another_taco_recipe = taco_recipe.recipe

The difference is that you're using the serialized column to store a Hash to use in the Recipe contructor. If you just wanted to serialize the object, the other posters are correct–just associate an object.

like image 200
Sam Coles Avatar answered Nov 20 '22 21:11

Sam Coles