Search code examples
ruby-on-railsrubywebmodel-view-controllerforum

ActiveModel::ForbiddenAttributesError when creating a reply to a forum post


I am using rails 5.0.0. I get the following error when I try to create a reply to a forum post.

Started POST "/discussions/7/replies" for 108.252.220.249 at 2018-03-19 15:54:44 +0000
Cannot render console from 108.252.220.249! Allowed networks: 127.0.0.1,::1, 127.0.0.0/127.255.255.255
Processing by RepliesController#create as JS
Parameters: {"utf8"=>"✓", "reply"=>{"reply"=>""}, "commit"=>"Submit Reply", "discussion_id"=>"7"}
User Load (0.6ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ?  [["id", 1], ["LIMIT", 1]]
Discussion Load (0.2ms)  SELECT  "discussions".* FROM "discussions" WHERE "discussions"."id" = ? LIMIT ?  [["id", 7], ["LIMIT", 1]]
(0.1ms)  begin transaction
(0.1ms)  rollback transaction
Completed 401 Unauthorized in 10ms (ActiveRecord: 1.0ms)



ActiveModel::ForbiddenAttributesError (ActiveModel::ForbiddenAttributesError):

This is my part of my replies controller file. The error makes it seem like there something is wrong with the create action so that is what I have included for this post. Also, my schema, there is a 'replies' table with a column named 'reply' which is the actual text the user wants to submit as their response. Sorry for the confusing naming scheme.

I feel like the create action is correct, and that there is something wrong elsewhere. Like a strong parameters type of problem? Are there any suggestions as to where else in my files could cause an error like this to arise?

class RepliesController < ApplicationController
before_action :authenticate_user!
before_action :set_reply, only: [:edit, :update, :show, :destroy]
before_action :set_discussion, only: [:create, :edit, :show, :update, :destroy]

def create
    #create a reply within the discussion and save userid to the reply
    @reply = @discussion.replies.create(params[:reply]).permit(:reply, :discussion_id)
    @reply.user_id = current_user.id

    respond_to do |format|
        if @reply.save
            format.html {redirect_to discussion_path(@discussion)}
            format.js #render create.js.erb

        else
            format.html{redirect_to discussion_path(@discussion), notice: 'Reply did not save. Try again'}
            format.js
        end
    end
end

...

private
def set_discussion
    @discussion = Discussion.find(params[:discussion_id])
end

def set_reply
    @reply = Reply.find(params[:id])
end

def reply_params
    params.require(:reply).permit(:reply)
end

end


Solution

  • The problem is in this line:

     @reply = @discussion.replies.create(params[:reply]).permit(:reply, :discussion_id)
    

    You should use something like:

     @reply = @discussion.replies.create(reply_params)
    

    I'd suggest you update the form to include discussion_id, and add this to the reply_params. That way you can create the record using the line suggested above.

    To do this, you'd need a line similar to the below in your form:

    <%= f.hidden_field :discussion_id, @discussion.id %>
    

    However, it's worth considering users can still edit this on the page if they have nefarious intentions. Therefore, if it's crucial to security that the discussion is fixed you can handle the assignment server side (as you're actually currently doing - see my update below).

    Hope that helps - let me know how you get on / if you have any questions.


    Update

    As you're using @discussion.replies.create... the discussion_id will be assigned automatically. This means the following should just work:

    @reply = @discussion.replies.create(reply_params)
    

    Strong params are used to prevent issues stemming from mass assignment, so as you're actually only assigning a single attribute here you could simplify it further still:

    @reply = @discussion.replies.create(params[:reply][:reply])
    

    However, I'd recommend the first option as you can simply add new columns to reply_params as your project scales.

    Hope that helps!