How to serialize nested Model in Rails? - ruby-on-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

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.

Why don't you just keep a field in your RecipeTemplate model with the label recipe_id
and link that to the recipe instead of trying to serialize a recipe object?

Related

Rails API nested attirbutes Find_or_create to avoid duplications doesn't work

I'm trying to control the nested attributes in case of duplications, do find the row and use it instead of creating a new one, it works fine lower nested level which is the meals.
however if I use it the commented code in the plan.rb ( you can check it below ) makes the meals blank, as if I'm not passing any meals inside my request, any idea about this?
Plan.rb
class Plan < ApplicationRecord
has_and_belongs_to_many :meals
has_and_belongs_to_many :days
has_one_attached :image, dependent: :destroy
validate :acceptable_image
accepts_nested_attributes_for :days, reject_if: ->(object) { object[:number].blank? }
#! this is causing meals to not save
# # before_validation :find_days
# def find_days
# self.days = self.days.map do |object|
# Day.where(number: object.number).first_or_initialize
# end
# end
#!
end
Day.rb
class Day < ApplicationRecord
has_and_belongs_to_many :meals
has_and_belongs_to_many :plans
accepts_nested_attributes_for :meals, reject_if: ->(object) { object[:name].blank? }
before_validation :find_meals
def find_meals
self.meals = self.meals.map do |object|
Meal.where(name: object.name).first_or_initialize
end
end
end
Meal.rb
class Meal < ApplicationRecord
has_and_belongs_to_many :plans
has_and_belongs_to_many :days
end
This is how I permit my params
def plan_params
params.require(:plan).permit(:name, :monthly_price, :image_url, days_attributes: [:number, meals_attributes: [:name, :calories, :protein, :fat, :carbohydrates, :categorie]])
end
I'm sorry for making this long, but I wanted to give as many details as possible.
Since you're mapping the self.days association, Day.where(number: object.number).first_or_initialize replaces the days array with Day objects without any meal attributes.
You need to do something like this instead, inside the map block:
day = Day.where(number: object.number).first_or_initialize
day.attributes = object.attributes
# or similar, to copy the nested attributes provided by the request
day

How to pass attributes in Serializer not in model in Rails (ActiveRecord)?

I am building a Rails backend that has Users, Sightings and Comments. The modelComment joins Sighting and User. I would like to pass the user's name or username along with the user_id which is an attribute of join table Comments in CommentSerializer to my front-end.
I can access this data using Active Record or Ruby methods but how do actually make this an attribute to be passed through Serializer?
Here's my CommentSerializer file:
class CommentSerializer < ActiveModel::Serializer
attributes :id, :body, :likes, :user_id
belongs_to :commentable, :polymorphic => true
belongs_to :sighting
has_many :comments, as: :commentable
end
add to your attributes :user_name for example and then add
def user_name
object.user.name
end
in your CommentSerializer class
So I had good luck with this method in my Serializer file to pass down this attribute:
def user_name
User.all.find { |f| f.id == object.user_id }.username
end

How can I create a model object with deeply nested associations in Rails using only my params whitelist method?

