how to use an instance of a model in another serializer - ruby-on-rails

I'm stuck here and couldn't find solution to proceed my work,
I have 3 models: plans, days, and meals.
This is my Plan Controller I've managed to get the correct answer in the controller, I want it nested and inside the serializer because I'm using URL helper to retrieve my images URLs, is there a possible way to use the #plan.id inside the DaySerializer?
def meals
#plan = Plan.find(params[:id])
#days = #plan.days
#meals = Meal.where("plan_id = ? ", #plan.id)
render :json => { :plan => #plan, :days => #days,
:meals => #meals }
end
This is my Plan model
class Plan < ApplicationRecord
has_many :days
has_one_attached :image, dependent: :destroy
end
This is my Day model
class Day < ApplicationRecord
has_many :meals
has_many :plans
end
This is my Meal model
class Meal < ApplicationRecord
belongs_to :plan
belongs_to :day
has_one_attached :image, dependent: :destroy
end
I want to show all meals for a specific Plan, to do that I need to use a variable inside the daySerializer but I couldn't find how to do it.
This is my planSerializer
class PlanSerializer < ActiveModel::Serializer
attributes :id, :name, :monthly_price, :plan_days
def plan_days
object.days.map do |day|
DaySerializer.new(day, scope: scope, root: false, event: object)
end
end
end
and this is my DaySerializer which I need to use the instance of the plan inside
class DaySerializer < ActiveModel::Serializer
attributes :number, :plan_meals
def plan_meals
#how to be able to use this line in Serilizer? !important
#plan = Plan.find(params[:id])
object.meals.map do |meal|
if meal.plan_id == #plan.id
MealSerializer.new(meal, scope: scope, root: false, event: object)
end
end
end
end
target reason response :
{
id: 8,
name: "Plan1",
monthly_price: 88,
plan_days: [
{
number: 5,
plan_meals: [],
},
{
number: 4,
plan_meals: [],
},
{
number: 3,
plan_meals: [],
},
{
number: 2,
plan_meals: [],
},
{
number: 1,
plan_meals: [
{
id: 11,
name: "test meal",
calories: 32,
protein: 32,
fat: 32,
carbohydrates: 32,
plan_id: 8,
},
],
},
],
}
currently it's showing all meals that belongs to each day,
not only the meals with the plan_id = Plan.find(params[:id])

In general I think you could use something like this should work.
ActiveModel::Serializer::CollectionSerializer.new. It actually by itself allows you to pass additional information to your serializer. It does the same as your current code just you are able to explicitly pass new data.
Controller:
def meals
#plan = Plan.find(params[:id])
#days = #plan.days
#meals = Meal.where("plan_id = ? ", #plan.id)
render :json => {
:plan => #plan,
:days => ActiveModel::Serializer::CollectionSerializer.new(#days, serializer: DaySerializer, plan_id: #plan.id),
:meals => #meals
}
end
And then in DaySerializer:
class DaySerializer < ActiveModel::Serializer
attributes :number, :plan_meals
def plan_meals
object.meals.map do |meal|
if meal.plan_id == instance_options[:plan_id]
MealSerializer.new(meal, scope: scope, root: false, event: object)
end
end
end
end
So in short ActiveModel::Serializer::CollectionSerializer.new in controller and instance_options in serializer to access passed additional parameters.

UPDATED:
How about add meal serializer?
class MealSerializer < ActiveModel::Serializer
attributes :id, :name, :calories, :protein, :fat, # etc
end
class DaySerializer < ActiveModel::Serializer
attributes :number
has_many :meals, serializer: MealSerializer
end
ORIGINAL:
class PlanSerializer < ActiveModel::Serializer
attributes :id, :name, :monthly_price, :plan_days
has_many :plan_days, serializer: DaySerializer
end
something like this.

Related

How can I call related tables with include option by as_json

current relation is below.
class Hoge < ApplicationRecord
belongs_to :hoge_category
class HogeCategory < ApplicationRecord
belongs_to :big_hoge_category
has_many :hoges
class BigHogeCategory < ApplicationRecord
has_many :hoge_categories
has_many :hoges, through: :hoge_categories
I want to extract Hoge's data which is related HogeCategory table and BigHogeCategory table.
Like this.
HogesController
hoges = Hoge.index( params[:keyword], nil, params[:hoge_category_id], params[:offset].to_i, 10 ).as_json(include: :hoge_category)
render status: 200, json: { hoges: hoges } #send API
Hoge.rb
class Hoge < ApplicationRecord
searchkick callbacks: :async, language: "japanese", word_middle: [:title, :message]
def self.index(keyword, connect_id, hoge_category_id, offset, limit) #execute search
where = {}
if keyword.present?
hoges = Hoge.search(keyword, fields: [:title, :message],misspellings: false, where: where, order: { created_at: :desc }, limit: limit, offset: offset).results
else
hoges = Hoge.search(fields: [:title, :message],misspellings: false, where: where, order: { created_at: :desc }, limit: limit, offset: offset).results
end
return hoges
end
def search_data
{
title: title,
message: message,
created_at: created_at,
hoge_category_id: hoge_category&.id
}
end
end
From my search, it's better to use as_json with include option.
But I don't know how to write when related tables are multiple.
How can I write that?
Or if you have a better idea, let me know please..

Destroy deep associated object on parent save, using assign_attributes

I want to delete an deep associated record inside assign_attributes.
Screen is the only object I need to save, but the deep associated NoteMember object should get deleted on save of Screen object, if params[:delete] is true for that particular NoteMember object.
Following is the table structure:
MODELS
class Screen < ActiveRecord::Base
has_many :alerts
accepts_nested_attributes_for :alerts
end
class Alert < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :notes
end
class Note < ActiveRecord::Base
has_many :note_members
accepts_nested_attributes_for :note_members
end
class NoteMember < ActiveRecord::Base
end
CONTROLLER
s = Screen.where(id: <some_id>).first
alert_attrs = []
params[:alerts].each do |a|
notes_attrs = []
params[:notes].each do |n|
note_member_attrs = []
params[:note_members].each do |nm|
# if nm[:delete] = true, I need to delete the note member on saving Screen object
note_member_attrs.push({
id: nm[:id],
visibility: nm[:visibility]
})
end
notes_attrs.push({
id: n[:id],
description: n[:description],
note_members_attributes: note_member_attrs
})
end
alert_attrs.push({
id: a[:id],
name: a[:name]
notes_attributes: notes_attrs
})
end
s.assign_attributes(
alerts_attributes: alert_attrs
)
s.save!
How can this be achieved?
You can use rails built-in destroy functionality:
accepts_nested_attributes_for :note_members, allow_destroy: true
and pass
note_members_attributes: [ { _destroy: true, id: 123 }]
for note_members that need to be deleted.

Unpermitted Parameter for Nested Resource in JSON Payload

I am having difficulty sending a JSON payload to an endpoint that contains a nested resource that I would like created with associations in-tact and persisted... no matter what I do I am presented in the console with Unpermitted parameter: :ingredients.
models/meal.rb
class Meal < ApplicationRecord
has_many :ingredients
accepts_nested_attributes_for :ingredients
end
models/ingredient.rb
class Ingredient < ApplicationRecord
belongs_to :meal
end
controller/meals_controller.rb
class MealsController < ApplicationController
def create
#meal = Meal.new(meal_params)
puts meal_params.inspect
# just want to see the parameters in the log
# ...
end
private
def meal_params
params.require(:meal).permit(
:name,
ingredients_attributes: [:id, :text]
)
end
end
db/migrate/xxx_create_inredients.rb
class CreateIngredients < ActiveRecord::Migration[5.1]
def change
create_table :ingredients do |t|
t.belongs_to :meal, index: true
t.string :text
t.timestamps
end
end
end
request JSON payload
{
"meal": {
"name": "My Favorite Meal",
"ingredients": [
{ "text": "First ingredient" },
{ "text": "Second ingredient" }
]
}
}
I tried another approach from a SO article encountering a similar problem that seemed to recognize the ingredients parameter, but ultimately threw a 500:
params.require(:meal).permit(:name, { ingredients: [:id, :text] })
When doing that I receive the following:
ActiveRecord::AssociationTypeMismatch (Ingredient(#70235637684840) expected, got {"text"=>"First Ingredient"} which is an instance of ActiveSupport::HashWithIndifferentAccess(#70235636442880))
Any help in pointing out my flaw is much appreciated. And yes, I want the nested ingredients resource to be part of the payload going to this endpoint.
The only problem that you have here is that you have in your meal params ingredients_attributes
def meal_params
params.require(:meal).permit(
:name,
ingredients_attributes: [:id, :text]
)
end
so in your payload you need to have it with that key too
{
"meal": {
"name": "My Favorite Meal",
"ingredients_attributes": [
{ "text": "First ingredient" },
{ "text": "Second ingredient" }
]
}
}
they need to match.

Rails - 5: How do I pass an array (has_many association) to the controller action

I have a model event and another model event_rule
class Event < ApplicationRecord
has_many :event_rules
end
class EventRule < ApplicationRecord
belongs_to :event
end
I have written an api event#create for saving an event. Here's the body of the POST request:
{
"name": "asd",
"code": "ad",
"isActive": true,
"description": "asd",
"notes": "",
"goalAmount": 0,
"exportId": "",
"defaultCurrency": 1,
"eventStartDate": "2017-04-25T18:30:00.000Z",
"eventEndDate": "2017-04-27T18:30:00.000Z",
"eventRules": [
{
"extraInformation": "{}",
"lookupKeyValuePairId": 40
}
]
}
Here's params hash:
Parameters: {"name"=>"asd", "code"=>"ad", "is_active"=>true, "description"=>"asd", "notes"=>"", "goal_amount"=>0, "export_id"=>"", "default_currency"=>1, "event_start_date"=>"2017-04-25T18:30:00.000Z", "event_end_date"=>"2017-04-27T18:30:00.000Z", "event_rules"=>[{"extra_information"=>"{}", "lookup_key_value_pair_id"=>40}], "client_id"=>"0", "event"=>{"name"=>"asd", "code"=>"ad", "description"=>"asd", "is_active"=>true, "goal_amount"=>0, "export_id"=>"", "event_start_date"=>"2017-04-25T18:30:00.000Z", "event_end_date"=>"2017-04-27T18:30:00.000Z", "default_currency"=>1, "notes"=>""}}
I want the 'event_rules' to be included INSIDE the event. How can do this?
def create
# initialize Event object with `event_params`
event = Event.new(event_params)
# initialize EventRule object per each `event_rule_params`, and associate the EventRule as part of `event.event_rules`
event_rules_params.each do |event_rule_params|
event.event_rules << EventRule.new(event_rule_params)
end
if event.save
# SUCCESS
else
# FAILURE
end
end
private
def event_params
params.require(:event).permit(:name, :code, :is_active, :description, :notes, :goal_amount, :export_id, :default_currency, :event_start_date, :event_end_date, :notes)
end
def event_rules_params
params.require(:event).fetch(:event_rules, []).permit(:extra_information, :lookup_key_value_pair_id)
end
Alternative Rails-way Solution:
if you have control over the parameters that get sent, reformat your request into something like the following (take note of changing event_rules into event_rules_attributes -- Rails Standard) (More Info Here)
Parameters: {
"event"=>{
"name"=>"asd",
"code"=>"ad",
"description"=>"asd",
"is_active"=>true,
"goal_amount"=>0,
"export_id"=>"",
"event_start_date"=>"2017-04-25T18:30:00.000Z",
"event_end_date"=>"2017-04-27T18:30:00.000Z",
"default_currency"=>1,
"notes"=>"",
"event_rules_attributes"=>[
{
"extra_information"=>"{}",
"lookup_key_value_pair_id"=>40
}
]
}
}
# controllers/events_controller.rb
def create
event = Event.new(event_params)
if event.save
# SUCCESS
else
# FAILURE
end
end
private
def event_params
params.require(:event).permit(:name, :code, :is_active, :description, :notes, :goal_amount, :export_id, :default_currency, :event_start_date, :event_end_date, :notes, event_rules_attributes: [:extra_information, :lookup_key_value_pair_id])
end
# models/event.rb
accepts_nested_attributes_for :event_rules

Rails - How to a validate model being altered in before filter from a different model?

Models - Purchaseorder, Purchaseorderadjustments, Productvariant, Location, Locationinventory
I'm storing inventory in Locationinventory which stores a location_id, productvariant_id, and quantity.
The situation arises when I want to create a purchaseorder. I'm using purchaseorderadjustments as a nested attribute to the purchaseorder. A purchaseorder has_many purchaseorderadjustments that store the productvariant_id and quantity.
I'm using before filters to create,update and destroy the related locationinventory records. Everything works well as it is now except that you can remove items from a location that doesn't have them available and the quantity just goes into the negative. I want to verify that the "From Location" has enough of the productvariant in stock to transfer to the "To Location".
Am I doing it wrong? thanks!
Rails 3.2.14
Purchaseorder.rb
class Purchaseorder < ActiveRecord::Base
attr_accessible :fromlocation_id, :status_id, :tolocation_id, :user_id, :purchaseorderadjustments_attributes
belongs_to :user
belongs_to :status
belongs_to :fromlocation, :class_name => "Location", :foreign_key => :fromlocation_id
belongs_to :tolocation, :class_name => "Location", :foreign_key => :tolocation_id
has_many :purchaseorderadjustments, :dependent => :destroy
accepts_nested_attributes_for :purchaseorderadjustments, allow_destroy: true
end
Purchaseorderadjustment.rb
class Purchaseorderadjustment < ActiveRecord::Base
attr_accessible :adjustmenttype_id, :productvariant_id, :purchaseorder_id, :quantity
belongs_to :purchaseorder
belongs_to :productvariant
belongs_to :adjustmenttype
validates_presence_of :quantity, :message => "You need a quantity for each product."
# On creation of a purchaseorderadjustment go ahead and create the record for locationinventory
before_create :create_locationinventory
def create_locationinventory
# Get some info before updating the locationinventory
if fromlocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.fromlocation_id, :productvariant_id => productvariant_id })
fromlocation_current_quantity = fromlocationinventory.quantity
end
if tolocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.tolocation_id, :productvariant_id => productvariant_id })
tolocation_current_quantity = tolocationinventory.quantity
end
# Create or update the from locationinventory
unless fromlocationinventory.nil?
fromlocationinventory.quantity = fromlocation_current_quantity - quantity
fromlocationinventory.save
else
new_fromlocationinventory = Locationinventory.new({ location_id: purchaseorder.fromlocation_id, productvariant_id: productvariant_id, quantity: 0 - quantity })
new_fromlocationinventory.save
end
# Create or update the to locationinventory
unless tolocationinventory.nil?
tolocationinventory.quantity = tolocation_current_quantity + quantity
tolocationinventory.save
else
new_tolocationinventory = Locationinventory.new({ location_id: purchaseorder.tolocation_id, productvariant_id: productvariant_id, quantity: quantity })
new_tolocationinventory.save
end
end
#On update of purchaseorderadjustment
before_update :update_locationinventory
def update_locationinventory
# Get some info before updating the locationinventory
fromlocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.fromlocation_id, :productvariant_id => productvariant_id })
tolocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.tolocation_id, :productvariant_id => productvariant_id })
fromlocation_current_quantity = fromlocationinventory.quantity
tolocation_current_quantity = tolocationinventory.quantity
fromlocationinventory.quantity = fromlocation_current_quantity - quantity + self.quantity_was
fromlocationinventory.save
tolocationinventory.quantity = tolocation_current_quantity + quantity - self.quantity_was
tolocationinventory.save
end
#On destroy of purchaseorderadjustment
before_destroy :destroy_locationinventory
def destroy_locationinventory
# Get some info before updating the locationinventory
fromlocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.fromlocation_id, :productvariant_id => productvariant_id })
tolocationinventory = Locationinventory.find(:first, conditions: { :location_id => purchaseorder.tolocation_id, :productvariant_id => productvariant_id })
fromlocation_current_quantity = fromlocationinventory.quantity
tolocation_current_quantity = tolocationinventory.quantity
fromlocationinventory.quantity = fromlocation_current_quantity + quantity
fromlocationinventory.save
tolocationinventory.quantity = tolocation_current_quantity - quantity
tolocationinventory.save
end
end
productvariant.rb
class Productvariant < ActiveRecord::Base
attr_accessible :barcode, :compare_at_price, :fulfillment_service, :grams,
:inventory_management, :inventory_policy, :inventory_quantity,
:option1, :option2, :option3, :position, :price, :product_id,
:requires_shipping, :shopify_id, :sku, :taxable, :title, :shopify_product_id, :product_title
belongs_to :product, primary_key: "shopify_id", foreign_key: "shopify_product_id"
has_many :purchaseorderadjustments
has_many :locationinventories
def product_plus_variant
"#{self.product.title} - #{self.title}"
end
end
locationinventory.rb
class Locationinventory < ActiveRecord::Base
attr_accessible :location_id, :productvariant_id, :quantity
belongs_to :productvariant
belongs_to :location
end
I'll write this answer because I feel you've provided so much code, you might have scared some answerers away!
Our experience is as follows:
Nested
You can validate nested models in several different ways
Your question is related to passing data in a accepts_nested_attributes_for - you can validate this directly:
#app/models/purchase.rb
Class Purchase < ActiveRecord::Base
has_many :purchase_items
accepts_nested_attributes_for :purchase_items
end
#app/models/purchase_item.rb
Class PurchaseItem < ActiveRecord::Base
belongs_to :purchase
validates :name,
presence: { message: "Your Purchase Needs Items!" } #Returns to initial form with this error
end
Standard
If you want to conditionally validate based on another model, you'll have to use inverse_of: to keep the object available throughout the data transaction:
#app/models/purchase.rb
Class Purchase < ActiveRecord::Base
has_many :purchase_items, inverse_of: :purchase
accepts_nested_attributes_for :purchase_items
end
#app/models/purchase_item.rb
Class PurchaseItem < ActiveRecord::Base
belongs_to :purchase, inverse_of: :purchase_items
validates :name,
presence: { message: "Your Purchase Needs Items!" },
if: :paid_with_card?
private
def paid_with_card?
self.purchase.payment_method == "card"
end
end

Resources