I have Dish
and Comment
models like below in my Rails 5.1 API app - code repo here . I need help with adding a new Comment
to a Dish
.
Post
class Dish < ApplicationRecord
has_many :comments
end
Comment
class Comment < ApplicationRecord
belongs_to :dish
end
Post Serializer (uses ActiveModel Seriazlier)
class DishSerializer < ActiveModel::Serializer
attributes :id, :name, :image, :category, :label, :price, :featured, :description, :created_at
has_many :comments
end
Comment Serializer
class CommentSerializer < ActiveModel::Serializer
attributes :id, :rating, :comment, :author, :date
def date
object.created_at
end
end
Post Controller - default rails scaffold
class DishesController < ApplicationController
before_action :set_dish, only: [:show, :update, :destroy]
# GET /dishes
def index
@dishes = Dish.all
render json: @dishes
end
# GET /dishes/1
def show
render json: @dish
end
# POST /dishes
def create
@dish = Dish.new(dish_params)
if @dish.save
render json: @dish, status: :created, location: @dish
else
render json: @dish.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /dishes/1
def update
# byebug
if @dish.update(dish_params)
render json: @dish
else
render json: @dish.errors, status: :unprocessable_entity
end
end
# DELETE /dishes/1
def destroy
@dish.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_dish
@dish = Dish.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def dish_params
params.require(:dish).permit(:name, :image, :category, :label, :price, :featured, :description)
end
end
Comment Controller - default rails scaffold
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :update, :destroy]
# GET /comments
def index
@comments = Comment.all
render json: @comments
end
# GET /comments/1
def show
render json: @comment
end
# POST /comments
def create
@comment = Comment.new(comment_params)
if @comment.save
render json: @comment, status: :created, location: @comment
else
render json: @comment.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /comments/1
def update
if @comment.update(comment_params)
render json: @comment
else
render json: @comment.errors, status: :unprocessable_entity
end
end
# DELETE /comments/1
def destroy
@comment.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
@comment = Comment.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def comment_params
params.require(:comment).permit(:rating, :comment, :author)
end
end
Issue
When a user visits /dishes/:id
and adds a comment to a dish via the front-end app ( Angular 2) , the comment
is push to the array of current comments and I'm calling PUT /dishes:id
with the dish
object nested with the existing comments
and the new comment. However the new comment
is not saved by rails - no error returned, rather the dish
object is returned. However I do see Unpermitted parameters: :id, :created_at
in rails s
console. How do I get rails to save the new comment ?
The page ( dishes/9
) from where I'm adding the comment to a dish looks like below on the Angular client side.
Rails Server logs
On the rails side, below is what I see in params
- I do see the new comment - {"author"=>"JANE7777", "rating"=>3, "comment"=>"COMMENT7777", "date"=>"2017-11-12T12:58:12.555Z"}
in there.
Started PUT "/dishes/9" for 127.0.0.1 at 2017-11-12 18:28:12 +0530
Processing by DishesController#update as HTML
Parameters: {"id"=>"9", "name"=>"Uthappizza", "image"=>"images/uthappizza.png", "category"=>"mains", "label"=>"Hot", "price"=>"4.99", "featured"=>true, "description"=>"A unique combination of Indian Uthappam (pancake) and Italian pizza, topped with Cerignola olives, ripe vine cherry tomatoes, Vidalia onion, Guntur chillies and Buffalo Paneer.", "created_at"=>"2017-11-01T04:30:09.407Z", "comments"=>[{"id"=>46, "rating"=>5, "comment"=>"Imagine all the eatables, living in conFusion!", "author"=>"John Lemon", "date"=>"2012-10-16T17:57:28.556Z"}, {"id"=>47, "rating"=>4, "comment"=>"Sends anyone to heaven, I wish I could get my mother-in-law to eat it!", "author"=>"Paul McVites", "date"=>"2014-09-05T17:57:28.556Z"}, {"id"=>48, "rating"=>3, "comment"=>"Eat it, just eat it!", "author"=>"Michael Jaikishan", "date"=>"2015-02-13T17:57:28.556Z"}, {"id"=>49, "rating"=>4, "comment"=>"Ultimate, Reaching for the stars!", "author"=>"Ringo Starry", "date"=>"2013-12-02T17:57:28.556Z"}, {"id"=>50, "rating"=>2, "comment"=>"It's your birthday, we're gonna party!", "author"=>"25 Cent", "date"=>"2011-12-02T17:57:28.556Z"}, {"id"=>51, "rating"=>4, "comment"=>"great dish", "author"=>"Jogesh", "date"=>"2017-10-30T05:03:39.656Z"}, {"author"=>"JANE7777", "rating"=>3, "comment"=>"COMMENT7777", "date"=>"2017-11-12T12:58:12.555Z"}], "dish"=>{"id"=>"9", "name"=>"Uthappizza", "image"=>"images/uthappizza.png", "category"=>"mains", "label"=>"Hot", "price"=>"4.99", "featured"=>true, "description"=>"A unique combination of Indian Uthappam (pancake) and Italian pizza, topped with Cerignola olives, ripe vine cherry tomatoes, Vidalia onion, Guntur chillies and Buffalo Paneer.", "created_at"=>"2017-11-01T04:30:09.407Z"}}
Dish Load (1.0ms) SELECT "dishes".* FROM "dishes" WHERE "dishes"."id" = $1 LIMIT $2 [["id", 9], ["LIMIT", 1]]
[25, 34] in C:/apps/railsApi/app/controllers/dishes_controller.rb
25: end
26:
27: # PATCH/PUT /dishes/1
28: def update
29: byebug
=> 30: if @dish.update(dish_params)
31: render json: @dish
32: else
33: render json: @dish.errors, status: :unprocessable_entity
34: end
(byebug) params
<ActionController::Parameters {"id"=>"9", "name"=>"Uthappizza", "image"=>"images/uthappizza.png", "category"=>"mains", "label"=>"Hot", "price"=>"4.99", "featured"=>true, "description"=>"A unique combination of Indian Uthappam (pancake) and Italian pizza, topped with Cerignola olives, ripe vine cherry tomatoes, Vidalia onion, Guntur chillies and Buffalo Paneer.", "created_at"=>"2017-11-01T04:30:09.407Z", "comments"=>[{"id"=>46, "rating"=>5, "comment"=>"Imagine all the eatables, living in conFusion!", "author"=>"John Lemon", "date"=>"2012-10-16T17:57:28.556Z"}, {"id"=>47, "rating"=>4, "comment"=>"Sends anyone to heaven, I wish I could get my mother-in-law to eat it!", "author"=>"Paul McVites", "date"=>"2014-09-05T17:57:28.556Z"}, {"id"=>48, "rating"=>3, "comment"=>"Eat it, just eat it!", "author"=>"Michael Jaikishan", "date"=>"2015-02-13T17:57:28.556Z"}, {"id"=>49, "rating"=>4, "comment"=>"Ultimate, Reaching for the stars!", "author"=>"Ringo Starry", "date"=>"2013-12-02T17:57:28.556Z"}, {"id"=>50, "rating"=>2, "comment"=>"It's your birthday, we're gonna party!", "author"=>"25 Cent", "date"=>"2011-12-02T17:57:28.556Z"}, {"id"=>51, "rating"=>4, "comment"=>"great dish", "author"=>"Jogesh", "date"=>"2017-10-30T05:03:39.656Z"}, {"author"=>"JANE7777", "rating"=>3, "comment"=>"COMMENT7777", "date"=>"2017-11-12T12:58:12.555Z"}], "controller"=>"dishes", "action"=>"update", "dish"=>{"id"=>9, "name"=>"Uthappizza", "image"=>"images/uthappizza.png", "category"=>"mains", "label"=>"Hot", "price"=>"4.99", "featured"=>true, "description"=>"A unique combination of Indian Uthappam (pancake) and Italian pizza, topped with Cerignola olives, ripe vine cherry tomatoes, Vidalia onion, Guntur chillies and Buffalo Paneer.", "created_at"=>"2017-11-01T04:30:09.407Z"}} permitted: false>
(byebug) c
Unpermitted parameters: :id, :created_at
(0.0ms) BEGIN
(0.0ms) COMMIT
[active_model_serializers] Comment Load (0.0ms) SELECT "comments".* FROM "comments" WHERE "comments"."dish_id" = $1 [["dish_id", 9]]
[active_model_serializers] Rendered DishSerializer with ActiveModelSerializers::Adapter::Attributes (31.29ms)
Completed 200 OK in 1901725ms (Views: 37.5ms | ActiveRecord: 5.0ms)
Client-side models
The Dish
model has Comment[]
as one of the members. When a new comment is added via the form the comment
is push to dish.comments
array before sending the Dish
object to the Rails API back-end.
Comment
model in client side
export class Comment {
rating: number;
comment: string;
author: string;
date: string;
}
Post
model in client side
import { Comment } from './comment';
export class Dish {
id: number;
name: string;
image: string;
category: string;
label: string;
price: string;
featured: boolean;
description: string;
comments: Comment[];
}
Objects can be nested inside other objects. Each nested object must have a unique access path. The same field name can occur in nested objects in the same document.
Nested objects are the objects that are inside an another object. In the following example 'vehicles' is a object which is inside a main object called 'person'. Using dot notation the nested objects' property(car) is accessed.
JSON can actually take the form of any data type that is valid for inclusion inside JSON, not just arrays or objects. So for example, a single string or number would be valid JSON. Unlike in JavaScript code in which object properties may be unquoted, in JSON only quoted strings may be used as properties.
Nested JSON is simply a JSON file with a fairly big portion of its values being other JSON objects. Compared with Simple JSON, Nested JSON provides higher clarity in that it decouples objects into different layers, making it easier to maintain.
I’d recommend you use the Comments controller and form a nested route to be RESTful.
Firstly, POST the request, including the dish_id to /comments. You’re trying to create a Comment, not update the Dish.
Secondly, form the nested route: /dishes/:dish_id/comments
Here’s a guide to nesting resources like this: http://guides.rubyonrails.org/routing.html#nested-resources
The reason for your error is that you’ve broken the expected parameter structure. For a PUT your params should be:
{
id: <record to update>,
dish: {
name: “blah”,
comments: [{...},{...}]
}
}
Again, I really recommend you don’t PUT to Dish if you just want to add a Comment. For instance: the above would be desired to replace all comments on the Dish! If you’re creating a record, create that record, don’t update it’s parent.
If you wanted to update the Dish when a new Comment is added you can add a callback to the Comment model on create or do it in the Comment create controller action.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With