To replicate this question, my base model is project:
class Project < ApplicationRecord
has_many :tones, as: :analyzable
has_many :sentences
accepts_nested_attributes_for :tones, :sentences
end
And I have two other models:
class Sentence < ApplicationRecord
belongs_to :project
has_many :tones, as: :analyzable
accepts_nested_attributes_for :tones
end
class Tone < ApplicationRecord
belongs_to :analyzable, polymorphic: true
end
In my ProjectsController, I want to have a single create action that just takes in the whitelisted params and creates a project with all the appropriate associations. My project_params method looks like this:
private
def project_params
params.permit(:text, :title, :img, sentences_attributes: [:id, :text, tones_attributes: [:score, :tone_name]], tones_attributes: [:id, :score, :tone_name])
end
But if I try something like:
project = Project.create(project_params)
I will get an error like the one below:
| ActiveRecord::AssociationTypeMismatch (Sentence(#70119358770360) expected, got {"text"=>"extremely disappointed with the level of service with Virgin Media.", "tones"=>[{"score"=>0.64031, "tone_name"=>"Sadness"}, {"score"=>0.618451, "tone_name"=>"Confident
"}]} which is an instance of ActiveSupport::HashWithIndifferentAccess(#70119356172580)):
I ended up writing this for my create action, which gets the job done but feels quite clumsy:
project = Project.create({text: params[:text], title: params[:title],
img: params[:img]})
params[:sentences].each do |sent|
sentence = project.sentences.create({text: sent["text"]})
sent["tones"].each do |tone|
sentence.tones.create({score: tone["score"], tone_name: tone["tone_name"]})
end
end
params[:tones].each do |tone|
project.tones.create({score: tone["score"], tone_name: tone["tone_name"]})
end
Is there a better way to handle this kind of situation than what I have immediately above, where one is trying to create an object from nested params that has several levels of association?

Rails 4 create model with nested attributes has_many

I have a many to many relationship with DoctorProfile and Insurance. I'd like to create these associations off of a form from a client side app. I'm sending back an array of doctor_insurances_ids and trying to create the association in one line. Is it possible to send back an array of doctor_insurances ids? If so what's the proper way to name it for mass assignment in the params?
The error I'm getting with the following code is
ActiveRecord::UnknownAttributeError: unknown attribute 'doctor_insurances_ids' for DoctorProfile.
class DoctorProfile
has_many :doctor_insurances
accepts_nested_attributes_for :doctor_insurances # not sure if needed
class Insurance < ActiveRecord::Base
has_many :doctor_insurances
class DoctorInsurance < ActiveRecord::Base
# only fields are `doctor_profile_id` and `insurance_id`
belongs_to :doctor_profile
belongs_to :insurance
def create
params = {"first_name"=>"steve",
"last_name"=>"johanson",
"email"=>"steve#ymail.com",
"password_digest"=>"password",
"specialty_id"=>262,
"doctor_insurances_ids"=>["44", "47"]}
DoctorProfile.create(params)
end
You're not putting a doctor_insurance_id in your Doctor Profile so your DoctorProfile.create(params) line isn't going to work. You could do something like this:
def create
doctor = DoctorProfile.create(doctor_profile_params)
params["doctor_insurances_ids"].each do |x|
DoctorInsurance.create(doctor_profile_id: doctor.id, insurance_id: x)
end
end
def doctor_profile_params
params.require(:doctor_profile).permit(:first_name, :last_name, :email, :password_digest, :specialty_id)
end

Saving Polymorphic model in rails

I have a polymorphic model; result, with belongs_to result_table, polymorphic: true which i'm using with two other models; team and sport, through a one to one relationship and its working. Due to the one to one relationship, i have a callback function,after_create :build_result_table which i use in setting and saving the result_table_id and result_table_type in the result table in the database. The issue I have is the input saved in result_table_type, its VARCHAR, but it seems to be saving the input as an integer
def build_result_table
Result.create(result_table_id: self.id, result_table_type: self)
end
I think the issue is probably with result_table_type: self, but i tried result_table_type: self.class.name to save the name of the class but it threw an error. Any advice on saving a unique value in the result_table_type column..
EDIT
I'm adding the models involved, though i have done the saving manually as said by #Mohammad in the comments, but will also be grateful, if i can be shown how i can ask rails to save the values automatically.
APP/MODEL/SPORT.RB
class Sport < ActiveRecord::Base
attr_accessible :sport_name, :sport_id, :result_attributes, :result_table_id
has_one :result, as: :result_table
after_create :build_result_table
accepts_nested_attributes_for :result
def build_result_table
Result.create(result_table_id: self.id, result_table_type: self.class.name)
end
end
APP/MODEL/TEAM.RB
class Team < ActiveRecord::Base
has_one :result, as: :result_table
attr_accessible :teamname, :color, :result_attributes
after_create :build_result_table
accepts_nested_attributes_for :result
def build_result_table
Result.create(result_table_id: self.id, result_table_type: self.class.name)
end
end
APP/MODEL/RESULT.RB
class Result < ActiveRecord::Base
attr_accessible :sport_id,:team_id, :result_table_id, :result_table_type
belongs_to :result_table, polymorphic: true
end
# Either one of these
Result.create(result_table: self)
Result.create(result_table_id: self.id, result_table_type: self.class.to_s)

Resources